Tools
The Harbor CLI
A single binary that puts your whole second brain in your terminal — and in your scripts. JSON output on every command, so it pipes.
harbor is a small, dependency-light Go binary that exposes the entire
Harbor API as composable commands: notebooks, notes, hierarchical tags,
full-text and OCR search, file attachments, sync, version history, trash,
templates, reminders, public sharing, client-side encryption, and Evernote
import/export. Every command takes --json, so it drops straight into a
pipeline.
It’s open source: github.com/HarborMyNotes/harbor-cli (MIT).
Install
Homebrew (macOS & Linux)
The repo is its own tap:
brew tap HarborMyNotes/harbor https://github.com/HarborMyNotes/harbor-cli
brew install harbor
Update later with brew upgrade harbor.
Prebuilt binary (any platform)
Grab the release asset for your OS and architecture (harbor-<os>-<arch>):
curl -L -o harbor \
https://github.com/HarborMyNotes/harbor-cli/releases/latest/download/harbor-darwin-arm64
chmod +x harbor && sudo mv harbor /usr/local/bin/
Windows (harbor-windows-amd64.exe) is download-only.
Go toolchain
go install github.com/HarborMyNotes/harbor-cli@latest
Check your version any time with harbor --version.
Sign in
harbor login opens Harbor’s sign-in page in your browser, so passkeys, social
login, and 2FA all work — the CLI never sees your password. When you approve, it
stores a long-lived personal access token in ~/.config/harbor/credentials.json
(mode 0600).
harbor login # opens your browser
harbor whoami # confirm you're signed in
For headless environments (SSH, CI), skip the browser and use a personal access token:
# Paste a token created in Settings → Developer:
harbor login --token hbp_...
# …or use it for a single command without logging in (nothing is persisted):
HARBOR_TOKEN=hbp_... harbor notes list
Sign out with harbor logout (add --all-devices to revoke everywhere).
Everyday commands
# Notebooks
harbor notebooks list
harbor notebooks create --name "Work" --stack "Cloudmanic"
# Notes (aliases: note, n)
harbor notes list --notebook 5b1f...
harbor notes create --title "Plan" --content "# Goals\n\n- ship it"
echo "# Standup" | harbor notes create --title Standup --stdin
harbor notes update 9c2e... --file updated.md
harbor notes tag 9c2e... --tag-name planning
# Search — Evernote-style grammar, including inside files
harbor search 'tag:finance resource:pdf "q3 plan"'
# Files
harbor files upload receipt.pdf
harbor files download 3fa4... --output receipt.pdf
# Reminders (alias: rem)
harbor reminders set 9c2e... --time "in 2h"
# Public sharing
harbor share publish 9c2e... --json | jq -r '.data.public_url'
Every command supports --json (data-only on stdout), --limit/--offset/
--order on lists, and --help with real examples. Times accept YYYY-MM-DD,
RFC 3339, or relative forms like "in 2h".
Command groups
| Group | What it covers |
|---|---|
notebooks, notes, tags | Core content and organization (tags are hierarchical). |
search | Full-text + OCR search; search coordinates returns highlight boxes. |
files | Upload, download, check-by-hash, and list attachments. |
templates, shortcuts, reminders | Reusable note structures, sidebar shortcuts, and reminders. |
share | Publish and revoke public, read-only note links. |
history, trash | Version history with revert; trash with restore/expunge. |
import, export | Evernote ENEX in and out. |
sync | The raw USN change engine, for building clients. |
crypto | Client-side, zero-knowledge encryption (see below). |
profile, sessions, settings, account | Your account, devices, preferences, and GDPR export/delete. |
status, api-version, openapi | Server health and the live OpenAPI spec. |
Run harbor --help for the grouped overview, or harbor <command> --help for
any command’s full flags.
Scripting
Stable exit codes (0 success, 1 error), data-only stdout, and logs on stderr
make harbor a good pipeline citizen:
# All note titles in a notebook
harbor notes list --notebook 5b1f... --json | jq -r '.data[].title'
# Pipe a generated report straight into a new note
./generate_report.sh | harbor notes create --title "Report $(date +%F)" --stdin
# Bulk-tag every note matching a search
harbor search 'intitle:standup' --json | jq -r '.data[].note_id' \
| xargs -I{} harbor notes tag {} --tag-name standup
# CI health check (exits non-zero when the API isn't ready)
harbor status --json >/dev/null || echo "Harbor API not ready"
# Fetch the OpenAPI spec for code generation
harbor openapi --output harbor-openapi.json
Encryption
Harbor’s zero-knowledge encryption works from the CLI
too. Set a passphrase and reads decrypt automatically; writes into an
auto-encrypt notebook (or --encrypt) encrypt automatically. The server only
ever sees ciphertext.
export HARBOR_PASSPHRASE="…" # or you'll be prompted
harbor crypto setup # one-time, per account
harbor notes create --title "Secrets" --content "..." --encrypt
Converting a note in place
notes encrypt and notes decrypt change an existing note’s mind without
recreating it — the note keeps its id, attachments, tags, reminders and
created_at through both directions. Its version history does not survive
either direction; see below.
harbor notes encrypt 9c2e... # seal one plaintext note
harbor notes encrypt --notebook 5b1f... # sweep a whole notebook
harbor notes decrypt 9c2e... # write it back as plaintext
harbor notes decrypt 9c2e... --yes # skip the confirmation (required in --json)
harbor notes decrypt 9c2e... --format markdown # body was authored as Markdown
Both unlock first and build the whole write in memory before sending it, so a
failure leaves the note exactly as it was — nothing is half-converted. Notes
already in the target state are skipped, which makes a --notebook sweep safe
to re-run.
notes decrypt asks you to type yes because it is a downgrade: the body goes
back to the server in the clear, where it is indexed for search and snapshotted
into the note’s history. Re-encrypting afterwards protects the note from then
on; it does not un-store what the server was already given.
Three things worth knowing before you script either of them:
- Both directions DELETE the note’s version history, and neither asks. The
server requires every snapshot to agree with the note’s
is_encrypted, so the write that flips the flag removes every snapshot that disagrees: encrypting discards the plaintext versions, decrypting discards the encrypted ones. It is a hard delete — not a tombstone, not syncable, not recoverable.notes encrypthas no confirmation at all, which makesnotes encrypt --notebook <id>a sweep that can quietly discard the history of every plaintext note in a notebook;notes decrypt’s prompt is about publishing readable text, not about the history it is also destroying. Capture anything you need fromharbor history list <id>first. Ignore the CLI’s own wording on this for now:notes encrypt --help, and the caveat it prints after a run, still say earlier versions stay readable. That was true once and is not any more — this page is right and the CLI’s text is being fixed. - Encrypting covers the title and body, not the attachment bytes. The references to attached files become ciphertext along with the body, but the files themselves are stored exactly as they were and can still be downloaded and read in full.
- A decrypt can cost you a task, and is refused rather than allowed to.
While a note is encrypted the server cannot read its body, so it stops
reconciling the note’s tasks. The decrypt is the first write it can read
again, and a task still linked to the note whose
<harbor-task>block the decrypted body no longer carries is deleted, not detached. That case exits non-zero with nothing written, naming the tasks; pass--allow-task-lossonce you have decided that is what you meant.
--format says how to interpret the decrypted body: html (the default, and
the exact inverse of a notes encrypt that sealed the note as the server
stored it) or markdown for a note created encrypted from Markdown source.
Moving a note into an encrypting notebook
A notebook’s
default_encrypt
flag is an entry rule, not just a default for new notes: the server rejects
a plaintext note landing in such a notebook with
422 cannot_move_plaintext_into_encrypted, writing nothing. So seal the note
first, then move it:
harbor notes encrypt 9c2e...
harbor notes update 9c2e... --notebook <encrypting-notebook-id>
Moving a note back out never decrypts it — run harbor notes decrypt once
the note is somewhere ordinary.
There is no recovery — a lost passphrase means those notes are permanently unreadable, by you or us. That’s the point.
Turn your AI into a Harbor assistant
The CLI ships an embedded agent skill that teaches an AI coding agent to drive Harbor — creating, formatting, organizing, and searching your notes. Install it into the agent you use:
harbor skill install # Claude Code → ~/.claude/skills/harbor/
harbor skill install --agent codex # OpenAI Codex → ~/.codex/AGENTS.md
harbor skill install --agent cursor # Cursor → .cursor/rules/harbor.mdc
Because the skill is embedded in the binary, upgrading the CLI upgrades the skill. This is the heart of Harbor’s bring-your-own-AI approach: your AI, your keys, your notes — no built-in model, no lock-in.
Reference
- Config:
~/.config/harbor/credentials.json(0600). - Env vars:
HARBOR_TOKEN(per-command auth),HARBOR_PASSPHRASE(transparent encryption),NO_COLOR. - Default endpoint:
https://app.harbor.my/api/v1. - Source & issues: github.com/HarborMyNotes/harbor-cli.
The CLI is the fastest way to explore the API by hand — then reach for the REST reference when you’re ready to build.