Adds ccam service install/uninstall/status for checkout installs and wires
the same systemd --user unit into plugin-bootstrap.js, so plugin-installed
colleagues get boot persistence too instead of needing a Claude Code
session to (re)start the server after every reboot.
A plugin update applied mid-session (/plugin followed by /reload-plugins)
fires no SessionStart, so the bootstrap's version handover never runs and the
dashboard server keeps executing a cache directory Claude Code has already
discarded. /ccam-open then rebuilt the bundle from the new root against that
old server, and the new bundle called routes the old server never had:
/api/lanes/pipeline-stats fell through to /:id and rendered "lane not found",
/api/workflows/skill-usage fell through to the SPA catch-all and broke
JSON.parse on the returned HTML. Both Workflows panels showed an error while
the code they needed was sitting on disk.
plugin-open now re-runs bootstrap when the live server's recorded pluginRoot
differs from the current one, or when no server is live at all. Bootstrap
already owns the stop/start and the dependency install the new root may need,
so this only restores the trigger that a mid-session update skips.
The Claude Config page was read-only for these four surfaces. It can now:
- register a marketplace from a pasted GitHub `owner/repo`, http(s) URL or
absolute path, and install / enable / disable / update / uninstall plugins,
all delegated to the `claude plugin …` CLI so its registry stays consistent;
- edit hooks and settings.json keys in the user, project and project-local
layers.
Server:
- `server/lib/cc-plugin-cli.js` wraps the CLI with `execFile` and an explicit
argv array — never a shell string. Every argument is validated before the
spawn (shape allowlist per kind, no control characters, no leading `-`, no
`..`), `CLAUDE_CONFIG_DIR` is pinned to the dashboard's CLAUDE_HOME, stdin is
closed so a prompt errors instead of hanging, and mutating calls are
serialized in-process.
- `cc-mutate.patchSettings` merges individual dotted keys into one settings
layer. It re-reads the file unredacted, so keys the UI never displayed —
including secret-valued ones — survive byte-for-byte. A payload carrying the
`<redacted>` placeholder is refused (EREDACTED), as are `__proto__` /
`prototype` / `constructor` segments; a settings file that does not parse is
reported (EBADJSON) rather than clobbered. Backup first, then atomic write.
- Six additive routes on /api/cc-config; a non-zero CLI exit is a 422 carrying
the CLI's own message, a missing binary a 503.
Client: install/add bars, per-card plugin and marketplace actions behind a
two-click confirm, and edit modes for hooks and settings that send only the
keys that changed. New i18n keys in en + vi; the explainer banners that said
these surfaces were read-only now describe what the writes actually do.
Tests: 61 new server tests (CLI argv construction and validation, the settings
key-merge, and the six routes against a fake `claude` on PATH).
Move .claude/agents/ and server/data/agent-templates/ship-feature-lane/
into one agents/ at the repo root — which IS the plugin root, so all five
agents now ship with the ccam plugin as ccam:<name>, the same way skills
already do.
The two pipeline agents were byte-identical copies in both places; that
duplication is gone. `ccam lanes agents install` now reads them straight
from agents/, so the per-lane copy in <lane>/.claude/agents/ still gives
the bare `subagent_type: qc-local` the ship-feature-lane skill prefers.
Skill and docs record both names: bare inside a lane, ccam: as fallback.
The repo root already IS the `ccam` plugin root (`"source": "./"`), so the
conventional `skills/` dir at the root is the plugin's skills dir — while
`.claude/skills/` only loaded for a session whose cwd was inside this
checkout. That is useless for a lane working in another repo, which is why
`ccam skills install` existed at all.
Move all seven skills to `skills/`: they now reach every plugin user as
`/ccam:<name>` (bare `/<name>` as an alias). `ccam skills install` stays as
the fallback for a plain checkout without the plugin, sourcing from the new
path.
Agents deliberately do NOT move. A plugin agent is addressed as
`ccam:qc-local`, which would break the `subagent_type: qc-local` references
in ship-feature-lane; `ccam lanes agents install` keeps writing them into the
lane's own `.claude/agents/`, the highest-priority agent source. MCP was
already plugin-shipped via `plugins/ccam/.mcp.json`.
Mechanical fallout: ~115 source files carry a doc-comment pointer to
`.claude/skills/file-headers/`, repointed to `skills/file-headers/`. The
generic Claude Code config-surface examples in openapi/cc-config (`.claude/
skills/code-reviewer`) describe any user's layout and are left alone.
Also fixes the two file-headers scripts, whose `ROOT=` climbed four levels
from `.claude/skills/file-headers/scripts/` and would have escaped the repo
from the new depth — it scanned sibling projects until corrected to three.
npm run test:server (1160 pass), test:client (422 pass), mcp:typecheck,
mcp:build + mcp:check-build all green.
Docs and the command's own instructions previously implied /ccam-update
fetches new code; it only reinstalled deps and restarted the server
against whatever version was already resolved. It now also runs
`claude plugin marketplace update` + `claude plugin update ccam@<mp> -y`
first, and says plainly that /reload-plugins is still a manual step
(no CLI/script equivalent exists to apply it automatically).
Verified against a real claude plugin uninstall: it only drops the plugin
from the enabled list. The server keeps running, the cached source stays on
disk, and the hook entries claude plugin install wrote into settings.json
are left behind pointing at the now-uninstalled cache dir — silently fails
once Claude Code eventually GCs it. The previous instructions ("uninstall
removes the hooks and the cached source") were untested assumptions; this
adds the missing settings.json cleanup step.
claude plugin install materializes the plugin's inline hooks into
~/.claude/settings.json itself, with \${CLAUDE_PLUGIN_ROOT} resolved to the
actual cache path — confirmed by installing the plugin for real and
inspecting the file. Those entries also contain "hook-handler.js", so
isOurEntry()'s plain substring match could not tell a legitimate
plugin-installed hook from a leftover npm run install-hooks entry: every
SessionStart would have stripped the plugin's own working hooks right back
out. isCheckoutHookEntry() only removes entries whose command does NOT
resolve under ~/.claude/plugins/cache/. plugin-doctor.js's duplicate-hook
count uses the same predicate.
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>
Internal SmartGift build of a Claude Code monitoring dashboard.
Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.
Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.
Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.