feat(plugins): make CCAM installable straight from a Claude Code plugin
Adds a root `ccam` plugin (`.claude-plugin/plugin.json`, `"source": "./"`) so
`/plugin marketplace add` + `/plugin install ccam@...` is enough on a machine
with nothing but Claude Code: no clone, no npm run setup, no manual npm start.
- scripts/plugin-bootstrap.js: SessionStart hook. Fast-path exit, Node >=22.5
gate (node:sqlite), mkdir lock with stale reclaim, deps installed into
~/.claude/agent-dashboard/runtime/ (never the plugin cache), legacy
checkout-hook cleanup (backed up), ~/.local/bin/ccam launcher, eager UI
build so client routes like /run work immediately, detached server spawn.
- scripts/plugin-open.js, scripts/plugin-doctor.js: /ccam-open, /ccam-doctor.
- server/index.js: DASHBOARD_CLIENT_DIST override (plugin cache is read-only).
- mcp/build/ is committed (plugin MCP servers start before any bootstrap could
build them) and kept honest by scripts/check-mcp-build.js (content hash,
not mtime), enforced by pre-commit when mcp/src changes.
- plugins/ccam-dashboard/.mcp.json moved under plugins/ccam/ with a working
${CLAUDE_PLUGIN_ROOT} path (the old relative path never resolved from a
marketplace-cached subdir).
- Docs: README, INSTALL, SETUP, ARCHITECTURE, CLAUDE.md, docs/PLUGINS.md,
docs/MCP.md, docs/CLI.md, docs/HOOKS.md.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,14 @@ ccam help
|
||||
|
||||
If linking needed elevated permissions in your environment, setup still succeeds and prints a hint — run `npm link` once from the repo root yourself, or invoke the CLI directly with `node bin/ccam.js <command>`.
|
||||
|
||||
### With a plugin install (no checkout)
|
||||
|
||||
The `ccam` plugin's bootstrap writes a launcher to `~/.local/bin/ccam` on
|
||||
session start — a small script rather than a symlink into the plugin cache,
|
||||
which is replaced on every plugin update. It never overwrites a `ccam` it did
|
||||
not write, so a linked checkout keeps winning. If `~/.local/bin` is not on your
|
||||
PATH, `/ccam-doctor` says so and prints the exact `export PATH=...` line to add.
|
||||
|
||||
## Server Discovery
|
||||
|
||||
The CLI finds your running dashboard the same way the Claude Code hook handler does:
|
||||
|
||||
@@ -155,6 +155,17 @@ graph TB
|
||||
npm run install-hooks
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Skip this entirely when the `ccam` plugin is installed.** The plugin declares
|
||||
> the same eight hooks itself (inline in `.claude-plugin/plugin.json`, each
|
||||
> running `${CLAUDE_PLUGIN_ROOT}/scripts/hook-handler.js`). Running both means
|
||||
> every event is POSTed twice — events carry no id, so ingest cannot deduplicate
|
||||
> them and every token and cost figure doubles. `scripts/plugin-bootstrap.js`
|
||||
> removes checkout-installed entries on session start (backup:
|
||||
> `~/.claude/settings.json.ccam-bak`), `install-hooks.js` warns when it detects
|
||||
> a plugin install, and `/ccam-doctor` reports any duplicates that remain. See
|
||||
> [PLUGINS.md](PLUGINS.md).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Hooks are a host-side step.** Claude Code runs on your host, so the hook
|
||||
> command must reference a `hook-handler.js` path that exists on the **host**.
|
||||
|
||||
+25
-2
@@ -135,13 +135,35 @@ graph TB
|
||||
# Install MCP server dependencies
|
||||
npm run mcp:install
|
||||
|
||||
# Build MCP server
|
||||
# Build MCP server (also stamps mcp/build/.srchash)
|
||||
npm run mcp:build
|
||||
|
||||
# Test MCP server
|
||||
npm run mcp:start
|
||||
```
|
||||
|
||||
Installing the `ccam` plugin needs none of this: it ships the built server and
|
||||
wires it up itself (see [PLUGINS.md](PLUGINS.md)).
|
||||
|
||||
### Why `mcp/build/` is committed
|
||||
|
||||
Claude Code starts a plugin's MCP servers the moment a session opens and offers
|
||||
no "not ready yet, retry" state, so an async bootstrap cannot win that race. The
|
||||
build artifact is therefore committed, and `plugins/ccam/.mcp.json` points at
|
||||
`${CLAUDE_PLUGIN_ROOT}/mcp/build/index.js`.
|
||||
|
||||
The cost is drift, so freshness is enforced by content hash — `mcp/src` plus the
|
||||
MCP manifests and tsconfig are hashed into `mcp/build/.srchash`:
|
||||
|
||||
```bash
|
||||
npm run mcp:check-build # fails when mcp/build is stale or unstamped
|
||||
```
|
||||
|
||||
`npm run mcp:build` re-stamps it, the pre-commit hook runs the check whenever
|
||||
`mcp/src` is part of the commit, and `/ccam-doctor` reports it. Modification
|
||||
times are deliberately not used: a fresh clone stamps every file at checkout
|
||||
time in arbitrary order.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
@@ -157,7 +179,8 @@ mcp/
|
||||
│ │ └── stats.ts # Statistics tools
|
||||
│ └── types.ts # TypeScript type definitions
|
||||
│
|
||||
├── dist/ # Compiled JavaScript (gitignored)
|
||||
├── build/ # Compiled JavaScript (COMMITTED — see above)
|
||||
│ └── .srchash # hash of mcp/src the build was produced from
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── README.md
|
||||
|
||||
+126
-14
@@ -1,6 +1,6 @@
|
||||
# Claude Code Agent Monitor — Plugin Marketplace
|
||||
|
||||
Official Claude Code plugins for the Agent Monitor dashboard. **10 plugins** extend Claude Code with skills, agents, slash commands, hooks, and CLI tools for deep analytics, cost guardrails, productivity automation, developer tools, AI-powered insights, session forensics, workflow/fleet intelligence, reliability & SLOs, config & memory governance, and dashboard connectivity.
|
||||
Official Claude Code plugins for the Agent Monitor dashboard. The **`ccam`** plugin _is_ the dashboard — hooks, server, `ccam` CLI and MCP tools, no checkout required. On top of it, **10 focused plugins** extend Claude Code with skills, agents, slash commands, hooks, and CLI tools for deep analytics, cost guardrails, productivity automation, developer tools, AI-powered insights, session forensics, workflow/fleet intelligence, reliability & SLOs, config & memory governance, and dashboard connectivity.
|
||||
|
||||
Every plugin is powered by the local Agent Monitor REST API at `http://localhost:4820`. They are read-only advisors unless a skill explicitly documents a mutating endpoint (and those preview + confirm before acting).
|
||||
|
||||
@@ -12,19 +12,43 @@ Every plugin is powered by the local Agent Monitor REST API at `http://localhost
|
||||
claude plugin marketplace add Smartgift-AI/Claude-Code-Monitor
|
||||
```
|
||||
|
||||
### Install a plugin
|
||||
### Install the dashboard itself
|
||||
|
||||
```bash
|
||||
claude plugin install ccam-analytics@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-cost-guard@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-productivity@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-devtools@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-insights@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-sessions@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-workflows@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-quality@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-config@smartgift-claude-code-monitor
|
||||
claude plugin install ccam-dashboard@smartgift-claude-code-monitor
|
||||
claude plugin install ccam@claude-code-agent-monitor-plugins
|
||||
```
|
||||
|
||||
That is the whole install: no clone, no `npm run setup`, no `npm run install-hooks`, no manual `npm start`. See [The `ccam` plugin](#the-ccam-plugin) below for what it does on first session start.
|
||||
|
||||
### Or via the Smartgift skills marketplace
|
||||
|
||||
`ccam` is also listed as a standalone entry in
|
||||
[`smartgift-claude-skills`](https://git.smartgift.io.vn/Smartgift-AI/smartgift-claude-skills)
|
||||
— its `source` still points at this repo's `main` branch, so the two entries
|
||||
install identically:
|
||||
|
||||
```bash
|
||||
claude plugin marketplace add https://git.smartgift.io.vn/Smartgift-AI/smartgift-claude-skills.git
|
||||
claude plugin install ccam@sg
|
||||
```
|
||||
|
||||
Pick whichever marketplace you already have added; installing `ccam` from both
|
||||
at once is redundant but harmless (Claude Code treats it as one plugin per
|
||||
marketplace name, not per source).
|
||||
|
||||
### Install a focused plugin
|
||||
|
||||
```bash
|
||||
claude plugin install ccam-analytics@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-cost-guard@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-productivity@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-devtools@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-insights@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-sessions@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-workflows@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-quality@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-config@claude-code-agent-monitor-plugins
|
||||
claude plugin install ccam-dashboard@claude-code-agent-monitor-plugins
|
||||
```
|
||||
|
||||
### Or install locally during development
|
||||
@@ -37,11 +61,99 @@ claude --plugin-dir plugins/ccam-analytics
|
||||
## Prerequisites
|
||||
|
||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
||||
- Agent Monitor dashboard running at `http://localhost:4820` (see [SETUP.md](../SETUP.md))
|
||||
- Hooks installed: `npm run setup` from the Agent Monitor project
|
||||
- Node **>= 22.5** when installing via the `ccam` plugin. A plugin install has no
|
||||
native `better-sqlite3`, so the server stores data through `node:sqlite`, which
|
||||
landed in 22.5. The bootstrap refuses with one line on anything older instead
|
||||
of letting the server crash. (A checkout install still works on Node >= 20.)
|
||||
- The Agent Monitor dashboard reachable at `http://localhost:4820` — the `ccam`
|
||||
plugin starts it for you; a checkout starts it with `npm start` (see
|
||||
[SETUP.md](../SETUP.md))
|
||||
|
||||
Skills and commands are invoked as `/ccam-<plugin>:<name>`. Agents are dispatched automatically by Claude Code (or named explicitly).
|
||||
|
||||
## The `ccam` plugin
|
||||
|
||||
The marketplace's root entry (`"source": "./"`) is the entire repository, so
|
||||
`server/`, `client/`, `mcp/`, `scripts/` and `bin/ccam.js` all land under
|
||||
`${CLAUDE_PLUGIN_ROOT}` when Claude Code caches it. That is what makes a
|
||||
checkout unnecessary.
|
||||
|
||||
### What it installs
|
||||
|
||||
| Component | Where it comes from |
|
||||
|---|---|
|
||||
| The eight event hooks | inline `hooks` in `.claude-plugin/plugin.json`, each running `scripts/hook-handler.js` |
|
||||
| The dashboard server | started detached by the bootstrap, from the plugin cache |
|
||||
| The `ccam` CLI | a launcher written to `~/.local/bin/ccam` |
|
||||
| The MCP tools | `plugins/ccam/.mcp.json`, pointing at the committed `mcp/build/index.js` |
|
||||
| The dashboard UI (`/run` and every other client route) | built into the runtime dir by the bootstrap, so it works the moment `claude` starts |
|
||||
| `/ccam-doctor`, `/ccam-update`, `/ccam-open` | `plugins/ccam/commands/` |
|
||||
|
||||
Because the plugin ships the hooks itself, `npm run install-hooks` is not needed
|
||||
for plugin users — and must not be run alongside it. Events carry no id, so two
|
||||
handlers mean every token and cost figure is counted twice. The bootstrap
|
||||
removes the older checkout-installed entries automatically (backing
|
||||
`~/.claude/settings.json` up as `settings.json.ccam-bak` first), and
|
||||
`/ccam-doctor` reports the state.
|
||||
|
||||
### First session start
|
||||
|
||||
`scripts/plugin-bootstrap.js` runs from `SessionStart`. It returns within
|
||||
milliseconds — the real work happens in a detached worker, so a session never
|
||||
waits on an install:
|
||||
|
||||
1. **Fast path** — recorded state matches this plugin build and a server is live → exit.
|
||||
2. **Node gate** — refuse below 22.5 with one line (see Prerequisites).
|
||||
3. **Lock** — atomic `mkdir` lock holding the PID; reclaimed when the owner is dead or the lock is older than 10 minutes, so two sessions cannot race the install or spawn two servers.
|
||||
4. **Install** — `npm install --omit=dev --ignore-scripts` into the runtime dir (never into the plugin cache, which is garbage-collected and replaced on every update). `--ignore-scripts` keeps the root `postinstall` from pulling the whole Vite client toolchain.
|
||||
5. **Legacy hook cleanup** — see above.
|
||||
6. **CLI** — write the `~/.local/bin/ccam` launcher, never clobbering a `ccam` the bootstrap did not write.
|
||||
7. **UI build** — `client/` is copied into the runtime dir and built there (`npm install && npm run build`), landing in `runtime/client-dist` — the same directory the server serves from. Skipped when a bundle for the current version already exists; a failure here does not fail the bootstrap (API and MCP still work, and `/ccam-open` can retry) but does mean `/run` 404s until it's fixed.
|
||||
8. **Server** — spawn `server/index.js` detached, with `NODE_PATH` at the runtime `node_modules` and `DASHBOARD_CLIENT_DIST` at the runtime `client-dist`.
|
||||
9. **Record state** — `runtime/state.json`.
|
||||
|
||||
The first run takes a few minutes — most of it is the UI build, which is what
|
||||
makes client-only routes (`http://localhost:4820/run`, and every other page)
|
||||
work the moment `claude` starts, with no manual `/ccam-open` step. Progress
|
||||
goes to `~/.claude/agent-dashboard/runtime/bootstrap.log`; `npm run build`'s own
|
||||
output goes to `client-build.log` next to it; the server's own output goes to
|
||||
`server.log`.
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Does |
|
||||
|---|---|
|
||||
| `/ccam-doctor` | Node version, bootstrap state, runtime deps, server liveness, duplicate hooks, CLI launcher + PATH, MCP build freshness, UI bundle |
|
||||
| `/ccam-update` | Reinstall dependencies and restart the server against the current plugin version (`plugin-bootstrap.js --force`) |
|
||||
| `/ccam-open` | Build the UI bundle if missing, then print the dashboard URL |
|
||||
|
||||
### Where things live
|
||||
|
||||
| Thing | Location | Survives a plugin update |
|
||||
|---|---|---|
|
||||
| SQLite DB, transcripts | `~/.claude/agent-dashboard/` | yes |
|
||||
| `node_modules`, `client-dist`, logs, lock, `state.json` | `~/.claude/agent-dashboard/runtime/` | yes (re-verified on every session start) |
|
||||
| Source, hooks, `mcp/build` | the plugin cache version directory | no — re-bootstrapped |
|
||||
|
||||
The bootstrap deliberately does **not** set `DASHBOARD_DATA_DIR`: leaving the
|
||||
default keeps a plugin-run server and a developer's `npm run dev` server on one
|
||||
data directory, where `ingestGroupKey` already deduplicates hook ingest to a
|
||||
single port.
|
||||
|
||||
### Uninstalling
|
||||
|
||||
`claude plugin uninstall ccam@claude-code-agent-monitor-plugins` removes the hooks
|
||||
and the cached source. It does not touch the state the plugin created outside
|
||||
the cache — remove those by hand if you want a clean machine:
|
||||
|
||||
```bash
|
||||
kill "$(node -e 'console.log(JSON.parse(require("fs").readFileSync(require("os").homedir()+"/.claude/.agent-dashboard.json","utf8")).servers[0].pid)')"
|
||||
rm -rf ~/.claude/agent-dashboard/runtime # deps, UI bundle, logs, state
|
||||
rm -f ~/.local/bin/ccam # the CLI launcher
|
||||
# ~/.claude/agent-dashboard/ still holds the database — delete it only if you
|
||||
# want the recorded history gone too.
|
||||
```
|
||||
|
||||
## Available Plugins
|
||||
|
||||
### 1. `ccam-analytics` — Analytics & Monitoring
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
# Plugin-first install — CCAM 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 CCAM — hooks flowing, server up, dashboard
|
||||
reachable, `ccam` CLI on PATH, MCP tools connected:
|
||||
|
||||
```
|
||||
/plugin marketplace add <ccam repo>
|
||||
/plugin install ccam@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, `ccam-dashboard`, declares its MCP
|
||||
server as `../../mcp/build/index.js` (`plugins/ccam-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 `ccam` with `"source": "."` in
|
||||
`.claude-plugin/marketplace.json`. Claude Code then caches the *entire repo*
|
||||
into `~/.claude/plugins/cache/<marketplace>/ccam/<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/ccam.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:
|
||||
|
||||
```json
|
||||
{ "name": "ccam", "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. `/.ccam/` 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` `require`s `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 CCAM 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/ccam.js` to `~/.local/bin/ccam`. Skills and
|
||||
commands throughout the repo (`ccam stage`, `ship-feature-lane`) shell out
|
||||
to `ccam` 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 `/ccam-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. `/ccam-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 `CCAM_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/ccam-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/ccam/commands/`
|
||||
|
||||
The plugin root is the repo root, so `plugin.json` declares an explicit
|
||||
`"commands": "./plugins/ccam/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.
|
||||
|
||||
- `/ccam-doctor` — bootstrap state, Node version, server liveness, DB path,
|
||||
duplicate-hook detection, CLI symlink, MCP build freshness.
|
||||
- `/ccam-update` — refresh dependencies and restart the server after a plugin
|
||||
update.
|
||||
- `/ccam-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 `/ccam-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.
|
||||
- *`ccam` not on PATH* when `~/.local/bin` is absent from the user's PATH.
|
||||
`/ccam-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>/ccam/*/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.
|
||||
Reference in New Issue
Block a user