server/lib/ai-tools.js loads server/data/ai-tools/*.json (38 tools, data from
Orca, MIT) plus DASHBOARD_AI_TOOLS_DIR, detects each CLI by walking PATH, and
keeps enable/default in <data dir>/ai-tools.json. shell-path.js merges the
login shell's PATH at startup and on Refresh. /api/ai-tools lists, refreshes
and toggles; /api/run/binary resolves through the catalog.
Lanes accept any catalog id (EDISABLED for a disabled one on creation, ENOTOOL
at start once a tool left the catalog). pty-run keeps the Claude/Codex
builders and builds every other tool's argv from its catalog file, pasting an
after-start prompt through a tmux buffer; every pane gets the hydrated PATH.
A per-run bypass switch appends the catalog's bypass args (Codex included).
POST /api/run now forwards sandbox/approval/resumeLast, which it dropped.
agenthub lanes add gains --tool.
Part A — a fetch/pull/push against a remote that needs a login no longer
dies on "terminal prompts disabled": the server answers 409 EGITAUTH with
the host, the git bar asks for a username + password/access token and
resends the same action. AgentHub keeps its OWN credential store
(<CLAUDE_HOME>/agenthub-git-credentials/, 0700/0600), one login per host,
shared by every user of the install, managed from Settings -> Git
credentials. A login is only sent to one of the repo's own remote hosts,
saved only after git accepted it, never returned by any route, and reaches
git through an exclusive credential helper (never argv).
Part B — merge a branch (or the diverged upstream) from the git bar; a
merge that stops on conflicts lists them in the Changes panel with Keep
mine / Take theirs / Mark resolved, then Complete merge or Abort merge
(confirm gate). Clean tree, repo-root lane and an existing ref only;
pull/switch/hunk actions are blocked mid-merge; staging a conflicted file
with markers left is refused. Rebase/reset/stash stay out.
- Settings → Appearance: mode Dark / Light / System (follows the OS live) and
a named theme remembered per mode — dark: Default, Darcula, Apple Dark;
light: Default, Apple Light, IntelliJ Light, Canvas. Each theme is a
CSS-variable override block keyed by `data-theme` on <html>; the sidebar
quick toggle gains System and swaps between the two remembered themes.
- An inline boot script in index.html applies the stored mode and theme
before first paint, so a reload no longer flashes the default colours.
- Diff +/- rows are easier to tell apart on every theme: a /20 status tint,
a 3px status bar and a bold coloured marker; the changed-span emphasis no
longer vanishes on light themes.
- #42: the Add-lane modal (Repo mode / "Current branch") asks
POST /api/lanes/ensure with `exact: true`. Without it a folder nested inside
an existing lane resolved to that parent lane with created:false, so the
modal closed and no new card appeared. When the folder really already has a
lane, the modal now says which one instead of closing.
Closes#42
A new **AI Router** tab in Settings, next to Codex, stores named router
endpoints (9Router, OpenRouter, a self-hosted gateway) as profiles: a base
URL, an API key, a default model and an optional per-tier override. The tab
only stores them; a lane CHOOSES one in the Add-lane modal, via a Native /
AI Router radio, and that choice is written into the lane's own config.
The two CLIs are configured differently, and the difference is theirs:
- a claude lane gets eight `env` keys merged into its own
`.claude/settings.local.json` (`.local` because the value holds a key);
- a codex lane gets `$CODEX_HOME/<name>.config.toml` — the layer
`codex -p <name>` loads — because Codex has NO project-level config.
Its base `config.toml` is never touched.
Native is a built-in profile rather than a code branch, so "go back to
native" is the same write path. It blanks the two connection variables
instead of removing them (an absent key at project scope lets a USER-level
router apply) and removes the model names instead of writing them (writing
them would override the user's own global model choice).
Key material never leaves the server: profiles live one file each under
`<CLAUDE_HOME>/agenthub-routers/` (0700/0600), `GET /api/ai-routers`
reports only `hasKey`, and there is deliberately no route returning one
profile in full.
Three things this had to get right, each found by review and verified
against the real tools rather than assumed:
- `wire_api = "chat"` is rejected outright by codex-cli 0.149 ("no longer
supported"), so every codex lane on a router would have failed to start.
The layer writes "responses", and writes `env_key` only when the profile
actually has a key — naming an unset variable is itself a startup error.
- The key must never be TYPED into a pane. `tmux.sendCommand` used
`send-keys` with a `KEY='…' cmd` prefix, which the dashboard streams to
every attached browser and bash appends to history; it now takes over the
idle pane with `respawn-pane -k -e`, passing env through argv the way
`newSession` always has. Verified on tmux 3.4: multi-element argv arrives
intact and the pane renders no trace of the value.
- Attaching COPIES a profile, so editing or deleting one in Settings has to
reach the lanes already using it. An edit re-applies it (a rotated key
would otherwise never reach the lane still holding the revoked one); a
delete resets those lanes to native and removes the Codex layer file.
Both report per-lane failures, which the tab surfaces.
`.claude/settings.local.json` is added to the lane's git exclude, for both
native and router lanes: `agenthub lanes reset` runs `git clean -fd` without
`-x`, so an un-excluded settings file is deleted on reset — and untracked
meant the key was committable.
DB: `lanes.router_profile`, nullable, additive. Written only by
`setRouterProfile` beside the disk write, never through `PATCHABLE`.
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).
A lane on the 16-node `ship-feature` template showed an untouched pipeline
map and never declared a stage. Four independent causes, fixed at their
shared choke points:
1. Codex emits no hooks, so `touchLaneFromHook` — the only caller of
`stage-detect.detect()`, and correctly pinned to `claude` — could never
see a `codex` lane. New `server/lib/codex-ingest.js` tails Codex's own
rollout journals (`$CODEX_HOME/sessions/**/rollout-*.jsonl`), maps its
tool calls onto the hook-shaped `{tool_name, tool_input}` the matcher
already understands, and feeds the existing detect path plus an
idle/running status mirror under the same guard hooks use. Polled every
`DASHBOARD_CODEX_POLL_MS` ms (default 15000, `0` disables).
2. `resolveLaneArg` matched on cwd alone, so with a claude and a codex lane
at one directory (the composite `idx_lanes_cwd_tool`) a `ccam stage`
call could drive the sibling lane's card. It now resolves by
`--lane` → `CCAM_LANE_ID` → `--tool`/harness marker → owning lane, and
errors naming both candidates rather than picking one. Every pane the
dashboard starts carries `CCAM_LANE_ID` (`tmux new-session -e`, or an
assignment prefix when typing into a live shell pane).
3. `recordDetection`'s declared-wins veto had no exit: a lane left at the
pipeline's last node rejected every detection for the rest of its life.
A declaration on the LAST node no longer vetoes — the pipeline is over
there, so a later detection is new work. Nothing else is relaxed, and
detection still never writes `stage` nor renders `done`.
4. The run console pre-filled the pipeline's Claude slash command into
codex panes, which Codex reads as prose — so the skill that would have
called `ccam stage` never ran. Claude lanes only now.
A codex lane's *declared* stage still depends on the session calling
`ccam stage`; `AGENTS.md` is the only lever CCAM has, so it now says so.
Verified: server 1345/1345, client 441/441, and end-to-end against a real
codex lane row (detected_stage/detected_signal set, declared stage
untouched, no node rendered done).
These were committed on main's local history but this worktree
branched from origin/main, which doesn't have them yet — copying the
files in so subagent-driven-development has a plan to read from this
branch.
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>
3 tasks: POST /:id/profile/init route (wraps already-built
lane-detect.js), AddLaneModal.tsx wiring (Promise.allSettled over
profile-init + agents-install + mcp-sync, none blocking lane creation
or each other) + client API method + tests, and docs.
One click instead of four: after AddLaneModal creates a worktree lane,
fire profile-init + agents-install + mcp-sync in parallel
(Promise.allSettled, best-effort — none of the three blocks lane
creation or each other). Needs one new route (POST
/:id/profile/init, wrapping already-built lane-detect.js) since
profile-init was CLI-only until now.
LaneCard gets agents-install/mcp-sync buttons, integration status
badges, and a sync-base --check button (read-only preflight only — no
merge button, that stays a session/skill action). Settings gets
skills-install and housekeeping (gc) buttons, machine-wide. Two new
routes needed (POST /api/skills/install, POST /api/lanes/gc) since
those two primitives were CLI-only until now.
Scopes lane-gc.sh down to the two pieces that match CCAM's actual
architecture (orphan Playwright MCP reap, oversized-log capping).
Drops auto-removing stale worktrees by age — that's exactly the kind
of automatic destructive action this repo's own CLAUDE.md forbids
(destroy always goes through the three-check guard, never automatic).
State archiving and scratch-debris sweep don't apply either (different
storage architecture; CCAM doesn't generate those files).
Found auditing E1-F3a for Shipyard parity: .claude/skills/ship-feature-lane/
only exists inside ccam-lanes' own repo, never installed globally, so
/ship-feature-lane is unreachable from its actual intended invocation
context (a session inside some OTHER lane's repo). Shipyard's own
install-claude-assets.sh solves this for its bundled skills the same way.
3 tasks: isIntegrationEnabled in lane-profile.js (reuses parseEnvFile),
the route + CLI, and wiring SKILL.md's hardcoded-off Setup text to the
real check (still no-op behaviorally — no ticketer/dev-qc agent exists
to act on an enabled toggle yet, but the check itself is now honest).
Scopes down F3 to just the generic <name>_ENABLED toggle-check primitive
(ccam lanes integration <name>) — ticketer/dev-qc agents stay deferred,
same reasoning E3 already used for qc-local's credential gap: no tracker/
dev-QC MCP configured anywhere, no per-lane credential source to consume.
4 tasks: the lane-mcp.js sync core (relocate + pin + seed profiles, no
permission/settings writes per the design spec's scope decision), the
route, the CLI, and docs.
Scopes the first of F's four pieces to .mcp.json relocation + Chromium
profile seeding only. Drops Shipyard's permission/auto-approval writes
(no existing CCAM pattern for a backend command granting permissions,
and the rules named agents not in scope: pr-reviewer has no driving
skill to port, ticketer is a later task) and the node-version wrapper
(environment-specific workaround, no evidence this repo needs it).
5 tasks: the two ported agent templates (every Shipyard placeholder/script
reference resolved at port time), the lane-agents.js install core (git-dir
vs git-common-dir correctness verified against a real worktree fixture,
same lesson E2 already learned for info/attributes), the route, the CLI,
and docs.
Scopes the third of E's remaining pieces to the two agents the current
skill text actually invokes (Stage 6/7). ticketer/dev-qc (F-gated) and
pr-reviewer (unreferenced) stay out of scope; per-lane credential
embedding is deferred pending a seed-account system this repo doesn't
have yet.
5 tasks: MIGRATIONS_DIR/GENERATED_MERGE_PATHS profile declarations, the
lane-sync.js git core (check/merge/continue, ported against a real
bare-origin fixture mirroring lane-sync-dev.sh's own test suite, plus a
dedicated git-worktree fixture to catch the git-dir vs git-common-dir
distinction MERGE_HEAD/info-attributes depend on), the sync-base route,
the CLI subcommand, and the SKILL.md/docs edits that turn three "if it
exists yet" conditionals into real instructions.
Scopes the second of E's remaining pieces: migration-collision preflight,
keep-ours merge driver for generated files, and the single sanctioned
origin/development-into-feature-branch merge. Agents and F's integrations
stay out of scope, per the roadmap's own subsystem split.
4 tasks: --qc boot flag + QC_BOOT_ENV, pipeline template JSON, the ported
skill text (Stages 0-14, integrations hardcoded off pending F), and a dry
run + docs. Corrected the pipeline-template task against the real node
schema (id/label/icon/gate/aliases, not the detect.stage sketch) and the
real getPipeline never-throws behavior during self-review.
Scopes the first of E's four independent pieces: --qc boot flag +
QC_BOOT_ENV, the pipeline template, and the ported skill text. Agents,
sync-base, and F's integrations are named but deliberately out of scope,
per the roadmap's own subsystem split.
Help text for "lanes up|down" implied --no-build applied to both;
it's only read by up. Also commits the A3 profile-scaffolding design
spec and implementation plan that weren't yet in git.
Gives each lane its own slot-derived runtime (ports, detached process
lifecycle, profile-driven hooks) and its own database/Redis logical
index/.env file, so two lanes running the same repo's stack at once no
longer share state. Machine-level DB/Redis credentials live at
~/.ccam/secrets.env (mode 0600, never returned by any route); a hook's
output is redacted of that password (raw and URL-encoded forms) before
it reaches a log file or the lane_hook_output websocket broadcast.
Wired into provision/up/reset/remove; reset accepts --keep-db to skip
the drop/recreate/migrate/reseed block entirely.
Adds a working Dark/Light toggle (next to the language switcher, same row
as EN/VI) and re-themes the whole dashboard, not just the handful of
components that already used semantic tokens.
- Tailwind darkMode:"class" + CSS-variable color tokens (client/src/index.css,
tailwind.config.js): surface.0-5, border/border-light, accent/accent-hover,
fg.primary/secondary/muted, status.success/danger/warning. One class flip
on <html> re-themes everything — no per-element dark: variant pairs.
- useTheme() hook: localStorage-persisted, defaults to dark, no
prefers-color-scheme fallback (client/src/hooks/useTheme.ts).
- Mechanical, table-driven migration (scripts/migrate-color-tokens.mjs,
scripts/tokenize-status-colors.mjs, scripts/darken-status-colors.mjs) of
every raw neutral/gray/slate + emerald/red/amber Tailwind utility across
client/src onto the new tokens, so every badge/button/component pulls the
same shade per status/role instead of each picking its own.
- Palette values are the literal Radix Colors (radix-ui.com/colors) scale
constants — slate/blue/green/red/amber steps 1-12 — adopted after three
rounds of hand-picked values that kept overshooting (flat, then too dark,
then glaring); see docs/superpowers/specs/2026-07-31-color-redesign-
dark-light-mode-design.md for the full history and role mapping.
- PipelineMap: done/current/failed/passed-no-evidence/detected share one
visual language (border + text + translucent wash of the same status
color); `current` alone stays a solid accent fill, the one state that
gets to look bolder ("you are here").
- LaneCard: removed the stage/kind/auto-stage chips that duplicated the
Workspace lane-detail header already showing them.
Categorical/decorative hues (violet, indigo, cyan, teal, sky, rose, pink,
orange, yellow, and blue where it plays a role-coloring part e.g. message
bubbles) are deliberately out of scope — collapsing those onto shared
tokens would erase the distinction between different kinds of thing, not
a status.
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.