npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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 init

Zero 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@harbor

Then /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 --headless

The 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 --headless

init --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.com and mcp.gethrbr.com allowed. With it Off only the guardrails synced at setup still block; nothing reaches Harbor.
  • --codex writes Harbor's hooks and MCP entry into the Codex the agent runs (/opt/codex in 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.
  • --origin names the repo. Codex clones it with no origin remote, 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 review

The 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 bookkeeping

Then 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 names

Skills 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:5174

The 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.