Files
AgentHub/docs/superpowers/specs/2026-08-10-plugin-first-install-design.md
nntrivi2001 17d56d0e01 refactor!: rebrand CCAM to AgentHub
Clean break, no compatibility alias. Renames the product name, the CLI
binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` ->
`AGENTHUB_`), the Prometheus metric namespace (`ccam_*` ->
`agenthub_*`), the tmux session prefix (`ccam-lane-<id>` ->
`agenthub-lane-<id>`), the per-repo runtime profile directory
(`.ccam/profile` -> `.agenthub/profile`), the lanes state root
(`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled
plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash
commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` ->
`/agenthub:*`).

Because there is no alias, an existing install has to be updated by
hand: rename its `.ccam/profile` directory and any `CCAM_*` variables,
and reinstall the plugin under its new name.

Two other name families are deliberately left alone here, since they are
not just strings: the `Claude-Code-Monitor` repository/marketplace name,
and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard`
runtime directory, which holds the live database and needs a migration
rather than a rename.

Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across
tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
2026-09-07 12:09:15 +07:00

11 KiB
Raw Permalink Blame History

Plugin-first install — AgentHub usable straight from a Claude Code plugin install

Date: 2026-08-10 Status: approved (design)

Goal

On a machine with nothing but Claude Code installed, these two commands must leave the user with a working AgentHub — hooks flowing, server up, dashboard reachable, agenthub CLI on PATH, MCP tools connected:

/plugin marketplace add <agenthub repo>
/plugin install agenthub@claude-code-agent-monitor-plugins

No repo clone, no npm run setup, no npm run install-hooks, no manual npm start. This mirrors how claude-mem installs.

Why the current state does not do this

  • The only plugin with a server connection, agenthub-dashboard, declares its MCP server as ../../mcp/build/index.js (plugins/agenthub-dashboard/.mcp.json). Plugins installed from a marketplace subdir are cached as that subdir only — verified against ~/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0/, which contains the plugin directory's contents and nothing above it. The relative path escapes the cached tree and resolves to nothing.
  • Every plugin's hooks and MCP config assume a dashboard is already listening on localhost:4820. Nothing starts one.
  • Hook installation today mutates ~/.claude/settings.json through scripts/install-hooks.js, which requires a checkout to point at.

Approach: declare the whole repo as one plugin

Add a root-level plugin named agenthub with "source": "." in .claude-plugin/marketplace.json. Claude Code then caches the entire repo into ~/.claude/plugins/cache/<marketplace>/agenthub/<version>/ — verified against ~/.claude/plugins/cache/caveman/, whose cached tree contains the full source repo (src/, tests/, benchmarks/), because that marketplace declares its plugin with "source": "./".

So server/, client/, mcp/, scripts/ and bin/agenthub.js are all present under ${CLAUDE_PLUGIN_ROOT} the moment the plugin is installed. No second clone, no credentials beyond the marketplace add itself, no committed copy of the server, no dependency on undocumented cache internals beyond the documented ${CLAUDE_PLUGIN_ROOT} variable.

The ten existing subdirectory plugins stay exactly as they are.

Repo working tree is 23 MB — an acceptable cache footprint.

Components

1. Root plugin manifest — .claude-plugin/plugin.json

New file, alongside the existing marketplace.json. Declares name, metadata, inline hooks, and paths to commands. Hooks are inlined in plugin.json (as caveman does) rather than in a root hooks/hooks.json, so the repo root gains no new top-level directories.

.claude-plugin/marketplace.json gains one entry:

{ "name": "agenthub", "source": ".", "description": "...", "tags": [...] }

2. Hooks — inline in plugin.json

All eight hook types currently installed by scripts/install-hooks.js (install-hooks.js:86,93):

  • with "matcher": "*" — PreToolUse, PostToolUse, Stop, SubagentStop, Notification
  • without a matcher — SessionStart, SessionEnd, UserPromptSubmit

Each runs:

node "${CLAUDE_PLUGIN_ROOT}/scripts/hook-handler.js" <HookType>

scripts/hook-handler.js is used unchanged: it is dependency-free (node:http only), resolves ports via server/lib/server-info.js — present in the cached tree — falls back to 4820, and already fails silently so a not-yet-booted server never blocks Claude Code.

SessionStart additionally runs the bootstrap (below).

Consequence: npm run install-hooks becomes unnecessary for plugin users, and uninstalling the plugin removes the hooks cleanly. It stays supported for checkout users.

3. Bootstrap — scripts/plugin-bootstrap.js (new)

Runs from the SessionStart hook. Never blocks a session.

Runtime state lives under ~/.claude/agent-dashboard/runtime/, reusing the existing data-dir convention (server/lib/claude-home.js:37, where the SQLite DB already lives) rather than inventing a new directory. /.agenthub/ at the repo root is an unrelated lane profile and must not be confused with it.

