e109d2804c
A codex session emits no hooks and rarely calls `ccam stage` on its own, so its card sat at `idle` forever while the amber "detected" badge was the only thing that ever moved. `codex-ingest.js` now advances the declared `stage` from its own written detection — the one place in CCAM where an inference moves the blue `current` ring, and only for `codex` lanes. It stays honest: gated on `recordDetection` having written (so forward-only, declared-wins and the detection TTL are inherited unchanged), passes NO evidence (the node renders `passed-no-evidence` amber, never `done`), and no template's last node carries detect rules, so a lane can never be auto-declared `done`. `setStage` grows a `keepDetection` option — its sole caller — so the detection columns survive the advance and the card keeps showing which command moved it instead of looking like an agent's own claim. Two levers then get the evidence auto-advance cannot supply: - `codex-nudge.js` now nudges on a stage held 2+ min with no evidence, instead of on a detection sitting ahead of the declared stage — after auto-advance the old condition can never fire, since the two now match. - `ccam lanes agents install` on a codex lane writes the marker-delimited `<!-- ccam:stage:start -->` block into the lane's own AGENTS.md — the one instruction channel Codex reads — instead of copying agent templates into `.claude/agents/`, which it never loads. Appended, user prose preserved, reinstall replaces exactly that block. Verified with a real `codex exec` run: stage moved to `watching-pr` with `evidence: null` and the detection signal intact. 1364 server tests pass.
206 lines
8.9 KiB
Markdown
206 lines
8.9 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.
|
|
|
|
## 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 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
|