Harbor
Developer docs menu

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

GroupWhat it covers
notebooks, notes, tagsCore content and organization (tags are hierarchical).
searchFull-text + OCR search; search coordinates returns highlight boxes.
filesUpload, download, check-by-hash, and list attachments.
templates, shortcuts, remindersReusable note structures, sidebar shortcuts, and reminders.
sharePublish and revoke public, read-only note links.
history, trashVersion history with revert; trash with restore/expunge.
import, exportEvernote ENEX in and out.
syncThe raw USN change engine, for building clients.
cryptoClient-side, zero-knowledge encryption (see below).
profile, sessions, settings, accountYour account, devices, preferences, and GDPR export/delete.
status, api-version, openapiServer 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 encrypt has no confirmation at all, which makes notes 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 from harbor 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-loss once 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.