Sequence:

  1. Fast path. Read runtime/state.json. If the recorded plugin version matches and a live server is found via ~/.claude/.agent-dashboard.json (the existing PID-checked discovery file), exit in milliseconds.
  2. Node version gate. server/db.js requires better-sqlite3 — which is not in dependencies — and falls back to node:sqlite, available only on Node >= 22.5. On anything older the server cannot start at all. Refuse with one clear line rather than letting the server crash.
  3. Lock. runtime/.bootstrap.lock created with an atomic mkdir, holding the PID. A lock whose PID is dead, or older than 10 minutes, is stale and reclaimed. Prevents concurrent sessions racing an install or double-spawning the server.
  4. Install. npm install --omit=dev --ignore-scripts with the dependency tree written to runtime/node_modules, never into the plugin cache directory. Two reasons: ~/.claude/plugins/cache/ shows GC machinery (.in_use markers, .last_inuse_sweep), and every plugin update creates a fresh version directory, discarding anything installed into the old one. --ignore-scripts is required because scripts/postinstall.js otherwise runs a full client/ install, pulling the whole Vite dev toolchain.
  5. Legacy hook cleanup. If ~/.claude/settings.json still carries AgentHub hook entries from a previous npm run install-hooks, remove them and print one line. Without this, every event is POSTed twice and token/cost figures double. Detection reuses isOurEntry() from scripts/install-hooks.js.
  6. CLI on PATH. Symlink bin/agenthub.js to ~/.local/bin/agenthub. Skills and commands throughout the repo (agenthub stage, ship-feature-lane) shell out to agenthub and fail without it.
  7. Start server. Spawn server/index.js detached, stdio: "ignore", unref(), with NODE_PATH pointed at runtime/node_modules. The hook returns immediately; the first install (1–3 minutes) proceeds in the background and prints progress to a log under runtime/.
  8. Record state. Write runtime/state.json.

The client UI bundle is not built here. Hooks, the API and MCP do not need it; it is built lazily by /agenthub-open.

4. Client dist relocation — server/index.js

server/index.js:156 hardcodes path.join(__dirname, "..", "client", "dist"). Since the plugin cache tree must be treated as read-only, add a DASHBOARD_CLIENT_DIST env override, defaulting to the current path so checkout behavior is unchanged.

The bootstrap always starts the server with DASHBOARD_CLIENT_DIST set to runtime/client-dist/, whether or not a bundle is there yet — a missing directory serves the API fine and only the UI route 404s. /agenthub-open builds into that same directory, so no server restart is needed to pick the bundle up.

5. MCP — commit mcp/build/

Claude Code starts a plugin's MCP servers as the session opens and offers no "not ready yet, retry" state, so an async bootstrap cannot win that race. mcp/build/ is therefore committed (removed from .gitignore and mcp/.gitignore) and the root .mcp.json points at ${CLAUDE_PLUGIN_ROOT}/mcp/build/index.js with AGENTHUB_DASHBOARD_URL defaulted to http://localhost:4820.

Cost: the build artifact must stay in sync with mcp/src. A pre-commit check fails when mcp/src is newer than mcp/build.

The existing plugins/agenthub-dashboard/.mcp.json keeps its broken relative path fixed to ${CLAUDE_PLUGIN_ROOT} form as part of this work, so the subdir plugin is not left in a knowingly broken state.

6. Commands — plugins/agenthub/commands/

The plugin root is the repo root, so plugin.json declares an explicit "commands": "./plugins/agenthub/commands" path. This keeps the new command files inside plugins/, next to the ten existing plugins, instead of adding a top-level commands/ directory to the repo root.

  • /agenthub-doctor — bootstrap state, Node version, server liveness, DB path, duplicate-hook detection, CLI symlink, MCP build freshness.
  • /agenthub-update — refresh dependencies and restart the server after a plugin update.
  • /agenthub-open — lazily build the client bundle if missing, then open the dashboard.

Data and lifecycle

Thing Location Survives plugin update
SQLite DB, transcripts ~/.claude/agent-dashboard/ (unchanged) yes
node_modules, client dist, logs, lock, state ~/.claude/agent-dashboard/runtime/ yes (re-verified each boot)
Source, hooks, MCP build plugin cache version dir no — re-bootstrapped

Bootstrap must not set DASHBOARD_DATA_DIR. Leaving it at the default keeps a plugin-run server and a developer's npm run dev server on the same data directory, where ingestGroupKey in server/lib/server-info.js already deduplicates hook ingest to a single port.

Failure modes

High severity

  • SessionStart blocks the session. The first install takes minutes. Mitigated by full detachment (detached, stdio: "ignore", unref()) and an immediate hook return. Verified by timing the hook on a cold HOME.
  • Duplicate hooks double-count cost. Events carry no id, so ingest cannot deduplicate. Mitigated by step 5 of the bootstrap plus a /agenthub-doctor check.
  • MCP unavailable at session start. Resolved by committing mcp/build/.

Medium severity

  • Two sessions spawn two servers → EADDRINUSE. Mitigated by the lock, with a stale-lock timeout so a crashed bootstrap does not wedge every later session.
  • agenthub not on PATH when ~/.local/bin is absent from the user's PATH. /agenthub-doctor reports it and prints the line to add.
  • Windows. npm.cmd resolution (already handled in scripts/postinstall.js via shell: true), detached spawn semantics, and mkdir locking are the least-tested paths.

Low severity

  • Node 20 users are refused rather than broken; the message names the required version.
  • 23 MB cache footprint.
  • Marketplace mixing one root-source plugin with ten subdir plugins is unverified in this combination; it is the first thing the plan verifies, and the fallback is a separate marketplace file for the root plugin.

Verification

  1. Install the local marketplace and confirm ~/.claude/plugins/cache/<mp>/agenthub/*/server/ exists — this gates everything else.
  2. npm run test:server for the DASHBOARD_CLIENT_DIST override.
  3. Unit tests for bootstrap: fast path, stale-lock reclaim, Node version gate, legacy-hook detection — all with injected paths, no real $HOME writes.
  4. Cold-machine smoke: HOME=$(mktemp -d) with no checkout on PATH, install the plugin, wait for bootstrap, curl /api/health, fire one synthetic hook, and assert the event lands in the DB.
  5. npm run mcp:typecheck && npm run mcp:build for the committed artifact.

Out of scope

  • Publishing to npm.
  • Docker-based bootstrap.
  • Changing the ten existing subdir plugins beyond the .mcp.json path fix.