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
Quick install (macOS & Linux)
curl -fsSL https://harbor.my/install.sh | sh
That works out your platform, downloads the right binary, checks it against the
release’s published SHA-256 and puts it in /usr/local/bin. It tells you before
it asks for sudo, and it refuses to install anything whose checksum doesn’t
match.
Two knobs, both optional. They go on the right of the pipe — a variable set
on the left is handed to curl, which ignores it:
curl -fsSL https://harbor.my/install.sh | HARBOR_VERSION=v0.1.36 sh
curl -fsSL https://harbor.my/install.sh | HARBOR_INSTALL_DIR=$HOME/.local/bin sh
Re-run it any time to upgrade in place. Windows isn’t covered — grab the binary below.
Homebrew (macOS & Linux)
brew install HarborMyNotes/harbor/harbor
That’s the whole thing. Naming the formula in full adds the tap and approves it in the same step, so there’s nothing to run beforehand.
Rather approve the tap up front and install by short name? Three commands instead:
brew tap HarborMyNotes/harbor
brew trust HarborMyNotes/harbor
brew install harbor
Don’t skip trust on that route. Homebrew 6.0 won’t run code from a tap
outside its official ones until you’ve approved it, and it fails outright rather
than prompting:
Error: Refusing to load formula harbormynotes/harbor/harbor from untrusted tap harbormynotes/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/
Every release also publishes checksums.txt, so you can verify what you got:
curl -L -O https://github.com/HarborMyNotes/harbor-cli/releases/latest/download/checksums.txt
shasum -a 256 harbor # macOS — compare against the matching line
sha256sum harbor # Linux
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
Commands that act on one note take that note’s ID. harbor notes list prints them,
and every Harbor app has an ID row in the note’s Info panel that copies the full
ID when you click it — see How do I find a note’s ID?
# 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'
# Export one note to Markdown (a .zip if it has attachments — trust the filename)
harbor notes export 9c2e... --output note.md
# Export the whole account, or one notebook, as Markdown
harbor account export --format markdown --wait --download ./harbor-markdown.zip
harbor account export --format markdown --notebook 5b1f...
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 the full account export (--format enex|html|markdown, whole account or one notebook) and 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
# Nightly Markdown backup — an Obsidian vault of the whole account
harbor account export --format markdown --wait \
--download "$HOME/backups/harbor-$(date +%F).zip"
harbor notes export streams whatever the server sends — text/markdown for a
note with no attachments, application/zip for one with them — so read the name
from the response rather than assuming an extension, or pass --zip to force
the archive form and get one predictable shape. harbor notes get --format markdown is still the read (it prints a body for piping into jq); notes export is the export, and it is the one that brings attachments.
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.