harborloop
v0.5.13
Published
Install and health-check the harbor knowledge loop in your terminal. Recall in Claude Code, Codex and Cursor; a doctor that can say no.
Maintainers
Readme
harborloop
Your team's knowledge, in your terminal. Recall in Claude Code, Codex and
Cursor; capture on the way back out; and a doctor that can tell you it is not
working.
npx harborloop initZero runtime dependencies. Node ≥ 18.
Or install Harbor from inside your agent, as a plugin:
claude plugin marketplace add gethrbr/harbor-plugins && claude plugin install harbor@harbor
codex plugin marketplace add gethrbr/harbor-plugins && codex plugin add harbor@harborThen /harbor:login in Claude Code, or $harbor:login in Codex. See
gethrbr/harbor-plugins.
Commands
| | |
|---|---|
| harbor init | Sign in, install the hooks, bind this repo. Idempotent. Wires every agent it finds; --cursor / --no-cursor and --codex / --no-codex override detection. Asks before sending the repo's committed CLAUDE.md / AGENTS.md to your team's review queue, once per file; --import-rules sends without asking, --no-import-rules never sends, and harbor update never sends. --headless is for a cloud agent's setup script, see below. |
| harbor doctor | Is it actually working? --offline, --json, --days N |
| harbor status | This repo, from local files only. No network. |
| harbor bind [project] | Tie this repo to a project. |
| harbor workspaces | Which workspaces you can reach, and the one this machine uses. --json |
| harbor sync | Put this repo's team skills, memories and documents on disk. No desktop app; HARBOR_TOKEN + HARBOR_WORKSPACE_ID for CI. |
| harbor unbind | Take those files back off this machine. Local only — the repo stays bound, and a file you edited stays put. |
| harbor off | Pause this repo. --capture keeps recall. --global for the machine. |
| harbor on | Resume. --global clears a machine-wide pause. |
| harbor login | Re-authenticate. --no-browser to paste a code instead. --workspace <id> switches workspace without the browser. |
| harbor logout | Forget this machine's credential. Leaves the config alone. |
There is deliberately no ask, no learnings, no sources. Your agent reaches
those over MCP and you reach them in the webapp; a second copy of the product in
your terminal is not a feature.
In a cloud agent
Claude Code on the web and other containers have no browser to sign in with. Create a workspace agent key in Harbor's settings, then give the environment this setup script:
HARBOR_AGENT_KEY=<your key> npx -y [email protected] init --headlessThe key goes on the line itself. Claude Code on the web does not pass the
environment's "Environment variables" to the setup script, so a key set there
is never seen by init --headless.
Network access. Claude Code on the web's default "Trusted" policy blocks
Harbor. Choose "Custom", add app.gethrbr.com and mcp.gethrbr.com, and tick
"Also include default list of common package managers" so npx can still
reach npm.
If the setup script runs inside a repo that has its own package named
harborloop, npx picks that one up instead. Install it globally there:
npm i -g [email protected] && HARBOR_AGENT_KEY=<your key> harbor init --headlessinit --headless wires Claude Code with the key: the MCP entry, the hooks, the
repo's origin, and the workspace's guardrails. The setup script usually runs in
the parent of the clone (/home/user, with the repo at /home/user/<repo>),
so it uses the repo it is standing in, or else every repo directly under it.
--repo <path> names one instead.
It never opens a browser, never writes ~/.harbor/auth.json and never
schedules the daemon. It prints one line and exits 0 whatever happens, so a
Harbor problem cannot stop the session. With no HARBOR_AGENT_KEY it prints
Harbor: no HARBOR_AGENT_KEY, skipping and changes nothing.
Blocked commands reach Harbor. When a guardrail blocks a command in the container, the Stop hook at the end of that turn sends the record to Harbor with the key: the rule, the program name, the session, never a path or an argument. If Harbor cannot be reached, the record waits for the next turn.
A repo that commits its own hooks. If the repo's .claude/settings.json
already runs Harbor's hooks, add --no-hooks. It sets up everything else and
leaves the hooks unregistered, so each one runs once instead of twice.
Codex cloud
In the Codex environment's settings, add the key as a secret named
HARBOR_AGENT_KEY (secrets reach the setup script, and only the setup script),
then use this setup script:
npx -y [email protected] init --headless --codex --origin https://github.com/<owner>/<repo>- Agent internet access must be On, with
app.gethrbr.comandmcp.gethrbr.comallowed. With it Off only the guardrails synced at setup still block; nothing reaches Harbor. --codexwrites Harbor's hooks and MCP entry into the Codex the agent runs (/opt/codexin Codex cloud), and has that Codex trust the hooks: Codex runs no hook it has not trusted. The line reports how many it trusted.- The key goes into the MCP entry's header on disk, because Codex starts hooks and MCP servers without the environment's variables.
--originnames the repo. Codex clones it with nooriginremote, and Harbor stays off in a repo it cannot name. An origin the clone already has is never replaced.
Cursor cloud agents are not supported yet.
Why a CLI at all
Three config-lifecycle jobs used to be owned by a running desktop app, and each of them failed silently without it:
| Job | Without the app running | |---|---| | MCP token refresh | expires, never re-mints | | Hook command path | absolute into the app bundle — move or uninstall it and the path is dead | | Repo allowlist | bind a repo on the web, the terminal never learns |
The hook is a read-only consumer of all three and exits 0 on any failure,
because a UserPromptSubmit hook must never block a prompt. So "install the
app once, close it, live in the terminal" was broken by construction, and what
you saw was nothing at all: no error, no injection, a product that appears
inert.
doctor exists because of that last sentence.
doctor
Endpoint ✓ https://app.gethrbr.com/v1
Signed in ✓ workspace "acme"
Token ✓ valid, expires in 11 months
Hooks ✓ 5 installed from ~/.harbor/bin
This repo ✓ https://github.com/acme/repo
Recording ✓ on
Recall ✓ last 7 days 41 sessions · 28 of 41 (68%) received context
Fit ⚠ avg bundle 7.2 200 of 300 of served atoms go uncited
Attribution ✓ 41/41 recalls counted
Contributed ✓ 3 learnings 2 approved · 1 pending reviewThe first block is am I connected. The second is is it working — every line with a denominator, because a percentage you cannot see the bottom half of is a number you cannot disbelieve.
Fit is the one worth staring at. It is the only place in the product that
tells you how much of what gets injected is actually used.
doctor can say no. Run it on an unbound repo, or with a dead token, and it
fails with the fix on the next line. A health check that only ever prints green
is not a health check.
A partial install is one of the things it says no to. init registers five
events; a hand-edited settings.json that kept one of them gets
Hooks ⚠ 1 of 5 events registered — capture, the end-of-session report and tool
context are not wired, not a tick over a count. Counting what is there answers
a narrower question than the one you are asking it.
A paused repo prints ⏸ paused, on purpose, never a red error. Reporting a
silence you asked for as a fault just teaches you to stop reading the output.
off — the part people actually use
A lot of sessions are throwaway: a spike, a rename, an afternoon of vibe-coding. None of it belongs in your team's graph.
harbor off # this repo: no context in, nothing recorded
harbor off --capture # keep the recall, record nothing
harbor off --global # the whole machine
harbor on # back on. Beats a --global pause.The pause is stored locally and read by the hook before it touches the network, so it still works on a plane — and so a toggle cannot fail at exactly the moment you need it.
Switching workspaces
One account, several workspaces — harbor workspaces lists them and marks the
one this machine is bound to:
* Acme ws_2f9c1a4b
Beta Industries ws_7d3e08f2
Switch with: harbor login --workspace <id>The switch needs no browser. The runner credential is scoped to the user,
not the workspace, so login --workspace <id> re-points it in place and
re-mints the MCP token against the new one. That matters more than it sounds:
the login rate limit is 5 requests per 15 minutes per IP and a browser sign-in
spends two of them, so a switch that went through the browser would let you
change your mind about twice.
If the list says you are signed in to a workspace that is not on it, you were removed from it. Every sync and mint against it will fail until you switch.
What it writes
~/.harbor/auth.json your credential (0600)
~/.harbor/synced-origins.json which repos are yours, and what is paused
~/.harbor/bin/*.mjs the hook scripts
~/.harbor/cli-sessions/ per-session bookkeepingThen one entry per agent you actually have — its own bearer, in its own file, in its own vocabulary, so each agent gets its own connection row and a 401 in one never re-mints another:
~/.claude.json `mcpServers.harbor` — the MCP entry and its bearer
~/.claude/settings.json five hook events (NOT the MCP entry: Claude Code
reads `mcpServers` from `~/.claude.json` only)
~/.codex/config.toml the MCP server + four events (Codex has no SessionEnd)
~/.codex/hooks.json the registrations, plus the trust stamps Codex will not run without
~/.cursor/mcp.json `mcpServers.harbor`, Cursor's `{url, headers}` shape
~/.cursor/hooks.json five events, in Cursor's own event and tool namesSkills fan out the same way — ~/.claude/skills/, ~/.codex/skills/,
~/.cursor/skills/, and .<agent>/skills/ inside a checkout. One copy per root
your machine actually has.
⚠️ Cursor recalls at session start, not on every prompt. Its prompt event has
no field to inject context into, so a Cursor session gets the bundle when it
opens and on every edit, and no per-turn delta. doctor says so on its healthy
line rather than implying parity.
Every one of these files is yours, not ours: each write is a merge that leaves
your other MCP servers, your other hooks, and every key we do not recognise
exactly as it found them, under a cross-process lock so the desktop app and this
CLI cannot clobber each other. ~/.cursor/* is parsed as JSONC, so if you have
put comments in it we decline to rewrite it rather than delete them — and
doctor names the file when that happens.
The credential is a 0600 file rather than the OS keychain — the same choice
gh and npm make. A keychain needs a native module (and there goes the
zero-dependency npx install) and a running secret service that headless boxes
do not have, to defend against an attacker who can already read your files as
you — and who could equally read the same bearer out of your agent's own config.
Without installing anything
Any MCP client can have pull recall today, with no install:
claude mcp add --transport http harbor <your-mcp-url>What that does not get you: push injection at the start of a session, the write
-back that closes the loop, or a session key — so none of it is measurable, and
doctor has nothing to report. That is the difference init buys.
Configuration
| Variable | Default |
|---|---|
| HARBOR_API_URL | https://app.gethrbr.com/v1 |
| HARBOR_MCP_URL | https://mcp.gethrbr.com/mcp |
| HARBOR_WEB_URL | https://app.gethrbr.com |
| HARBOR_HOME | ~/.harbor |
You need none of them to install — the defaults are the hosted harbor. Set them to run against your own backend, or against a local stack:
export HARBOR_API_URL=http://localhost:8100/v1
export HARBOR_MCP_URL=http://localhost:8101/mcp
export HARBOR_WEB_URL=http://localhost:5174The local web port is 5174, not 5173. 5173 is another product's vite on the
same machine, and /desktop-auth redeems its auth code against its own API —
so pointing this at 5173 signs you into a different database and the CLI then
fails on a token the local backend has never heard of.
doctor prints the endpoint it resolved, and marks it (local) when it is one,
so a run never quietly reports on a workspace you did not mean.
