diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 73c7fb69..224107db 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,6 +1,6 @@ { "name": "claude-code-agent-monitor-plugins", - "description": "Official plugin marketplace for Claude Code Agent Monitor — the `ccam` plugin installs the dashboard itself (hooks, server, CLI, MCP), plus 10 focused plugins for analytics, cost guardrails, productivity, developer tools, AI insights, session forensics, workflow/fleet intelligence, reliability/SLOs, config & memory governance, and dashboard connectivity. Every plugin is powered by the local Agent Monitor API.", + "description": "Official plugin marketplace for AgentHub — the `agenthub` plugin installs the dashboard itself (hooks, server, CLI, MCP), plus 10 focused plugins for analytics, cost guardrails, productivity, developer tools, AI insights, session forensics, workflow/fleet intelligence, reliability/SLOs, config & memory governance, and dashboard connectivity. Every plugin is powered by the local Agent Monitor API.", "owner": { "name": "smartgift", "url": "https://git.smartgift.io.vn/Smartgift-AI" @@ -9,68 +9,68 @@ "repository": "https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor", "plugins": [ { - "name": "ccam", + "name": "agenthub", "source": "./", - "description": "The dashboard itself. Installs the Claude Code event hooks, boots the local server, puts the `ccam` CLI on PATH and connects the MCP tools — no checkout and no `npm run setup`. Install this one first; the ten focused plugins below all read from the API it provides.", + "description": "The dashboard itself. Installs the Claude Code event hooks, boots the local server, puts the `agenthub` CLI on PATH and connects the MCP tools — no checkout and no `npm run setup`. Install this one first; the ten focused plugins below all read from the API it provides.", "tags": ["dashboard", "monitoring", "hooks", "mcp", "cli"] }, { - "name": "ccam-analytics", - "path": "plugins/ccam-analytics", + "name": "agenthub-analytics", + "path": "plugins/agenthub-analytics", "description": "Deep analytics on Claude Code sessions — token usage (total_input/total_output/total_cache_read/total_cache_write with compaction baselines pre-summed), cost breakdowns via the pricing engine, usage trends over 365 days, cache efficiency, model mix, and productivity scoring.", "tags": ["analytics", "tokens", "cost", "productivity"] }, { - "name": "ccam-cost-guard", - "path": "plugins/ccam-cost-guard", + "name": "agenthub-cost-guard", + "path": "plugins/agenthub-cost-guard", "description": "Budget guardrails — set spend limits, forecast week/month-end cost from daily trends, surface cost-threshold alerts via the alert-rule API, and quantify model-routing savings.", "tags": ["budget", "cost", "forecast", "alerts"] }, { - "name": "ccam-productivity", - "path": "plugins/ccam-productivity", + "name": "agenthub-productivity", + "path": "plugins/agenthub-productivity", "description": "Productivity automation — daily standups, weekly/monthly reviews, sprint summaries, time-of-day focus analysis, and workflow optimization using the workflow intelligence API's 11 datasets.", "tags": ["productivity", "reports", "workflows", "standup"] }, { - "name": "ccam-devtools", - "path": "plugins/ccam-devtools", + "name": "agenthub-devtools", + "path": "plugins/agenthub-devtools", "description": "Developer tools for the Agent Monitor — session debugging with full event-chain inspection, data-integrity inspection, event tracing, transcript search, hook diagnostics, data export (JSON/CSV/Markdown), and system health checks.", "tags": ["devtools", "debug", "diagnostics", "export"] }, { - "name": "ccam-insights", - "path": "plugins/ccam-insights", + "name": "agenthub-insights", + "path": "plugins/agenthub-insights", "description": "AI-powered insights — pattern detection via workflow intelligence (tool flow transitions, agent co-occurrence), anomaly alerting, trend forecasting, regression watch, benchmarking, optimization recommendations, and session comparison.", "tags": ["insights", "patterns", "anomaly", "optimization"] }, { - "name": "ccam-sessions", - "path": "plugins/ccam-sessions", + "name": "agenthub-sessions", + "path": "plugins/agenthub-sessions", "description": "Session forensics — search by project/model/status, reconstruct event timelines, replay transcripts turn-by-turn, roll up activity per working directory, and review stale sessions before cleanup.", "tags": ["sessions", "search", "transcript", "replay"] }, { - "name": "ccam-workflows", - "path": "plugins/ccam-workflows", + "name": "agenthub-workflows", + "path": "plugins/agenthub-workflows", "description": "Multi-agent orchestration & fleet intelligence — map the subagent DAG, audit model delegation and effectiveness, report concurrency lanes, trace error propagation by depth, and review Workflow-tool fleet runs.", "tags": ["workflows", "orchestration", "subagents", "fleet"] }, { - "name": "ccam-quality", - "path": "plugins/ccam-quality", + "name": "agenthub-quality", + "path": "plugins/agenthub-quality", "description": "Reliability & SLOs — scan for API errors and tool failures, audit hook delivery health (PreToolUse/PostToolUse balance), track completion/error-rate SLOs with error budgets, and alert on reliability regressions.", "tags": ["reliability", "errors", "slo", "quality"] }, { - "name": "ccam-config", - "path": "plugins/ccam-config", + "name": "agenthub-config", + "path": "plugins/agenthub-config", "description": "Claude Code config & memory governance — audit skills/subagents/commands/MCP servers/hooks/settings via the Config Explorer API and curate the file-based memory store (CLAUDE.md + per-project auto-memory files).", "tags": ["config", "memory", "governance", "mcp"] }, { - "name": "ccam-dashboard", - "path": "plugins/ccam-dashboard", + "name": "agenthub-dashboard", + "path": "plugins/agenthub-dashboard", "description": "Dashboard connector — quick status checks, one-line metrics, live activity watch, endpoint probing, and MCP server integration for direct tool access to the Agent Monitor API.", "tags": ["dashboard", "status", "mcp", "connector"] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 56da6e4c..92f04c62 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { - "name": "ccam", - "description": "Claude Code Agent Monitor — the full local-first dashboard as a plugin. Installs the event hooks, boots the dashboard server, puts the `ccam` CLI on PATH, and connects the MCP tools. No checkout, no npm run setup.", + "name": "agenthub", + "description": "AgentHub — the full local-first dashboard as a plugin. Installs the event hooks, boots the dashboard server, puts the `agenthub` CLI on PATH, and connects the MCP tools. No checkout, no npm run setup.", "author": { "name": "Nguyễn Ngọc Trí Vĩ", "url": "https://git.smartgift.io.vn/Smartgift-AI" @@ -10,29 +10,29 @@ "license": "MIT", "keywords": ["dashboard", "monitoring", "sessions", "cost", "lanes", "mcp", "claude-code"], "commands": [ - "./plugins/ccam/commands/ccam-doctor.md", - "./plugins/ccam/commands/ccam-update.md", - "./plugins/ccam/commands/ccam-open.md", - "./plugins/ccam/commands/intake.md", - "./plugins/ccam/commands/plan.md", - "./plugins/ccam/commands/implement.md", - "./plugins/ccam/commands/tests.md", - "./plugins/ccam/commands/e2e.md", - "./plugins/ccam/commands/e2e-passed.md", - "./plugins/ccam/commands/review.md", - "./plugins/ccam/commands/qc-plan.md", - "./plugins/ccam/commands/qc.md", - "./plugins/ccam/commands/gate.md", - "./plugins/ccam/commands/publishing.md", - "./plugins/ccam/commands/pr-open.md", - "./plugins/ccam/commands/reported.md", - "./plugins/ccam/commands/watching-pr.md", - "./plugins/ccam/commands/merged.md", - "./plugins/ccam/commands/done.md", - "./plugins/ccam/commands/ship-full.md", - "./plugins/ccam/commands/ship-quick.md" + "./plugins/agenthub/commands/agenthub-doctor.md", + "./plugins/agenthub/commands/agenthub-update.md", + "./plugins/agenthub/commands/agenthub-open.md", + "./plugins/agenthub/commands/intake.md", + "./plugins/agenthub/commands/plan.md", + "./plugins/agenthub/commands/implement.md", + "./plugins/agenthub/commands/tests.md", + "./plugins/agenthub/commands/e2e.md", + "./plugins/agenthub/commands/e2e-passed.md", + "./plugins/agenthub/commands/review.md", + "./plugins/agenthub/commands/qc-plan.md", + "./plugins/agenthub/commands/qc.md", + "./plugins/agenthub/commands/gate.md", + "./plugins/agenthub/commands/publishing.md", + "./plugins/agenthub/commands/pr-open.md", + "./plugins/agenthub/commands/reported.md", + "./plugins/agenthub/commands/watching-pr.md", + "./plugins/agenthub/commands/merged.md", + "./plugins/agenthub/commands/done.md", + "./plugins/agenthub/commands/ship-full.md", + "./plugins/agenthub/commands/ship-quick.md" ], - "mcpServers": "./plugins/ccam/.mcp.json", + "mcpServers": "./plugins/agenthub/.mcp.json", "hooks": { "PreToolUse": [ { @@ -101,7 +101,7 @@ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/plugin-bootstrap.js\"", "timeout": 10, - "statusMessage": "Checking the CCAM dashboard..." + "statusMessage": "Checking the AgentHub dashboard..." }, { "type": "command", diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 00253c25..6f9b0fcd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -# Optional dev-container image for Claude Code Agent Monitor. +# Optional dev-container image for AgentHub. # Node 22 to match the production Dockerfile and the project's engines. # Adds the native-addon toolchain (better-sqlite3 builds via node-gyp) plus # Python 3 (statusline.py and helper scripts) and the sqlite3 CLI. diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 0a04a89a..486963e4 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,6 +1,6 @@ # Dev Container (optional) -A ready-to-use, **opt-in** development environment for Claude Code Agent Monitor. +A ready-to-use, **opt-in** development environment for AgentHub. It is used **only** when you explicitly choose it — it changes nothing about host-based development (`npm run dev` / `npm start` still work exactly as before). @@ -62,7 +62,7 @@ The host hook handler POSTs to `http://localhost:4820`, which this container forwards — so a host-installed hook reaches the containerized dashboard. > Escape hatch: if you genuinely run Claude Code *inside* this same container, -> set `CCAM_ALLOW_CONTAINER_HOOKS=1` before `npm run install-hooks`. +> set `AGENTHUB_ALLOW_CONTAINER_HOOKS=1` before `npm run install-hooks`. ## Not supported in the container diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a7685aa2..1a0995af 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { // ───────────────────────────────────────────────────────────────────────── - // Optional, opt-in dev environment for Claude Code Agent Monitor. + // Optional, opt-in dev environment for AgentHub. // Used only when you choose "Dev Containers: Reopen in Container" (VS Code) or // open the repo in a GitHub Codespace. It changes nothing for host-based dev. // @@ -13,7 +13,7 @@ // installer refuses to run inside a container. Run `npm run install-hooks` on // your host so hooks POST to http://localhost:4820 (forwarded from here). // ───────────────────────────────────────────────────────────────────────── - "name": "Claude Code Agent Monitor", + "name": "AgentHub", "build": { "dockerfile": "Dockerfile", "context": ".." diff --git a/.env.example b/.env.example index 5f259176..045326ad 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ # DASHBOARD_PORT=4820 # Directory the built client is served from in production (default: client/dist). -# The `ccam` plugin install points this at ~/.claude/agent-dashboard/runtime/client-dist +# The `agenthub` plugin install points this at ~/.claude/agent-dashboard/runtime/client-dist # because the plugin cache directory is read-only and replaced on every update. # DASHBOARD_CLIENT_DIST= @@ -98,8 +98,8 @@ # Directory dashboard-managed worktrees are provisioned under. It is also the # boundary safety check 2 resolves every destructive lane path against: nothing # outside it can be reset or removed. Keep it readable only by the service user -# (default: ~/.claude/ccam-lanes). -# LANES_ROOT=~/.claude/ccam-lanes +# (default: ~/.claude/agenthub-lanes). +# LANES_ROOT=~/.claude/agenthub-lanes # Default base branch a new managed worktree resolves against when the request # omits `base`. An explicit --base / request body `base` always wins diff --git a/.gitignore b/.gitignore index bb2376c5..bc6e1578 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,8 @@ desktop/assets/icon.iconset/ *.db-shm # Local lane profile - only present when this repo itself is adopted as a -# lane (`ccam lanes add --cwd`); machine-specific runtime config, not source. -/.ccam/ +# lane (`agenthub lanes add --cwd`); machine-specific runtime config, not source. +/.agenthub/ # Environment variables .env diff --git a/AGENTS.md b/AGENTS.md index 7bda2674..a633e0ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,27 +13,27 @@ ## Declare your lane stage as you work -This repository is watched by CCAM, its own dashboard. A **lane** is keyed by +This repository is watched by AgentHub, its own dashboard. A **lane** is keyed by working directory, and the pipeline map only moves forward when the agent doing the work declares its stage: ```bash -ccam stage --tool codex --evidence "" +agenthub stage --tool codex --evidence "" ``` - Call it at every real transition — starting to plan, starting to implement, running tests, opening the PR — not once at the end. - Stage names, `default` template: `intake → plan → implement → tests → review → gate → ship → done` (the 16-node `ship-feature` template accepts these as - aliases too; `ccam stage` warns on stderr and names the valid nodes if a stage + aliases too; `agenthub stage` warns on stderr and names the valid nodes if a stage matches none). - **`--tool codex` matters.** One directory can back both a `claude` lane and a `codex` lane, and without a tool the command fails rather than guess (it names both candidates). Inside a pane the dashboard started you need nothing — that - pane carries `CCAM_LANE_ID`, which outranks everything but `--lane`. -- If `ccam stage` reports that no lane owns the directory, this checkout was + pane carries `AGENTHUB_LANE_ID`, which outranks everything but `--lane`. +- If `agenthub stage` reports that no lane owns the directory, this checkout was never adopted: skip the stage calls and carry on. Stage reporting is - reporting, never control flow — a failed `ccam stage` never justifies skipping + reporting, never control flow — a failed `agenthub stage` never justifies skipping a step of the actual work. The dashboard also *infers* your stage from your tool calls (it reads Codex's @@ -44,8 +44,8 @@ green. Only your own declaration does that — which is why the call above is worth making even when the map already looks right. If you hold a stage for a while with no evidence recorded, the dashboard may -type a one-line `[ccam]` reminder straight into this pane asking you to run -`ccam stage ... --evidence "..."`. That is the dashboard, not the user — treat +type a one-line `[agenthub]` reminder straight into this pane asking you to run +`agenthub stage ... --evidence "..."`. That is the dashboard, not the user — treat it as the same instruction as this section and just make the real call. ## Where to work diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6a097045..371c8b68 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -78,7 +78,7 @@ Architectural overview and technical reference for the Agent Dashboard system, c Agent Dashboard is a local-first monitoring platform for Claude Code sessions. It captures agent lifecycle events via Claude Code's native hook system, persists them in SQLite, and presents them through a React dashboard with real-time WebSocket updates. -> **Cursor sessions (informational):** The same `~/.claude` transcript paths also pick up **Cursor** agent sessions — Cursor happens to use that layout locally alongside Claude Code. CCAM does not distinguish which editor created a session. +> **Cursor sessions (informational):** The same `~/.claude` transcript paths also pick up **Cursor** agent sessions — Cursor happens to use that layout locally alongside Claude Code. AgentHub does not distinguish which editor created a session. ```mermaid C4Context @@ -124,16 +124,16 @@ graph TB subgraph "Plugin Layer" direction TB PM["Plugin Marketplace
(10 plugins, 53 skills)"] - PA["ccam-analytics"] - PP["ccam-productivity"] - PD["ccam-devtools"] - PI["ccam-insights"] - PC["ccam-dashboard"] - PG["ccam-cost-guard"] - PS["ccam-sessions"] - PW["ccam-workflows"] - PQ["ccam-quality"] - PF["ccam-config"] + PA["agenthub-analytics"] + PP["agenthub-productivity"] + PD["agenthub-devtools"] + PI["agenthub-insights"] + PC["agenthub-dashboard"] + PG["agenthub-cost-guard"] + PS["agenthub-sessions"] + PW["agenthub-workflows"] + PQ["agenthub-quality"] + PF["agenthub-config"] PM --> PA & PP & PD & PI & PC & PG & PS & PW & PQ & PF end @@ -337,11 +337,11 @@ graph TD | `routes/workflows.js` | Aggregate workflow visualization data (agent orchestration graphs, tool transition flows, collaboration networks, workflow pattern detection, model delegation, error propagation, concurrency timelines, session complexity metrics, compaction impact). Accepts `?status=active\|completed` query parameter to filter all data by session status. `GET /api/workflows/skill-usage` reports per-skill and per-plugin call counts, counting `PreToolUse` rows only so a call is not double-counted against its `PostToolUse` twin. Per-session drill-in endpoint with agent tree, tool timeline, and event details | | `lib/transcript-cache.js` | Stat-based JSONL transcript cache with incremental byte-offset reads. Shared between `hooks.js` (token extraction on every event) and the periodic compaction scanner (`index.js`). Extracts tokens, compaction entries, API errors (`isApiErrorMessage` + raw error responses), turn durations (`system` subtype `turn_duration`), thinking block counts, usage extras (service_tier, speed, inference_geo), user-interrupt markers (the transcript `[Request interrupted by user]` entry / `interruptedMessageId` field — surfaced as `pendingInterrupt`, computed from transcript ordering: latest interrupt vs latest real turn activity, both on Claude Code's clock), and the latest session title — `custom-title` (`/rename`, `claude -n`, picker Ctrl+R) and `ai-title` (auto / plan-accept), append-only so the last value wins, carried through both full and incremental reads — plus the session's **first user prompt** (`firstUserMessage`: tool-result, meta/caveat, slash-command plumbing, compact-summary, and interrupt entries skipped; whitespace-collapsed, capped at 500 chars; first value wins across incremental reads), used as a fallback descriptor for placeholder-named sessions/agents. Uses `(path, mtime, size)` cache key — unchanged files return cached results instantly, grown files only parse new bytes, shrunk files (compaction) trigger full re-read. Each cache entry stores **only** `{mtimeMs, size, bytesRead, result}` — the previous shape that duplicated every growable array at both the top level and inside `result` is gone, halving steady-state memory per entry. Per-entry growable arrays (`turnDurations`, `errors`, `compaction.entries`, `usageExtras.*`) are bounded to `TRANSCRIPT_CACHE_MAX_ARRAY_LEN` (default `1000`, tail-kept) — older items remain in the `events` table thanks to hook dedup, so the cap only affects the in-memory view. Trimming runs both during parse (when an array reaches `2 * MAX_ARRAY_LEN`, amortized O(N)) and at finalize, so even a fresh full-file parse on a multi-day session cannot accumulate an unbounded transient before returning. **Chunked sync byte-stream reader** (`_streamRange`, 4 MiB chunks split on `0x0A` bytes — safe across UTF-8 multibyte sequences — with a growable per-line byte buffer capped at 64 MiB) replaces the previous `readFileSync("utf8")` so transcripts larger than V8's max JS string length (~512 MiB on 64-bit Node 20) parse without aborting Node with `FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal`. Both full and incremental reads share the same line-level state machine (`_initParseState` / `_consumeLine` / `_finalizeState`). LRU eviction caps at 200 entries. Entries evicted on SessionEnd and abandoned session cleanup | | `lib/session-liveness.js` | Process-liveness probe for the watchdog's dead-session reap. `probeLiveCwds()` enumerates running `claude` CLI processes (`ps -Ao pid=,args=`, then `lsof -a -p -d cwd -Fn` on macOS or `/proc//cwd` on Linux) and returns the set of their working directories; `isClaudeCommand()` matches the bare binary and `node`/`bun`-launched shims while rejecting lookalikes (`claude-mem`, `Claude.app`). Fail-safe by contract: returns `available: false` (callers must change nothing) on Windows, inside containers (reuses `isInsideContainer` from `scripts/install-hooks.js` — host processes are invisible there, so an empty list would lie), on `ps`/`lsof` failure, or when `DASHBOARD_LIVENESS_PROBE=0` | -| `bin/ccam.js` | Dependency-free umbrella CLI (`ccam `) exposing the full dashboard surface in the terminal: monitoring (health / stats / kanban / tail via short-interval event polling), data browsing (sessions, per-session detail with an indented agent tree + cost + recent events, agents, events), insights (analytics, workflow intelligence, dynamic Workflow-tool runs, per-model cost), alerts + rules + webhook test probes, pricing CRUD, imports (rescan / scan-path), and administration (doctor, info, export, cleanup, reinstall-hooks, clear-data). Linked globally by `npm run setup` via a fail-soft `npm link` (`link-cli` script). Discovers the live server through `server/lib/server-info.js` (`~/.claude/.agent-dashboard.json`, PID-liveness-checked) with `CLAUDE_DASHBOARD_PORT` / `DASHBOARD_PORT` env overrides and a 4820 fallback; renders a full terminal UI — box-drawn width-fitted tables with right-aligned numeric columns, status icons, inline bar charts (stats / analytics / cost), `├─`/`└─` agent trees, and a TTY spinner for `start` — whose ANSI styling degrades to plain text when piped (`--no-color` / `NO_COLOR` / `FORCE_COLOR` / `CCAM_COLOR` respected); the one destructive command (`clear-data`) refuses to run without `--yes`. Server lifecycle: `ccam status` shows a ●/○ up/down indicator and `ccam start` boots a detached production server (waits for /api/health, logs to `data/ccam-server.log`). `ccam repl` (aliases `shell` / `i`) opens an interactive shell — a readline prompt with tab-completion (commands / subcommands / flags), persisted arrow-key history (`data/.ccam_repl_history`), and a live server-status prompt; each entered line runs as a short-lived child `ccam` process (via `runCommand` dispatch), so an offline refusal or a blocking `tail` cannot take the shell down, and piped input runs each line in order then exits at EOF. When the server is down, read-only commands (sessions / session / agents / events / kanban / stats / pricing list / alerts list / rules / export / doctor) fall back to direct SQLite reads of `data/dashboard.db` under a ⚠ Offline-mode banner — the connection is opened without SQLite readonly mode so a live WAL stays visible, and is SELECT-only by construction — while server-only commands (tail, analytics, workflows, runs, cost, mutations) refuse with the specific reason | -| `scripts/import-history.js` | Batch history importer used by (a) server startup auto-import, (b) the `/api/import/*` routes, (c) the `import-history` CLI, and (d) live `SubagentStop` ingestion via the exported `scanAndImportSubagents(dbModule, sessionId, transcriptPath)`. Exposes `importAllSessions(dbModule)` for the default `~/.claude/projects` tree, `syncDefaultProjects(dbModule, {mtimeCache})` (the incremental, mtime-fingerprinted re-sweep that backs the continuous background sync — parses only new/changed files and reports `[{sessionId, isNew}]`), and the generalized `importFromDirectory(dbModule, rootDir, {onProgress})` which walks any directory recursively, classifies each `.jsonl` as session vs subagent (with `findSessionSubagents` probing both `//subagents/*` and `/subagents//*` layouts), and funnels everything through the shared `parseSessionFile` + `importSession` pipeline. The durable transcript snapshot (`snapshotTranscript`) additionally preserves **nested** Workflow-tool inner-agent transcripts (`subagents/workflows//agent-*.jsonl`) via the separate `findSessionWorkflowSubagents` probe — mirroring the run subpath so the read route resolves the snapshot identically to the live file, without pulling those nested agents into the flat sub-agent import (no double-count). After each batch imports, `importAllSessions` / `importFromDirectory` also call `ingestWorkflowsForSession` (from `server/lib/workflow-ingest.js`) per session — outside the SQLite transaction, since the ingest is async — so a **Workflow-tool** run whose journal never reached a live server (a headless `claude -p` run, a CI job, or an HPC/cluster node emits no hooks) still links its inner agents to their `run_id` on a plain `ccam import rescan` / `ccam import path`, instead of leaving them orphaned (`workflow_run_id = NULL`) with the run stuck at 1 agent — see "Workflow-Tool Run Ingestion". `parseSubagentFile` extracts ordered `toolEvents` (tool_use + tool_result paired by `tool_use_id`) so `importSubagentFromJsonl` can emit per-tool `PreToolUse` + `PostToolUse` rows under each subagent's own `agent_id`. The importer dedups against live hook-created subagent rows via `findLiveSubagentForJsonl` (session + subagent_type + start-time within 30 s) so backfill never produces parallel `-jsonl-*` rows. It also **skips `importSubagents` entirely when subagent transcripts exist** — the main-transcript `Agent`-block rows (`-subagent-N`) and the transcript rows (`-jsonl-*`) would otherwise both be created and only deduped by a fragile type+timing match, doubling every subagent; when transcripts are present the richer `-jsonl-` rows are authoritative and the `-subagent-N` fallback runs only when there are none. `importSession` now also **persists `transcript_path`** on the session row (via `setSessionTranscriptPath`) so the abandon sweep, compaction scanner, and per-agent cost backfill can locate the transcript later, and **stamps each subagent's own token buckets into `metadata.tokens`** (used for per-agent cost). `backfillSubagentTokenMetadata` (a deferred, self-limiting startup pass) fills `metadata.tokens` on subagents that predate per-agent cost, deriving the transcript path from `//.jsonl` when `transcript_path` is null and covering both subagent-dir layouts — metadata-only, so it never touches session `token_usage`. `classifyJsonl` treats any file under a `subagents/` ancestor at any depth (including the `subagents/workflows//` tree) as a subagent, so workflow inner-agent transcripts are never misimported as top-level sessions. **Nested-subagent hierarchy** is rebuilt by `reconcileSubagentParents`: every subagent is inserted flat under the main agent (no single hook/JSONL carries the spawner id), then `parseSubagentFile` also returns `spawnedChildren` — the child agent ids named on each Task tool result (`toolUseResult.agentId`) — which are inverted to a child→parent map and used to repoint `parent_agent_id` (via the `setAgentParent` statement) so a subagent that spawns its own subagents nests under its true spawner instead of collapsing flat under main; any subagent no other subagent claims stays under main. It resolves both the child and parent to their live-or-jsonl DB id (mirroring `importSubagentFromJsonl`), so it also corrects the live hook heuristic's guesses once the transcripts land. Idempotent and additive (only rewrites `parent_agent_id`), it runs in all three group-import paths (`importSession` ×2, live `scanAndImportSubagents`); `scanAndImportSubagents` returns a `reparented` count alongside `created`. **Re-import is fully incremental**: for each existing session a per-event-type high-water mark (`MAX(created_at) GROUP BY event_type`) is read up-front and only JSONL entries with `ts > cutoff[type]` are inserted for Stop / PostToolUse / TurnDuration / ToolError — so long-running sessions whose transcripts grow across multiple days continue to receive new events on every re-run instead of being blocked by the old "if zero of type X then dump all" check. `sessions.ended_at` is rolled forward to the JSONL's last activity when it surpasses the stored value, and `metadata.user_messages` / `assistant_messages` / `turn_count` are refreshed on every pass. `parseSessionFile` also captures the transcript title (`custom-title` / `ai-title`) and `importSession` prefers it for `sessions.name` over the cwd-folder fallback, backfilling existing auto/placeholder names on re-import (same precedence as the live hook sync). Other idempotency keys are unchanged: `data LIKE '%"tool_use_id":"X"%'` skips any tool event already inserted, compaction agents/events dedup by uuid, API errors dedup by summary, and `baseline_*` columns preserve pre-compaction token totals. Token totals, per-model cost, compactions, subagents, tool events, API errors, and turn durations are identical to live ingestion. Creates `APIError`, `TurnDuration`, and `ToolError` event types during import; subagent tool events carry `imported: true, source: "subagent_jsonl"` in their data payload so analytics can distinguish backfilled rows when needed | -| `server/routes/import.js` | Express router for the Import History feature. Three endpoints funnel into the same pipeline: `POST /api/import/rescan` (default projects dir), `POST /api/import/scan-path` (arbitrary absolute dir with `~` expansion), `POST /api/import/upload` (multer multipart accepting `.jsonl`, `.meta.json`, `.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`). `GET /api/import/guide` returns OS-aware instructions + archive command + default-dir stats. Each request uses a per-request temp dir (`req._ccamUploadDir` for multer staging, a separate `workDir` for extraction) that is reclaimed in `finally`. Progress is broadcast as `import.progress` websocket messages throttled at ~150 ms. Limits configurable via `CCAM_IMPORT_MAX_BYTES` / `CCAM_IMPORT_MAX_FILES` | -| `server/lib/data-transfer.js` | Full-dataset export/import ("backup / restore"). `buildExportBundle(db, stmts)` serializes every user-owned table (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing) into one versioned JSON bundle (`format: "ccam-export"`); machine-bound/secret tables (push_subscriptions, webhook_targets/deliveries, alert_events) are excluded. `importExportBundle(db, bundle)` restores it session-atomically inside one transaction with `defer_foreign_keys` ON: a session already present (by UUID) is skipped WHOLE (with its agents/events/token_usage/workflows) so re-import and cross-machine merges never duplicate or clobber; events are re-inserted without their non-portable autoincrement id; config rows use `INSERT OR IGNORE` on their natural key. Backs `GET/POST /api/settings/{export,import}` and `ccam import-data` | -| `server/lib/archive.js` | Safe archive extraction: `.zip` via `adm-zip`, `.tar`/`.tar.gz`/`.tgz` via `tar`, plain `.gz` via `zlib` in streaming mode. Every entry is validated through `safeJoin` which rejects absolute paths and `..` traversal before any bytes are written. Enforces a hard extraction cap (`MAX_EXTRACT_BYTES`, default 4 GB, tunable via `CCAM_IMPORT_MAX_EXTRACT_BYTES`) with `ExtractionLimitError` surfaced as HTTP 413 from the upload route — defense against zip/tar/gzip bombs. Also provides `detectKind` for filename-based dispatch and `mkTempDir`/`rmTempDir` helpers | +| `bin/agenthub.js` | Dependency-free umbrella CLI (`agenthub `) exposing the full dashboard surface in the terminal: monitoring (health / stats / kanban / tail via short-interval event polling), data browsing (sessions, per-session detail with an indented agent tree + cost + recent events, agents, events), insights (analytics, workflow intelligence, dynamic Workflow-tool runs, per-model cost), alerts + rules + webhook test probes, pricing CRUD, imports (rescan / scan-path), and administration (doctor, info, export, cleanup, reinstall-hooks, clear-data). Linked globally by `npm run setup` via a fail-soft `npm link` (`link-cli` script). Discovers the live server through `server/lib/server-info.js` (`~/.claude/.agent-dashboard.json`, PID-liveness-checked) with `CLAUDE_DASHBOARD_PORT` / `DASHBOARD_PORT` env overrides and a 4820 fallback; renders a full terminal UI — box-drawn width-fitted tables with right-aligned numeric columns, status icons, inline bar charts (stats / analytics / cost), `├─`/`└─` agent trees, and a TTY spinner for `start` — whose ANSI styling degrades to plain text when piped (`--no-color` / `NO_COLOR` / `FORCE_COLOR` / `AGENTHUB_COLOR` respected); the one destructive command (`clear-data`) refuses to run without `--yes`. Server lifecycle: `agenthub status` shows a ●/○ up/down indicator and `agenthub start` boots a detached production server (waits for /api/health, logs to `data/agenthub-server.log`). `agenthub repl` (aliases `shell` / `i`) opens an interactive shell — a readline prompt with tab-completion (commands / subcommands / flags), persisted arrow-key history (`data/.agenthub_repl_history`), and a live server-status prompt; each entered line runs as a short-lived child `agenthub` process (via `runCommand` dispatch), so an offline refusal or a blocking `tail` cannot take the shell down, and piped input runs each line in order then exits at EOF. When the server is down, read-only commands (sessions / session / agents / events / kanban / stats / pricing list / alerts list / rules / export / doctor) fall back to direct SQLite reads of `data/dashboard.db` under a ⚠ Offline-mode banner — the connection is opened without SQLite readonly mode so a live WAL stays visible, and is SELECT-only by construction — while server-only commands (tail, analytics, workflows, runs, cost, mutations) refuse with the specific reason | +| `scripts/import-history.js` | Batch history importer used by (a) server startup auto-import, (b) the `/api/import/*` routes, (c) the `import-history` CLI, and (d) live `SubagentStop` ingestion via the exported `scanAndImportSubagents(dbModule, sessionId, transcriptPath)`. Exposes `importAllSessions(dbModule)` for the default `~/.claude/projects` tree, `syncDefaultProjects(dbModule, {mtimeCache})` (the incremental, mtime-fingerprinted re-sweep that backs the continuous background sync — parses only new/changed files and reports `[{sessionId, isNew}]`), and the generalized `importFromDirectory(dbModule, rootDir, {onProgress})` which walks any directory recursively, classifies each `.jsonl` as session vs subagent (with `findSessionSubagents` probing both `//subagents/*` and `/subagents//*` layouts), and funnels everything through the shared `parseSessionFile` + `importSession` pipeline. The durable transcript snapshot (`snapshotTranscript`) additionally preserves **nested** Workflow-tool inner-agent transcripts (`subagents/workflows//agent-*.jsonl`) via the separate `findSessionWorkflowSubagents` probe — mirroring the run subpath so the read route resolves the snapshot identically to the live file, without pulling those nested agents into the flat sub-agent import (no double-count). After each batch imports, `importAllSessions` / `importFromDirectory` also call `ingestWorkflowsForSession` (from `server/lib/workflow-ingest.js`) per session — outside the SQLite transaction, since the ingest is async — so a **Workflow-tool** run whose journal never reached a live server (a headless `claude -p` run, a CI job, or an HPC/cluster node emits no hooks) still links its inner agents to their `run_id` on a plain `agenthub import rescan` / `agenthub import path`, instead of leaving them orphaned (`workflow_run_id = NULL`) with the run stuck at 1 agent — see "Workflow-Tool Run Ingestion". `parseSubagentFile` extracts ordered `toolEvents` (tool_use + tool_result paired by `tool_use_id`) so `importSubagentFromJsonl` can emit per-tool `PreToolUse` + `PostToolUse` rows under each subagent's own `agent_id`. The importer dedups against live hook-created subagent rows via `findLiveSubagentForJsonl` (session + subagent_type + start-time within 30 s) so backfill never produces parallel `-jsonl-*` rows. It also **skips `importSubagents` entirely when subagent transcripts exist** — the main-transcript `Agent`-block rows (`-subagent-N`) and the transcript rows (`-jsonl-*`) would otherwise both be created and only deduped by a fragile type+timing match, doubling every subagent; when transcripts are present the richer `-jsonl-` rows are authoritative and the `-subagent-N` fallback runs only when there are none. `importSession` now also **persists `transcript_path`** on the session row (via `setSessionTranscriptPath`) so the abandon sweep, compaction scanner, and per-agent cost backfill can locate the transcript later, and **stamps each subagent's own token buckets into `metadata.tokens`** (used for per-agent cost). `backfillSubagentTokenMetadata` (a deferred, self-limiting startup pass) fills `metadata.tokens` on subagents that predate per-agent cost, deriving the transcript path from `//.jsonl` when `transcript_path` is null and covering both subagent-dir layouts — metadata-only, so it never touches session `token_usage`. `classifyJsonl` treats any file under a `subagents/` ancestor at any depth (including the `subagents/workflows//` tree) as a subagent, so workflow inner-agent transcripts are never misimported as top-level sessions. **Nested-subagent hierarchy** is rebuilt by `reconcileSubagentParents`: every subagent is inserted flat under the main agent (no single hook/JSONL carries the spawner id), then `parseSubagentFile` also returns `spawnedChildren` — the child agent ids named on each Task tool result (`toolUseResult.agentId`) — which are inverted to a child→parent map and used to repoint `parent_agent_id` (via the `setAgentParent` statement) so a subagent that spawns its own subagents nests under its true spawner instead of collapsing flat under main; any subagent no other subagent claims stays under main. It resolves both the child and parent to their live-or-jsonl DB id (mirroring `importSubagentFromJsonl`), so it also corrects the live hook heuristic's guesses once the transcripts land. Idempotent and additive (only rewrites `parent_agent_id`), it runs in all three group-import paths (`importSession` ×2, live `scanAndImportSubagents`); `scanAndImportSubagents` returns a `reparented` count alongside `created`. **Re-import is fully incremental**: for each existing session a per-event-type high-water mark (`MAX(created_at) GROUP BY event_type`) is read up-front and only JSONL entries with `ts > cutoff[type]` are inserted for Stop / PostToolUse / TurnDuration / ToolError — so long-running sessions whose transcripts grow across multiple days continue to receive new events on every re-run instead of being blocked by the old "if zero of type X then dump all" check. `sessions.ended_at` is rolled forward to the JSONL's last activity when it surpasses the stored value, and `metadata.user_messages` / `assistant_messages` / `turn_count` are refreshed on every pass. `parseSessionFile` also captures the transcript title (`custom-title` / `ai-title`) and `importSession` prefers it for `sessions.name` over the cwd-folder fallback, backfilling existing auto/placeholder names on re-import (same precedence as the live hook sync). Other idempotency keys are unchanged: `data LIKE '%"tool_use_id":"X"%'` skips any tool event already inserted, compaction agents/events dedup by uuid, API errors dedup by summary, and `baseline_*` columns preserve pre-compaction token totals. Token totals, per-model cost, compactions, subagents, tool events, API errors, and turn durations are identical to live ingestion. Creates `APIError`, `TurnDuration`, and `ToolError` event types during import; subagent tool events carry `imported: true, source: "subagent_jsonl"` in their data payload so analytics can distinguish backfilled rows when needed | +| `server/routes/import.js` | Express router for the Import History feature. Three endpoints funnel into the same pipeline: `POST /api/import/rescan` (default projects dir), `POST /api/import/scan-path` (arbitrary absolute dir with `~` expansion), `POST /api/import/upload` (multer multipart accepting `.jsonl`, `.meta.json`, `.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`). `GET /api/import/guide` returns OS-aware instructions + archive command + default-dir stats. Each request uses a per-request temp dir (`req._agenthubUploadDir` for multer staging, a separate `workDir` for extraction) that is reclaimed in `finally`. Progress is broadcast as `import.progress` websocket messages throttled at ~150 ms. Limits configurable via `AGENTHUB_IMPORT_MAX_BYTES` / `AGENTHUB_IMPORT_MAX_FILES` | +| `server/lib/data-transfer.js` | Full-dataset export/import ("backup / restore"). `buildExportBundle(db, stmts)` serializes every user-owned table (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing) into one versioned JSON bundle (`format: "agenthub-export"`); machine-bound/secret tables (push_subscriptions, webhook_targets/deliveries, alert_events) are excluded. `importExportBundle(db, bundle)` restores it session-atomically inside one transaction with `defer_foreign_keys` ON: a session already present (by UUID) is skipped WHOLE (with its agents/events/token_usage/workflows) so re-import and cross-machine merges never duplicate or clobber; events are re-inserted without their non-portable autoincrement id; config rows use `INSERT OR IGNORE` on their natural key. Backs `GET/POST /api/settings/{export,import}` and `agenthub import-data` | +| `server/lib/archive.js` | Safe archive extraction: `.zip` via `adm-zip`, `.tar`/`.tar.gz`/`.tgz` via `tar`, plain `.gz` via `zlib` in streaming mode. Every entry is validated through `safeJoin` which rejects absolute paths and `..` traversal before any bytes are written. Enforces a hard extraction cap (`MAX_EXTRACT_BYTES`, default 4 GB, tunable via `AGENTHUB_IMPORT_MAX_EXTRACT_BYTES`) with `ExtractionLimitError` surfaced as HTTP 413 from the upload route — defense against zip/tar/gzip bombs. Also provides `detectKind` for filename-based dispatch and `mkTempDir`/`rmTempDir` helpers | | `server/lib/remote-sync.js` | **Remote Data Sources** — live remote/multi-machine data collection over SSH. Mirrors the remote's `~/.claude/projects` via **scp** (or `wsl.exe` + `tar` for WSL-hosted Claude on Windows SSH) into a **sandboxed per-source staging dir** under the data dir, then feeds it through the **same** importer used for local history (`importFromDirectory` from `scripts/import-history.js`) and tags every imported session with the source id (`sessions.source`). Also runs the connectivity probe behind the test route. Auth **defers entirely to the host SSH stack** (ssh-agent / `~/.ssh/config` / identity file) — **no secrets are ever stored**; every shell-out uses `execFile`/`spawn` argument arrays (never a shell string), and `StrictHostKeyChecking` is left at its SSH default. Background poller `DASHBOARD_REMOTE_SYNC_MS` (default 15000); add/re-enable triggers immediate pull. Sync timeout `DASHBOARD_REMOTE_SYNC_TIMEOUT_MS` (default 600000); connectivity-test timeout `DASHBOARD_REMOTE_TEST_TIMEOUT_MS` (default 15000). Broadcasts `remote_source.status`, `remote_data.updated`, and per-session `session_created`/`session_updated` on successful sync. After each pull, `reconcileRemoteSessionStatus` sets each source session's live status from the mirror — newest JSONL event timestamp within `DASHBOARD_REMOTE_ACTIVE_WINDOW_MS` (default 600000 = 10 min) ⇒ `active`, otherwise `completed`. Remote sessions get no live hooks and are excluded from every local liveness/stale heuristic (all gated on `source = 'local'`), so this reconciliation is the sole owner of their active/completed lifecycle | | `server/routes/remote-sources.js` | HTTP surface for **Remote Data Sources**: `GET /api/remote-sources` (list), `POST /api/remote-sources` (create), `PATCH /api/remote-sources/:id`, `DELETE /api/remote-sources/:id` (`?purge=true` also deletes that source's imported sessions), `POST /api/remote-sources/:id/test` (SSH connectivity probe), and `POST /api/remote-sources/:id/sync` (on-demand pull). Delegates the pull/validation to `remote-sync.js`; broadcasts `remote_source.status` on every transition | | `server/lib/source-filter.js` | Parses the optional `?sources=` query param (comma-separated source ids) into SQL predicates + bind params, shared by the data endpoints (`GET /api/sessions`, `/api/events`, `/api/agents`, `/api/stats`, `/api/analytics`) so a client **data-scope** selection narrows every query consistently. No filter → the existing unscoped queries run unchanged | @@ -351,21 +351,21 @@ graph TD | `routes/cc-config.js` | HTTP surface for the Claude Config Explorer. Read endpoints for every surface (skills, agents, commands, output-styles, plugins, marketplaces, mcp, hooks, hook-scripts, keybindings, statusline, settings, memory, file, overview), plus mutation endpoints (`PUT /file`, `DELETE /file`, and a structured `PUT /keybindings`) that delegate to `cc-mutate.js`, plus a `GET /backups` listing for the recovery modal. After every successful PUT/DELETE the route broadcasts `cc_config_changed` over the WebSocket so any open `/cc-config` tab refetches without polling. All errors return structured `{error: {code, message}}` shapes mapped to 400/404/413/500 statuses | | `lib/cc-watcher.js` | Best-effort `fs.watch` over `~/.claude/` (recursive where the platform / Node version honors it — macOS / Windows always; Linux from Node 20) plus `~/.claude.json`. Coalesces bursts at 500 ms and broadcasts `cc_config_changed` with `{ source: "fs", paths: [...] }` so the Config Explorer picks up changes from external tools (CLI installs a plugin, manual `settings.json` edits, dropping a new skill) without a manual refresh. Started from `server/index.js` after the HTTP server boots; failures are caught and logged so a flaky watcher can't take the server down | | `lib/tmux.js` | Wrapper around tmux CLI for session management. `createSession(sessionName, cwd)` creates a new tmux session in the specified working directory. `sendCommand(sessionName, command)` sends a command into the session. `killSession(sessionName)` terminates the session. `listSessions()` returns all active sessions. Session management is the foundation for the PTY transport layer | -| `lib/pty-run.js` | PTY lifecycle for tmux-backed runs. Manages one tmux session per lane, named `ccam-lane-`. Exports `startRun()` to create/attach a session and return a `runId` opaque handle; internally uses tmux to manage the pseudoterminal. Spawned `claude` processes run inside the session and fire the dashboard's hooks like any other CLI session, so they show up in `/api/sessions`, the analytics, the Kanban board, and the Workflows page automatically. The PTY frames (terminal input/output deltas) are streamed to the client over `/ws-pty/:runId` (see `server/websocket.js`) at binary frame granularity rather than as JSON envelopes; the client's xterm.js terminal widget renders these raw PTY updates live | -| `lib/pty-attach.js` | Client-side PTY attachment via WebSocket. Establishes a `/ws-pty/:runId` connection, receives binary PTY frames, and feeds them to an xterm.js terminal instance. Handles reconnection, resize events (sending `TIOCSWINSZ` ioctl to the tmux pane), and cleanup on disconnect. A single tmux session can have many simultaneous PTY clients (browser Workspace, `ccam lanes shell` CLI, etc.), all synced live | +| `lib/pty-run.js` | PTY lifecycle for tmux-backed runs. Manages one tmux session per lane, named `agenthub-lane-`. Exports `startRun()` to create/attach a session and return a `runId` opaque handle; internally uses tmux to manage the pseudoterminal. Spawned `claude` processes run inside the session and fire the dashboard's hooks like any other CLI session, so they show up in `/api/sessions`, the analytics, the Kanban board, and the Workflows page automatically. The PTY frames (terminal input/output deltas) are streamed to the client over `/ws-pty/:runId` (see `server/websocket.js`) at binary frame granularity rather than as JSON envelopes; the client's xterm.js terminal widget renders these raw PTY updates live | +| `lib/pty-attach.js` | Client-side PTY attachment via WebSocket. Establishes a `/ws-pty/:runId` connection, receives binary PTY frames, and feeds them to an xterm.js terminal instance. Handles reconnection, resize events (sending `TIOCSWINSZ` ioctl to the tmux pane), and cleanup on disconnect. A single tmux session can have many simultaneous PTY clients (browser Workspace, `agenthub lanes shell` CLI, etc.), all synced live | | `routes/run.js` | HTTP surface for the tmux+PTY Run feature. **Same-origin guard** on every route — browser requests must come from a localhost-ish Origin (`localhost`, `127.0.0.1`, `::1`, `0.0.0.0`); missing-Origin (curl/CLI) requests pass. When `DASHBOARD_TOKEN` is configured it is **also** required on these routes (same as the rest of `/api/*`). `GET /api/run` lists all live runs (computed fresh from tmux state via `tmux list-sessions`). `GET /api/run/tmux` reports whether `tmux` is installed and on PATH (required for the feature to work). `GET /api/run/binary` probes whether `claude` is on `PATH`. `GET /api/run/cwds` suggests cwds (dashboard + home + recent from sessions table). `GET /api/run/history?laneId=...` returns persisted run history, optionally scoped to one lane. `GET /api/run/files?cwd=&q=` powers the Workspace page's `@`-file autocomplete: scoped fuzzy search inside `cwd` skipping `node_modules`, `.git`, `dist`, `build`, `.next`, `.cache`, `coverage`, `vendor`, etc., capped result count, ranked by basename match. `POST /api/run` requires `laneId` in the body and starts/attaches a tmux-backed run. `GET /api/run/:id` returns the run handle. `DELETE /api/run/:id` kills the tmux session (sends SIGTERM to the pane, escalates to SIGKILL after 5 s). The PTY frames are streamed to the client over `/ws-pty/:runId` as binary frames (not JSON), rendering a real interactive terminal in xterm.js on the Workspace page. `tmux` must be installed on the dashboard server's machine (same as better-sqlite3's native-module requirements) | -| `routes/lanes.js` | Durable-lane API. `POST /api/lanes/worktree`, `PATCH /api/lanes/:id`, destructive actions, and `DELETE /api/lanes/:id` use the Run route's same-origin guard. Worktree provisioning validates an absolute source git repository, persists a managed lane as `provisioning`, returns `202`, then uses the per-lane lock to resolve the base and add the worktree. Completion broadcasts the existing `lane_update` payload as `idle`; a git failure leaves a row that the non-destructive delete route can forget. `GET /api/lanes/:id/preflight?action=reset\|remove\|purge` produces counted confirmation facts. Confirmed `POST /:id/{reset,remove,purge}` actions require a complete `expect`, run under the same lock, kill a recorded run and wait for the spawner's actual child-exit timestamp (or return `500 ERUNTIMEOUT` before git), clear `run_id`, reject changed facts with `409 ESTALE` including expected/current diagnostics, and require `force` for unpushed managed reset/remove work. Reset and managed removal call the worktree's independent managed-kind, realpath-within-`LANES_ROOT`, and listed-worktree guard; adopted reset is refused, while adopted remove only forgets its row and never modifies its directory, and a managed lane whose directory was deleted by hand takes a prune path that still enforces the managed-kind and inside-`LANES_ROOT` checks. `start` returns `409 ERUNLIVE` rather than overwriting a live `run_id` and orphaning its child. `kind`, `source_repo`, `slug`, `base_branch`, `slot` and `ports` are not patchable — provisioning writes them through `lanesLib.setProvisioningFacts`. Worktree provisioning also runs `lane-runtime.js:provisionLane` (A2) when the repo declares a `.ccam/profile` — seed `.env`, `bootstrap`, create the database, migrate, seed — before the lane reports `idle`; `reset`/`remove` likewise call `resetLaneData`/`removeLaneData`, with `reset` accepting a body `keepDb: true` to skip the whole drop-recreate-migrate-reseed block. The runtime routes (`GET /:id/runtime`, `POST /:id/up`, `POST /:id/down`, `POST /:id/hook/:name`, `GET /:id/logs/:svc`) are registered **before** the `/:id/:action` catch-all so `up`/`down` are not swallowed as unknown actions, and are deliberately kept out of it: that catch-all drives a lane's Claude run, these drive the application the lane is working on. `GET /api/lanes/pipeline-stats` aggregates per-node pass counts over every lane and every archived feature. | +| `routes/lanes.js` | Durable-lane API. `POST /api/lanes/worktree`, `PATCH /api/lanes/:id`, destructive actions, and `DELETE /api/lanes/:id` use the Run route's same-origin guard. Worktree provisioning validates an absolute source git repository, persists a managed lane as `provisioning`, returns `202`, then uses the per-lane lock to resolve the base and add the worktree. Completion broadcasts the existing `lane_update` payload as `idle`; a git failure leaves a row that the non-destructive delete route can forget. `GET /api/lanes/:id/preflight?action=reset\|remove\|purge` produces counted confirmation facts. Confirmed `POST /:id/{reset,remove,purge}` actions require a complete `expect`, run under the same lock, kill a recorded run and wait for the spawner's actual child-exit timestamp (or return `500 ERUNTIMEOUT` before git), clear `run_id`, reject changed facts with `409 ESTALE` including expected/current diagnostics, and require `force` for unpushed managed reset/remove work. Reset and managed removal call the worktree's independent managed-kind, realpath-within-`LANES_ROOT`, and listed-worktree guard; adopted reset is refused, while adopted remove only forgets its row and never modifies its directory, and a managed lane whose directory was deleted by hand takes a prune path that still enforces the managed-kind and inside-`LANES_ROOT` checks. `start` returns `409 ERUNLIVE` rather than overwriting a live `run_id` and orphaning its child. `kind`, `source_repo`, `slug`, `base_branch`, `slot` and `ports` are not patchable — provisioning writes them through `lanesLib.setProvisioningFacts`. Worktree provisioning also runs `lane-runtime.js:provisionLane` (A2) when the repo declares a `.agenthub/profile` — seed `.env`, `bootstrap`, create the database, migrate, seed — before the lane reports `idle`; `reset`/`remove` likewise call `resetLaneData`/`removeLaneData`, with `reset` accepting a body `keepDb: true` to skip the whole drop-recreate-migrate-reseed block. The runtime routes (`GET /:id/runtime`, `POST /:id/up`, `POST /:id/down`, `POST /:id/hook/:name`, `GET /:id/logs/:svc`) are registered **before** the `/:id/:action` catch-all so `up`/`down` are not swallowed as unknown actions, and are deliberately kept out of it: that catch-all drives a lane's Claude run, these drive the application the lane is working on. `GET /api/lanes/pipeline-stats` aggregates per-node pass counts over every lane and every archived feature. | | `lib/ports.js` | TCP probing for runtime allocation. `isListening(port)` connects rather than binds (binding races with the hook about to bind, and says nothing about a listener held by another user); a connect timeout counts as occupied. `listenerPids(port)` shells to `lsof`, falls back to `ss`, and returns `[]` with a one-time warning when neither exists — a missing tool must never fail a lane operation | -| `lib/lane-slots.js` | Slot and port allocation — the numbering Shipyard gets free from fixed `lane1..lane9` directories and CCAM, keyed by `cwd`, must allocate. `allocateSlot` takes the lowest free of `LANE_MAX_SLOTS` (default 9) under the per-lane lock, with a partial unique index on `lanes.slot` as the backstop; allocation is **lazy**, so a lane that is only watched never consumes one. `releaseSlot` runs on remove but never on reset (moving a lane's ports mid-feature is a silent failure, not a fresh start). `resolvePorts` prefers `PORT_BASE_ + slot`, then steps `+100` at a time so the last digit still reads as the slot, skipping anything listening, recorded by another lane, or already taken in the same boot. `slotDirs` puts run/log state under `LANES_ROOT/.state/lane/` — outside the worktree, because `reset`'s `git clean -fd` would otherwise sweep live pid files. `dbName`/`dataFacts` (A2) derive the same kind of slot-based fact one layer up: database name, `DATABASE_URL`/`TEST_DATABASE_URL`, a Redis logical index, and the upload directory — each `null` when its owning profile declaration (`DB_PREFIX`, `REDIS`, `UPLOAD_SUBDIR`) is absent | -| `lib/lane-profile.js` | The stack seam. Resolves `/.ccam/profile/` from the lane's own working copy first (a branch that edits its boot command must boot with it), the source repo second. `profile.env` is **parsed, not sourced** — `$(…)` stays literal, because sourcing repo shell into the dashboard process would be a code-execution path. `runHook` spawns `bash ` with an argv array through a wrapper that `export -f`s `harness_spawn`/`die`, exports Shipyard's env contract verbatim (`LANE` is the **slot**, plus `_PORT` per declared port, plus A2's `DB_NAME`/`DATABASE_URL`/`TEST_DATABASE_URL`/`PG_HOST`/`PG_PORT`/`PG_USER`/`REDIS_URL`/`REDIS_HOST`/`REDIS_PORT`/`UPLOAD_DIR` when their profile declaration is present) so its profiles port unchanged, scrubs `GIT_*` exactly as `worktree.js:git()` does, redacts any `secrets.js` password from its output before it reaches the log file or the `lane_hook_output` broadcast, and streams the rest to both `$LOG_DIR/.log` and that broadcast. Hook names come from a fixed allowlist, never from a request | +| `lib/lane-slots.js` | Slot and port allocation — the numbering Shipyard gets free from fixed `lane1..lane9` directories and AgentHub, keyed by `cwd`, must allocate. `allocateSlot` takes the lowest free of `LANE_MAX_SLOTS` (default 9) under the per-lane lock, with a partial unique index on `lanes.slot` as the backstop; allocation is **lazy**, so a lane that is only watched never consumes one. `releaseSlot` runs on remove but never on reset (moving a lane's ports mid-feature is a silent failure, not a fresh start). `resolvePorts` prefers `PORT_BASE_ + slot`, then steps `+100` at a time so the last digit still reads as the slot, skipping anything listening, recorded by another lane, or already taken in the same boot. `slotDirs` puts run/log state under `LANES_ROOT/.state/lane/` — outside the worktree, because `reset`'s `git clean -fd` would otherwise sweep live pid files. `dbName`/`dataFacts` (A2) derive the same kind of slot-based fact one layer up: database name, `DATABASE_URL`/`TEST_DATABASE_URL`, a Redis logical index, and the upload directory — each `null` when its owning profile declaration (`DB_PREFIX`, `REDIS`, `UPLOAD_SUBDIR`) is absent | +| `lib/lane-profile.js` | The stack seam. Resolves `/.agenthub/profile/` from the lane's own working copy first (a branch that edits its boot command must boot with it), the source repo second. `profile.env` is **parsed, not sourced** — `$(…)` stays literal, because sourcing repo shell into the dashboard process would be a code-execution path. `runHook` spawns `bash ` with an argv array through a wrapper that `export -f`s `harness_spawn`/`die`, exports Shipyard's env contract verbatim (`LANE` is the **slot**, plus `_PORT` per declared port, plus A2's `DB_NAME`/`DATABASE_URL`/`TEST_DATABASE_URL`/`PG_HOST`/`PG_PORT`/`PG_USER`/`REDIS_URL`/`REDIS_HOST`/`REDIS_PORT`/`UPLOAD_DIR` when their profile declaration is present) so its profiles port unchanged, scrubs `GIT_*` exactly as `worktree.js:git()` does, redacts any `secrets.js` password from its output before it reaches the log file or the `lane_hook_output` broadcast, and streams the rest to both `$LOG_DIR/.log` and that broadcast. Hook names come from a fixed allowlist, never from a request | | `lib/lane-runtime.js` | Lane stack lifecycle. `upLane` runs `boot` then `health` (never `bootstrap` — that belongs to provision/reset) and leaves processes running on a failed health check, because their logs are the evidence. Before `boot` it also repairs `.env`, ensures the database exists, migrates every boot, and seeds only the boot that created the database (A2). `downLane` kills each recorded pid tree bottom-up (a parent killed first reparents children to init) and only sweeps port listeners when a pid file existed — an unconditional sweep would kill a server the user started on a lane's stale port. `runtimeFacts` **computes** liveness on every read from pid files and probes rather than caching it (plus the database name/Redis index, never a connection string), which is both correct when a process dies unobserved and why adopting a stack after a dashboard restart needs no code at all. `provisionLane`/`resetLaneData`/`removeLaneData` (A2) drive the data-isolation lifecycle at worktree-provision, reset, and remove time — see `docs/LANES.md#data-isolation-database-redis-and-env-a2`. Writes `slot`/`ports` and nothing else on the lane row: `status=running` means an agent is working, not that a server is listening, and conflating them would corrupt lane liveness | -| `lib/lane-detect.js` | (A3) Node.js project detection and `.ccam/profile/` scaffolding. `detectNode` reads `package.json` layout (root, or `backend/`+`frontend/`) and `docker-compose.yml` (a service matching `/postgres/i` or `/redis/i`) — read-only, never executes anything. Any detected name written into GENERATED SHELL TEXT (an npm script name, a compose service name) must pass a strict identifier check first; a `profile.env` value is already safe regardless, since that file is parsed, never sourced. `scaffoldProfile` writes the profile, always leaving `migrate`/`seed` as an `exit 0` TODO stub when a database is found rather than guessing a migration tool. `checkProfile` is the read-only hard gate: unresolved `TODO:`, a missing/non-executable hook, or a port already in use all fail it; a missing `~/.ccam/secrets.env` only warns | -| `lib/secrets.js` | (A2) Reads `~/.ccam/secrets.env` — machine-level database/Redis credentials, deliberately outside any repository. Parsed with `lane-profile.js`'s literal `KEY=VALUE` reader, never sourced. Falls back to local defaults (with a one-time warning) when the file is absent; refuses to load a file readable by group or world rather than trusting it. Never returned by any route | +| `lib/lane-detect.js` | (A3) Node.js project detection and `.agenthub/profile/` scaffolding. `detectNode` reads `package.json` layout (root, or `backend/`+`frontend/`) and `docker-compose.yml` (a service matching `/postgres/i` or `/redis/i`) — read-only, never executes anything. Any detected name written into GENERATED SHELL TEXT (an npm script name, a compose service name) must pass a strict identifier check first; a `profile.env` value is already safe regardless, since that file is parsed, never sourced. `scaffoldProfile` writes the profile, always leaving `migrate`/`seed` as an `exit 0` TODO stub when a database is found rather than guessing a migration tool. `checkProfile` is the read-only hard gate: unresolved `TODO:`, a missing/non-executable hook, or a port already in use all fail it; a missing `~/.agenthub/secrets.env` only warns | +| `lib/secrets.js` | (A2) Reads `~/.agenthub/secrets.env` — machine-level database/Redis credentials, deliberately outside any repository. Parsed with `lane-profile.js`'s literal `KEY=VALUE` reader, never sourced. Falls back to local defaults (with a one-time warning) when the file is absent; refuses to load a file readable by group or world rather than trusting it. Never returned by any route | | `lib/lane-env.js` | (A2) `seedEnv` copies a repo's real `.env` into a lane on first boot (or `--force`) and rewrites the declared `ENV_REWRITE` keys (`DATABASE_URL`/`REDIS_URL`/`UPLOAD_DIR`) in place, byte-identical otherwise. A `--force` refresh preserves `ENV_PRESERVE` keys (e.g. `JWT_SECRET`) from the lane's own existing file — swapping in the source's secret would 401 a running lane until reboot. Falls back to `.env.example` with a warning when the source is missing. Refuses on an adopted lane: that file is the user's real config | -| `lib/lane-services.js` | (A2) `ensureDatabase`/`dropDatabase` call the profile's `db-create`/`db-drop` hooks — CCAM stays stack-agnostic on purpose. A state-dir marker file tracks whether a slot's database was already created, since a plain `createdb` can't be re-run safely and CCAM can't assume the hook is idempotent; this is also how `upLane` knows to seed only a freshly-created database. `dropDatabase` asserts the lane is `managed` and that the name being dropped is one this lane's own slot actually derives (itself or its `_test` sibling) before spawning anything | +| `lib/lane-services.js` | (A2) `ensureDatabase`/`dropDatabase` call the profile's `db-create`/`db-drop` hooks — AgentHub stays stack-agnostic on purpose. A state-dir marker file tracks whether a slot's database was already created, since a plain `createdb` can't be re-run safely and AgentHub can't assume the hook is idempotent; this is also how `upLane` knows to seed only a freshly-created database. `dropDatabase` asserts the lane is `managed` and that the name being dropped is one this lane's own slot actually derives (itself or its `_test` sibling) before spawning anything | | `lib/lane-features.js` | (B) Per-feature state and archive. `activateFeature` archives the lane's current active feature (if different) and restores the target's saved stage onto the live `lanes` row — the row stays the one live view every other reader already uses. `canonicalizeSlug` is a DELIBERATELY separate rule from `worktree.js:slugify` (drops a leading `feat/`, keeps `[A-Za-z0-9._-]`, does not lowercase) — the two must never be conflated. `clearLane` (`lib/lanes.js`) archives the active feature (if any) before resetting; a lane that never activated one is unaffected | -| `lib/proof.js` | (C) Proof gallery: lists/serves/deletes QC screenshots grouped by feature slug (Task B's `lane_features`) and phase, under `/.playwright-mcp/proof///`. Every path is resolved, realpath'd, and containment-checked before touching `fs` — the entire security surface. `ensureProofLink` ports Shipyard's `ensure_proof_link` (converge a stray clone-root `proof/` onto the canonical dir); never auto-invoked, exposed only as `ccam lanes proof-link` | -| `lib/named-lock.js` | (D) Cross-lane named locks — the OTHER axis from `lib/lane-lock.js`'s per-lane, in-process serialization, deliberately a separate module. `mkdir` is the atomicity primitive (EEXIST decides "already held" in one syscall, never check-then-create). `LOCK_MAX_HOLD` (default 2700s) breaks a stale holder on the next acquire, floored at 300s so the floor — not the configurable default — is the actual safety property: nothing can force-break a live holder by setting the env var low. Single-shot only; the CLI's `ccam lock acquire` owns the polling loop, keeping the server side non-orchestrating like every other lane primitive | +| `lib/proof.js` | (C) Proof gallery: lists/serves/deletes QC screenshots grouped by feature slug (Task B's `lane_features`) and phase, under `/.playwright-mcp/proof///`. Every path is resolved, realpath'd, and containment-checked before touching `fs` — the entire security surface. `ensureProofLink` ports Shipyard's `ensure_proof_link` (converge a stray clone-root `proof/` onto the canonical dir); never auto-invoked, exposed only as `agenthub lanes proof-link` | +| `lib/named-lock.js` | (D) Cross-lane named locks — the OTHER axis from `lib/lane-lock.js`'s per-lane, in-process serialization, deliberately a separate module. `mkdir` is the atomicity primitive (EEXIST decides "already held" in one syscall, never check-then-create). `LOCK_MAX_HOLD` (default 2700s) breaks a stale holder on the next acquire, floored at 300s so the floor — not the configurable default — is the actual safety property: nothing can force-break a live holder by setting the env var low. Single-shot only; the CLI's `agenthub lock acquire` owns the polling loop, keeping the server side non-orchestrating like every other lane primitive | ### API Documentation @@ -614,7 +614,7 @@ graph LR | `/activity` | ActivityFeed | `GET /api/events?limit=100` — click row to expand inline payload; "Session →" button navigates to `/sessions/:id` | | `/workflows` | Workflows | `GET /api/workflows?status=active\|completed`, `GET /api/workflows/session/:id` + WebSocket auto-refresh (3s debounce) | | `/cc-config` | CcConfig | 12-tab Claude Code configuration explorer. Reads via `GET /api/cc-config/{overview,skills,agents,commands,output-styles,plugins,marketplaces,mcp,hooks,hook-scripts,keybindings,statusline,settings,memory}`. Mutations for skills/agents/commands/output-styles/memory — including the per-project file-based auto-memory store (`*.md` under `~/.claude/projects//memory/`, grouped by project and searchable in the Memory tab, with clickable `MEMORY.md` index links that scroll to + highlight the matching fact file) — via `PUT /api/cc-config/file` + `DELETE /api/cc-config/file` (timestamped backups, atomic writes). The Keybindings tab additionally offers a structured inline editor that persists via `PUT /api/cc-config/keybindings` (same backup-first, atomic-write guarantees). `GET /api/cc-config/file?path=…` for single-file viewer. `GET /api/cc-config/backups` for the recovery modal. Subscribes to `cc_config_changed` WS messages for live refresh on both dashboard mutations and external file edits picked up by `cc-watcher`. The Settings tab leads with a client-side **Current configuration** summary that resolves the `/config` options (model, verbose, theme, output style, effort, auto-compact, notifications, …) across user / project / project-local scopes, showing defaults when unset. Live / Offline indicator next to the title | -| `/run` | Workspace | Merged workspace page combining lanes and runs. Attaches to a tmux-backed pseudoterminal tied to a lane: the UI selects a lane, calls `POST /api/run` with that lane's `id`, and receives a `runId` + tmux session name. The Workspace displays a horizontal lane strip at the top, the selected lane's pipeline map, and a real interactive terminal (xterm.js) fed by `/ws-pty/:runId` binary frames below. `TerminalView` re-attaches 1.5 s after any unexpected socket close (server restart included) and stops only on the server's `exit` frame — a dead socket used to leave the pane frozen on its last frame, looking live while swallowing every keystroke and mouse report. It also calls `preventDefault()` on Shift+Tab (keeping xterm's own handling): xterm emits `ESC[Z` for that combo but, unlike plain Tab, never marks the event cancelled, so the browser's default moved focus out of the terminal and the pane saw only the first press — `shift+tab to cycle` (claude's permission mode) looked dead in the browser while working in a real terminal. It also installs a custom wheel handler that returns `false`: without it, xterm falls back on an alt-screen buffer to translating each wheel notch into a cursor-key press (`ESC[A`/`ESC[B`), which a `claude` pane reads as arrow up/down and uses to walk the prompt history instead of scrolling. That handler suppresses only the emulation branch — real mouse reports are emitted by a separate listener xterm registers when the pane's program enables mouse tracking. Its xterm viewport scrollbar is hidden by design (`.xterm-viewport` rule in `client/src/index.css`): tmux repaints the whole pane, so xterm's scrollback is always empty and scrolling happens via mouse reports forwarded to the pane's program, not the DOM. Pre-flight: `GET /api/run/{tmux,binary,cwds,files}` for tmux availability + `claude` binary check + `@`-file autocomplete. Start/resume: `POST /api/run` (requires `laneId`; optionally accepts `prompt` to send immediately); `GET /api/run/:id` (returns handle); `DELETE /api/run/:id` (stops). History: `GET /api/run/history?laneId=` lists only that lane's runs. PTY streaming: `/ws-pty/:runId` delivers raw PTY frames as binary WebSocket frames — no JSON envelope overhead, direct to xterm.js for live rendering; the same tmux session can have multiple simultaneous clients (browser Workspace, `ccam lanes shell` CLI, other tools), all synced live. Lane self-heal: `GET /api/lanes/:id` auto-corrects `run_id`/`status` if the tmux session has been killed externally. Tier 1 TUI parity: tmux session is a real shell, not headless — supports editors, pagers, interactive subcommands. **The console never writes a lane's stage** — stage moves only through `ccam stage` commands. Live / Offline indicator next to the title | +| `/run` | Workspace | Merged workspace page combining lanes and runs. Attaches to a tmux-backed pseudoterminal tied to a lane: the UI selects a lane, calls `POST /api/run` with that lane's `id`, and receives a `runId` + tmux session name. The Workspace displays a horizontal lane strip at the top, the selected lane's pipeline map, and a real interactive terminal (xterm.js) fed by `/ws-pty/:runId` binary frames below. `TerminalView` re-attaches 1.5 s after any unexpected socket close (server restart included) and stops only on the server's `exit` frame — a dead socket used to leave the pane frozen on its last frame, looking live while swallowing every keystroke and mouse report. It also calls `preventDefault()` on Shift+Tab (keeping xterm's own handling): xterm emits `ESC[Z` for that combo but, unlike plain Tab, never marks the event cancelled, so the browser's default moved focus out of the terminal and the pane saw only the first press — `shift+tab to cycle` (claude's permission mode) looked dead in the browser while working in a real terminal. It also installs a custom wheel handler that returns `false`: without it, xterm falls back on an alt-screen buffer to translating each wheel notch into a cursor-key press (`ESC[A`/`ESC[B`), which a `claude` pane reads as arrow up/down and uses to walk the prompt history instead of scrolling. That handler suppresses only the emulation branch — real mouse reports are emitted by a separate listener xterm registers when the pane's program enables mouse tracking. Its xterm viewport scrollbar is hidden by design (`.xterm-viewport` rule in `client/src/index.css`): tmux repaints the whole pane, so xterm's scrollback is always empty and scrolling happens via mouse reports forwarded to the pane's program, not the DOM. Pre-flight: `GET /api/run/{tmux,binary,cwds,files}` for tmux availability + `claude` binary check + `@`-file autocomplete. Start/resume: `POST /api/run` (requires `laneId`; optionally accepts `prompt` to send immediately); `GET /api/run/:id` (returns handle); `DELETE /api/run/:id` (stops). History: `GET /api/run/history?laneId=` lists only that lane's runs. PTY streaming: `/ws-pty/:runId` delivers raw PTY frames as binary WebSocket frames — no JSON envelope overhead, direct to xterm.js for live rendering; the same tmux session can have multiple simultaneous clients (browser Workspace, `agenthub lanes shell` CLI, other tools), all synced live. Lane self-heal: `GET /api/lanes/:id` auto-corrects `run_id`/`status` if the tmux session has been killed externally. Tier 1 TUI parity: tmux session is a real shell, not headless — supports editors, pagers, interactive subcommands. **The console never writes a lane's stage** — stage moves only through `agenthub stage` commands. Live / Offline indicator next to the title | | `/settings` | Settings | `GET /api/settings/info`, `GET /api/pricing`, `GET /api/pricing/cost` + `localStorage` for notification prefs. Hosts the **Remote Data Sources** panel (`components/RemoteSources.tsx`) — CRUD + test + sync over `/api/remote-sources`, live status from `remote_source.status` WS messages | | `/*` | NotFound | None (static 404 page) | @@ -1084,7 +1084,7 @@ flowchart TD ### Plugin installs declare the same hooks instead -When the dashboard is installed as the `ccam` Claude Code plugin, the eight hook +When the dashboard is installed as the `agenthub` Claude Code plugin, the eight hook entries are declared inline in `.claude-plugin/plugin.json` (each running `${CLAUDE_PLUGIN_ROOT}/scripts/hook-handler.js`), and `install-hooks.js` is not used at all. `claude plugin install` itself materializes these into @@ -1097,19 +1097,19 @@ would double. Two guards keep that from happening silently: - `scripts/plugin-bootstrap.js` strips checkout-style `hook-handler.js` entries out of `~/.claude/settings.json` on session start (backing the file up as - `settings.json.ccam-bak` first) and logs what it removed. Because the + `settings.json.agenthub-bak` first) and logs what it removed. Because the plugin's own entries ALSO contain `hook-handler.js` (just resolved to a cache path instead of a raw filesystem one), a plain substring match cannot tell them apart — `isCheckoutHookEntry()` only treats an entry as removable when its command does NOT resolve under `~/.claude/plugins/cache/`, so the plugin's own legitimate hooks are never touched. - `install-hooks.js` warns when the plugin runtime state exists, and - `/ccam-doctor` reports any surviving checkout-style duplicates as a `FAIL` + `/agenthub-doctor` reports any surviving checkout-style duplicates as a `FAIL` using the same `isCheckoutHookEntry()` predicate. `scripts/plugin-bootstrap.js` also owns the rest of the plugin's runtime: the Node >= 22.5 gate (`node:sqlite`), an atomic `mkdir` lock, the dependency -install into `~/.claude/agent-dashboard/runtime/`, the `~/.local/bin/ccam` +install into `~/.claude/agent-dashboard/runtime/`, the `~/.local/bin/agenthub` launcher, and the detached server spawn with `NODE_PATH` and `DASHBOARD_CLIENT_DIST` pointed at that runtime directory. It never writes into the plugin cache, which Claude Code garbage-collects and replaces on every @@ -1118,7 +1118,7 @@ update. Last in that worker — deliberately, because it clones from the network and nothing above it may wait — it installs the plugins the pipeline templates drive (`superpowers`) via `server/lib/skill-plugins.js`, the same module -`/ccam-doctor`'s `Skill plugins` row reports from. The report is derived from +`/agenthub-doctor`'s `Skill plugins` row reports from. The report is derived from the templates; the install consults a fixed allowlist in that file instead, because a template is user-writable. Full behavior: [`docs/PLUGINS.md`](docs/PLUGINS.md). @@ -1237,12 +1237,12 @@ sequenceDiagram UI->>API: POST multipart files[] API->>M: route through uploadMiddleware - M->>M: mkTempDir('ccam-upload-*')
stored on req._ccamUploadDir - M->>M: fileFilter: reject unsupported
(tracked in req._ccamRejected) + M->>M: mkTempDir('agenthub-upload-*')
stored on req._agenthubUploadDir + M->>M: fileFilter: reject unsupported
(tracked in req._agenthubRejected) alt All files rejected API-->>UI: 400 NO_FILES
+ rejected_files[] else Files accepted - API->>AR: mkTempDir('ccam-import-work-*') + API->>AR: mkTempDir('agenthub-import-work-*') loop per uploaded file API->>AR: extractInto(srcPath, workDir, name) AR->>AR: safeJoin: reject absolute / .. @@ -1266,7 +1266,7 @@ sequenceDiagram API-->>UI: 200 {imported, backfilled,
skipped, errors, rejected_files} end API->>AR: rmTempDir(workDir) - API->>M: rmTempDir(req._ccamUploadDir) + API->>M: rmTempDir(req._agenthubUploadDir) ``` ### Idempotence and cost accuracy @@ -1325,7 +1325,7 @@ accumulated a baseline ~11× its real usage before this was fixed. | Per-file upload size abuse | multer `limits.fileSize = MAX_UPLOAD_BYTES` (default 1 GB) | | Too many files per request | multer `limits.files = MAX_UPLOAD_FILES` (default 2000) | | Unsupported file types | `fileFilter` drops them early and reports them in `rejected_files[]` | -| Concurrent upload temp-dir collisions | Per-request temp dir on `req._ccamUploadDir`; created in multer `destination`, cleaned in `finally` | +| Concurrent upload temp-dir collisions | Per-request temp dir on `req._agenthubUploadDir`; created in multer `destination`, cleaned in `finally` | | Arbitrary absolute path on `scan-path` | Validated: must be absolute (after `~` expansion), exist, and be a directory | | Relative / traversal paths on `scan-path` | Rejected with `INVALID_INPUT` | @@ -1333,9 +1333,9 @@ accumulated a baseline ~11× its real usage before this was fixed. | Variable | Default | Purpose | | --------------------------------- | ----------- | ----------------------------------------------------------------- | -| `CCAM_IMPORT_MAX_BYTES` | 1 GB | Maximum size per uploaded file | -| `CCAM_IMPORT_MAX_FILES` | 2000 | Maximum files per upload request | -| `CCAM_IMPORT_MAX_EXTRACT_BYTES` | 4 GB | Ceiling on total uncompressed bytes from any single archive | +| `AGENTHUB_IMPORT_MAX_BYTES` | 1 GB | Maximum size per uploaded file | +| `AGENTHUB_IMPORT_MAX_FILES` | 2000 | Maximum files per upload request | +| `AGENTHUB_IMPORT_MAX_EXTRACT_BYTES` | 4 GB | Ceiling on total uncompressed bytes from any single archive | ### WebSocket progress events @@ -1354,7 +1354,7 @@ are never throttled. "source": "upload", "processed": 184, "total": 512, - "current": "/tmp/ccam-import-work-xyz/project/.jsonl", + "current": "/tmp/agenthub-import-work-xyz/project/.jsonl", "counters": { "imported": 120, "backfilled": 40, "skipped": 20, "errors": 4 } } } @@ -1453,8 +1453,8 @@ Ingestion runs from five fail-safe, off-the-response-path triggers: 4. **Backfill** — a one-time pass in `autoImportLegacySessions` ingests historical on-disk workflows for every recorded session. 5. **Offline CLI import** — the batch importer (`importAllSessions` / - `importFromDirectory` in `scripts/import-history.js`, backing `ccam import - rescan` / `ccam import path` and the `/api/import/*` routes) calls + `importFromDirectory` in `scripts/import-history.js`, backing `agenthub import + rescan` / `agenthub import path` and the `/api/import/*` routes) calls `ingestWorkflowsForSession` per imported session, **after** the batch's transcripts land and **outside** the SQLite transaction (the ingest is async). Triggers 1–3 all run inside the server; this is the only path that @@ -1579,7 +1579,7 @@ if that changes. A written detection then **also advances the lane's declared `stage`** — `setStage(lane.id, { stage: nodeId, keepDetection: true })`, the only place in -CCAM where an inference moves the blue `current` ring. It is gated on +AgentHub where an inference moves the blue `current` ring. It is gated on `recordDetection` having written, so it inherits forward-only/declared-wins/TTL unchanged; it passes no `evidence`, so the node renders `passed-no-evidence` (amber) and never `done`; and `keepDetection` is the sole exception to @@ -1590,18 +1590,18 @@ last node carries `detect` rules, so a lane can never be auto-declared `done`. ### Codex evidence reliability (`server/lib/codex-nudge.js`) Auto-advance moves the ring but can never supply evidence; only the session's -own `ccam stage --evidence` call can, so three mechanisms make that more likely +own `agenthub stage --evidence` call can, so three mechanisms make that more likely without weakening declared-wins/inferred-never-`done`: - **Opening-prompt priming** (`LaneConsolePane.tsx`'s `promptPrefill`): a codex pane's first prompt is pre-filled with a plain-text checklist of - `ccam stage --tool codex` calls, one per node of `Lane.pipeline_nodes` + `agenthub stage --tool codex` calls, one per node of `Lane.pipeline_nodes` — the same up-front nudge a Claude lane gets from its pre-filled - `/ccam:ship-feature-lane` skill command, in plain text since Codex has no + `/agenthub:ship-feature-lane` skill command, in plain text since Codex has no slash commands. - **`AGENTS.md` stage block** (`server/lib/lane-agents.js`): on a `codex` lane, - `ccam lanes agents install` writes a marker-delimited - `` section into `/AGENTS.md` instead of + `agenthub lanes agents install` writes a marker-delimited + `` section into `/AGENTS.md` instead of copying the two agent templates into `.claude/agents/`, which Codex never reads. Appended to an existing file (user prose preserved), and a reinstall replaces exactly that block. @@ -1609,13 +1609,13 @@ without weakening declared-wins/inferred-never-`done`: `server/index.js`, tunable via `DASHBOARD_CODEX_NUDGE_MS`, `0` disables): when a codex lane's current stage has been held for at least two minutes with `stages[stage].evidence` still null and hasn't already been nudged for that - exact stage, it types a one-line `[ccam] ... run: ccam stage ... --evidence + exact stage, it types a one-line `[agenthub] ... run: agenthub stage ... --evidence "..."` reminder into the lane's live tmux pane via `tmux.sendText` (skipped if the pane isn't running an interactive program — `pty-run.js`'s `SHELL_COMMANDS` — since Enter there would execute the reminder as a shell command instead of submitting it as a chat turn). It never writes `stage`, `stages` or `detected_stage` — any resulting evidence is still something the - codex session declared itself, through the real `ccam stage` call. + codex session declared itself, through the real `agenthub stage` call. --- @@ -1635,7 +1635,7 @@ graph TD CLAUDE --> C_MEM["CLAUDE.md"] CLAUDE --> C_RULES[".claude/rules/*"] - CLAUDE --> C_SKILLS["skills/*
ccam plugin skills"] + CLAUDE --> C_SKILLS["skills/*
agenthub plugin skills"] CLAUDE --> C_PLUGINS["plugins/
10 plugins, 53 skills"] CODEX --> X_MEM["AGENTS.md"] @@ -1650,27 +1650,27 @@ graph TD - `CLAUDE.md` defines always-on project working agreements. - `.claude/rules/` adds path-scoped guidance by file area. -- `skills/` provides reusable workflows, shipped as the `ccam` plugin's own skills (`/ccam:`, bare `/` alias): +- `skills/` provides reusable workflows, shipped as the `agenthub` plugin's own skills (`/agenthub:`, bare `/` alias): - onboarding - feature shipping - MCP operations - live issue debugging -- `agents/` provides specialized review workers (shipped as the `ccam` plugin's agents): +- `agents/` provides specialized review workers (shipped as the `agenthub` plugin's agents): - backend reviewer - frontend reviewer - MCP reviewer - qc-local, senior-gate-reviewer (the ship-feature-lane pipeline's QC and publish gate) - `plugins/` provides distributable plugin marketplace (see [Plugin Marketplace](#plugin-marketplace)): - - ccam-analytics (session reports, cost analysis, usage trends, productivity scoring) - - ccam-productivity (standups, weekly reports, sprint summaries, workflow optimization) - - ccam-devtools (session debugging, hook diagnostics, data export, health checks) - - ccam-insights (pattern detection, anomaly alerting, optimization, session comparison) - - ccam-dashboard (status checks, quick stats, MCP integration) - - ccam-cost-guard (budget guardrails, week/month-end spend forecasting, cost-threshold alerts, model-routing savings) - - ccam-sessions (session forensics: search, timeline, transcript replay, per-cwd rollup, cleanup) - - ccam-workflows (multi-agent orchestration & fleet intelligence: DAG map, delegation audit, concurrency, error propagation, fleet runs) - - ccam-quality (reliability & SLOs: error scan, API-error report, hook-failure audit, SLO check, regression alert) - - ccam-config (Claude Code config & memory governance: config audit, memory review, skill/MCP/hook inventory) + - agenthub-analytics (session reports, cost analysis, usage trends, productivity scoring) + - agenthub-productivity (standups, weekly reports, sprint summaries, workflow optimization) + - agenthub-devtools (session debugging, hook diagnostics, data export, health checks) + - agenthub-insights (pattern detection, anomaly alerting, optimization, session comparison) + - agenthub-dashboard (status checks, quick stats, MCP integration) + - agenthub-cost-guard (budget guardrails, week/month-end spend forecasting, cost-threshold alerts, model-routing savings) + - agenthub-sessions (session forensics: search, timeline, transcript replay, per-cwd rollup, cleanup) + - agenthub-workflows (multi-agent orchestration & fleet intelligence: DAG map, delegation audit, concurrency, error propagation, fleet runs) + - agenthub-quality (reliability & SLOs: error scan, API-error report, hook-failure audit, SLO check, regression alert) + - agenthub-config (Claude Code config & memory governance: config audit, memory review, skill/MCP/hook inventory) ### Codex extension scope @@ -1683,7 +1683,7 @@ graph TD ## Plugin Marketplace -The repository includes an official Claude Code plugin marketplace with ten production-ready plugins. These extend Claude Code itself (not just the dashboard) with skills, agents, slash commands, hooks, CLI tools, and MCP integration — all deeply grounded in the actual dashboard data model. The five original plugins (ccam-analytics, ccam-productivity, ccam-devtools, ccam-insights, ccam-dashboard) were each deepened with more agents/skills and a `commands/` dir of slash commands, and five new plugins were added: **ccam-cost-guard** (budget guardrails — set budgets, forecast week/month-end spend, cost-threshold alerts, model-routing savings, with a fail-safe Stop hook), **ccam-sessions** (session forensics — search, timeline, transcript replay, per-cwd rollup, cleanup), **ccam-workflows** (multi-agent orchestration & fleet intelligence — DAG map, delegation audit, concurrency, error propagation, fleet runs, built on the 11-dataset workflow intelligence API), **ccam-quality** (reliability & SLOs — error scan, API-error report, hook-failure audit, SLO check, regression alert), and **ccam-config** (Claude Code config & memory governance — config audit, memory review, skill/MCP/hook inventory via the Config Explorer API). +The repository includes an official Claude Code plugin marketplace with ten production-ready plugins. These extend Claude Code itself (not just the dashboard) with skills, agents, slash commands, hooks, CLI tools, and MCP integration — all deeply grounded in the actual dashboard data model. The five original plugins (agenthub-analytics, agenthub-productivity, agenthub-devtools, agenthub-insights, agenthub-dashboard) were each deepened with more agents/skills and a `commands/` dir of slash commands, and five new plugins were added: **agenthub-cost-guard** (budget guardrails — set budgets, forecast week/month-end spend, cost-threshold alerts, model-routing savings, with a fail-safe Stop hook), **agenthub-sessions** (session forensics — search, timeline, transcript replay, per-cwd rollup, cleanup), **agenthub-workflows** (multi-agent orchestration & fleet intelligence — DAG map, delegation audit, concurrency, error propagation, fleet runs, built on the 11-dataset workflow intelligence API), **agenthub-quality** (reliability & SLOs — error scan, API-error report, hook-failure audit, SLO check, regression alert), and **agenthub-config** (Claude Code config & memory governance — config audit, memory review, skill/MCP/hook inventory via the Config Explorer API). ### Marketplace Architecture @@ -1694,16 +1694,16 @@ graph TD end subgraph Plugins["plugins/"] - A["ccam-analytics
4 skills, 1 agent, 1 CLI"] - P["ccam-productivity
4 skills, 1 agent"] - D["ccam-devtools
4 skills, 1 agent, 2 CLIs"] - I["ccam-insights
4 skills, 1 agent"] - C["ccam-dashboard
2 skills, MCP config"] - G["ccam-cost-guard
5 skills, 1 agent, Stop hook"] - S["ccam-sessions
5 skills, 1 agent"] - W["ccam-workflows
5 skills, 1 agent"] - Q["ccam-quality
5 skills, 1 agent"] - F["ccam-config
5 skills, 1 agent"] + A["agenthub-analytics
4 skills, 1 agent, 1 CLI"] + P["agenthub-productivity
4 skills, 1 agent"] + D["agenthub-devtools
4 skills, 1 agent, 2 CLIs"] + I["agenthub-insights
4 skills, 1 agent"] + C["agenthub-dashboard
2 skills, MCP config"] + G["agenthub-cost-guard
5 skills, 1 agent, Stop hook"] + S["agenthub-sessions
5 skills, 1 agent"] + W["agenthub-workflows
5 skills, 1 agent"] + Q["agenthub-quality
5 skills, 1 agent"] + F["agenthub-config
5 skills, 1 agent"] end subgraph API["Dashboard API (port 4820)"] @@ -1742,7 +1742,7 @@ graph TD Each plugin follows the official Claude Code plugin specification: ``` -plugins/ccam-{name}/ +plugins/agenthub-{name}/ ├── .claude-plugin/ │ └── plugin.json # Manifest: name, version, description, author ├── skills/ @@ -1758,22 +1758,22 @@ plugins/ccam-{name}/ └── settings.json # Plugin settings (optional) ``` -Skills are namespaced: `/ccam-analytics:session-report`, `/ccam-productivity:daily-standup`, etc. +Skills are namespaced: `/agenthub-analytics:session-report`, `/agenthub-productivity:daily-standup`, etc. ### Plugin Catalog | Plugin | Skills | Agent | CLI Tools | Hooks | |--------|--------|-------|-----------|-------| -| **ccam-analytics** | `session-report`, `cost-breakdown`, `usage-trends`, `productivity-score` | `analytics-advisor` | `ccam-stats` | Stop, SubagentStop | -| **ccam-cost-guard** | `budget-set`, `spend-forecast`, `cost-alert`, `model-savings`, `daily-budget-check` | `budget-sentinel` | — | Stop | -| **ccam-productivity** | `daily-standup`, `weekly-report`, `sprint-summary`, `workflow-optimizer` | `productivity-coach` | — | SessionStart, SessionEnd | -| **ccam-devtools** | `session-debug`, `hook-diagnostics`, `data-export`, `health-check` | `issue-triager` | `ccam-doctor`, `ccam-export` | — | -| **ccam-insights** | `pattern-detect`, `anomaly-alert`, `optimization-suggest`, `session-compare` | `insights-advisor` | — | — | -| **ccam-sessions** | `session-search`, `session-timeline`, `transcript-replay`, `cwd-rollup`, `session-cleanup` | `session-investigator` | — | — | -| **ccam-workflows** | `dag-map`, `delegation-audit`, `concurrency-report`, `error-propagation`, `fleet-runs` | `orchestration-analyst` | — | — | -| **ccam-quality** | `error-scan`, `api-error-report`, `hook-failure-audit`, `slo-check`, `regression-alert` | `reliability-engineer` | — | — | -| **ccam-config** | `config-audit`, `memory-review`, `skill-inventory`, `mcp-audit`, `hook-inventory` | `config-auditor` | — | — | -| **ccam-dashboard** | `dashboard-status`, `quick-stats` | — | — | — | +| **agenthub-analytics** | `session-report`, `cost-breakdown`, `usage-trends`, `productivity-score` | `analytics-advisor` | `agenthub-stats` | Stop, SubagentStop | +| **agenthub-cost-guard** | `budget-set`, `spend-forecast`, `cost-alert`, `model-savings`, `daily-budget-check` | `budget-sentinel` | — | Stop | +| **agenthub-productivity** | `daily-standup`, `weekly-report`, `sprint-summary`, `workflow-optimizer` | `productivity-coach` | — | SessionStart, SessionEnd | +| **agenthub-devtools** | `session-debug`, `hook-diagnostics`, `data-export`, `health-check` | `issue-triager` | `agenthub-doctor`, `agenthub-export` | — | +| **agenthub-insights** | `pattern-detect`, `anomaly-alert`, `optimization-suggest`, `session-compare` | `insights-advisor` | — | — | +| **agenthub-sessions** | `session-search`, `session-timeline`, `transcript-replay`, `cwd-rollup`, `session-cleanup` | `session-investigator` | — | — | +| **agenthub-workflows** | `dag-map`, `delegation-audit`, `concurrency-report`, `error-propagation`, `fleet-runs` | `orchestration-analyst` | — | — | +| **agenthub-quality** | `error-scan`, `api-error-report`, `hook-failure-audit`, `slo-check`, `regression-alert` | `reliability-engineer` | — | — | +| **agenthub-config** | `config-audit`, `memory-review`, `skill-inventory`, `mcp-audit`, `hook-inventory` | `config-auditor` | — | — | +| **agenthub-dashboard** | `dashboard-status`, `quick-stats` | — | — | — | **Totals**: 10 plugins, 53 skills, 14 agents, 30 slash commands, 3 CLI tools, 3 hook configurations, 1 MCP config. Each plugin is installable via `claude plugin install @smartgift-claude-code-monitor`, and a server test (`server/__tests__/plugins-marketplace.test.js`) validates the marketplace↔directory bijection plus every `plugin.json`, agent, skill, and command. @@ -1805,10 +1805,10 @@ Plugins compute these from raw API data: ```bash # Marketplace install claude plugin marketplace add Smartgift-AI/Claude-Code-Monitor -claude plugin install ccam-analytics@smartgift-claude-code-monitor +claude plugin install agenthub-analytics@smartgift-claude-code-monitor # Local development testing -claude --plugin-dir plugins/ccam-analytics +claude --plugin-dir plugins/agenthub-analytics ``` Full documentation: [`docs/plugins.md`](docs/PLUGINS.md) @@ -2329,7 +2329,7 @@ Sagi's only contact with the rest of the app is four light, additive touchpoints ## VS Code Extension Architecture -The **Claude Code Agent Monitor** VS Code extension provides an integrated monitoring experience directly within the editor. It communicates with the local dashboard server via standard HTTP APIs and renders the dashboard UI in a webview. +The **AgentHub** VS Code extension provides an integrated monitoring experience directly within the editor. It communicates with the local dashboard server via standard HTTP APIs and renders the dashboard UI in a webview.

VS Code Extension Screenshot @@ -2381,7 +2381,7 @@ graph TD For the extension source code, refer to the [vscode-extension/](./vscode-extension/) directory. > [!TIP] -> Extension on VS Code Marketplace: [Claude Code Agent Monitor](https://marketplace.visualstudio.com/items?itemName=hoangsonw.claude-code-agent-monitor) +> Extension on VS Code Marketplace: [AgentHub](https://marketplace.visualstudio.com/items?itemName=hoangsonw.claude-code-agent-monitor) --- @@ -2554,7 +2554,7 @@ docker run -d -p 4820:4820 \ ``` > [!NOTE] -> **Hook note:** Claude Code hooks run on the host, not inside the container. The containerized server still receives hook events via HTTP on `localhost:4820` — run `npm run install-hooks` on the host after the container is up. `scripts/install-hooks.js` detects container execution and refuses there (issue #193) so it cannot write a container-internal handler path into a bind-mounted host `~/.claude`; the containerized server's boot-time auto-install is skipped for the same reason. Override with `CCAM_ALLOW_CONTAINER_HOOKS=1` only when Claude Code itself runs inside the container. +> **Hook note:** Claude Code hooks run on the host, not inside the container. The containerized server still receives hook events via HTTP on `localhost:4820` — run `npm run install-hooks` on the host after the container is up. `scripts/install-hooks.js` detects container execution and refuses there (issue #193) so it cannot write a container-internal handler path into a bind-mounted host `~/.claude`; the containerized server's boot-time auto-install is skipped for the same reason. Override with `AGENTHUB_ALLOW_CONTAINER_HOOKS=1` only when Claude Code itself runs inside the container. --- diff --git a/CLAUDE.md b/CLAUDE.md index e2e88cf1..95e50d6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Claude Code Working Guide -_Last synced: 58774f2 (2026-09-04) + per-node run command (`ccam lanes node`) and its slash commands._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. +_Last synced: 58774f2 (2026-09-04) + per-node run command (`agenthub lanes node`) and its slash commands._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. ## Project mission - Maintain a reliable local-first dashboard for Claude Code session monitoring. @@ -10,11 +10,11 @@ _Last synced: 58774f2 (2026-09-04) + per-node run command (`ccam lanes node`) an ## Repo map - `server/`: Express API, hook ingestion, SQLite access, websocket broadcast (includes optional git upstream checks and `routes/updates.js`, plus `lib/workflow-ingest.js` which ingests on-disk Workflow-tool run journals — fleets that emit no hooks — and `lib/codex-ingest.js`, the same idea for `codex` lanes: it tails Codex's own rollout journals because Codex emits no hooks at all). - `client/`: React + Vite UI. -- `scripts/`: hook installer/handler, import, seed, cleanup utilities. (Update detection lives server-side in `server/lib/update-check.js`. `POST /api/updates/apply` (only when the checkout is fast-forwardable) pulls, rebuilds, and self-restarts via `server/lib/self-restart.js` + the detached `scripts/restart-helper.js`; otherwise users run the printed manual command, surfaced in the UI and by `ccam update-check`.) -- `mcp/`: local MCP server exposing dashboard operations as tools. **`mcp/build/` is committed on purpose** — plugin MCP servers start before any bootstrap could build them; `scripts/check-mcp-build.js` (content hash in `mcp/build/.srchash`, run by pre-commit and `/ccam-doctor`) keeps it honest. Rebuild with `npm run mcp:build`, never hand-edit `mcp/build/`. -- `skills/`: this repo's skills. Because the repo root **is** the `ccam` plugin root, this is also the plugin's skills directory — every skill here ships to anyone who installs the plugin, addressed as `/ccam:` (bare `/` works as an alias). Put a skill here only if it should reach plugin users; `ccam skills install` remains a fallback that copies `ship-feature-lane` into `~/.claude/skills/` for plain checkouts. - `agents/`: the same story for subagents — plugin root, so all five (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`) ship as `ccam:`. This is also the source `ccam lanes agents install` copies the two pipeline agents from into a lane's `.claude/agents/`, which is what keeps the bare `subagent_type: qc-local` working there. One file per agent, never a second copy. -- `.claude-plugin/`: the marketplace plus the root `ccam` plugin manifest (`"source": "./"` — the whole repo is the plugin). Its hooks are inline in `plugin.json`; its commands live in `plugins/ccam/commands/`, which is NOT a subdirectory plugin. `scripts/plugin-bootstrap.js` runs from `SessionStart` and owns the writable runtime under `~/.claude/agent-dashboard/runtime/` — it never writes into the plugin cache, which Claude Code replaces on every update. See `docs/PLUGINS.md`. -- **Reporting a required plugin is derived; installing one is allowlisted.** `server/lib/skill-plugins.js` reads the `/plugin:skill` strings out of the loaded pipeline templates — right for the `/ccam-doctor` row, wrong for an install: a template is a file anyone can drop into `DASHBOARD_PIPELINES_DIR`, so installing whatever name it contains would make "edit a JSON file" mean "run this vendor's install command next session". The bootstrap installs only what `AUTO_INSTALLABLE` (a constant in that file, `superpowers` today) lists, in its DETACHED worker, never in the 10 s hook, recorded once as `state.skillPluginsSettled` (also gating `fastPathOk`), never throwing, and off under `CCAM_AUTO_INSTALL_PLUGINS=0|false|off`. Do not derive an install target from a template. +- `scripts/`: hook installer/handler, import, seed, cleanup utilities. (Update detection lives server-side in `server/lib/update-check.js`. `POST /api/updates/apply` (only when the checkout is fast-forwardable) pulls, rebuilds, and self-restarts via `server/lib/self-restart.js` + the detached `scripts/restart-helper.js`; otherwise users run the printed manual command, surfaced in the UI and by `agenthub update-check`.) +- `mcp/`: local MCP server exposing dashboard operations as tools. **`mcp/build/` is committed on purpose** — plugin MCP servers start before any bootstrap could build them; `scripts/check-mcp-build.js` (content hash in `mcp/build/.srchash`, run by pre-commit and `/agenthub-doctor`) keeps it honest. Rebuild with `npm run mcp:build`, never hand-edit `mcp/build/`. +- `skills/`: this repo's skills. Because the repo root **is** the `agenthub` plugin root, this is also the plugin's skills directory — every skill here ships to anyone who installs the plugin, addressed as `/agenthub:` (bare `/` works as an alias). Put a skill here only if it should reach plugin users; `agenthub skills install` remains a fallback that copies `ship-feature-lane` into `~/.claude/skills/` for plain checkouts. - `agents/`: the same story for subagents — plugin root, so all five (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`) ship as `agenthub:`. This is also the source `agenthub lanes agents install` copies the two pipeline agents from into a lane's `.claude/agents/`, which is what keeps the bare `subagent_type: qc-local` working there. One file per agent, never a second copy. +- `.claude-plugin/`: the marketplace plus the root `agenthub` plugin manifest (`"source": "./"` — the whole repo is the plugin). Its hooks are inline in `plugin.json`; its commands live in `plugins/agenthub/commands/`, which is NOT a subdirectory plugin. `scripts/plugin-bootstrap.js` runs from `SessionStart` and owns the writable runtime under `~/.claude/agent-dashboard/runtime/` — it never writes into the plugin cache, which Claude Code replaces on every update. See `docs/PLUGINS.md`. +- **Reporting a required plugin is derived; installing one is allowlisted.** `server/lib/skill-plugins.js` reads the `/plugin:skill` strings out of the loaded pipeline templates — right for the `/agenthub-doctor` row, wrong for an install: a template is a file anyone can drop into `DASHBOARD_PIPELINES_DIR`, so installing whatever name it contains would make "edit a JSON file" mean "run this vendor's install command next session". The bootstrap installs only what `AUTO_INSTALLABLE` (a constant in that file, `superpowers` today) lists, in its DETACHED worker, never in the 10 s hook, recorded once as `state.skillPluginsSettled` (also gating `fastPathOk`), never throwing, and off under `AGENTHUB_AUTO_INSTALL_PLUGINS=0|false|off`. Do not derive an install target from a template. ## Lanes @@ -22,16 +22,16 @@ A **lane** is a durable unit of parallel agent work — one working directory, m **Key points:** - Sessions are bound to lanes by longest path-boundary prefix match on their `cwd` (set in hook data from `claude` CLI). -- A lane's stage is **declared** by the driving Claude session (via `ccam stage [--evidence "..."]`), not inferred by the dashboard. -- **A CLI command resolves its lane by (cwd, tool), never cwd alone.** `resolveLaneArg` (`bin/ccam.js`) answers in order: `--lane`/leading id → `CCAM_LANE_ID` (every tmux pane the dashboard starts carries it, via `tmux new-session -e`) → `--tool`, else the harness marker (`CLAUDECODE` ⇒ claude; Codex has NO reliable env marker, so it passes `--tool codex`) → the owning lane. Two candidates at one directory and no signal is an ERROR that names both, never a pick — writing a codex session's stage onto the claude lane's card is a lie about which agent did the work. -- **The console never writes a lane's stage.** Declared stages come from `ccam stage` (a skill calling the CLI command), and inferred stages come from tool-event detection only. **One exception, `codex` lanes only:** `codex-ingest.js` advances `stage` from its own written detection (`setStage(..., { keepDetection: true })`) — a codex session has no hook and rarely calls `ccam stage`, so nothing else would ever move its map. It passes NO evidence, so the node renders `passed-no-evidence` (amber) and never `done`, and `keepDetection` preserves the detection columns so the card still shows the command that moved it. Do not extend this to `claude` lanes or to the console. The UI's run console and the Workspace page never call `POST /api/lanes/:id/stage` — that boundary exists because the console observes tool calls but cannot see their outcomes. A lane's stage is the session's truth about progress; the console's observation of tool execution would not be proof of completion. -- **Going back is a declaration, and it takes the nodes ahead with it.** A declaration landing on a node BEFORE the lane's current one (a fix-loop re-entry, or a new request on a lane that already reached `done`) clears the detection columns like every real transition AND drops the `stages` records of every node ahead of it — those belong to the previous pass, and left in place they render `done` with the old work's evidence beside a `current` ring further back, contradicting `progress` (computed from the declared index alone). Records under a key matching no node are left alone: unplaceable, so unprovably ahead. `ccam feature activate ` is the other half of this — a feature switch archives the outgoing pipeline and clears detection too, because a detection belongs to the feature that produced it. +- A lane's stage is **declared** by the driving Claude session (via `agenthub stage [--evidence "..."]`), not inferred by the dashboard. +- **A CLI command resolves its lane by (cwd, tool), never cwd alone.** `resolveLaneArg` (`bin/agenthub.js`) answers in order: `--lane`/leading id → `AGENTHUB_LANE_ID` (every tmux pane the dashboard starts carries it, via `tmux new-session -e`) → `--tool`, else the harness marker (`CLAUDECODE` ⇒ claude; Codex has NO reliable env marker, so it passes `--tool codex`) → the owning lane. Two candidates at one directory and no signal is an ERROR that names both, never a pick — writing a codex session's stage onto the claude lane's card is a lie about which agent did the work. +- **The console never writes a lane's stage.** Declared stages come from `agenthub stage` (a skill calling the CLI command), and inferred stages come from tool-event detection only. **One exception, `codex` lanes only:** `codex-ingest.js` advances `stage` from its own written detection (`setStage(..., { keepDetection: true })`) — a codex session has no hook and rarely calls `agenthub stage`, so nothing else would ever move its map. It passes NO evidence, so the node renders `passed-no-evidence` (amber) and never `done`, and `keepDetection` preserves the detection columns so the card still shows the command that moved it. Do not extend this to `claude` lanes or to the console. The UI's run console and the Workspace page never call `POST /api/lanes/:id/stage` — that boundary exists because the console observes tool calls but cannot see their outcomes. A lane's stage is the session's truth about progress; the console's observation of tool execution would not be proof of completion. +- **Going back is a declaration, and it takes the nodes ahead with it.** A declaration landing on a node BEFORE the lane's current one (a fix-loop re-entry, or a new request on a lane that already reached `done`) clears the detection columns like every real transition AND drops the `stages` records of every node ahead of it — those belong to the previous pass, and left in place they render `done` with the old work's evidence beside a `current` ring further back, contradicting `progress` (computed from the declared index alone). Records under a key matching no node are left alone: unplaceable, so unprovably ahead. `agenthub feature activate ` is the other half of this — a feature switch archives the outgoing pipeline and clears detection too, because a detection belongs to the feature that produced it. - **A detection expires, an evidence rule does not.** `recordDetection` skips its forward-only comparison once `detected_at` is older than `DETECTION_TTL_MS` (default 5 min), so a lane can move backwards between work sessions. That window changes only WHICH detection is current — it never relaxes declared-wins (an agent's own claim has no expiry) and never lets an inferred node render `done`. Declared-wins has exactly ONE exception: a declaration on the pipeline's LAST node (`done`) stops vetoing, because the pipeline is over there and a later detection is new work — without it a lane left at `done` rejects every detection for the rest of its life. - **Working-copy facts live at `GET /api/lanes/:id/git`, never inside `GET /api/lanes`.** That endpoint shells out to git three times; the lane list is polled and re-broadcast on every hook. A cwd that is not a readable repo returns `{available:false}` with HTTP 200 — a normal state, not a fault. Cards fetch it themselves every 30s and fail silently. - **The Workspace console collapses with CSS, never by unmounting — and it has exactly ONE render site.** Unmounting `RunConsole` disposes the run subscription and drops a live run's rendered history. The same rule binds one level up: `ConsoleArea` lives inside the always-rendered `lane-detail` section in `client/src/pages/Workspace.tsx`, never inside a `{currentLane && …}` branch. A second render site makes React unmount the whole console every time `currentLane` flips — lanes arriving on mount, the selected lane removed, a refetch re-picking the selection — which kills the terminal and lets `LaneConsolePane`'s lane-switch effect wipe a half-typed prompt. Header and info block are the conditional parts; the console is not. -- **CCAM does not orchestrate:** no chaining, no queue, no retry logic, no gate evaluation. The session in control makes all decisions; the dashboard records the claimed stage and shows evidence. The runtime layer (`ccam lanes up|down|hook`) does not change this — it offers *primitives* a session calls; nothing in the dashboard sequences them. +- **AgentHub does not orchestrate:** no chaining, no queue, no retry logic, no gate evaluation. The session in control makes all decisions; the dashboard records the claimed stage and shows evidence. The runtime layer (`agenthub lanes up|down|hook`) does not change this — it offers *primitives* a session calls; nothing in the dashboard sequences them. - **The runtime never writes `stage`, `status` or `notes`** — only `slot` and `ports`. `status=running` means an AGENT is working, not that a server is listening; merging the two would corrupt `classifyLiveness`. Boot failures live in `LANES_ROOT/.state/lane/last-error.json` and surface via `GET /api/lanes/:id/runtime`. Same boundary as the console-never-writes-stage rule above. -- **A lane's stack is up or down as a computed fact, never a stored one.** `runtimeFacts` re-derives it from pid files and port probes on every read. A process dies to OOM, a stray `kill`, a reboot — caching a truth CCAM does not control buys ghost state. Slot allocation is the opposite (fully controlled, must be race-free), so that one does live in the DB, under `withLaneLock` plus a partial unique index. +- **A lane's stack is up or down as a computed fact, never a stored one.** `runtimeFacts` re-derives it from pid files and port probes on every read. A process dies to OOM, a stray `kill`, a reboot — caching a truth AgentHub does not control buys ghost state. Slot allocation is the opposite (fully controlled, must be race-free), so that one does live in the DB, under `withLaneLock` plus a partial unique index. - **A profile's `profile.env` is parsed, never sourced.** Hooks are executed deliberately; config is only read. Sourcing arbitrary shell from a user's repository into the dashboard process would be a code-execution path. Hook names always come from the fixed allowlist in `server/lib/lane-profile.js`, never from a request. - Pipeline templates are JSON files (`server/data/pipelines/`) with node definitions; custom templates override built-ins when `DASHBOARD_PIPELINES_DIR` is set. - Nodes render in five states: `failed` (rejected), `current` (now), `done` (with evidence), `passed-no-evidence` (claimed or skipped, amber), `pending` (not reached). @@ -60,7 +60,7 @@ See `docs/LANES.md` for full guide: stage reporting, custom templates, lane acti - MCP install/build/start: `npm run mcp:install`, `npm run mcp:build`, `npm run mcp:start` - MCP build freshness: `npm run mcp:check-build` (must pass whenever `mcp/src` changes) - MCP typecheck: `npm run mcp:typecheck` -- CLI (after setup): `ccam ` — terminal access to the full dashboard surface (`bin/ccam.js`; `ccam help` lists commands) +- CLI (after setup): `agenthub ` — terminal access to the full dashboard surface (`bin/agenthub.js`; `agenthub help` lists commands) ## Testing and verification policy - Backend changes: run `npm run test:server` before finishing. @@ -79,7 +79,7 @@ See `docs/LANES.md` for full guide: stage reporting, custom templates, lane acti - Explore first, then implement. - For larger tasks, propose/check a short plan before broad edits. - Use file-specific rules in `.claude/rules/` when working in scoped areas. -- Use project skills from `skills/` for repeatable workflows (they ship with the `ccam` plugin: `/ccam:`, or the bare `/` alias). -- Use `agents/` subagents for focused review or investigation passes (they ship with the plugin: `ccam:backend-reviewer`, etc.). -- **Declare lane stage even in plain chat, not just inside skills.** `ccam stage` (see `docs/LANES.md` § Reporting a stage) is a reporting command, not a skill-only ritual — any Claude session working inside an adopted lane's cwd should call it on real stage transitions (starting to plan, starting to implement, running tests, opening the PR, etc.), whether or not a skill is driving. Tool-event detection (`server/lib/stage-detect.js`) only ever paints the amber "detected" badge, never the blue `current` ring — a lane worked entirely through plain chat with no `ccam stage` calls will show a stale `current` stage no matter how much real work happens. Skip it only when `ccam stage` reports no lane owns the cwd (not adopted). -- **Prefer `ccam:ship-feature`(`-lane`) over invoking `superpowers:brainstorming`/`writing-plans`/`executing-plans`/`subagent-driven-development`/`test-driven-development` directly for implementation work inside a lane's cwd.** Those Superpowers skills know nothing about CCAM lanes and never call `ccam stage` themselves — going straight to them (skipping the CCAM wrapper) is exactly how a lane's `current` stage goes stale mid-implementation while `ccam stage` is never called for real transitions. `ccam:ship-feature`/`ship-feature-lane` drive the same underlying Superpowers skills phase-by-phase and declare `ccam stage` at every boundary — pick one of them first for any multi-phase feature/bugfix inside a lane; reach for a bare Superpowers skill directly only for work with no lane cwd to report against (e.g. a scratch repo, or explicitly told to skip the CCAM wrapper). +- Use project skills from `skills/` for repeatable workflows (they ship with the `agenthub` plugin: `/agenthub:`, or the bare `/` alias). +- Use `agents/` subagents for focused review or investigation passes (they ship with the plugin: `agenthub:backend-reviewer`, etc.). +- **Declare lane stage even in plain chat, not just inside skills.** `agenthub stage` (see `docs/LANES.md` § Reporting a stage) is a reporting command, not a skill-only ritual — any Claude session working inside an adopted lane's cwd should call it on real stage transitions (starting to plan, starting to implement, running tests, opening the PR, etc.), whether or not a skill is driving. Tool-event detection (`server/lib/stage-detect.js`) only ever paints the amber "detected" badge, never the blue `current` ring — a lane worked entirely through plain chat with no `agenthub stage` calls will show a stale `current` stage no matter how much real work happens. Skip it only when `agenthub stage` reports no lane owns the cwd (not adopted). +- **Prefer `agenthub:ship-feature`(`-lane`) over invoking `superpowers:brainstorming`/`writing-plans`/`executing-plans`/`subagent-driven-development`/`test-driven-development` directly for implementation work inside a lane's cwd.** Those Superpowers skills know nothing about AgentHub lanes and never call `agenthub stage` themselves — going straight to them (skipping the AgentHub wrapper) is exactly how a lane's `current` stage goes stale mid-implementation while `agenthub stage` is never called for real transitions. `agenthub:ship-feature`/`ship-feature-lane` drive the same underlying Superpowers skills phase-by-phase and declare `agenthub stage` at every boundary — pick one of them first for any multi-phase feature/bugfix inside a lane; reach for a bare Superpowers skill directly only for work with no lane cwd to report against (e.g. a scratch repo, or explicitly told to skip the AgentHub wrapper). diff --git a/Dockerfile b/Dockerfile index 19d40bef..5b5285bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Multi-stage Dockerfile for Claude Code Agent Monitor - a Node.js server with a React client. +# Multi-stage Dockerfile for AgentHub - a Node.js server with a React client. # This setup optimizes the final image size by separating the build and runtime stages. # The first stage installs only the production dependencies for the server, while the second stage builds the React client. # The final stage combines the necessary files and dependencies to run the application in production. diff --git a/INSTALL.md b/INSTALL.md index c301efb1..896a9fbd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,6 +1,6 @@ # Installation -A step-by-step guide to get the Claude Code Agent Monitor up and running on your machine, with optional sections for importing history and running in a container. +A step-by-step guide to get AgentHub — the local control surface for Claude Code agents — up and running on your machine, with optional sections for importing history and running in a container. ## Fastest path — install it as a Claude Code plugin @@ -8,13 +8,13 @@ If all you want is a working dashboard, skip every step below. From Claude Code: ``` /plugin marketplace add Smartgift-AI/Claude-Code-Monitor -/plugin install ccam@claude-code-agent-monitor-plugins +/plugin install agenthub@claude-code-agent-monitor-plugins ``` -The next session start installs the hooks, boots the server, puts the `ccam` CLI +The next session start installs the hooks, boots the server, puts the `agenthub` CLI on PATH and connects the MCP tools — no clone, no `npm run setup`, no -`npm run install-hooks`, no manual `npm start`. Run `/ccam-doctor` to check the -result and `/ccam-open` to build the UI and get the URL. +`npm run install-hooks`, no manual `npm start`. Run `/agenthub-doctor` to check the +result and `/agenthub-open` to build the UI and get the URL. Requires Node **>= 22.5** (a plugin install has no native `better-sqlite3`, so the server stores data through `node:sqlite`). Full behavior, including the @@ -32,7 +32,7 @@ dashboard, run it from source, or stay on Node 20. | Claude Code | 2.x+ | Required for hook integration | | Python | 3.6+ | Optional — statusline utility only | | Git | Any | For cloning the repository | -| `superpowers` plugin | Any | Optional — required only by `/ccam:ship-feature` and `/ccam:ship-feature-lane`, which drive its skills step by step (and which the built-in pipeline templates name as their per-node commands). Nothing else in CCAM needs it; `/ccam-doctor` reports it as `WARN` when absent. **Installed automatically** by the plugin's `SessionStart` bootstrap (opt out with `CCAM_AUTO_INSTALL_PLUGINS=0`); by hand: `claude plugin install superpowers@claude-plugins-official` | +| `superpowers` plugin | Any | Optional — required only by `/agenthub:ship-feature` and `/agenthub:ship-feature-lane`, which drive its skills step by step (and which the built-in pipeline templates name as their per-node commands). Nothing else in AgentHub needs it; `/agenthub-doctor` reports it as `WARN` when absent. **Installed automatically** by the plugin's `SessionStart` bootstrap (opt out with `AGENTHUB_AUTO_INSTALL_PLUGINS=0`); by hand: `claude plugin install superpowers@claude-plugins-official` | --- @@ -40,7 +40,7 @@ dashboard, run it from source, or stay on Node 20. ```bash git clone https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor.git -cd Claude-Code-Agent-Monitor +cd Claude-Code-Monitor ``` --- @@ -51,7 +51,7 @@ cd Claude-Code-Agent-Monitor npm run setup ``` -This installs all server and client dependencies, plus the VS Code extension, and links the `ccam` CLI. +This installs all server and client dependencies, plus the VS Code extension, and links the `agenthub` CLI. A plain root install already covers server **and** client — a `postinstall` hook installs the client dependencies automatically, so this alone is enough to build and run the dashboard: @@ -59,7 +59,7 @@ A plain root install already covers server **and** client — a `postinstall` ho npm install ``` -`npm run setup` additionally installs the VS Code extension and links the `ccam` CLI. (If you install with `--ignore-scripts`, the `postinstall` hook is skipped — run `cd client && npm install` manually in that case.) +`npm run setup` additionally installs the VS Code extension and links the `agenthub` CLI. (If you install with `--ignore-scripts`, the `postinstall` hook is skipped — run `cd client && npm install` manually in that case.) Or via Makefile (also installs MCP dependencies): @@ -165,14 +165,14 @@ can be raised (the defaults are generous for typical use): | Variable | Default | Purpose | | --------------------------------- | ------- | ----------------------------------------------------------- | -| `CCAM_IMPORT_MAX_BYTES` | 1 GB | Maximum size per uploaded file | -| `CCAM_IMPORT_MAX_FILES` | 2000 | Maximum files per upload request | -| `CCAM_IMPORT_MAX_EXTRACT_BYTES` | 4 GB | Ceiling on uncompressed bytes per archive (zip-bomb guard) | +| `AGENTHUB_IMPORT_MAX_BYTES` | 1 GB | Maximum size per uploaded file | +| `AGENTHUB_IMPORT_MAX_FILES` | 2000 | Maximum files per upload request | +| `AGENTHUB_IMPORT_MAX_EXTRACT_BYTES` | 4 GB | Ceiling on uncompressed bytes per archive (zip-bomb guard) | Set them before `npm run dev` or `npm start`: ```bash -CCAM_IMPORT_MAX_EXTRACT_BYTES=17179869184 npm start # allow 16 GB extraction +AGENTHUB_IMPORT_MAX_EXTRACT_BYTES=17179869184 npm start # allow 16 GB extraction ``` --- @@ -188,15 +188,15 @@ npm start # Start Express serving client/dist on port 4820 Open **http://localhost:4820** in your browser. -`npm start` (and `ccam start`, its backgrounded form) only run while that +`npm start` (and `agenthub start`, its backgrounded form) only run while that process is alive — a reboot loses it. On Linux, run the server as a systemd `--user` service instead so it comes back on its own after every reboot, with no manual step and no Claude Code (or any other tool) needed to trigger it: ```bash -ccam service install # writes + enables ~/.config/systemd/user/ccam.service -ccam service status # systemctl --user status ccam -ccam service uninstall # disable and remove it +agenthub service install # writes + enables ~/.config/systemd/user/agenthub.service +agenthub service status # systemctl --user status agenthub +agenthub service uninstall # disable and remove it ``` See [`README.md` → Run as a service](./README.md#run-as-a-service-auto-start-on-boot) @@ -252,8 +252,8 @@ This repository includes extension packs for both Claude Code and Codex. - Claude Code loads project extensions from: - `CLAUDE.md` - `.claude/rules/` - - `skills/` (shipped as the `ccam` plugin's skills) - - `agents/` (shipped as the `ccam` plugin's agents) + - `skills/` (shipped as the `agenthub` plugin's skills) + - `agents/` (shipped as the `agenthub` plugin's agents) - Codex project packs live under `.codex/`: - `AGENTS.md` - `.codex/rules/` @@ -266,7 +266,7 @@ See [`.codex/README.md`](./.codex/README.md) for Codex extension details. ## Optional: VS Code extension -The **Claude Code Agent Monitor** is also available as a dedicated VS Code extension for seamless, integrated monitoring. +**AgentHub** is also available as a dedicated VS Code extension for seamless, integrated monitoring.

VS Code Extension Screenshot @@ -293,7 +293,7 @@ The **Claude Code Agent Monitor** is also available as a dedicated VS Code exten For advanced configuration, refer to the [.vscode](./.vscode) and [vscode-extension](./vscode-extension) directories. > [!TIP] -> Extension on VS Code Marketplace: [Claude Code Agent Monitor](https://marketplace.visualstudio.com/items?itemName=hoangsonw.claude-code-agent-monitor) +> Extension on VS Code Marketplace: [AgentHub](https://marketplace.visualstudio.com/items?itemName=hoangsonw.claude-code-agent-monitor) --- @@ -344,7 +344,7 @@ podman run -d --name agent-monitor \ > The image sets `DASHBOARD_HOST=0.0.0.0` and `DASHBOARD_DATA_DIR=/app/data` internally (baked into the `Dockerfile`). The `0.0.0.0` bind is required because a container's loopback is a separate namespace the published port cannot reach; `/app/data` is bind-mounted to `~/.claude/agent-dashboard` on the host so Docker uses the same database as `npm start`. The trust boundary is the **host** port publish — the examples use `-p 127.0.0.1:4820:4820`, so the dashboard is local-only. To expose it on a LAN, publish on `0.0.0.0` (`-p 4820:4820`) **and** set `DASHBOARD_TOKEN`. > [!IMPORTANT] -> Claude Code hooks run on the host, not inside the container. After the container is healthy on `http://localhost:4820`, run `npm run install-hooks` on the host so Claude Code posts hook events back to the containerized server. The installer refuses to run inside a container (issue #193) to avoid writing a container-internal handler path into a bind-mounted `~/.claude`; use `CCAM_ALLOW_CONTAINER_HOOKS=1` only if you run Claude Code inside the same container. +> Claude Code hooks run on the host, not inside the container. After the container is healthy on `http://localhost:4820`, run `npm run install-hooks` on the host so Claude Code posts hook events back to the containerized server. The installer refuses to run inside a container (issue #193) to avoid writing a container-internal handler path into a bind-mounted `~/.claude`; use `AGENTHUB_ALLOW_CONTAINER_HOOKS=1` only if you run Claude Code inside the same container. --- ## Troubleshooting diff --git a/Makefile b/Makefile index 1c47bcc6..4b0e0c4b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # ───────────────────────────────────────────────────────────────────────────── -# Claude Code Agent Monitor — Makefile +# AgentHub — Makefile # A collection of convenient commands for development, testing, and deployment. # Prerequisites: GNU Make, Node.js, npm # Author: Nguyễn Ngọc Trí Vĩ diff --git a/README.md b/README.md index 1a0ce5ec..fd931639 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ -# Claude Code Monitor +# AgentHub -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 SmartGift build. Local-first **control surface** for Claude Code agents — +monitoring is one part of it, not the whole. Hooks POST every tool call to an +Express + SQLite server and a React UI updates over WebSocket; **lanes** track +parallel agent work through a pipeline; and the `agenthub` CLI drives real terminal +sessions, lane runtimes, and Claude Code's own configuration. + +AgentHub records and shows — it does not orchestrate. The session in control makes +every decision. Internal build — all rights reserved. @@ -15,12 +20,12 @@ Internal build — all rights reserved. - **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 + that never calls `agenthub 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 + terminal too via `agenthub 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. @@ -32,29 +37,29 @@ 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 +by the dashboard. `/agenthub:ship-feature` and `/agenthub: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 +normally — the two pipeline skills are what stop partway. `/agenthub-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` +logged to `~/.claude/agent-dashboard/runtime/bootstrap.log`, and `/agenthub-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 +Set `AGENTHUB_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 +plugin's command is *reported* by `/agenthub-doctor` and never installed, because a template is a file anyone can drop into `DASHBOARD_PIPELINES_DIR`. ## Install as a Claude Code plugin @@ -64,13 +69,13 @@ Two commands on a machine that has nothing but Claude Code, no clone and no ``` /plugin marketplace add Smartgift-AI/Claude-Code-Monitor -/plugin install ccam@claude-code-agent-monitor-plugins +/plugin install agenthub@claude-code-agent-monitor-plugins ``` -The first session start installs the hooks, boots the server, puts `ccam` on +The first session start installs the hooks, boots the server, puts `agenthub` 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 +it. `/agenthub-doctor` reports the state, `/agenthub-open` builds the UI and prints the +URL, `/agenthub-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). @@ -94,25 +99,25 @@ 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 +that feed the dashboard — do not run it when the `agenthub` 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 +`npm start` only runs while that terminal (or `agenthub start`'s background +process) is alive — a reboot loses it. On Linux, `agenthub 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 +agenthub service install # writes + enables ~/.config/systemd/user/agenthub.service +agenthub service status # systemctl --user status agenthub +agenthub service uninstall # disable and remove it ``` -`ccam service install` also tries `loginctl enable-linger` so the service +`agenthub 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 ` command to run once yourself. Plugin installs get the same persistence automatically — see @@ -120,22 +125,22 @@ once yourself. Plugin installs get the same persistence automatically — see ## The CLI -`ccam` is linked by `npm run setup`; otherwise call `node bin/ccam.js`. +`agenthub` is linked by `npm run setup`; otherwise call `node bin/agenthub.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 # run ONE pipeline node's own command (no id: list them) +agenthub status # is the dashboard up +agenthub start # start it in the background and wait for healthy +agenthub service install # auto-start on boot via systemd --user (Linux) +agenthub sessions # recent sessions +agenthub lanes # lanes with stage and progress +agenthub lanes pipeline # this lane's pipeline template, or switch it +agenthub lanes node # run ONE pipeline node's own command (no id: list them) # (in a session: /intake, /qc, /ship-full, /ship-quick …) -ccam stage # declare the current lane's stage -ccam tail # live event feed +agenthub stage # declare the current lane's stage +agenthub tail # live event feed ``` -`ccam --help` lists the rest. +`agenthub --help` lists the rest. ## Lanes @@ -148,8 +153,8 @@ A lane is a working directory the dashboard watches. Two kinds: 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 +agenthub lanes add --cwd /path/to/repo --title "My feature" # adopt +agenthub lanes add --repo /path/to/repo --slug my-feature # managed worktree ``` Every lane also carries the CLI that drives it — `claude` (the default) or @@ -166,33 +171,33 @@ 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 +from its own pipeline nodes, `agenthub 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 +The declared stage comes from `agenthub 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 +A lane's pipeline template can be switched after creation — `agenthub lanes pipeline