3223c8edac
A pipeline was all-or-nothing: `ccam ship-full` / `ship-quick` hand the whole
thing to a skill, and a node's own `command` — added so the map would stop
hiding the driver behind its detect regexes — was display-only. Re-running just
the CI gate, or redoing the brainstorm, meant clicking copy on the node and
pasting the string into a session by hand.
`ccam lanes node [<id>] [extra…] [--print]` runs it, in the lane's own cwd. A
slash-command node spawns `claude` with it as the prompt (same shape as
`ccam ship-full`); anything else is spawned directly. No id lists every node
and its command, which is also what the two error paths print.
The node is matched by id OR alias, the same way `ccam stage` matches one:
`default` calls the step `implement` and `ship-feature` calls it `implementing`,
and one command name must not depend on which template the lane happens to be
on. That needs the template's `aliases`, which the lane payload's
`pipeline_nodes` does not carry — so the two are merged, template for the
definition, lane for the `current` marker.
Both shipped templates now fill EVERY node's `command`. The seven that were
empty are the nodes whose only work IS the declaration — a terminal `done`, a
gate another agent declares — so they get `ccam stage <id>`, which keeps all 16
(and all 8) runnable instead of leaving holes a caller has to know about.
Slash commands for the same thing, because a session should not shell out to a
nested `claude`: `/intake`, `/plan`, `/implement`, `/tests`, `/e2e`,
`/e2e-passed`, `/review`, `/qc-plan`, `/qc`, `/gate`, `/publishing`, `/pr-open`,
`/reported`, `/watching-pr`, `/merged`, `/done`, plus `/ship-full` and
`/ship-quick`. Each resolves its node through `ccam lanes node <id> --print` and
runs the result IN the current session — invoking the slash command itself when
that is what the node names, otherwise running the shell argv. Resolving at call
time rather than baking the string into the file is what stops the 18 command
files from drifting when a template changes.
Two limits are deliberate:
- The command is split on whitespace and spawned WITHOUT a shell. A template
is data — anyone can drop a file into DASHBOARD_PIPELINES_DIR — so its
`command` must never be interpreted as shell. A node needing a pipe names a
script that contains one.
- Running a node does not declare its stage. A command executing is not proof
the node passed, same boundary as the console never writing a lane's stage.
Verified: npm run test:server (1403, +3 covering alias matching, full command
coverage of both templates, and every command file being listed in the
manifest), npm run test:client (459), prettier and check-headers clean. Docs:
docs/LANES.md § Running ONE node, docs/PLUGINS.md commands table, README +
README.vi CLI blocks, CLAUDE.md marker.
234 lines
10 KiB
Markdown
234 lines
10 KiB
Markdown
# Claude Code Monitor
|
|
|
|
Internal SmartGift build. Local-first dashboard for Claude Code: hooks POST every
|
|
tool call to an Express + SQLite server, a React UI updates over WebSocket, and
|
|
**lanes** track parallel agent work through a pipeline.
|
|
|
|
Internal build — all rights reserved.
|
|
|
|
*(Tiếng Việt: [README.vi.md](README.vi.md))*
|
|
|
|
## What it does
|
|
|
|
- **Sessions, agents, events.** Everything Claude Code emits, recorded and
|
|
searchable: tool calls, token usage, cost, subagent trees, transcripts.
|
|
- **Lanes.** One lane per working directory, surviving session restarts. A lane
|
|
moves through pipeline stages and the dashboard shows where it is.
|
|
- **Stage detection.** The stage is inferred from the tool stream, so a session
|
|
that never calls `ccam stage` still shows progress — rendered dashed amber and
|
|
never as done, because an inference is not evidence.
|
|
- **Run Claude Code or Codex from the browser.** A real terminal (tmux + a real
|
|
PTY, rendered with xterm.js) attached to a lane's directory — the exact TUI
|
|
you'd see locally, fully interactive, resumable, and attachable from a real
|
|
terminal too via `ccam lanes shell`. Which CLI it starts is the lane's own
|
|
setting.
|
|
- **Analytics (in-dashboard tab), alerts, and a workflow view**, plus an MCP server and a CLI.
|
|
|
|
## Requirements
|
|
|
|
Node **>= 20** (`engines` in `package.json`). Node **24** is what the test suites
|
|
are verified on — node 25 currently breaks 6 server tests through a
|
|
better-sqlite3 ABI mismatch and 20 client tests through a global `localStorage`
|
|
change.
|
|
|
|
The **`superpowers` plugin is required by the ship-feature pipelines only**, not
|
|
by the dashboard. `/ccam:ship-feature` and `/ccam:ship-feature-lane` drive
|
|
`superpowers:brainstorming`, `writing-plans`, `test-driven-development`,
|
|
`requesting-code-review` and `verification-before-completion` phase by phase,
|
|
and both built-in pipeline templates name those same slash commands as their
|
|
per-node drivers. Without it the dashboard, hooks, CLI and MCP all work
|
|
normally — the two pipeline skills are what stop partway. `/ccam-doctor` reports
|
|
it as a `WARN` row.
|
|
|
|
**You do not normally have to install it.** The plugin's `SessionStart`
|
|
bootstrap installs it for you, once, in its detached worker — so a session never
|
|
waits on the clone, and the current session needs `/reload-plugins` before the
|
|
skills resolve (any later session picks them up on its own). The outcome is
|
|
logged to `~/.claude/agent-dashboard/runtime/bootstrap.log`, and `/ccam-update`
|
|
retries a failed one. To install it by hand instead, or after opting out:
|
|
|
|
```bash
|
|
claude plugin install superpowers@claude-plugins-official
|
|
```
|
|
|
|
Set `CCAM_AUTO_INSTALL_PLUGINS=0` to opt out of the automatic install. Only the
|
|
plugins on a fixed allowlist in `server/lib/skill-plugins.js` are ever installed
|
|
this way — `superpowers` today. A custom pipeline template naming some other
|
|
plugin's command is *reported* by `/ccam-doctor` and never installed, because a
|
|
template is a file anyone can drop into `DASHBOARD_PIPELINES_DIR`.
|
|
|
|
## Install as a Claude Code plugin
|
|
|
|
Two commands on a machine that has nothing but Claude Code, no clone and no
|
|
`npm run setup`:
|
|
|
|
```
|
|
/plugin marketplace add Smartgift-AI/Claude-Code-Monitor
|
|
/plugin install ccam@claude-code-agent-monitor-plugins
|
|
```
|
|
|
|
The first session start installs the hooks, boots the server, puts `ccam` on
|
|
PATH and connects the MCP tools; it runs detached, so the session never waits on
|
|
it. `/ccam-doctor` reports the state, `/ccam-open` builds the UI and prints the
|
|
URL, `/ccam-update` refreshes after a plugin update. This path needs Node
|
|
**>= 22.5** (no native `better-sqlite3`, so the server uses `node:sqlite`).
|
|
Details, including what to delete on uninstall:
|
|
[`docs/PLUGINS.md`](docs/PLUGINS.md).
|
|
|
|
## Install from a checkout
|
|
|
|
```bash
|
|
npm run setup # root, client and vscode-extension dependencies
|
|
npm run build # builds the client into client/dist
|
|
npm start # serves the built client and the API on :4820
|
|
```
|
|
|
|
Open <http://localhost:4820>.
|
|
|
|
Development, with hot reload:
|
|
|
|
```bash
|
|
npm run dev # server on :4820, Vite client on :5173
|
|
```
|
|
|
|
`DASHBOARD_PORT` overrides the port, `DASHBOARD_CLIENT_DIST` overrides where the
|
|
built UI is served from (defaults to `client/dist`; the plugin install points it
|
|
at its own runtime directory). `postinstall` writes the Claude Code hook entries
|
|
that feed the dashboard — do not run it when the `ccam` plugin is installed, or
|
|
every event is counted twice.
|
|
|
|
### Run as a service (auto-start on boot)
|
|
|
|
`npm start` only runs while that terminal (or `ccam start`'s background
|
|
process) is alive — a reboot loses it. On Linux, `ccam service install` sets
|
|
up a systemd `--user` service instead, so `:4820` is up again on its own after
|
|
every reboot, with no manual step and no Claude Code (or any AI tool) needed
|
|
to trigger it:
|
|
|
|
```bash
|
|
npm run build # the service serves the built client, same as npm start
|
|
ccam service install # writes + enables ~/.config/systemd/user/ccam.service
|
|
ccam service status # systemctl --user status ccam
|
|
ccam service uninstall # disable and remove it
|
|
```
|
|
|
|
`ccam service install` also tries `loginctl enable-linger` so the service
|
|
starts on a cold boot, before anyone logs in; if that needs elevated rights on
|
|
your machine it prints the `sudo loginctl enable-linger <user>` command to run
|
|
once yourself. Plugin installs get the same persistence automatically — see
|
|
[Auto-start on boot](docs/PLUGINS.md#auto-start-on-boot) in `docs/PLUGINS.md`.
|
|
|
|
## The CLI
|
|
|
|
`ccam` is linked by `npm run setup`; otherwise call `node bin/ccam.js`.
|
|
|
|
```bash
|
|
ccam status # is the dashboard up
|
|
ccam start # start it in the background and wait for healthy
|
|
ccam service install # auto-start on boot via systemd --user (Linux)
|
|
ccam sessions # recent sessions
|
|
ccam lanes # lanes with stage and progress
|
|
ccam lanes pipeline # this lane's pipeline template, or switch it
|
|
ccam lanes node <id> # run ONE pipeline node's own command (no id: list them)
|
|
# (in a session: /intake, /qc, /ship-full, /ship-quick …)
|
|
ccam stage <name> # declare the current lane's stage
|
|
ccam tail # live event feed
|
|
```
|
|
|
|
`ccam --help` lists the rest.
|
|
|
|
## Lanes
|
|
|
|
A lane is a working directory the dashboard watches. Two kinds:
|
|
|
|
- **adopted** — a directory you already had. The dashboard only reads it; it is
|
|
never reset or deleted.
|
|
- **managed** — a git worktree the dashboard created under `LANES_ROOT`. It owns
|
|
the full lifecycle and may reset or remove it, behind a three-check destroy
|
|
guard and a counted preflight the caller has to echo back.
|
|
|
|
```bash
|
|
ccam lanes add --cwd /path/to/repo --title "My feature" # adopt
|
|
ccam lanes add --repo /path/to/repo --slug my-feature # managed worktree
|
|
```
|
|
|
|
Every lane also carries the CLI that drives it — `claude` (the default) or
|
|
`codex` — picked in the Add-lane modal and fixed for the life of the lane. A
|
|
Codex lane's run panel offers Codex's own sandbox, approval and reasoning-effort
|
|
choices, and resumes with `codex resume --last`. Codex emits none of this
|
|
dashboard's hooks, so a Codex lane records its runs but stays absent from
|
|
Sessions and Analytics. Its **stage detection** works anyway, from Codex's own
|
|
rollout journals on disk (`$CODEX_HOME/sessions/**/rollout-*.jsonl`, polled every
|
|
`DASHBOARD_CODEX_POLL_MS` ms, default 15000), and on a Codex lane a detection
|
|
also **advances the declared stage** — the one place an inference moves the blue
|
|
`current` ring, because a Codex session has no hook and nothing else would ever
|
|
move it. What a Codex lane cannot get for free is *evidence*: an auto-advanced
|
|
node stays amber (`passed-no-evidence`), never green, since only the session
|
|
itself can say what it finished. Three things help it get there: the run console
|
|
pre-fills a codex pane's opening prompt with a stage-declaration checklist built
|
|
from its own pipeline nodes, `ccam lanes agents install` writes the same
|
|
instructions as a marker-delimited block in the lane's `AGENTS.md`, and a 60s
|
|
sweep (`DASHBOARD_CODEX_NUDGE_MS`, `0` disables) types a one-line reminder into
|
|
a lane's pane if its current stage sits without evidence for more than two
|
|
minutes.
|
|
|
|
The declared stage comes from `ccam stage`. The inferred stage comes from tool
|
|
events and expires after `DETECTION_TTL_MS` (default 5 minutes), so a lane can
|
|
move backwards between work sessions. Detection never writes the declared stage,
|
|
and an inferred node never renders as done.
|
|
|
|
A lane's pipeline template can be switched after creation — `ccam lanes
|
|
pipeline <template>` from the terminal, or the pipeline-template picker next to
|
|
the lane's title in the Workspace detail panel. Both re-resolve the lane's
|
|
current declared stage against the new template's nodes and warn if it no
|
|
longer matches one.
|
|
|
|
A lane can also run **its own application stack**, isolated per lane, when its
|
|
repository declares a profile at `<repo>/.ccam/profile/` — a `profile.env` of
|
|
declarations plus shell hooks the dashboard calls. Each lane gets a slot, and its
|
|
ports and per-lane directories derive from it:
|
|
|
|
```bash
|
|
ccam lanes up # boot the stack of the lane owning this directory
|
|
ccam lanes runtime # slot, ports, service health
|
|
ccam lanes logs api # tail a service log
|
|
ccam lanes down
|
|
```
|
|
|
|
Services are fully detached, so restarting the dashboard never stops a running
|
|
lane. This is resource namespacing on the host, not a container: lanes run as the
|
|
same user and share the network.
|
|
|
|
[`docs/LANES.md`](docs/LANES.md) has the pipeline model, the destroy guard, the
|
|
preflight contract, the Workspace page, `GET /api/lanes/:id/git`, and the full
|
|
runtime/profile contract.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
npm run test:server # node:test
|
|
npm run test:client # Vitest
|
|
```
|
|
|
|
Both must be green before a commit; the pre-commit hook runs them plus Prettier.
|
|
|
|
## Layout
|
|
|
|
| Path | What |
|
|
|---|---|
|
|
| `server/` | Express API, SQLite schema, hook ingest, lane and worktree libraries |
|
|
| `client/` | React 18 + Vite + Tailwind dashboard |
|
|
| `bin/ccam.js` | CLI |
|
|
| `mcp/` | MCP server exposing read-only dashboard tools |
|
|
| `docs/` | Architecture, API, lanes, database |
|
|
| `plugins/` | Claude Code plugins shipped with the dashboard |
|
|
|
|
## Docs
|
|
|
|
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — request flow, schema, WebSocket surface
|
|
- [`docs/LANES.md`](docs/LANES.md) — lanes, pipelines, stage detection
|
|
- [`docs/API.md`](docs/API.md) — REST endpoints (`openapi.yaml` is generated)
|
|
- [`docs/DATABASE.md`](docs/DATABASE.md) — tables and migrations
|
|
- [`INSTALL.md`](INSTALL.md)
|
|
- [`CLAUDE.md`](CLAUDE.md) — the rules an agent working in this repo must follow
|