Skip to content

Latest commit

 

History

History
2323 lines (1883 loc) · 205 KB

File metadata and controls

2323 lines (1883 loc) · 205 KB

Agent Dashboard for Claude Code & Codex

Real-time monitoring platform for Claude Code & Codex agent activity 🚀

A professional dashboard to track and visualize your Claude Code and Codex agent sessions, tool usage, and subagent orchestration in real-time. Built with Node.js, Express, React, and SQLite, it integrates directly with Claude Code and Codex via their native hook systems for seamless session tracking and analytics.

Claude Code OpenAI Codex Claude Code Plugins Model Context Protocol Node.js Python Express ws web-push swagger-ui-express multer adm-zip tar React TypeScript Javascript Vite Tailwind CSS PostCSS Autoprefixer React Router Lucide D3.js Mermaid i18next i18next Language Detector SQLite better--sqlite3 better-sqlite3 WAL WebSocket SSE OpenAPI Swagger VS Code Electron electron-builder macOS Windows SMAppService macOS DMG NSIS Installer Vitest React Testing Library ESLint Prettier Docker Podman Open Container Initiative Prometheus Grafana Terraform Kubernetes Helm Kustomize Nginx Coralogix OpenTelemetry AWS Google Cloud Azure Oracle Cloud GitHub Actions Make Auto Release MIT License

Tip

See also: README-CN.md (中文版本), README-VN.md (Phiên bản tiếng Việt), README-KO.md (한국어 버전), and README-ES.md (versión en español) for localized documentation with region-specific tips and best practices.

Note

Need task-first help? The GitHub Wiki is the practical handbook for everyday use, team operations, troubleshooting, CLI/MCP automation, and deployment recipes. The localized static Wiki remains the English, Vietnamese, Chinese, Korean, and Spanish product and architecture tour; exact technical contracts stay in docs/.


Table of Contents


Overview

Track sessions, monitor agents in real-time, visualize tool usage, and observe subagent orchestration through a professional dark-themed web interface. Integrates directly with Claude Code & Codex via their native hook systems.

graph LR
    A["Claude Code<br/>Session"] -->|hooks fire on<br/>tool use / stop| B["Hook Handler<br/>(Node.js script)"]
    B -->|HTTP POST| C["Dashboard Server<br/>(Express + SQLite)"]
    C -->|WebSocket<br/>broadcast| D["Dashboard UI<br/>(React + Tailwind)"]
    style A fill:#6366f1,stroke:#818cf8,color:#fff
    style B fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
    style C fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
    style D fill:#10b981,stroke:#34d399,color:#fff
Loading

In addition to the real-time monitoring dashboard, it also includes a local MCP server implementation in mcp/ that exposes a catalog of tools for introspecting and managing the dashboard itself, making it easy to integrate dashboard operations directly into your Claude Code & Codex workflows. There is also an agent extension layer, which provides Claude Code & Codex plugins, skills, and subagents for dashboard interaction, analytics, and workflow intelligence.

Internationalization (i18n)

The UI ships with built-in locale switching for English (en), Chinese (zh), Vietnamese (vi), Korean (ko), and Spanish (es). A custom language dropdown prevents the selector from crowding the sidebar as more locales are added. Language resources are loaded by namespace and persisted through browser storage for stable user preference across refreshes.

flowchart LR
    A["User selects language<br/>en / zh / vi / ko / es"] --> B["i18next detector<br/>localStorage -> navigator"]
    B --> C["Resolved language<br/>fallback: en"]
    C --> D["Namespace resources<br/>common/nav/dashboard/..."]
    D --> E["React useTranslation hooks"]
    E --> F["Localized UI + accessibility labels"]
Loading

For full architecture and operational guidance, see docs/I18N.md.

User Interface

Comes with a sleek dark theme, responsive design, and intuitive navigation to explore your agent activity:

Dashboard Overview
📡 Dashboard · Monitor — overview stats, active agent cards, and recent activity feed

Task Progress Overview on Dashboard agent cards
📋 Task Progress · Overview — Dashboard agent cards and Sessions rows reuse the same compact completion donut beside status; hover or focus opens an owner-aware preview of current work and task states

Dashboard — System Health tab
🩺 Dashboard · Health — composite health score ring, storage engine donut chart, cache hit / error / success gauges, tool invocation bars, subagent effectiveness, model token distribution, and compaction stats — all auto-refreshing every 5 s

Kanban Board — Agents view
📋 Kanban Board (agents) — agents grouped by status across 4 columns: Working / Waiting / Completed / Error. The yellow Waiting column surfaces sessions blocked on user input (permission prompts, end-of-turn, or sitting at a fresh prompt) — hover a Waiting badge to see why (Needs input / Turn done / At prompt / Interrupted). Each card shows model, cost, and current tool at a glance.

Kanban Board — Sessions view
🗂️ Kanban Board (sessions) — sessions grouped by status across 5 columns: Active / Waiting / Completed / Error / Abandoned, toggleable from the same page. Hover any column header for a tooltip explaining the lifecycle transition.

Sessions Overview
📂 Sessions — searchable, filterable, server-paginated table of every recorded session with cost, model, agent count, and duration; the project picker supports searchable multi-selection and sorting uses a custom menu

Session Detail — Agents tab
🤖 Session Detail · Agents — real-time overview tiles (events, tool calls, subagents, compactions, errors, duration), top-tool usage bars, subagent type breakdown, token flow, and the agent hierarchy tree

Task Progress panel on Session Detail
Task Progress · Session Detail — the full owner-aware tracker combines a segmented completion donut, active task, completion bar, owner breakdown, and a task list paginated at 10 rows per page

Session Detail — Conversation tab
💬 Session Detail · Conversation — live transcript viewer with markdown rendering, syntax-highlighted code blocks (line numbers + copy), per-tool styled tool calls, slash-command pills with their captured TUI output, and inline session-rename markers

Session Detail — Timeline tab
🔬 Session Detail · Timeline — chronological event timeline with multi-dimension filters, Pre/Post grouping by `tool_use_id`, and tool-aware payload renderers

Activity Feed Overview
📰 Activity Feed — real-time event log with pause / resume, grouping, multi-dimension filters, and a "Session →" jump button per row

Analytics Overview
📊 Analytics — token usage by model, tool frequency, activity heatmap, and session trends with live / offline indicator; long chart legends paginate while short legends stay unchanged

Workflows Overview
🔀 Workflows — agent orchestration DAGs, tool execution Sankey diagrams, collaboration networks, bounded data-driven legends, and 11 interactive sections of workflow intelligence

Dynamic Workflow Runs on the Workflows page
🧬 Workflow Runs (Workflows page) — "dynamic workflows" spawned by the Workflow tool, reconstructed from on-disk run journals: status, agent count, tokens, and tool calls, expandable into a per-agent breakdown (phase, state, tokens, tools, duration) with humanized result previews

Dynamic Workflow Run expanded with phase filters and per-agent results
🧬 Workflow Runs · expanded — a run opened up: clickable color-coded phase filters, the per-agent metrics table, and a full list of clickable result items that expand to each agent's complete prompt and result

Dynamic Workflow Runs on the session detail page
🧬 Workflow Runs (Session detail) — the same fleets linked to their launching session, so a session's dynamic-workflow sub-agents and their folded-in token cost are visible inline

Agent Config — Claude Code and Codex explorers
🧰 Agent Config — switch between the full Claude Code explorer and a live Codex workspace for defaults, models, profiles, MCP, projects, skills, rules, hooks, plugins, and instructions. Codex previews redact secrets; its user-maintained config, hooks, rules, skills, and instructions can be edited safely with backups.

Codex Config Explorer — overview, configuration source, and workspace tabs
🧰 Codex Config Explorer — the Codex workspace brings together config.toml, account models, profiles, MCP servers, projects, skills, hooks, rules, plugins, and instructions. Edit supported user-managed files with timestamped backups; config.toml is edit-only.

Claude Config Explorer — Skills tab
🧩 Claude Config Explorer · Skills — the Skills tab lists every discovered skill (user, project, and plugin) with its description and source, is searchable across the whole set, and opens any skill file for a safe, timestamp-backed edit

Run Agent — Claude Code and Codex launch selection
▶️ Run Agent — choose Claude Code or Codex every time you open the launcher. Claude keeps Conversation / One-shot controls; Codex starts a native interactive app-server thread with its own approval and sandbox controls. Codex models come from the signed-in CLI catalog, while Claude lists observed models plus its supported aliases.

Run Agent — live streaming output
💬 Run Agent · live stream — Claude stream-json and Codex app-server events both render as a chat, including reasoning, command, file-change, and tool activity. Dashboard Runs lets you leave an agent working in the background and re-attach later.

Settings Overview
⚙️ Settings — model pricing rules, hook installation status, data management, notification preferences, and system info

Settings — Alerts & Webhooks
🔔 Settings · Alerts — rules-based alerting engine and outbound webhooks in one place: alert rules (event pattern / inactivity / stuck agent / token threshold) with per-rule cooldown, a live fired-alert feed, and 14 first-class webhook providers (Slack, Discord, Teams, Google Chat, Mattermost, Rocket.Chat, Telegram, PagerDuty, Opsgenie, Splunk On-Call, Zapier, Make, n8n, Pipedream) plus a generic JSON endpoint with optional HMAC signing

Settings — Remote Data Sources
🛰️ Settings · Remote Data Sources — pull Claude Code and Codex activity from other machines over SSH: optionally set independent Remote Claude home and Remote Codex home paths, test each provider, sync manually or on a background poller, and switch the global data scope between local, all sources, or a specific machine, with per-session source badges

The sidebar provides quick access to the Dashboard, Kanban Board, Sessions list, Activity Feed, Analytics, Workflows, and Settings. Each page is designed to give you deep insights into your Claude Code agent activity with real-time updates and rich visualizations.


Features

The dashboard offers a comprehensive set of features to monitor and analyze your Claude Code sessions and agents:

Cursor sessions too (informational): CCAM ingests whatever agent transcripts land under ~/.claude — on this machine and on synced remotes. Cursor usage counts the same way: Cursor happens to store its agent sessions in those paths alongside Claude Code. CCAM does not distinguish which app wrote a file.

Feature Description
Task Progress Owner-attributed task tracking derived from observable provider state: current Claude TaskCreate / TaskGet / TaskUpdate / TaskList and lifecycle events, legacy TodoWrite, and direct or unified-exec-wrapped Codex update_plan. Sessions with task state show the same compact donut and portal-rendered hover/focus preview beside status in the Sessions table and every Dashboard Agent card; Session Detail shows the full progress panel with status segments, active work, owner breakdown, and task rows paged 10 at a time. Progress is scoped to the latest top-level work: a newer Claude human turn or Codex task with no emitted tracker clears older state, and unfinished state is discarded when that turn/task ends without a final update. Fully completed history remains visible.
Dashboard Two tabs persisted in localStorage: Monitor — overview stats (6 stat cards), active agent cards with collapsible subagent hierarchy, and recent activity feed with dynamic item counts that fill available viewport height via ResizeObserver. Health — composite system health score ring (weighted: 0.4 × success rate + 0.25 × cache hit rate + 0.25 × (100 − error rate) + 0.1 × (100 − heap %)), storage engine donut chart with record distribution, cache performance / error rate / success rate gauges, tool invocation horizontal bar chart (top 8), subagent effectiveness bars, model token distribution, and compaction impact stats. All health metrics auto-refresh every 5 s from /api/settings/info and /api/workflows. Cursor-following tooltips with viewport edge detection on every chart
Kanban Board Two views with a header toggle (persisted in localStorage): Agents — 4 columns (Working / Waiting / Completed / Error) — and Sessions — 5 columns (Active / Waiting / Completed / Error / Abandoned). The Waiting column maps directly to the persisted waiting status on agents — set when Claude Code is sitting at a prompt (fresh session, between turns, or blocked on a permission Notification) and transitions to working the moment the user resumes (UserPromptSubmit / PreToolUse). Each column header shows a ? tooltip explaining lifecycle transitions. Cards fetch by persisted status from the server (effectively unlimited per status), then paginate client-side at 10 cards per column with a "Show more" affordance. WS subscription scopes to the active view (agent_* vs session_* frames) so off-view updates don't trigger refetches. Waiting badges expose the row's awaiting_reason as a hover tooltip — Needs input (notification), Turn done (stop), At prompt (session_start), Interrupted (interrupted) — kept tooltip-only on the compact cards so titles keep their space; wider surfaces (Sessions table, session-detail header) additionally show the reason inline as a nested chip, with urgent reasons (permission prompts, interruptions) in a hotter amber
Sessions Searchable, filterable, server-paginated table of every recorded session. Each page click hits /api/sessions?status=&q=&limit=10&offset=…, so cost computation runs only over the visible page — independent of how many sessions exist in the database. The first page also opts into the same local, in-memory Codex startup row shown on Dashboard and Kanban; it is visible immediately but non-navigable until a durable session ID replaces it, and it does not change durable totals or pagination. The search box (q=) does case-insensitive matching across id / name / cwd on the server with a 300 ms debounce, and the response carries a total count for the paginator UI. Status filter, search, and pagination compose. Each session's human-readable name is read from the transcript and kept in sync in real time — an explicit title from /rename, claude -n, or the picker's Ctrl+R (the JSONL custom-title line) always wins, otherwise the auto-generated ai-title fills in, otherwise the session's first user prompt (truncated, with tool-result / slash-command noise skipped) fills the placeholder name and the main agent's placeholder name/task — so sessions that never get a title (including imported ones) still say what they're doing; the dashboard surfaces that name (falling back to the short ID) on cards, the Dashboard, the Activity Feed, and the Run resume picker.
Session Detail Per-session real-time overview panel with active-agent banner (current tool + task), six tile counters (events with events/min rate, tool calls, subagents, compactions, errors, ticking duration), top-tool usage bars, subagent type breakdown, stacked token-flow strip, and event-type pill cloud — all live-refreshed on hook events. Below it: agent hierarchy tree, full event timeline with multi-dimension filters (status, event type, tool, agent, text search, date range), Pre/Post grouping by tool_use_id, human-readable summary block, tool-aware input/response renderers (terminal for Bash, unified diff for Edit, line-numbered code for Read/Write, match list for Grep, key/value card for MCP tools), and a Conversation tab that renders transcripts — including messages typed mid-turn (queued while Claude was still working), placed where Claude actually received them, with harness notifications attributed to System — with markdown (headings, lists, blockquotes, tables, task lists), syntax-highlighted code blocks (js/ts, python, json, bash, html, css, sql, yaml, diff) with line numbers and copy-to-clipboard, and per-tool styled tool calls (Bash → terminal, Edit → side-by-side old/new, Write → file label, Read → path chip, Grep → pattern card). When the session is blocked on the human, a yellow waiting-for-input banner under the header names the awaiting_reason, its explanation, and how long the session has been waiting (pulsing dot + relative time); the header's Waiting badge carries the same reason as a nested chip
Activity Feed Real-time streaming event log with pause/resume, multi-dimension filters (same toolbar as Session Detail plus a Session filter), server-driven "Load more" pagination, debounced filter-aware live refresh preserving the loaded page size, grouping toggle, origin prefix showing project › session › subagent, and a "Session →" button per row
Analytics Token usage, tool frequency, activity heatmap (centered, day-of-week aligned starting Sunday, day-name tooltips), session trends, live/offline connection indicator. While the analytics payload loads, the chart region (not just the stat tiles) shows pulsing skeleton placeholders that mirror the chart layout, so the page never flashes empty/zero charts. Long legends on Analytics and Workflows paginate; legends that fit one page remain unchanged
Live Updates WebSocket push -- no polling, instant UI updates
Auto-Discovery Sessions and agents are created automatically from provider signals. Claude Code creates an immediate Waiting card at SessionStart. Codex first exposes a local, in-memory Waiting card as soon as its interactive TUI process starts, including before Codex assigns a stable session ID. A hook, live-thread row, or rollout then creates the durable session and replaces that temporary card. If the user opens Codex's Resume picker and selects an existing thread, CCAM detects the rollout or writer lock already opened by that exact Codex PID and switches to the durable resumed session before the first new message. The pre-identity card is never written to SQLite, history, analytics, pricing, workflows, alerts, or completion notifications, and it disappears when the process exits.
History Import Provider-aware Import History brings in Claude Code transcripts from ~/.claude/ and Codex rollout JSONL from ~/.codex/sessions. Each tab has its own default path, instructions, folder scan, and upload flow; both reuse their live ingestion logic, preserve token/cost/tool accounting, and are idempotent. External Codex rollouts are snapshotted into dashboard storage so their conversation remains available after the archive or source folder is removed.
Subagent Hierarchy Collapsible parent-child agent tree on Dashboard and Session Detail. Agents with subagents show expand/collapse chevrons; leaf agents show a dot indicator. Auto-expands when subagents are active
Background Agents Correctly tracks backgrounded subagents without premature completion
Subagent Tool Attribution Subagent-internal tool calls (Read, Bash, Edit, Grep, …) live only in per-subagent JSONL files — Claude Code emits no hooks for them. On every SubagentStop the dashboard fires a fire-and-forget scanAndImportSubagents pass that parses each subagents/agent-*.jsonl, pairs tool_use blocks with their matching tool_result by tool_use_id, and emits PreToolUse + PostToolUse events under the subagent's own agent_id. Idempotent (data LIKE '%"tool_use_id":"X"%' dedup) and merges into a live hook-created subagent row when one matches by type + start-time within 30 s, so no parallel <sid>-jsonl-* rows are created. The same path runs on npm run setup startup import for full historical backfill — sessions that pre-date the dashboard get full per-subagent tool timelines. Activity Feed and Session Detail render the parent chain as main › coder › explorer for nested subagents. That chain is reconstructed authoritatively by reconcileSubagentParents: a subagent row is first inserted flat under the main agent (a single hook event or JSONL file carries no spawner identity), then the spawner is recovered from each subagent transcript's Task tool result (toolUseResult.agentId, captured as spawnedChildren) so a subagent that spawns its own subagents nests under its true spawner instead of collapsing to one level under main. Idempotent and additive — it only repoints parent_agent_id, never inserts or deletes rows — and runs on the same SubagentStop scan, which returns a reparented count so the dashboard refetches even when re-parenting alone changed the tree shape
Cost Tracking Per-model cost estimation with configurable pricing rules and per-session breakdowns. Supports time-limited introductory rates (intro_* + intro_until on a pricing rule): usage on/before the cutoff date is priced at the intro rate and usage after it at the standard rate, so a promo like Claude Sonnet 5's launch discount (through 2026-08-31) stays correct for historical and future usage — the cost endpoint prices each day's usage at the rate effective on that date. Introductory rates are fully editable in Settings — the Model Pricing editor exposes a promo cutoff date plus per-category intro prices (input / output / cache-read / cache-write 5m & 1h), so a future model launch promo needs no code change, just an edit. Subagent cards show each subagent's OWN cost (derived from that subagent's transcript token usage and priced at the current rates), not the session total — a main-agent card stands in for the whole session and shows the session total, while a subagent card shows only what that subagent spent, so a subagent card no longer misleadingly reads as if it cost the entire session. Compaction-aware token accounting preserves totals across context compressions. Transcript reads are cached with incremental byte-offset updates for efficient token extraction
Transcript Cache Real-time extraction from JSONL transcripts: tokens, compactions, API errors (isApiErrorMessage entries stored as APIError events), turn durations (stored as TurnDuration events), thinking block counts, and usage extras (service_tier, speed, inference_geo). Turn durations carry stable transcript identities; complete parses repair legacy duplicate rows and inflated metadata totals, while capped tail parses stay append-only. Per-entry growable arrays are tail-capped at TRANSCRIPT_CACHE_MAX_ARRAY_LEN (default 1000, configurable) — both during parse and at finalize — so even a session that runs for days cannot grow a single cache entry without bound. Each entry stores only {mtimeMs, size, bytesRead, result}, so there's no shadow copy of the same data at both the top level and inside result. Session metadata is enriched with these fields in real-time
Notifications Full Web Push (VAPID) pipeline for reliable delivery. Arrive even when the tab is backgrounded or the browser is closed. Explicitly configured for macOS audio support. Configurable per-event toggles with subscription management
Alerts Rules-based alerting engine — configured entirely in Settings → Alerts & Notifications, a tabbed Rules / Channels / Activity control center (no separate page). Define alert rules with four condition types — event pattern (match event type / tool name / summary text, optionally requiring N matching events inside a time window, e.g. "more than 5 errors in 2 minutes"), inactivity (active session with no events for N minutes), stuck agent (agent sitting in working/waiting with no activity for N minutes), and token threshold (session total tokens past a limit). Event-driven rules evaluate server-side on every hook ingest (after the ingest transaction — alerting can never slow down or fail hook delivery); time-based rules run on a 60 s sweep. Fired alerts are persisted to alert_events with per-rule + per-session cooldown dedup (default 300 s), broadcast as alert_triggered WebSocket messages, and surface in the Activity tab's live feed with acknowledge / acknowledge-all, an unacknowledged-only filter, and per-alert "View session" links. Rules support enable/disable toggling and cascade their history on delete. Fired alerts also fan out to universal webhook targets configured in the Channels tab — 14 first-class providers plus a generic endpoint: Slack, Discord, Microsoft Teams, Google Chat, Mattermost, Rocket.Chat (native chat payloads); Telegram (Bot API), PagerDuty (Events API v2), Opsgenie (Alert API + GenieKey auth), Splunk On-Call (VictorOps REST); and Zapier, Make, n8n, Pipedream, or any generic endpoint (clean JSON envelope with optional HMAC-SHA256 signing and custom headers). Each provider is described by a server-side registry that declares its payload formatter, how its URL is resolved (some derive it from credentials — e.g. Telegram from the bot token, Opsgenie from the region — others default it), and which credential fields the UI renders. Targets support optional per-rule scoping, a synchronous "Send test" probe, and a recorded delivery log. Delivery runs detached from the alert path with a request timeout and bounded retry/backoff, so it can never slow or block monitoring; target URLs, secrets, and credential fields are stored server-side and never returned by the API (masked/redacted in every response)
Update Notifier Server periodically runs a non-blocking git fetch and compares the local checkout to origin/master/origin/main/origin/HEAD. When upstream is ahead, the UI surfaces a modal with the exact git pull && npm run setup command and a one-click Copy button; the Sidebar gets a persistent "Check for updates" button with live badge. The dashboard never pulls or restarts itself — the user runs the command in a terminal — so the mechanism cannot break dev sessions, pm2/systemd/Docker supervision, or leave orphaned processes
Settings System info, hook status, model pricing management, notification preferences, data export and restore (the Import History panel's Restore backup mode accepts one export .json up to 25 MiB and re-imports it idempotently without overwriting existing rows, so you can consolidate several machines' history into one dashboard), session cleanup. The Model Pricing section separates Anthropic Claude Model Pricing from OpenAI GPT Model Pricing with matching header layouts, provider-scoped Reset Defaults and Add Model controls, and info popovers explaining first-match rule lookup, SQL-style % wildcard syntax, manual price updates, and API-rate caveats. The GPT popover also explains USD-per-million-token units, the 272K Short/Long boundary, Fast-mode rates, and why unpublished tiers remain unpriced rather than estimated. The Dashboard Data control immediately re-fetches sessions, agents, events, tokens, workflows, analytics, and costs for Claude Code, Codex, or both. Separate Claude Code and Codex home inputs are fully i18n-driven and save at runtime; a Codex save re-arms live rollout watching and scans its new tree.
Run Agent + Agent Config /run begins with a Claude Code / Codex choice and keeps the provider toggle beside its Live status. Claude runs retain their headless and stream-json conversation modes; Codex runs use the CLI's local app-server protocol for a real interactive thread, native approval/sandbox policy, resume, stop, live output, and re-attach. Codex model choices come directly from the signed-in CLI, so model releases need no dashboard update; Claude shows its durable aliases plus locally observed models because its CLI has no model-list command. /cc-config pairs the established editable Claude Code explorer with a Codex workspace for config defaults, model cache, profiles, MCP, projects, skills, rules, hooks, installed plugins, and instruction files. Its normal previews redact secrets; the explicit local editor supports config.toml, hooks.json, user rules, skills, and instructions with atomic saves and mandatory timestamped backups, while warning that it cannot validate syntax. Codex profile commands and managed artifact paths copy in one click, and plugin cards use Codex's installed-plugin registry rather than showing cache folders. Both explorers refresh through their provider-specific filesystem watcher.
Codex Agent Config The Codex half of Agent Config reads the full local account model catalog without the generic preview limit that could falsely show zero models, and always includes base/profile overrides. Create standard Codex <name>.config.toml overlays directly in the app; each card copies its exact codex --profile <name> command in one click and opens a guarded editor. Preview paths are canonicalized before containment checks. The editor rejects symlinked path components below the trusted root, verifies canonical parent containment, and refuses [redacted] preview content. Profiles, hooks, rules, skills, and instructions share Claude-style View source / Copy path / Edit / Delete actions. Every allowed deletion is confirmed and backed up first (a skill keeps its entire directory); config.toml is permanently edit-only.
MCP Server (Local) Comprehensive local MCP server in mcp/ with three transport modes (stdio, HTTP+SSE, interactive REPL) and 97 typed tools across 16 domain modules. It covers observability, scoped sessions/agents/events, transcripts and images, Claude/GPT pricing, workflows, alerts, webhooks, imports and backup restore, Claude/Codex config, Run Agent, remote sources, hooks/homes/updates, push, and maintenance. Protocol and REPL modes share one validated catalog, with localhost-only targets and tiered mutation/destructive gates. Direct loopback HTTP may carry a bearer token; tokenized container-host aliases require HTTPS. Redirects are rejected, uploads are capped at 50 MiB per file and 100 MiB per call, binary responses at 10 MiB, and backup restore at 25 MiB
Workflows D3.js-powered visualization page with 11 interactive sections: agent orchestration DAG, tool execution Sankey diagram, collaboration network, subagent effectiveness (day-of-week sparklines with portal-rendered tooltips that escape the card's overflow:hidden and clamp to the viewport so they never get clipped), detected workflow patterns, model delegation flow, error propagation map (horizontal bars with rate badges, agent type breakdown, API/session error cards), concurrency timeline, session complexity scatter, compaction impact analysis (redesigned as a clear "sessions by compaction count" histogram with axis titles, stat tiles — total / sessions affected / avg / peak — an explanatory help line, and per-bar hover tooltips), and per-session drill-in. Each section's right-aligned subtitle clamps to a single line (ellipsis + hover title) so a long translation never wraps the header. Rich, i18n-aware tooltips throughout: every chart's section title carries an i icon that opens a structured "What this shows / How to read it / Why it matters" popover; hovering nodes, edges, bars, and bubbles surfaces multi-section tooltips with deterministic, value-dependent interpretations (e.g. share-of-source / share-of-target percentages, success-rate health buckets, family descriptions for Opus / Sonnet / Haiku, timing patterns like front-loaded / mid-session / back-loaded). Each of the six headline stat cards has a bottom-right info popover explaining how the metric is calculated and what its current value means in plain language. Tooltips are DOM-mutated through a single ref per chart with container-level mouseleave fallbacks, so they never lag behind the cursor or stick after re-render. Clicking a row in Detected Workflow Patterns expands an in-place detail panel with the full step sequence, stats grid, a deterministic narrative (loop detection, frequency bucket), and a practical suggestion. Status filter tabs (Active Only / Completed / All) filter all 11 sections. Cross-filtering, JSON export, and real-time WebSocket auto-refresh with 3-second debounce. A Workflow Runs panel surfaces "dynamic workflows" — the fleets of sub-agents spawned by the Workflow tool (and self-paced /loop) — which emit no hooks and are instead reconstructed from on-disk run journals (workflows/wf_<runId>.json): each run shows its phases and a per-agent token / tool-call / duration breakdown, with live running detection before the journal is written and a linked subsection on each Session Detail page
Compaction Tracking Detects /compact events from JSONL transcripts, creates compaction agents and events. Backfills legacy compactions on startup. A periodic scanner (cadence derived from DASHBOARD_STALE_MINUTES) catches compactions even when no hooks fire. Reads each active session's transcript path directly from sessions.transcript_path (populated by the hook handler on the first event that carries it, plus a one-time backfill from events) instead of doing a SELECT DISTINCT json_extract(events.data, '$.transcript_path') over the entire events table — so the sweep is O(active sessions) and stays cheap on a mature database. Shares the transcript cache so no duplicate file reads occur. Synthetic compaction rows are stamped with the transcript timestamp on both started_at and ended_at so duration is exactly 0 (compaction is instantaneous); a startup repair migration also heals any pre-existing rows where ended_at < started_at (issue #156)
Subsessions/Resumed Sessions Automatically reactivates sessions when new events arrive, correctly handles /resume and orphaned sessions. Periodic sweep (every ¼ of DASHBOARD_STALE_MINUTES, clamped to 60 s – 5 min) marks abandoned sessions that slip past event-based detection
Pre-Existing Session Detection Sessions already running when the server starts are imported as "active" (based on recent JSONL file modification). Stop events also reactivate imported completed/abandoned sessions, so the first hook from an in-progress session always surfaces it on the dashboard
Continuous Project Sync The startup auto-import of ~/.claude/projects is one-time (marker-gated), so a project folder created after first launch — whose sessions never flow through hooks (e.g. host-only hooks disabled) — would stay invisible until a manual rescan. A background sync (startSessionSync) closes that gap via three triggers sharing one mtime cache + a single coalesced sweep: an immediate sweep at startup, a debounced fs.watch that fires the instant a new session file/project folder appears (recursive on macOS/Windows; root + immediate children on Linux to avoid the userland recursive-watcher hazard), and a periodic poll (DASHBOARD_SESSION_SYNC_MS, default 30 s). Each sweep re-parses only files whose mtime advanced and broadcasts session_created/session_updated (plus the main agent) so the UI refreshes live; an unchanged session already in the DB is skipped without re-parsing, so restart cost stays O(new/changed files)
Remote Data Sources Live remote / multi-machine Claude Code and Codex collection over SSH. One source independently mirrors ~/.claude/projects and ~/.codex/sessions (plus Codex's lightweight session_index.jsonl for native renamed titles) through scp, or wsl.exe + tar for WSL-hosted CLIs on a Windows SSH host. Each isolated stage goes through its provider's normal importer and tags rows with sessions.source; a source is healthy when either provider is available, so Claude-only, Codex-only, and mixed machines all work. The 15 s DASHBOARD_REMOTE_SYNC_MS poller and immediate add/re-enable pulls broadcast provider-aware remote_source.status, remote_data.updated, and per-session updates. Remote lifecycle is reconciled from each mirrored transcript. If a provider is unavailable, errors, or is stuck syncing beyond DASHBOARD_STALE_MINUTES, only that provider's old remote sessions fall back to the ordinary stale sweep; a healthy sibling provider remains mirror-owned. Configure optional independent Remote Claude home and Remote Codex home paths in Settings → Remote Data Sources or use ccam remote-sources; SSH authentication remains entirely on the host (no passwords/secrets stored).
Responsive Design Mobile-friendly layouts with stacking grids, scrollable tables, and collapsible sidebar
UI Localization Built-in custom-dropdown language switching with translated UI copy and accessibility labels for English (en), Chinese (zh), Vietnamese (vi), Korean (ko), and Spanish (es). Coverage extends end-to-end through the Workflows tooltips: stat-card calculations and value-bucket interpretations, per-chart "What / How to read / Why" popovers, every graph's hover tooltip (orchestration, tool flow, pipeline, model delegation, concurrency), the Workflow Patterns detail-panel narratives and suggestions, the Settings → Model Pricing info popover, the CLAUDE_HOME panel, and the entire Import History flow.
Seed Data Built-in seed script for demos and development
Statusline Color-coded CLI statusline showing model, context usage, git branch, per-direction tokens, and session cost (USD)
Model Name Formatting Human-friendly model names throughout the UI: raw identifiers like claude-opus-4-7-20260101 or claude-opus-4-7[1m] display as "Claude Opus 4.7" or "Claude Opus 4.7 (1M)". Handles Claude, GPT, and Gemini families with automatic version dot-joining, date/latest suffix stripping, provider prefix removal, and context-window tag formatting. Settings page retains raw names for pricing rule configuration
Claude + Codex Plugin Marketplace One 14-plugin source tree ships canonical Claude manifests, Codex .codex-plugin/plugin.json manifests, both marketplace catalogs, 66 bundled plugin skills, 18 Claude subagents, 34 Claude commands, 3 CLI helpers, and OpenAI skill metadata. The skills.sh CLI discovers 75 total repository skills with npx skills add hoangsonww/Claude-Code-Agent-Monitor --list. Install with claude plugin marketplace add, codex plugin marketplace add, or npx skills add
Run Claude Spawn claude subprocesses directly from the dashboard with a chat-style streaming UI. Two modes: Conversation (multi-turn — stdin stays open, follow-up turns are piped as stream-json envelopes) and One-shot (headless, single prompt → single response). Conversation mode also supports resuming any existing session via claude --resume <id> — pick from your full sessions history with a searchable picker. The unified active-runs / history modal also offers two zero-config jump buttons: Resume on any past conversation row spawns claude --resume <id> immediately and seeds the chat with the prior transcript so you land in the live view with full context (no need to retype a prompt — the spawn idles on stdin until you send a follow-up); View on any past one-shot row loads the captured transcript inline into the run viewer as read-only (no spawn — same panel, no Stop/follow-up controls). Active runs switcher in the header lets you leave a run in the background, start another, and re-attach later. Re-attach is durable: the client reconciles the spawner's in-memory envelope log (?envelopes=1) with the session's on-disk JSONL transcript and prefers whichever has more user/assistant messages, so navigating away from a resumed run and coming back keeps the full prior history visible (the spawner only sees post-spawn turns; the transcript file has prior + current). Model dropdown (Opus 4.7 / 1M / Sonnet 4.6 / Haiku 4.5 / custom), permission-mode picker with explicit bypassPermissions warning, thinking-effort field (low / medium / high — wired to --effort), cwd autocomplete pre-filled with the user's home directory — a neutral spawn location that doesn't inherit the dashboard repo's own .claude project context (agents, skills, rules, CLAUDE.md, .mcp.json); falls back to the dashboard cwd if no home suggestion is available, with home listed first in the suggestion groups (home → dashboard → recent). Real character-by-character streaming via --include-partial-messages, plus a client-side typewriter smoothing layer that drips each text_delta / thinking_delta through requestAnimationFrame so even short replies (where claude bundles the whole answer into one or two chunks) appear to type in. The merge code keeps the _streaming flag and the delta-accumulated content array intact when claude's canonical assistant envelope arrives mid-stream, so thinking blocks aren't dropped at completion. WebSocket dispatch wraps each envelope in flushSync so React auto-batching doesn't collapse bursts of deltas into a single render. TUI parity (Tier 1): a collapsible limitations banner that minimizes to a slim pill (never disappears) explaining what stream-json mode can and can't do vs. the terminal TUI; a prompt editor with slash-command autocomplete with tiered scoring (exact name → starts-with → word-boundary → contains → subsequence → description-contains) that lists user / project / plugin commands (executed client-side via template expansion before send) and surfaces built-in CLI commands like /clear, /model, /config with a "CLI only — won't run from here" badge; @-file references with debounced fuzzy-search across the run's cwd (skipping node_modules, .git, dist, build, etc.); a live context-window / token meter showing input + output + cache-read tokens and running cost, computed from stream_event and result.usage envelopes during live streaming and from finalized assistant usage blocks (input / output / cache-read / cache-creation) when seeded from a transcript on resume / view / re-attach, so the meter populates immediately instead of sitting at 0/200k. Progress bar goes indigo → amber → red at 80% / 95% of the model's context cap; a status header with the active model, effort, permission mode, cwd, session ID, envelope count, and elapsed time. Autocomplete dropdowns open upward so they don't collide with the cwd picker below. Live / Offline indicator next to the title. Same-origin guard on the route prevents browser drive-by spawning. Concurrency is effectively uncapped by default (sanity ceiling of 10000 to prevent fork-bomb footguns from a buggy client; the terminal TUI has no cap and neither do we). Set RUN_MAX_CONCURRENT if you want a real ceiling. Spawned sessions fire the same hooks any claude process does, so they show up automatically in Sessions / Analytics / Kanban / Workflows — and Sessions / SessionDetail surface a green ▶ Run badge / banner that links back to the Run page for any session that's currently being driven from there
Claude Config Explorer A 12-tab inspector at /cc-config for everything Claude Code knows about: skills, subagents, slash commands, output styles, plugins (with per-plugin contributions count + author/license/homepage from plugin.json), marketplaces (with plugin counts read from each marketplace.json), MCP servers, hooks (with ~/.claude/hooks/ script listing), settings (an at-a-glance Current configuration summary of the options /config controls — model, verbose, theme, output style, effort, auto-compact, notifications, … — resolved across user/project/project-local scopes with unset options shown as defaults, plus the per-file structured key-value view + raw JSON toggle, secret-key redaction), memory (the user + project CLAUDE.md files plus the per-project file-based memory store — every *.md under ~/.claude/projects/<slug>/memory/, i.e. a MEMORY.md index plus one file per remembered fact, often 100+; grouped by project in collapsible sections that split index files from per-fact files, with a search box and clickable MEMORY.md index links that jump to — scroll to + highlight — the matching fact file), keybindings (grouped by context with <kbd> chips), and statusline (config + script content). Read paths and their allowed roots are canonicalized with realpath, so symlinks cannot escape the trusted Claude roots. For low-risk text-file surfaces (skills / agents / commands / output styles / memory — including the per-project auto-memory files) the page supports create / edit / delete with mandatory timestamped backups atomically written outside the directories Claude Code scans, plus a Backups modal with auto-built mv restore commands. Plugins, MCP, hooks-in-settings, and settings.json files stay read-only with explainer banners + copy-able CLI commands so the user knows the exact command to run themselves. Live updates: a cc-watcher running on the server uses fs.watch on ~/.claude/ (recursive where the platform supports it) plus ~/.claude.json, debounced at 500 ms, to broadcast a cc_config_changed WebSocket message whenever Claude Code config changes — either via dashboard mutations or external tools (CLI installing a plugin, manually editing settings.json, dropping a new skill). The page subscribes and refetches automatically; a Live / Offline pill next to the title shows WebSocket status
Tabby A floating cat companion pinned to the bottom-right corner of every page. Built entirely on the existing WebSocket eventBusno new backend, no API key, no new dependencies. A reactive SVG mascot with cursor-tracking eyes and eight moods derived from the live session stream (idle, watching, happy, worried, stuck, thinking, sleeping, disconnected), each with its own animation (tail flick, ear perk, head bob, shake, sparkle, zzz, alert "!"). Auto-surface speech bubbles post short, throttled, coalesced quips on notable events (session started/finished, errors, run completed) and can be muted. Click the cat or press ⌘B / Ctrl+B (Esc closes) to open a panel with a live status line (N live · M errored · connection state), quick actions (jump to Run Claude / Activity / Sessions / errored sessions, mute bubbles, clear alerts), and an Ask box: simple status questions ("what's running", "any errors", "status") are answered locally from cached data, while any other question hands off to the Run Claude page (deep-links to /run?prompt=…) to spawn a real Claude Code session. Accessible (keyboard-operable, aria-live bubbles, honors prefers-reduced-motion), degrades safe to a calm disconnected state if the socket is down, toggleable in Settings (localized in en/zh/vi/ko/es). Implementation lives in client/src/components/Tabby/
Progressive Web App (PWA) Three independent PWAs — dashboard, landing page, and wiki — each with its own Web App Manifest and Service Worker. Install any of them to your home screen / dock for a standalone, chrome-less experience. The dashboard SW serves Vite's content-hashed bundles under /assets/ cache-first (URLs are immutable per build, so cache hits are always correct) and treats everything else — navigations, the SW itself, manifest.json, icons, root / — as network-first with cache fallback. Combined with explicit Cache-Control headers on the production Express static middleware (immutable for /assets/*, no-cache, must-revalidate for index.html, sw.js, manifest.json), a rebuild always replaces the in-browser bundle without a hard refresh; a controllerchange listener in the client reloads exactly once when a new SW takes over an already-controlled page. The VAPID push-notification pipeline is preserved. The landing-page and wiki SWs precache their respective shells and lazy-cache images on first visit, enabling offline access after a single load. All manifests use SVG icons (favicon.svg) with sizes="any" for modern browsers, and include apple-mobile-web-app-capable + apple-touch-icon meta tags for iOS standalone mode
Desktop App (macOS & Windows) Optional native desktop app built with Electron 35, living in the desktop/ workspace alongside client/, server/, mcp/, and vscode-extension/. Ships as a macOS .app (.dmg) and a Windows .exe (NSIS installer + no-install portable). It embeds the existing Express server in-process (require()s server/index.js — no child process, no IPC) and renders the built React client in a BrowserWindow. Adds a native title bar, a menu-bar / notification-area (tray) icon whose single-click dropdown shows a live status snapshot (sessions, agents, events today) pulled from SQLite at click time, a native application menu, auto-start at login (macOS Login Items via SMAppService; Windows per-user HKCU\…\Run), a ⌘Q / Ctrl+Q confirmation dialog (second press bypasses), window-close-hides-but-server-keeps-running, a single-instance lock, and tray actions for Open in Browser, Restart Server, and Show Logs. Prefers port 4820 (falls back to 4821–4829 then a random high port), adopts a healthy dashboard already running on 4820 instead of double-binding, and coexists with the web dashboard — both npm run dev and the desktop app can run together with hooks fanning out to both. Notifications fire as native OS toasts (Web Push doesn't work reliably inside Electron). On first owned-server boot it auto-installs Claude Code hooks and starts the background services, so an install-only user gets events flowing with zero manual setup. See DESKTOP.md and desktop/README.md
Self-hosted assets (no CDN) Every font and script is served locally, so the dashboard and docs make zero third-party CDN requests — they render fully offline and leak nothing to external hosts. The React app bundles Inter + JetBrains Mono via @fontsource (latin subset; Vite emits content-hashed WOFF2 into dist/assets/ at build time, no <link> to Google Fonts). The landing page and wiki load a self-hosted fonts/fonts.css @font-face sheet from the repo-root fonts/ directory. The wiki's Mermaid is vendored locally as wiki/mermaid.min.js (the genuine minified mermaid@10.9.6) instead of jsDelivr, and the VS Code extension's error page falls back to a system font stack. No fonts.googleapis.com, fonts.gstatic.com, or cdn.jsdelivr.net calls remain anywhere
Session splash screen A brief branding splash on app load (once per browser session): a time-aware greeting (Good morning / afternoon / evening / Working late), a bold tagline, two subtexts, and an animated node-graph brand mark over a dark atmospheric backdrop (radial glow + drifting constellation + grain). Fully localized (en/zh/vi/ko/es). The overlay is opaque from the first paint so the app never flashes through, holds ~2.5 s, then fades out; click anywhere to skip, and it honors prefers-reduced-motion. CSS-only animations, no added dependencies

Provider scope and homes: Settings keeps the Claude Code / Codex / Both choice globally consistent, and lets you change either session-data home without restarting the dashboard.

Local safety boundaries: Run Agent accepts any existing absolute working directory and canonicalizes it before use, so home and recent-project launches remain supported. Hosted webhook providers require HTTPS; generic and n8n targets may use HTTP for local/self-hosted receivers, and delivery never follows redirects.


Quick Start

Prerequisites

  • Node.js >= 22.22.0 (24 LTS recommended)
  • npm >= 9.0.0

1. Install

git clone https://github.com/hoangsonww/Claude-Code-Agent-Monitor.git
cd Claude-Code-Agent-Monitor
npm run setup

2. Configure Claude Code Hooks

npm run install-hooks

The installer opens an interactive multi-select: use arrow keys, Space, and Enter to choose Claude Code, Codex (beta), or both (Claude Code is preselected). Claude Code entries live in ~/.claude/settings.json; Codex entries live in ~/.codex/hooks.json. If a selected dashboard hook set already exists, it warns before replacing only this dashboard's entries — unrelated hooks are preserved. You can make the same selection later in Settings → Hook Configuration → Install hooks.

On first dashboard entry, choose the data source and the app checks hook readiness only for that selection. Claude Code requires Claude hooks, Codex requires Codex hooks, and Both requires both hook sets. When every required hook set is already installed, the dashboard opens immediately. Otherwise the setup gate lists and installs only the missing selected providers, while preserving unrelated hooks and letting a failed status check fall back safely to manual setup.

Codex rollouts in ~/.codex/sessions are also discovered continuously. The dashboard reads their append-only JSONL incrementally, prioritizes the newest rollouts, and isolates a bad historical file for retry, so sessions, tokens, costs, conversation rows, and WebSocket updates stay current even if a hook notification is missed.

Codex rollout lifecycle records drive the same live card states as Claude Code: user_message and task_started mark the main agent Working; task_complete leaves the session active but shows Waiting; and turn_aborted shows Waiting with an interrupted reason. A new rollout record self-heals an erroneously completed session. On supported local hosts, liveness is matched to the exact rollout-*.jsonl held open by each Codex process, so old rollouts that share a project directory are imported as completed instead of appearing as phantom active agents. The Node launcher and its native Codex child are collapsed into one logical process, so one TUI produces one card.

Codex /rename titles are read from its native session index and update session and agent cards in real time. Its conversation replay includes human turns plus exec custom-tool calls and outputs, with cursor pagination that loads older messages at the top of the transcript.

Claude Code and Codex cards show a compact, two-row history of their latest distinct human prompts beneath the provider-native title, so a short friendly name or terse follow-up never hides the active task. Claude refreshes this context from its local transcript cache during live hooks, imports, and watchdog sweeps; Codex refreshes it from rollout records and falls back to persisted user_message events for older imports. The transcript renders persisted PNG/JPEG/GIF/WebP attachments for Claude Code and Codex when available, while duplicate Codex response/event copies collapse into one human turn.

Codex response_item tool invocations are indexed once through a dedicated rollout cursor, so its Workflows tool flow, session drill-in, model/token totals, and context_compacted counts reflect the recorded Codex data without replaying lifecycle or token counters. In a Codex-only dashboard scope, the Claude Code-only Dynamic Workflows journal panel is hidden rather than presented as empty Codex data.

3. Start

# Development (hot reload on both server and client)
npm run dev

# Production (single process, built client)
npm run build && npm start

Tip

Makefile alternative — all commands are also available via make if you have it installed on your system. Run make help to see every target, or use shortcuts like make dev, make build, make test, etc.

4. Open

Mode URL
Development http://localhost:5173
Production http://localhost:4820

5. Optional: Run the local MCP server

npm run mcp:start              # stdio (default — for MCP host integration)
npm run mcp:start:http         # HTTP + SSE server on port 8819
npm run mcp:start:repl         # interactive CLI with tab completion
ccam mcp stdio                 # stable launcher used by bundled plugins

npm run setup installs and builds the MCP package before linking ccam. For stdio mode, configure your host with command ccam and args ["mcp", "stdio"].

For HTTP mode, point remote MCP clients at http://127.0.0.1:8819/mcp (Streamable HTTP) or http://127.0.0.1:8819/sse (legacy SSE).

See mcp/README.md for full host configuration, transport details, safety flags, and tool catalog.

Optional: Seed Demo Data

npm run seed

Creates 8 sample sessions, 23 agents, and 106 events so you can explore the UI immediately.

Alternative: Desktop App (macOS & Windows)

If you'd rather not keep a terminal open, install the optional native desktop app. It embeds the server in-process, adds a menu-bar / notification-area (tray) icon, and supports auto-start at login (macOS Login Items / Windows startup).

The fastest path is to download a pre-built installer from the latest GitHub Release (CI auto-publishes a vX.Y.Z whenever package.json is bumped on master):

  • macOS — grab ClaudeCodeMonitor-<version>-arm64.dmg (Apple Silicon) or -x64.dmg (Intel) and drag Claude Code Monitor.app into /Applications.
  • Windows — grab ClaudeCodeMonitor-Setup-<version>-x64.exe (installer) or ClaudeCodeMonitor-<version>-x64-portable.exe (no-install) and run it.

To build it yourself instead:

npm run desktop:install        # install Electron + electron-builder into desktop/ (preflights native deps; prints setup help on failure)
npm run desktop:dmg:arm64      # macOS: fast single-arch DMG (Apple Silicon)
npm run desktop:win            # Windows: NSIS installer .exe (run on Windows)

Full coverage of the desktop app — download, install, tray/menu features, build commands, and signing — is in the Desktop App (macOS & Windows) section below. See also DESKTOP.md (user guide) and desktop/README.md (architecture).

Alternative: Docker / Podman

The OCI image and Compose files support Docker and Podman. The runtime is non-root, drops all capabilities, uses Tini as PID 1, includes Git/OpenSSH/SQLite, and becomes read-only except for /app/data, /app/config, and /tmp.

# Dashboard only
docker compose up -d --build
# or
podman compose up -d --build

# Complete stack: dashboard + authenticated MCP + Nginx + Prometheus + Grafana
umask 077
openssl rand -hex 32 > deployments/secrets/dashboard-token
openssl rand -hex 32 > deployments/secrets/hook-token
openssl rand -hex 32 > deployments/secrets/mcp-token
openssl rand -base64 32 > deployments/secrets/grafana-admin-password
npm run docker:full:up

All host ports bind to loopback by default: dashboard 4820, MCP 8819, Nginx 8080, Prometheus 9090, and Grafana 3000. Claude and Codex homes are mounted read-only; named volumes persist SQLite and dashboard-owned configuration. Nginx proxies the UI, authenticated REST API, and WebSocket, while hooks, metrics, and MCP stay blocked at the edge unless explicitly enabled. The optional agent-runtime Docker target adds pinned Claude Code and Codex CLIs for container-native Run Agent workflows.

Important

Install Claude Code/Codex hooks on the host after the container starts. For remote cloud hooks, set CCAM_DASHBOARD_URL=https://... and a separate CCAM_HOOK_TOKEN; non-loopback hook URLs require HTTPS. See DEPLOYMENT.md.


How It Works

The dashboard integrates with Claude Code via its native hook system to provide real-time monitoring of agent activity. Here's an overview of the architecture and data flow:

sequenceDiagram
    participant CC as Claude Code
    participant HH as Hook Handler
    participant API as Express Server
    participant DB as SQLite
    participant WS as WebSocket
    participant UI as React Client

    CC->>HH: stdin (JSON event)
    HH->>API: POST /api/hooks/event
    API->>DB: Insert/update records
    API->>WS: Broadcast update
    WS->>UI: Push message
    UI->>UI: Re-render component

    Note over CC,HH: Hooks fire on SessionStart,<br/>PreToolUse, PostToolUse,<br/>Stop, SubagentStop,<br/>SessionEnd, Notification.<br/>Compaction detected from JSONL
    Note over API,DB: Transactional writes<br/>with auto session/agent creation
    Note over WS,UI: ~0ms latency,<br/>no polling
Loading

Important

See ARCHITECTURE.md for a deep dive into the server architecture, database schema, API routes, WebSocket design, client routing, hook handler flow, deployment modes, and detailed lifecycle diagrams for sessions and agents.

Hook Lifecycle

  1. Claude Code fires a hook on session start, tool use, turn end, subagent completion, and session exit
  2. Hook Handler (scripts/hook-handler.js) reads the JSON event from stdin, resolves live dashboards via ~/.claude/.agent-dashboard.json (or CLAUDE_DASHBOARD_PORT if set), and POSTs the same payload to one ingest target per unique SQLite data directory (lowest port wins when Docker and npm run dev share ~/.claude/agent-dashboard, so events are never double-ingested). Servers with different databases (e.g. the desktop app using its own Application Support dir alongside npm run dev) still each receive hooks. Fails silently with a 5 s safety-net timeout so it never blocks Claude Code, and per-target promises never reject so a single dead listener can't starve the others.
  3. Server processes the event inside a SQLite transaction:
    • Auto-creates sessions and main agents on first contact
    • Detects Agent tool calls to track subagent creation
    • On SessionStart, stamps the session and main agent's awaiting_input_since so a fresh CLI sitting at the prompt lands in Waiting immediately
    • On UserPromptSubmit (user hits enter), clears the waiting flag and promotes the main agent to working — the only reliable signal that text-only assistant turns have started, since they emit no PreToolUse
    • Sets agent to "working" on PreToolUse (also clears the waiting flag), keeps it working through PostToolUse
    • On Stop (Claude finishes responding), main agent goes to "waiting" — Claude finished its turn, ball is in the user's court. Background subagents continue running. Session stays active. Stop with stop_reason=error marks the agent error and the session error
    • On a permission Notification (matched by message pattern: permission, waiting for input, needs your approval, …), sets the agent to waiting and stamps awaiting_input_since
    • SubagentStop deliberately does NOT clear the waiting flag (a backgrounded subagent finishing tells us nothing about the human)
    • Marks subagents completed individually via SubagentStop. After res.json() returns, fires a fire-and-forget scanAndImportSubagents pass that walks the session's subagents/agent-*.jsonl files, pairs tool_usetool_result blocks by tool_use_id, and emits PreToolUse + PostToolUse events under each subagent's own agent_id — closing the gap where subagent-internal tool calls would otherwise be invisible to the dashboard
    • On SessionEnd (CLI process exits), drops the waiting flag. If the session is in error, the error state is preserved; otherwise marks all agents + the session as completed
    • On SessionStart, any other active session with no activity for DASHBOARD_STALE_MINUTES (default 180 = 3 h, env-overridable) is automatically marked "abandoned" with its agents completed. This handles /resume inside a session, Ctrl+C, and other scenarios where a session is orphaned without a clean SessionEnd
    • Reactivates completed/error/abandoned sessions when new work events arrive (session resumed). Stop and SubagentStop events also reactivate completed/abandoned sessions — this handles pre-existing sessions imported before the server started, where the first hook event may be a Stop
    • Error recovery: only UserPromptSubmit and PreToolUse can recover a session from error back to active — indicating the user actively retried
    • Detects conversation compaction (isCompactSummary entries in the JSONL transcript) and creates Compaction agents + events. Token baselines are preserved across compactions so no usage is lost. Transcript reads use a shared stat-based cache with incremental byte-offset reads — only new bytes appended since the last read are parsed, giving ~50x speedup for long sessions
    • Extracts API errors (isApiErrorMessage entries: quota limits, rate limits, invalid_request) and raw type: "error" responses from JSONL transcripts, stored as APIError events. Turn durations (system subtype turn_duration) are stored as TurnDuration events. Tool result errors (toolUseResult.is_error) are tracked as ToolError events
    • Error detection watchdog — a background timer runs every 15 seconds, scanning active sessions with no recent hook events (>10 s stale). It re-reads their transcript files looking for API errors (auth failures, rate limits, quota exhaustion), derives transcript paths from session cwd for imported sessions without transcript_path in event data, and marks sessions/agents as error when API errors are found. This catches cases where the Claude CLI does not fire a hook after an API error (e.g., 401 auth failures where the CLI shows the error and waits)
    • User-interrupt (Esc) recovery — cancelling a turn with Esc fires no hook (a documented Claude Code limitation), so without intervention the main agent would stay stuck in working forever. The same 15 s watchdog recovers these two ways: (1) when the cancel leaves a [Request interrupted by user] marker in the transcript (Esc after some output), the transcript cache flags it via pendingInterrupt — derived purely from transcript ordering (latest interrupt vs latest real turn activity, same clock, so it works even for a sub-second cancel) — and the session moves to Waiting within ~15 s; (2) when Esc is pressed before any output, Claude Code writes no marker at all, so an idle-timeout fallback applies — if the main agent has been working with no tool in flight and neither a hook event nor the transcript has advanced for DASHBOARD_WORKING_IDLE_SECONDS (default 120), the turn is treated as dead and the session moves to Waiting. Both paths log an Interrupted event and land the session in the same Waiting state a normal Stop produces. Streaming output (transcript still growing) and in-flight tool calls (current_tool set) are exempt; a rare false flip self-heals on the next real hook
    • Dead-session liveness reap — quitting Claude Code (Ctrl+C, closing the terminal) fires a SessionEnd hook, but if the dashboard isn't running at that moment the event is lost forever and the session would sit in Waiting until the stale sweep (3 h by default). The same 15 s watchdog closes the gap with a process-liveness probe: it lists running claude CLI processes (ps + lsof on macOS, /proc on Linux) and completes any active session whose cwd has no live claude process — landing it in the same completed state a real SessionEnd produces, with a synthetic SessionEnd event on the timeline. Guards: on watchdog ticks the session's transcript must not have been written for at least DASHBOARD_LIVENESS_IDLE_SECONDS (default 60; the last hook write is the fallback clock when no transcript exists on disk) — the boot passes skip this gate entirely, so a session quit even one second before launch clears immediately — and the probe reports "no answer" (changing nothing) on Windows, inside containers (host processes are invisible there), when ps/lsof fail, or when explicitly disabled via DASHBOARD_LIVENESS_PROBE=0. In a mixed deployment the reap also auto-skips any session whose cwd is not POSIX-absolute — a session forwarded from another machine via household hooks reports the origin's own path (e.g. a Windows D:\Git\ai-deck) that a local ps/lsof//proc scan can never match, so remote sessions are protected without disabling the probe for genuinely-local ones. Remote Data Source sessions (sessions.sourcelocal) are always skipped too — their cwd is legitimately POSIX-absolute on another machine, so the local process probe says nothing about them; their lifecycle is owned entirely by the remote-sync reconciliation described above. A false completion self-heals: the next hook event reactivates the session. Besides the 15 s watchdog cadence, the reap runs immediately at startup (clearing dead sessions already in the DB from a previous run before they ever render) and again ~5 s later (covering sessions the startup sync just imported), so a session that died while the dashboard was down never shows as Waiting
    • A periodic server sweep catches abandoned sessions and new compactions that slipped past event-based detection (e.g., /compact fires no hook, /resume within seconds of session creation). Cadence is derived from DASHBOARD_STALE_MINUTES (¼ of the threshold, clamped to 60 s – 5 min). The sweep reads transcript_path directly off each active session row (a small index lookup) instead of scanning the events table for it; the column is populated by the hook handler the first time it sees a transcript path and is one-time-backfilled from existing events by the db.js migration, with a partial index idx_sessions_active_tp covering exactly the rows the sweep reads. The sweep shares the transcript cache with the hook handler, avoiding duplicate I/O. Abandoned session cleanup also evicts the transcript cache entry to bound memory. Remote Data Source sessions (sourcelocal) remain mirror-owned only while their matching Claude Code or Codex provider is healthy; if that provider is unavailable, errors, or is stranded in syncing, the periodic and startup cleanup use the normal stale rule so an old Waiting card cannot persist forever
    • Continuous project sync (startSessionSync) keeps ~/.claude/projects discoverable beyond the one-time, marker-gated startup backfill: a project added later whose sessions never flow through hooks would otherwise stay invisible until a manual rescan. An immediate startup sweep, a debounced fs.watch (recursive on macOS/Windows; root + immediate children on Linux), and a DASHBOARD_SESSION_SYNC_MS poll (default 30 s; 0 disables the poll, watcher stays) share one mtime cache and a coalesced sweep that re-parses only files whose mtime advanced — and skips an already-imported, unchanged session without re-parsing, so restart cost stays O(new/changed files). Each newly discovered/grown session broadcasts session_created/session_updated plus its main agent, the same frames hooks emit
  4. WebSocket broadcasts the change to all connected clients
  5. UI receives the update and re-renders the affected components in real-time with no polling.

Agent State Machine

Persisted statuses: working | waiting | completed | error. The awaiting_input_since column is supplementary — it tracks when the agent started waiting and is used for duration display, but waiting is now a real persisted status.

stateDiagram-v2
    [*] --> waiting: ensureSession (first hook)
    waiting --> working: PreToolUse / UserPromptSubmit / Codex task_started / user_message
    working --> working: PostToolUse (tool completed)
    working --> waiting: Stop, non-error / Codex task_complete
    working --> waiting: Codex turn_aborted (interrupted)
    working --> waiting: Notification (input prompt)
    working --> waiting: Esc cancel (watchdog marker or idle timeout)
    waiting --> error: Stop with error
    working --> error: Stop with error
    waiting --> error: API error detected (watchdog)
    working --> error: API error detected (watchdog)
    error --> working: UserPromptSubmit / PreToolUse (recovery)
    working --> completed: SessionEnd
    waiting --> completed: SessionEnd

    note right of waiting
        Agent is between turns or
        awaiting user input
    end note
Loading

Session State Machine

Persisted statuses: active | completed | error | abandoned. The Waiting session state is a UI overlay (status=active with awaiting_input_since set).

stateDiagram-v2
    [*] --> waiting: SessionStart startup/resume/clear (status=active + flag)
    active --> active: SessionStart compact (mid-turn — state preserved, no flag)
    waiting --> active: UserPromptSubmit / PreToolUse / PostToolUse / Codex task_started / user_message
    active --> waiting: Stop, non-error / Codex task_complete (flag re-stamped)
    active --> waiting: Codex turn_aborted (interrupted)
    active --> waiting: Permission Notification (agent → waiting)
    active --> waiting: Esc cancel (watchdog marker or idle timeout)
    active --> error: Stop, stop_reason=error
    active --> error: API error detected (watchdog)
    waiting --> error: API error detected (watchdog)
    error --> active: UserPromptSubmit / PreToolUse (recovery)
    waiting --> completed: SessionEnd (CLI exited)
    active --> completed: SessionEnd (CLI exited)
    error --> error: SessionEnd (preserves error)
    waiting --> abandoned: Stale > DASHBOARD_STALE_MINUTES (default 180)
    active --> abandoned: Stale > DASHBOARD_STALE_MINUTES
    completed --> active: Session resumed (new work event)
    error --> active: Session resumed (new work event)
    abandoned --> active: Session resumed (new work event)
    completed --> [*]
    error --> [*]
    abandoned --> [*]
Loading

Cost Calculation Flow

flowchart LR
    TU["token_usage rows<br/>(per session × model)"] --> GROUP["Group by model"]
    PR["model_pricing rules<br/>(pattern-based)"] --> SORT["Sort by specificity<br/>(longest pattern first)"]
    GROUP --> MATCH{"Match model<br/>to pricing rule"}
    SORT --> MATCH
    MATCH --> CALC["cost = Σ (tokens / 1M) × rate<br/>for input, output, cache_read, cache_write"]
    CALC --> RESULT["{ total_cost, breakdown[] }"]
    style TU fill:#003B57,stroke:#005f8a,color:#fff
    style PR fill:#6366f1,stroke:#818cf8,color:#fff
    style RESULT fill:#10b981,stroke:#34d399,color:#fff
Loading

Important

The cost calculation flow is based on token usage and model pricing rules. Ensure your pricing rules are up-to-date to reflect accurate costs. Update the model pricing table via the Settings page to maintain accurate cost tracking - the dashboard does not automatically fetch pricing updates from external sources. Once you set the pricing rules, the dashboard applies them retroactively to all sessions for consistent cost reporting.


Configuration

Environment Variable Default Description
DASHBOARD_PORT 4820 Port for the Express server
CLAUDE_DASHBOARD_PORT 4820 Port used by hook handler to reach the server
NODE_ENV development Set to production to serve the built client
DASHBOARD_UPDATE_CHECK (enabled) Set to 0 / false / off to disable periodic git upstream checks
DASHBOARD_UPDATE_CHECK_INTERVAL_MS 300000 (5 min) Interval between automatic checks; floor 60 000 ms. Users can also click Check now in the update modal or in the sidebar to run one on demand.
DASHBOARD_STALE_MINUTES 180 (3 h) Minutes of inactivity before a still-active session (including one sitting in Waiting on user input — "Waiting" is a UI overlay on an active row, not a stored status) is auto-marked abandoned and drops off the active list. Enforced by the 15 s watchdog and the periodic maintenance sweep (which runs every ¼ of this value, clamped to 60 s – 5 min). Lower it (e.g. 60) for a shorter idle timeout
DASHBOARD_WORKING_IDLE_SECONDS 120 Idle-working timeout for recovering a turn cancelled with Esc before any output (which leaves no transcript marker). When the main agent has been working with no tool in flight and neither a hook event nor the transcript has advanced for this long, the watchdog moves the session to Waiting. Lower it for snappier recovery at the cost of occasional false flips on long silent-thinking turns (which self-heal)
DASHBOARD_LIVENESS_PROBE 1 (on) Set to 0 to disable the watchdog's dead-session liveness reap (the ps/lsof-based probe that completes active local Claude Code or Codex sessions whose matching CLI process no longer exists — recovering a SessionEnd lost while the dashboard was down). Sessions forwarded from another machine (household hooks) report a non-POSIX cwd and are auto-skipped by the reap, so a mixed local + forwarded deployment no longer needs this off; disable it only for a purely-remote setup where local processes prove nothing. Auto-disabled on Windows and inside containers
DASHBOARD_LIVENESS_IDLE_SECONDS 60 Idle gate for the watchdog-tick liveness reap: a session is only completed when its transcript hasn't been written for at least this long (the last hook write is the fallback clock when no transcript exists on disk), so a mid-turn or just-resumed session never flickers out on a transient probe miss. The startup passes ignore this gate — at boot the probe alone decides, so sessions quit moments before launch clear immediately
DASHBOARD_SESSION_SYNC_MS 30000 Poll interval (ms) for the continuous ~/.claude/projects background sync that surfaces projects added after startup whose sessions never flow through hooks. The fs.watch watcher fires near-instantly regardless; this poll is the safety net (watchers can miss events / not fire on network filesystems). Set to 0 to disable the poll while leaving the watcher running
DASHBOARD_CODEX_HOME CODEX_HOME or ~/.codex Optional local Codex state directory. Rollouts are read only from its sessions/ tree; saving a new location in Settings persists this dashboard-only override, re-arms live watching, and immediately scans the new tree.
DASHBOARD_CODEX_SYNC_MS 4000 Safety-net poll interval (ms) for append-only Codex rollouts. Codex hooks trigger the same incremental ingest immediately; set to 0 to disable only the poll while retaining the filesystem watcher when available.
DASHBOARD_REMOTE_SYNC_MS 15000 (15 s) Poll interval (ms) for the Remote Data Sources background sync that independently pulls each enabled remote's ~/.claude/projects and ~/.codex/sessions (plus Codex's lightweight session_index.jsonl title index) over SSH, then re-imports each through its local importer. New/enabled sources also sync immediately. Set to 0 to disable the poller (manual / on-demand syncs still work)
DASHBOARD_REMOTE_ACTIVE_WINDOW_MS 600000 (10 min) Freshness window for a Remote Data Source session's live status. On each sync, a remote Claude Code or Codex session whose matching mirrored transcript has a last JSONL event within this window is treated as still running (active); once the mirror stops advancing for longer than this, the session is reconciled to completed. Remote sessions receive no live hooks, so provider-aware mirror reconciliation replaces local liveness; failed, unavailable, or stuck provider mirrors fall back to the normal stale sweep. Raise it for slow links or very long idle turns
DASHBOARD_REMOTE_SYNC_TIMEOUT_MS 600000 (10 min) Per-source timeout (ms) for a single remote sync (scp pull + import) before it is aborted
DASHBOARD_REMOTE_TEST_TIMEOUT_MS 15000 (15 s) Timeout (ms) for the Test SSH probe (POST /api/remote-sources/:id/test) that verifies a remote source is reachable
DASHBOARD_HOST 127.0.0.1 Interface the server binds to. Loopback by default (not network-reachable). Set to 0.0.0.0 to expose on a LAN (logs a startup warning)
DASHBOARD_TOKEN (unset) When set, every /api/* request and the WebSocket must present the token (Authorization: Bearer <token>, x-dashboard-token header, or ?token=). Off by default — loopback bind is the trust boundary
DASHBOARD_TOKEN_FILE (unset) File-backed dashboard token for Docker/Kubernetes secrets; direct DASHBOARD_TOKEN wins
DASHBOARD_HOOK_TOKEN / DASHBOARD_HOOK_TOKEN_FILE (unset) Independent token for /api/hooks/*; required for authenticated remote hook ingestion
DASHBOARD_ALLOWED_HOSTS (loopback) Comma-separated extra Host values allowed on HTTP + WebSocket upgrades (DNS-rebinding guard). Add your LAN hostnames here when binding beyond loopback
DASHBOARD_ENV_PATH repo .env Writable dotenv path used when Settings persists Claude/Codex home overrides; container default /app/config/.env
CCAM_DASHBOARD_URL (localhost discovery) Optional remote hook destination. Non-loopback URLs must use HTTPS and a hook token
CCAM_HOOK_TOKEN / CCAM_HOOK_TOKEN_FILE (unset) Hook-client credential sent as x-ccam-hook-token

Important

Secure by default. The server binds 127.0.0.1 and is not reachable from the network out of the box (GHSA-gr74-4xfh-6jw9). To expose it on a LAN, set both DASHBOARD_HOST (e.g. 0.0.0.0) and DASHBOARD_TOKEN (which then gates /api/* and the WebSocket), and list your LAN hostnames in DASHBOARD_ALLOWED_HOSTS. See .env.example and .github/SECURITY.md for details.

For git clones, the server periodically git fetches origin and compares your checkout to origin/master, origin/main, or origin/HEAD. When you are behind, a message appears in the server terminal and a modal appears in the UI with the exact command to run. The dashboard never pulls or restarts itself — you copy the command, run it in a terminal, then restart the server the same way you started it.


ccam CLI

The dashboard's full feature surface is also available from any terminal via the dependency-free ccam CLI (bin/ccam.js). It is linked automatically by npm run setup (via npm link), after which ccam <command> works from any directory. It discovers the running dashboard through ~/.claude/.agent-dashboard.json (the same live-server registry the hook handler uses), with CLAUDE_DASHBOARD_PORT / DASHBOARD_PORT env overrides, falling back to http://127.0.0.1:4820.

# Server
ccam status                       # ● running / ○ not running indicator
ccam start [--port N]             # start the server in the background (detached)
ccam stop                         # stop the background server gracefully
ccam repl                         # interactive shell (also: shell, i)

# Monitoring
ccam health                       # is the dashboard up?
ccam stats                        # totals, today's events, status distributions
ccam kanban                       # sessions + agents grouped by status columns
ccam tail [--session <id>]        # live event feed in the terminal (Ctrl+C stops)

# Data
ccam sessions [--status s] [--q text] [--limit n]
ccam session <id>                 # detail: agent tree, cost, recent events
ccam agents   [--status s] [--session id]
ccam events   [--session id] [--limit n]

# Insights
ccam analytics                    # token totals, top tools, agent types
ccam workflows [--session id]     # workflow intelligence stats and patterns
ccam runs [--session id]          # dynamic Workflow-tool runs
ccam cost [--session <id>]        # total estimated cost with per-model breakdown
                                  # (--session scopes to one; shows tool surcharges;
                                  #  warns about models with usage but no pricing rule)

# Alerts & webhooks
ccam alerts [--unacked]           # fired-alert feed
ccam alerts ack <id> | ack-all    # acknowledge alerts
ccam rules                        # list alert rules
ccam webhooks                     # list webhook targets
ccam webhooks test <id>           # send a synthetic test alert

# Pricing
ccam pricing                      # list model pricing rules (incl. fast-mode & intro columns)
ccam pricing set <pattern> --input N --output N [--cache-read N --cache-write N]
                 [--cache-write-1h N] [--fast-input N --fast-output N]
                 [--intro-input N --intro-output N … --intro-until YYYY-MM-DD]
ccam pricing delete <pattern>
ccam pricing reset

# Import
ccam import rescan                # re-scan ~/.claude/projects
ccam import path <dir>            # import every .jsonl under a directory

# Administration
ccam doctor                       # connectivity, hooks, and database diagnosis
ccam info                         # raw system info JSON
ccam export [file.json]           # full JSON data export
ccam import-data <file.json>      # restore an export (idempotent, non-destructive)
ccam cleanup --hours N --days M   # abandon stale / purge old sessions
ccam reinstall-hooks              # reinstall Claude Code hooks
ccam update-check                 # is the checkout behind upstream? (prints the update command)
ccam clear-data --yes             # delete ALL data (requires --yes)
ccam open                         # open the dashboard in your browser
ccam version                      # print the CLI version (also --version / -v)

API-backed commands need the server running — when it isn't, read-only commands fall back to reading data/dashboard.db directly (with an explicit ⚠ Offline mode banner, and stored-but-dead active sessions corrected display-side by the same process-liveness probe the server's watchdog uses), while commands that can't run correctly without the server (live tail, analytics/cost math, mutations) print the ○ Dashboard server is NOT running indicator with the specific reason and the start commands; ccam start brings a production server up in the background. Read commands are always safe; the one destructive command (clear-data) refuses to run without an explicit --yes. Output is a full terminal UI — box-drawn tables with right-aligned numeric columns, status icons (● active, ○ waiting, ✔ completed, ✖ error), inline bar charts for stats/analytics/cost, and real ├─/└─ agent trees — with ANSI colors auto-enabled on a TTY, off when piped, and controllable via --no-color / NO_COLOR / FORCE_COLOR. For a live monitoring session, ccam repl (aliases shell / i) opens an interactive shell where you type commands without the ccam prefix — with a CCAM welcome banner, tab-completion, persisted arrow-key history, a live server-status prompt (● host up / ○ offline down), a grouped help / help <cmd> menu, and a watch [secs] <cmd> built-in that auto-refreshes any command (e.g. watch 5 kanban); each line runs as an isolated child process, so an offline refusal or a blocking tail never takes the shell down. If ccam is not on your PATH (e.g. npm link needed elevated permissions), run npm link once from the repo root. Full reference — flags, discovery order, the REPL, safety model, scripting/exit codes, troubleshooting — in docs/CLI.md.

npm Scripts

Command Description
npm run setup Install root/client/extension/MCP dependencies, build MCP, and link ccam
npm run update:pull-setup git pull --ff-only then npm run setup (manual upgrade)
npm run dev Start server (watch mode) + client (Vite HMR) concurrently
npm run dev:server Start only the Express server with --watch
npm run dev:client Start only the Vite dev server
npm run build Build the React client to client/dist/
npm start Start production server (serves built client)
npm test Run the full suite (server node --test + client Vitest)
npm run test:server Run backend tests (node --test server/__tests__/)
npm run test:client Run frontend Vitest tests, including render snapshots for every screen (client/src/pages/__tests__/screens.snapshot.test.tsx); regenerate baselines after intentional UI changes with cd client && npx vitest run -u
npm run install-hooks Configure Claude Code hooks in ~/.claude/settings.json
npm run seed Populate database with sample data
npm run import-history Import legacy sessions from ~/.claude/ (also runs on startup)
npm run clear-data Delete all sessions, agents, events, and token usage
npm run mcp:install Install dependencies for local MCP package (mcp/)
npm run mcp:build Build MCP server TypeScript into mcp/build/
npm run mcp:start Start MCP server (stdio transport — for MCP hosts)
npm run mcp:start:http Start MCP server (HTTP + SSE transport on port 8819)
npm run mcp:start:repl Start MCP server (interactive REPL with tab completion)
npm run mcp:dev Run MCP server in dev mode (tsx, stdio)
npm run mcp:dev:http Run MCP server in dev mode (tsx, HTTP + SSE)
npm run mcp:dev:repl Run MCP server in dev mode (tsx, interactive REPL)
npm run mcp:typecheck Type-check MCP source without emitting build output
npm run extensions:sync Regenerate skill names/OpenAI metadata, Codex manifests, and both marketplaces
npm run extensions:validate Validate all 14 dual-format plugins and 66 bundled skills
npm run mcp:docker:build Build MCP container image with Docker (agent-dashboard-mcp:local)
npm run mcp:podman:build Build MCP container image with Podman (localhost/agent-dashboard-mcp:local)
npm run desktop:install Install Electron + electron-builder into the desktop/ workspace (rebuilds better-sqlite3 for Electron's ABI); preflights the native better-sqlite3 build and prints actionable setup help (incl. a no-toolchain alternative) on failure
npm run desktop:dev Build and launch the Electron desktop app for local iteration
npm run desktop:build Compile the desktop TypeScript sources into desktop/out/
npm run desktop:test Run the desktop smoke test (spawn Electron, probe /api/health)
npm run desktop:dmg Build both macOS DMGs (arm64 + x64) — correct for release, slower (packages each arch)
npm run desktop:dmg:arm64 Build an Apple-Silicon-only DMG — fast, recommended for your own Mac
npm run desktop:dmg:x64 Build an Intel-only DMG — fast
npm run desktop:dmg:universal Build one merged universal DMG (arm64 + x86_64) — optional, slowest, not what the release ships
npm run desktop:win Build a Windows NSIS installer .exe (x64) — run on Windows
npm run desktop:win:portable Build a Windows portable (no-install) .exe (x64) — run on Windows
npm run monitoring:install Run npm install in monitoring/ — downloads Prometheus + Grafana via postinstall
npm run monitoring:setup Alias for monitoring:install
npm run monitoring:up Start Prometheus (:9090) + Grafana (:3000) in the background (no Docker)
npm run monitoring:down Stop the npm-managed monitoring stack
npm run monitoring:start Foreground monitoring stack (Ctrl+C stops both)
npm run monitoring:docker:up Start Prometheus + Grafana via Docker Compose
npm run monitoring:docker:down Tear down the Docker monitoring stack
npm run monitoring:verify Health-check dashboard, Prometheus, Grafana, and scrape target
npm run docker:up Start the dashboard in Docker (docker compose up -d --build)
npm run docker:down Stop the dashboard container
npm run docker:full:up Dashboard + authenticated MCP + rootless Nginx + Prometheus + Grafana
npm run docker:full:down Tear down the full Docker stack
npm run deploy:validate Validate Docker, Compose, Nginx, Helm, Kustomize, Terraform, dependency audits, and the one-writer invariant

Agent Extensions

This repository includes a comprehensive extension layer for both Claude Code and Codex:

  • Claude Code: CLAUDE.md, .claude/rules/, .claude/skills/
  • Claude subagents: .claude/agents/
  • Codex: AGENTS.md, .codex/rules/, .codex/agents/, .codex/skills/
  • Shared distributable plugins: plugins/, with both .claude-plugin/plugin.json and .codex-plugin/plugin.json
  • Marketplaces: .claude-plugin/marketplace.json for Claude Code and .agents/plugins/marketplace.json for Codex
  • Open Agent Skills: all plugin skills carry canonical frontmatter plus agents/openai.yaml; npx skills add hoangsonww/Claude-Code-Agent-Monitor --list discovers 75 repository skills

Extension Architecture

graph TD
    USER["Developer"]
    CLAUDE["Claude Code"]
    CODEX["Codex"]
    MEMORY["CLAUDE.md + .claude/rules/*"]
    C_SKILLS[".claude/skills/*"]
    AGENTS_MD["AGENTS.md"]
    X_RULES[".codex/rules/*.rules"]
    X_AGENTS[".codex/agents/*.toml"]
    X_SKILLS[".codex/skills/*"]

    USER --> CLAUDE
    USER --> CODEX
    CLAUDE --> MEMORY
    CLAUDE --> C_SKILLS
    CODEX --> AGENTS_MD
    CODEX --> X_RULES
    CODEX --> X_AGENTS
    CODEX --> X_SKILLS
Loading

Claude Code Layer

Codex Layer


MCP Integration

This project includes a local MCP server at mcp/ with 97 tools across 16 domain modules. It exposes every supported app action, including scoped data reads, transcripts/images, Claude and GPT pricing, workflows, alerts/webhooks, imports and backup restore, Claude/Codex config, Run Agent, remote sources, settings, push, and guarded maintenance. It supports three transport modes.

MCP Transport Modes

flowchart LR
    subgraph Transports["Transport Modes"]
        STDIO["stdio\n(default)"]
        HTTP["HTTP + SSE\n(port 8819)"]
        REPL["Interactive REPL\n(terminal CLI)"]
    end

    subgraph Protocols["Wire Protocols"]
        P1["JSON-RPC\nstdin/stdout"]
        P2["Streamable HTTP (2025-11-25)\nLegacy SSE (2024-11-05)"]
        P3["Direct invocation\ntab completion + colored output"]
    end

    STDIO --> P1
    HTTP --> P2
    REPL --> P3

    style STDIO fill:#6366f1,stroke:#818cf8,color:#fff
    style HTTP fill:#f59e0b,stroke:#fbbf24,color:#000
    style REPL fill:#a855f7,stroke:#c084fc,color:#fff
Loading
Mode Command Use Case
stdio npm run mcp:start Claude Code, Claude Desktop, IDE MCP hosts
HTTP npm run mcp:start:http Remote MCP clients, web integrations, multi-session
REPL npm run mcp:start:repl Ops debugging, manual tool invocation, local admin

MCP REPL

MCP Architecture

graph LR
    HOST["MCP Host<br/>(Claude Code / Claude Desktop)"]
    HTTP_CLIENT["Remote MCP Client"]
    OPERATOR["Operator CLI"]

    MCP_STDIO["MCP Server<br/>stdio"]
    MCP_HTTP["MCP Server<br/>HTTP :8819"]
    MCP_REPL["MCP Server<br/>REPL"]

    API["Dashboard API<br/>Express /api/*"]
    DB["SQLite<br/>data/dashboard.db"]

    HOST -->|"stdin/stdout"| MCP_STDIO
    HTTP_CLIENT -->|"POST /mcp · GET /sse"| MCP_HTTP
    OPERATOR -->|"interactive CLI"| MCP_REPL

    MCP_STDIO --> API
    MCP_HTTP --> API
    MCP_REPL --> API
    API --> DB

    style HOST fill:#6366f1,stroke:#818cf8,color:#fff
    style HTTP_CLIENT fill:#f59e0b,stroke:#fbbf24,color:#000
    style OPERATOR fill:#a855f7,stroke:#c084fc,color:#fff
    style MCP_STDIO fill:#0f766e,stroke:#14b8a6,color:#fff
    style MCP_HTTP fill:#0f766e,stroke:#14b8a6,color:#fff
    style MCP_REPL fill:#0f766e,stroke:#14b8a6,color:#fff
    style API fill:#339933,stroke:#5cb85c,color:#fff
    style DB fill:#003B57,stroke:#005f8a,color:#fff
Loading

MCP Tool Surface

graph TD
    ROOT["MCP Tools"]
    OBS["Observability, sessions,<br/>agents, events, transcripts"]
    OPS["Workflows, alerts,<br/>webhooks, imports, push"]
    CFG["Claude/Codex config,<br/>hooks, homes, updates"]
    RUN["Run Agent, remote sources,<br/>pricing, maintenance"]

    ROOT --> OBS
    ROOT --> OPS
    ROOT --> CFG
    ROOT --> RUN
Loading

MCP Safety Model

flowchart TD
    CALL["tools/call"] --> VALIDATE["zod input validation"]
    VALIDATE --> TYPE{"Tool type?"}
    TYPE -->|Read-only| EXEC["Execute"]
    TYPE -->|Mutation| M_FLAG{"ALLOW_MUTATIONS?"}
    M_FLAG -->|No| DENY1["❌ Reject"]
    M_FLAG -->|Yes| DEST{"Destructive?"}
    DEST -->|No| EXEC
    DEST -->|Yes| D_FLAG{"ALLOW_DESTRUCTIVE?"}
    D_FLAG -->|No| DENY2["❌ Reject"]
    D_FLAG -->|Yes| TOKEN{"confirmation_token?"}
    TOKEN -->|Invalid| DENY3["❌ Reject"]
    TOKEN -->|Valid| EXEC
    EXEC --> RESULT["Return tool result"]

    style EXEC fill:#339933,stroke:#5cb85c,color:#fff
    style DENY1 fill:#dc2626,stroke:#f87171,color:#fff
    style DENY2 fill:#dc2626,stroke:#f87171,color:#fff
    style DENY3 fill:#dc2626,stroke:#f87171,color:#fff
Loading

MCP Operational Modes

  • Read-only mode (default): MCP_DASHBOARD_ALLOW_MUTATIONS=false
  • Admin mode: MCP_DASHBOARD_ALLOW_MUTATIONS=true
  • Authenticated dashboard: set MCP_DASHBOARD_API_TOKEN / _FILE to the dashboard token
  • Authenticated HTTP/SSE transport: set MCP_HTTP_AUTH_TOKEN / _FILE; clients send bearer auth or x-mcp-token
  • Transport guardrails: direct loopback HTTP may carry the token; container-host aliases require HTTPS; redirects are rejected
  • Payload guardrails: 50 MiB per uploaded history file, 100 MiB total per call, 10 MiB binary responses, and 25 MiB backup restore
  • Destructive mode: requires both:
    • MCP_DASHBOARD_ALLOW_MUTATIONS=true
    • MCP_DASHBOARD_ALLOW_DESTRUCTIVE=true
    • tool input confirmation_token: "CLEAR_ALL_DATA"

Full details: mcp/README.md


API Reference

All endpoints return JSON. Error responses follow the shape { error: { code, message } }.

OpenAPI / Swagger / ReDoc

There are three ways to explore the HTTP API, all driven by a single OpenAPI 3.0.3 spec (default server port 4820):

Method Path Description
GET /api/openapi.json Raw OpenAPI 3.0.3 JSON spec
GET /api/docs Interactive Swagger UI — try-it-out request execution
GET /api/redoc ReDoc reference — a clean, read-optimized three-panel rendering of the same spec
GET /api/redoc/redoc.standalone.js Self-hosted ReDoc bundle (served locally via the redoc dependency, never a CDN — works offline)

The OpenAPI document is generated from server/openapi.js (createOpenApiSpec()), merged with supplementary fragments under server/openapi-extra/. Swagger UI and ReDoc are both served directly by the backend; the ReDoc bundle is served locally (GET /api/redoc/redoc.standalone.js) so the reference works fully offline / air-gapped, consistent with the project's no-external-assets policy.

Coverage is comprehensive: every backend route is documented (82 path entries) across these tags — Health, Sessions, Agents, Events, Stats, Metrics, Analytics, Hooks, Pricing, Workflows, Settings, Updates, Alerts, Webhooks, Push, CcConfig (Claude Code config explorer), Run (dashboard-spawned runs), and Documentation — each with parameters, request/response schemas, field-level descriptions, and realistic examples.

A committed openapi.yaml at the repo root mirrors the live spec. It is generated from server/openapi.js (never hand-edited) — regenerate it after API changes with:

npm run openapi:yaml

Prometheus metrics & Grafana

GET /api/metrics exposes the dashboard's live counters — sessions/agents by status, event and token totals, connected realtime clients, configured remote sources, process uptime/memory, and build version — in the Prometheus text-exposition format, so CCAM can be scraped into your own observability stack. A turnkey Prometheus + Grafana stack with four auto-provisioned dashboards (default home: CCAM — Overview) lives in monitoring/.

npm (no Docker — macOS, Linux, or Windows):

npm start                          # dashboard on :4820
npm run monitoring:install         # one-time: npm postinstall pulls binaries
npm run monitoring:up              # Grafana on :3000, auto-provisioned; see monitoring/README.md for credentials

Docker / Podman (when the dashboard runs in a container or you prefer Compose):

# Dashboard only
npm run docker:up

# Dashboard + Prometheus + Grafana (one command)
npm run docker:full:up

# Or mix: native/docker dashboard + docker monitoring
DASHBOARD_ALLOWED_HOSTS=host.docker.internal npm start   # or docker:up with same env
npm run monitoring:docker:up
npm run monitoring:verify

Grafana CCAM — Overview dashboard with live session, event, and token metrics
📊 Grafana · CCAM — Overview — default home dashboard (four boards auto-provisioned): fleet snapshot, database totals, breakdown charts, and rates — all from live /api/metrics scrapes

Prometheus CCAM console with metric cards and session tables
🔥 Prometheus · CCAM console — pre-built landing page at /consoles/index.html that queries Prometheus directly for scrape health, session totals, events, tokens, and drill-down Graph links

Prometheus Graph UI with CCAM PromQL query
📈 Prometheus · Graph — run PromQL against scraped CCAM metrics (e.g. sum(ccam_sessions), ccam_events_total, rate(ccam_tokens_total[5m])) with starter links from the CCAM console and monitoring/README.md

See docs/API.md → Metrics for the full metric list and scrape/auth details.

Swagger UI

ReDoc UI

Health

Method Path Description
GET /api/health Returns { status: "ok", timestamp }

Sessions

Method Path Query Params Description
GET /api/sessions status, q, limit, offset List sessions with agent counts and per-session cost. q does case-insensitive search across id / name / cwd. limit defaults to 50, max 10000. Response includes total for paginators.
GET /api/sessions/:id -- Session detail with agents and events
GET /api/sessions/:id/stats -- Aggregated counts powering the Session Detail overview panel: events, events-by-type, top tool usage, error count, agent type/status counts, subagent type breakdown, token totals, time range
GET /api/sessions/:id/transcripts -- List available JSONL transcripts for the session (main + subagents + compactions)
GET /api/sessions/:id/transcript agent_id, limit, offset, after, before Stream messages from a specific transcript with cursor-based pagination. Assistant usage includes input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens so the Run page meter can hydrate fully on resume / re-attach
POST /api/sessions -- Create session (idempotent on id)
PATCH /api/sessions/:id -- Update session status/metadata

Agents

Method Path Query Params Description
GET /api/agents status, session_id, limit, offset List agents with filters
GET /api/agents/:id -- Single agent detail
POST /api/agents -- Create agent
PATCH /api/agents/:id -- Update agent status/task/tool

Events

Method Path Query Params Description
GET /api/events session_id, limit, offset List events (newest first)

Stats

Method Path Description
GET /api/stats Aggregate counts, status distributions, WS connections

Analytics

Method Path Description
GET /api/analytics Token/tool/session aggregates for charts and trend views

Remote Data Sources

Method Path Query Params Description
GET /api/remote-sources -- List configured remote sources with status, last error, and last-sync counts
POST /api/remote-sources -- Create a source. Body: { label, host, ssh_port?, identity_file?, remote_home?, remote_codex_home?, enabled? }
PATCH /api/remote-sources/:id -- Update a source (label, connection fields, enabled)
DELETE /api/remote-sources/:id purge Delete a source; ?purge=true also deletes that source's imported sessions
POST /api/remote-sources/:id/test -- Probe SSH connectivity to the source
POST /api/remote-sources/:id/sync -- Pull and re-import from the source now

GET /api/sessions, /api/events, /api/agents, /api/stats, and /api/analytics also accept an optional sources query param (comma-separated source ids; omit for all) to scope results by origin, and GET /api/sessions/facets returns a sources array for the data-scope selector.

Hooks

Method Path Description
POST /api/hooks/event Receive and process a Claude Code hook event

Hook event payload:

{
  "hook_type": "PreToolUse",
  "data": {
    "session_id": "abc-123",
    "tool_name": "Bash",
    "tool_input": { "command": "ls -la" }
  }
}

Pricing

Method Path Description
GET /api/pricing List all pricing rules
PUT /api/pricing Create or update a pricing rule
DELETE /api/pricing/:pattern Delete a pricing rule
GET /api/pricing/cost Total cost across all sessions
GET /api/pricing/cost/:id Cost breakdown for a specific session

Workflows

Method Path Description
GET /api/workflows Provider/source-scoped workflow data (orchestration, recorded tools, patterns, Codex compactions). Optional ?status=active|completed, ?sources=..., and ?providers=claude|codex filters apply to all 11 data sections
GET /api/workflows/session/:id Provider/source-scoped per-session drill-in (agent tree, recorded tool timeline, events)

Alerts

Method Path Description
GET /api/alerts Fired-alert feed, newest first (?unacked=true, limit, offset)
POST /api/alerts/:id/ack Acknowledge one alert
POST /api/alerts/ack-all Acknowledge every unacked alert
GET /api/alerts/rules List alert rules
POST /api/alerts/rules Create a rule (event_pattern | inactivity | status_duration | token_threshold)
PATCH /api/alerts/rules/:id Update name / config / enabled / cooldown (rule type is immutable)
DELETE /api/alerts/rules/:id Delete a rule and its fired-alert history

Webhooks

Method Path Description
GET /api/webhooks/providers Supported providers + their config fields (drives the UI form)
GET /api/webhooks List webhook targets (URLs masked, secrets redacted)
POST /api/webhooks Create a target (14 first-class providers + generic)
PATCH /api/webhooks/:id Update name / url / enabled / secret / headers / rule scope (type is immutable)
DELETE /api/webhooks/:id Delete a target and its delivery log
POST /api/webhooks/:id/test Send a synthetic test alert and report the delivery result
GET /api/webhooks/:id/deliveries Recent delivery log for a target (limit, offset)

Hosted providers require HTTPS. generic and n8n may use HTTP for local or self-hosted receivers. Delivery rejects redirects, so credentials, custom headers, and HMAC signatures are never forwarded to a second URL.

Settings

Method Path Description
GET /api/settings/info System info, DB stats, hook status
POST /api/settings/clear-data Delete all sessions, agents, events, token usage
POST /api/settings/reimport Re-import legacy sessions from ~/.claude/
POST /api/settings/reinstall-hooks Reinstall Claude Code hooks
POST /api/settings/reset-pricing Reset Claude, Codex, or both pricing tables to defaults
GET /api/settings/export Export all data (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing, gpt_model_pricing) as one versioned JSON download
POST /api/settings/import Restore one bundle up to 25 MiB from /export (multipart file or JSON { path }). Idempotent + non-destructive — existing sessions are skipped whole
POST /api/settings/cleanup Abandon stale sessions, purge old data

Claude Config Explorer (/api/cc-config)

Read-only inspection of every Claude Code configuration surface, plus carefully-gated mutations for low-risk text-file artifacts. File reads canonicalize the requested path and allowed roots, so symlinks cannot escape the trusted Claude directories. All write paths create timestamped backups under <root>/cc-config-backups/<type>/ before mutating.

Method Path Description
GET /api/cc-config/overview Roots (claude home, project .claude, project root, ~/.claude.json) + counts for every surface
GET /api/cc-config/skills Skills under <scope>/.claude/skills/<name>/SKILL.md with parsed frontmatter; ?scope=user|project|all
GET /api/cc-config/agents Subagents <scope>/.claude/agents/*.md
GET /api/cc-config/commands Slash commands <scope>/.claude/commands/*.md
GET /api/cc-config/output-styles Output styles <scope>/.claude/output-styles/*.md
GET /api/cc-config/plugins Installed plugins from ~/.claude/plugins/installed_plugins.json, joined with enabledPlugins from settings; each entry includes contributes (count of skills/agents/commands/hooks/output-styles inside the plugin's install dir) plus plugin.json metadata
GET /api/cc-config/marketplaces Registered marketplaces from known_marketplaces.json, enriched with each marketplace's own marketplace.json (plugin count, owner, description)
GET /api/cc-config/mcp MCP servers from ~/.claude.json (top-level + per-project) and settings.json
GET /api/cc-config/hooks Hooks aggregated across user / project / project-local settings.json files
GET /api/cc-config/hook-scripts Files in ~/.claude/hooks/ (the helper scripts referenced by hooks.<event>.command)
GET /api/cc-config/keybindings ~/.claude/keybindings.json parsed into context-grouped key/action pairs
PUT /api/cc-config/keybindings Overwrite keybindings.json from { groups: [{ context, bindings: [{ key, action }] }] }. Backs up first, preserves top-level metadata ($schema/$docs), rejects duplicate contexts/keys. Safe to edit (unlike settings.json, the CLI never rewrites it mid-session)
GET /api/cc-config/statusline settings.json.statusLine config + the actual statusline.py / statusline-command.sh content if present
GET /api/cc-config/settings User / project / project-local settings JSON, with secret-like keys (matching /token|secret|password|api[_-]?key|auth/i) replaced by "<redacted>"
GET /api/cc-config/memory CLAUDE.md files at user + project scope, plus per-project file-based memory: scope:"auto-memory" items (each carrying project, name, isIndex, and parsed frontmatter) for every *.md under ~/.claude/projects/<slug>/memory/. Mutate via PUT/DELETE /api/cc-config/file with { scope: "auto-memory", type: "auto-memory", project, name } (backups land in <memory-dir>/.cc-config-backups/auto-memory/)
GET /api/cc-config/file?path=… Body of a single file (path-contained to CLAUDE_HOME / project .claude / project CLAUDE.md)
GET /api/cc-config/backups Listing of all timestamped backups, optionally filtered ?scope=&type=
PUT /api/cc-config/file Create or overwrite a text-file artifact. Body: { scope, type, name?, content }. Auto-backs-up if file exists. Atomic temp + rename. 256 KB content cap, strict name regex
DELETE /api/cc-config/file Backup-then-delete a text-file artifact. Skill dirs are backed up whole (preserving bundled assets) before recursive removal

Run Claude (/api/run)

HTTP surface for spawning and supervising claude subprocesses from the dashboard. Same-origin guard on every route — browser requests must come from a localhost origin; missing-Origin (CLI/curl) requests pass. A supplied cwd must be an existing absolute directory and is canonicalized with realpath. It intentionally may be outside this repository so Run Agent can launch from the user's home or any recent project.

Method Path Description
GET /api/run List all in-memory run handles (live + recently finished); also returns maxConcurrent and activeCount
GET /api/run/binary Probe whether claude is on PATH and where it lives — used by the UI to surface a clear error before spawning
GET /api/run/cwds Suggested working directories: dashboard server cwd, $HOME, and recent cwds from the sessions table
GET /api/run/files?cwd=…&q=… Fuzzy file search inside cwd for the Run page's @-file autocomplete. Skips node_modules, .git, dist, build, .next, .cache, coverage, etc. Cwd is required and must exist; results are capped and ranked by basename match
POST /api/run Spawn a new run. Body: { prompt, mode: "headless"|"conversation", cwd?, model?, permissionMode?, resumeSessionId?, effort? }. Headless puts prompt in argv via -p and closes stdin. Conversation pipes the prompt over stdin as a stream-json envelope and keeps stdin open for follow-ups. resumeSessionId (conversation only) adds --resume <id>; when set, prompt may be empty — the spawner skips the initial stdin write and claude idles on the resumed conversation until the user posts a follow-up via POST /api/run/:id/message. effort (low / medium / high) maps to --effort. The spawner always passes --output-format stream-json --verbose --include-partial-messages so the UI can render character-by-character deltas. Concurrency is effectively uncapped (default ceiling 10000 — override with RUN_MAX_CONCURRENT)
GET /api/run/:id Current handle state. ?envelopes=1 includes the in-memory envelope log so the UI can replay history when re-attaching
POST /api/run/:id/message Send a follow-up turn to a running conversation (conversation mode only). Body: { text }
DELETE /api/run/:id Stop a run. SIGTERM, escalating to SIGKILL after 5 s

Output streams over the existing dashboard WebSocket as three message types: run_stream (parsed stream-json envelope, including stream_event deltas from --include-partial-messages), run_status (status transitions), run_input_ack (stdin write confirmed). The Config Explorer page subscribes to a fourth message — cc_config_changed — broadcast by server/lib/cc-watcher.js (via fs.watch on ~/.claude/) and by routes/cc-config.js after every successful PUT/DELETE, with payload { source: "dashboard"|"fs", action?, scope?, type?, name?, paths? }. The Sessions list and SessionDetail page poll /api/run (and listen for run_status) to badge any session currently being driven by an in-flight Run with a clickable ▶ Run indicator that links back to /run.

Import History

Import existing Claude Code or Codex history through the provider tabs in Settings → Import History. Claude Code uses its shared JSONL parser for ~/.claude/projects; Codex uses the same append-only rollout ingestor as real-time monitoring for ~/.codex/sessions, including token snapshots, response-item tool calls, lifecycle state, and native /rename titles when session_index.jsonl is included. Re-imports are idempotent: Claude preserves compaction baselines and Codex retains byte cursors, so neither provider double-counts usage or cost. Folder and browser-uploaded Codex history is copied into dashboard-owned storage before temporary files are cleaned up, keeping the conversation view available later.

A fourth mode — Restore backup — imports a full dashboard export .json (produced by the Export data button, ccam export, or GET /api/settings/export) rather than raw Claude transcripts. This is the round-trip counterpart to Export: it restores every table (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing, gpt_model_pricing) and is idempotent + non-destructive — a session already present is skipped whole, so you can safely consolidate several machines into one dashboard without duplicating or overwriting anything. Backed by server/lib/data-transfer.js and POST /api/settings/import (also ccam import-data <file>).

flowchart LR
    subgraph Sources
      A1["Default folder<br/>~/.claude/projects"]
      A2["Custom folder<br/>any absolute path"]
      A3["Uploaded files<br/>.jsonl / .meta.json /<br/>.zip / .tar(.gz) / .gz"]
    end

    A1 -->|POST /api/import/rescan| R["server/routes/import.js"]
    A2 -->|POST /api/import/scan-path| R
    A3 -->|POST /api/import/upload<br/>multipart| R

    R -->|archive extract<br/>+ path-traversal guard<br/>+ zip-bomb cap| X["server/lib/archive.js"]
    R -->|walks recursively| I["importFromDirectory<br/>(scripts/import-history.js)"]
    X --> I
    I -->|same pipeline as live<br/>hook ingestion| P["parseSessionFile +<br/>importSession"]
    P -->|prepared statements,<br/>in one transaction| D[("SQLite<br/>sessions / agents / events /<br/>token_usage")]
    I -.->|import.progress<br/>throttled| W["WebSocket /ws"]
    W -.-> U["Settings → Import History<br/>progress bar + result card"]

    style A1 fill:#6366f1,stroke:#818cf8,color:#fff
    style A2 fill:#6366f1,stroke:#818cf8,color:#fff
    style A3 fill:#6366f1,stroke:#818cf8,color:#fff
    style R fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
    style X fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
    style I fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
    style P fill:#f59e0b,stroke:#fbbf24,color:#000
    style D fill:#10b981,stroke:#34d399,color:#fff
    style U fill:#a855f7,stroke:#c084fc,color:#fff
Loading

Routes

Method Path Description
GET /api/import/guide Provider-aware OS paths, archive command, extensions, and instructions (?provider=claude|codex)
POST /api/import/rescan Rescan the selected default: ~/.claude/projects or ~/.codex/sessions ({ provider })
POST /api/import/scan-path Scan an absolute directory with { path, provider }; walks recursively
POST /api/import/upload Multipart upload of .jsonl, .meta.json, .zip, .tar(.gz), .gz with provider

Supported inputs. Loose JSONL (.jsonl) session transcripts, their companion .meta.json sidecars, and archives (.zip, .tar, .tar.gz/.tgz, plain .gz) containing any nested directory layout. Both canonical Claude Code layouts are recognized automatically: <project>/<sessionId>/subagents/agent-*.jsonl (default) and <project>/subagents/<sessionId>/agent-*.jsonl (alternative). For Codex, the importer recognises recursive rollout-*.jsonl session files (including loose JSONL files with session_meta) and an optional session_index.jsonl for native session names.

Accuracy guarantees. Sessions are deduplicated by UUID; re-running the importer is always safe. The compaction baseline_input / baseline_output / baseline_cache_read / baseline_cache_write columns preserve token counts from before a transcript was compacted, so re-ingesting a post-compaction JSONL never erases historical cost. Event-level dedup uses a per-event-type high-water mark (MAX(created_at) GROUP BY event_type for the session): on every re-import only JSONL entries with ts > cutoff[type] are inserted, so long-running sessions whose transcripts grow across multiple days continue to receive Stop / PostToolUse / TurnDuration / ToolError events without duplicating earlier work. sessions.ended_at is rolled forward to the JSONL's last activity when it surpasses the stored value, and message-count metadata is refreshed on every pass.

Huge-transcript safety. The shared transcript cache (server/lib/transcript-cache.js) reads JSONL files in 4 MiB chunks and decodes only one line at a time, so transcripts larger than V8's max JS string length (~512 MiB on 64-bit Node 20) parse without aborting the process with FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal. The same chunked path is used by hook ingestion, the periodic compaction sweep, and the history importer — none of them materialize the full file as a single JS string. Per-entry growable arrays (turnDurations, errors, compaction.entries, usageExtras.*) are tail-capped at TRANSCRIPT_CACHE_MAX_ARRAY_LEN (default 1000), with trimming applied during parse at a 2 × cap watermark so a fresh full-file parse on a multi-day session can't build an unbounded transient before finalization.

Safety. Archive extraction validates every entry against path traversal (absolute paths and .. segments are rejected). A configurable extraction cap (CCAM_IMPORT_MAX_EXTRACT_BYTES, default 4 GB) stops zip/tar/gzip bombs. Upload size is capped per file (CCAM_IMPORT_MAX_BYTES, default 1 GB) and per request (CCAM_IMPORT_MAX_FILES, default 2000). All staging directories are per-request and reclaimed in finally, including when multer rejects all files up front.

Progress. Import activity is broadcast over the existing WebSocket as import.progress messages (phase: start / scan / extract / parse / complete / error), throttled to avoid flooding the channel on large imports.

UI. Use the Settings → Import History panel for a guided, drag-and-drop experience with step-by-step instructions, live progress, and a post-import summary showing imported / enriched / skipped / error counts.

Import History UI

WebSocket

Connect to ws://localhost:4820/ws to receive real-time push messages:

{
  "type": "agent_updated",
  "data": { "id": "...", "status": "working", "current_tool": "Edit" },
  "timestamp": "2026-03-05T15:43:01.800Z"
}

Message types: session_created, session_updated, agent_created, agent_updated, new_event, alert_triggered, alert_updated, remote_source.status (data { id, status: "idle"|"syncing"|"ok"|"error"|"deleted", error?, last_sync_at? })

stateDiagram-v2
    [*] --> Connecting: Component mounts
    Connecting --> Connected: onopen
    Connected --> Closed: onclose / onerror
    Closed --> Connecting: setTimeout(2000ms)
    Connected --> [*]: Component unmounts
    Closed --> [*]: Component unmounts
Loading

Hook Events

The dashboard processes these Claude Code hook types:

Hook Type Trigger Dashboard Action
SessionStart Claude Code session begins Creates session and main agent. Stamps awaiting_input_since (with awaiting_reason=session_start) so a fresh session lands in Waiting — except a compact-source SessionStart (mid-turn auto-compaction), which leaves the flag untouched so a working session stays Active. Reactivates resumed sessions. Abandons orphaned sessions with no activity for DASHBOARD_STALE_MINUTES (default 180)
UserPromptSubmit User hits enter on a prompt Clears the waiting flag and promotes the main agent to working — the only signal that text-only assistant turns have started, since they emit no PreToolUse
PreToolUse Agent starts using a tool Clears the waiting flag, sets agent to working, sets current_tool. If tool is Agent, creates a subagent record
PostToolUse Tool execution completed Clears the waiting flag (handles permission-prompt approvals where the Notification stamped it mid-tool). Clears current_tool. Agent stays working
Stop Claude finishes responding Non-error: main agent → waiting — Claude finished its turn, ball is in the user's court. stop_reason=error: marks the agent and session error. Background subagents keep running
SubagentStop Background agent finished Matches and completes the subagent by description, type, or task. Deliberately does NOT clear the waiting flag — a subagent finishing tells us nothing about the human. Triggers a fire-and-forget JSONL scan (scanAndImportSubagents) that emits per-tool PreToolUse + PostToolUse events under the subagent's own agent_id so the Timeline shows every tool the subagent ran, not just the spawn marker
Notification Agent notification Logs event. Permission/input-prompt messages set the agent to waiting and stamp awaiting_input_since (with awaiting_reason=notification, matched by pattern: permission, waiting for input, needs your approval, …). Compaction notifications are tagged as Compaction events. Triggers a browser notification if enabled
SessionEnd Claude Code CLI process exits Drops the waiting flag. If the session is already in error, the error state is preserved; otherwise marks all agents and the session as completed
Compaction /compact detected in JSONL Creates a compaction subagent (type compaction) and Compaction event. Detected via isCompactSummary entries in the transcript JSONL. Also detected by periodic scanner for active sessions
APIError API error in JSONL transcript Extracted from isApiErrorMessage entries (quota, rate limit, invalid_request) and raw type: "error" responses. Now immediately marks the session and agent as error — previously recorded as events without changing status. Stored as event with error details
TurnDuration Turn timing in JSONL transcript Extracted from system subtype turn_duration messages with durationMs. Stored as event for turn-level timing analysis
ToolError Tool result error in JSONL Extracted from toolUseResult.is_error entries. Tracks tool-level failures for error propagation analysis
Interrupted Turn cancelled by the user (Esc) Synthesized by the watchdog — Esc fires no hook, so a stuck working session is detected from the transcript's [Request interrupted by user] marker or, when Esc preceded any output, from the idle-working timeout (DASHBOARD_WORKING_IDLE_SECONDS). The session moves to Waiting (same as a normal Stop)

Browser Notifications

The dashboard supports persistent browser notifications via Web Push (VAPID) for real-time alerts even when the dashboard tab is not focused or the browser is backgrounded.

How It Works

  1. Enable notifications in the Settings page via the master toggle
  2. Grant browser permission when prompted — this registers a Service Worker and creates a push subscription
  3. Configure which events trigger notifications:
Event Default Description
New session starts On Fires when a new Claude Code session is created
Claude finished responding Off Fires on Stop events when Claude finishes a response turn
Session closed Off Fires on SessionEnd when the CLI process exits
Session errors On Fires when a session ends with an error
Subagent spawned Off Fires when a background subagent is created

Additionally, any Notification hook event from Claude Code triggers a browser notification regardless of the per-event toggles (as long as the master toggle is enabled).

Notifications Architecture

  • VAPID Pipeline: Uses web-push on the server for secure message delivery. VAPID keys are auto-generated and stored in data/vapid-keys.json.
  • Service Worker: A dedicated worker (client/public/sw.js) handles incoming push events and displays notifications with silent: false to ensure audio playback on macOS.
  • Subscriptions: Browser-specific endpoints are stored in the push_subscriptions table in SQLite.
  • Persistence: Notifications arrive even if the browser is closed, as the Service Worker operates in the background.
  • Test notification: button in Settings lets you verify the VAPID pipeline and audio playback.

PWA & Offline Support

The project ships three independent Progressive Web Apps — one each for the dashboard, landing page, and wiki. Each has its own manifest.json and Service Worker so the browser treats them as separate installable applications.

Surface Manifest Service Worker Caching Strategy
Dashboard (client/) client/public/manifest.json client/public/sw.js Vite's content-hashed bundles under /assets/* are served cache-first (URLs are immutable per build). Everything else — navigations, the SW itself, manifest.json, icons, root / — is network-first with cache fallback, so a rebuild always shows the freshest UI without a hard refresh. API (/api/*), WebSocket (/ws), and Vite HMR requests are never cached. Push notification handlers are preserved alongside the caching logic. The Express static middleware (server/index.js) reinforces this by sending Cache-Control: public, max-age=31536000, immutable for /assets/* and Cache-Control: no-cache, must-revalidate for index.html, sw.js, and manifest.json. client/src/main.tsx listens for controllerchange: when a new SW activates on an already-controlled page it reloads exactly once (first installs do not).
Landing page (root) manifest.json sw.js Precaches the HTML shell, favicon, and OG image. Screenshot PNGs are lazy-cached on first view (cache-first) to avoid a heavy initial precache. Navigation is network-first with offline fallback.
Wiki (wiki/) wiki/manifest.json wiki/sw.js Precaches index.html, style.css, script.js, manifest, and favicon. Fully offline-capable after one visit. Network-first HTML, cache-first for CSS/JS.

Cache lifecycle: All three SWs call skipWaiting() on install and delete stale caches on activate (keyed by version strings like dashboard-v2, landing-v1, wiki-v1). Bumping the version constant forces a clean refresh.

iOS support: All three HTML files include <meta name="apple-mobile-web-app-capable" content="yes"> and <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> for standalone home-screen mode on Safari.

Icons: Manifests reference favicon.svg with sizes="any" and type="image/svg+xml" — supported in Chrome 107+, Firefox 110+, Edge 107+. Apple touch icons also use the SVG favicon.


Update Notifier

The dashboard watches its own git checkout and surfaces a modal whenever the canonical default branch has commits ahead of HEAD. Branch- and fork-aware: if you have an upstream remote (the standard convention for forks), it's preferred over origin; the chosen remote's master/main/HEAD is the comparison ref. The manual_command adapts to your situation — git pull --ff-only only when your branch actually tracks the canonical ref, otherwise a git fetch (and a fast-forward merge in the fork case) so the command never lies. Users get the exact command to run in a terminal — the server never pulls or restarts itself, which keeps the mechanism portable across dev sessions, pm2/systemd/launchd/Docker supervision, and remote deployments.

Dashboard update modal with copy-to-clipboard command

How It Works

flowchart LR
    S["Server startup"] --> SCHED["Update scheduler<br/>poll every 5 min"]
    SCHED --> PICK["Pick canonical remote<br/>upstream then origin"]
    PICK --> FETCH["git fetch remote prune<br/>execFile 120s timeout"]
    FETCH --> CMP["rev-list HEAD vs<br/>remote master main HEAD"]
    CMP --> FP["Fingerprint changed?"]
    FP -->|yes| WS["broadcast<br/>update_status"]
    FP -->|no| IDLE["skip broadcast"]
    WS --> CLIENT["UpdateNotifier<br/>+ Sidebar badge"]

    CHECK["POST updates check"] --> FETCH
    STATUS["GET updates status"] -.-> CMP

    style WS fill:#6366f1,stroke:#818cf8,color:#fff
    style CLIENT fill:#10b981,stroke:#34d399,color:#fff
Loading

A single check is cheap (git fetch <remote> --prune against the canonical remote — upstream if configured, else origin), wrapped with execFile (no shell) and a 120s timeout. Failures — offline network, non-git install, no remotes configured, unresolvable upstream ref — all return soft payloads (e.g. fetch_error: "...") rather than throwing, so a flaky remote never blocks the dashboard.

UI Surfaces

Surface Behavior
Modal (client/src/components/UpdateNotifier.tsx) Appears when update_available === true and the user hasn't already dismissed this specific remote_sha. Shows commits-behind, the tracked ref, an optional situation_note (when on a feature branch / fork the note explains why the command differs), the copy-pastable command, and three buttons: Copy command (primary), Check now, Dismiss. ESC and backdrop clicks dismiss. Keyed by remote_sha in localStorage, so a newer upstream commit re-opens the modal automatically.
Sidebar button (client/src/components/Sidebar.tsx) Always-visible "Check for updates" button in the footer. Emerald border + green badge dot when behind, amber when the last check hit a fetch error. Clicking it clears any prior dismissal, then fires POST /api/updates/check.
Server terminal When the scheduler transitions from "up to date" to "behind," it prints a framed block to stdout with the command so users running headless still see it.

API Surface

Endpoint Purpose
GET /api/updates/status Read-only check: runs git fetch against the canonical remote, compares HEAD to its default branch, returns the payload.
POST /api/updates/check Same check, but also broadcasts update_status over WebSocket so all connected clients update at once.

Both endpoints return the same payload shape:

{
  "git_repo": true,
  "update_available": true,
  "repo_root": "/Users/you/Claude-Code-Agent-Monitor",
  "remote_ref": "upstream/master",
  "canonical_remote": "upstream",
  "current_branch": "master",
  "tracking_upstream": "origin/master",
  "tracks_canonical": false,
  "situation": "fork_or_diverged_tracking",
  "local_sha": "abc1234...",
  "remote_sha": "def5678...",
  "commits_behind": 3,
  "manual_command": "cd \"/...\" && git fetch upstream && git merge --ff-only upstream/master && npm run setup",
  "situation_note": "You're on 'master' tracking 'origin/master'. This command fast-forwards your branch from upstream/master (the canonical default).",
  "message": "3 commit(s) on upstream/master not in your checkout."
}

situation is one of tracking_canonical (typical clone on the default branch — git pull --ff-only works), fork_or_diverged_tracking (local branch name matches canonical, but tracks a different remote — git fetch <remote> && git merge --ff-only <ref>), feature_branch (off the default branch — fetch only, integration left to the user), or detached_head.

What's Intentionally Not Here

There is no POST /api/updates/apply and no self-restart helper, by design. Self-updating a process from inside itself is unreliable without an external supervisor — npm run dev (concurrently), npm start, pm2, systemd, launchd, and Docker each need different restart logic, and git pull / npm install failures on a dying server have no clean rollback path. Detection-only keeps behaviour predictable across every supervisor, every OS, and every branch state, while still closing the "when do I need to pull?" information gap; the user owns the actual update in their own shell.

Configuration

Env Var Default Notes
DASHBOARD_UPDATE_CHECK enabled Set to 0 / false / off to disable the scheduler entirely.
DASHBOARD_UPDATE_CHECK_INTERVAL_MS 300000 (5 min) Interval between automatic checks. Floor is 60 000 ms — values below are clamped.

Tabby — Floating Cat Companion

Tabby is a cute floating cat companion pinned to the bottom-right corner of every page in the dashboard. Always present, it turns the live session stream into an at-a-glance, reactive mascot you can also talk to.

Tabby the reactive dashboard mascot, shown in various moods and with a speech bubble

Reactive mascot

Tabby is a cursor-tracking SVG cat with eight moods derived from the live session stream, each with its own animation:

Mood When Animation
idle Nothing notable happening Resting tail flick
watching Sessions are active Ear perk, eyes follow the cursor
happy A session or run finished cleanly Head bob + sparkle
worried Something looks off Subtle shake
stuck A session appears blocked Alert "!"
thinking An agent is mid-work Slow head bob
sleeping Quiet for a while zzz
disconnected WebSocket is down Calm, still pose

Speech bubbles

Tabby auto-surfaces short quips on notable events (session started/finished, errors, run completed). Bubbles are throttled and coalesced so a burst of events never spams the screen, they use aria-live for screen readers, and they can be muted from the panel.

Panel

Open the panel by clicking the cat or pressing ⌘B / Ctrl+B (Esc closes). It shows:

  • A live status lineN live · M errored · connection state.
  • Quick actions — jump to Run Claude, Activity, Sessions, or errored sessions; mute bubbles; clear alerts.
  • An Ask box (see below).

Ask box → Run Claude handoff

The Ask box answers simple status questions locally from cached data — for example "what's running", "any errors", or "status". Any other question is handed off to the existing Run Claude page (it deep-links to /run?prompt=…) to spawn a real Claude Code session. Tabby never calls an LLM itself; it simply reuses the Run Claude page for anything beyond a quick status lookup.

Accessibility & safe degradation

Tabby is keyboard-operable, uses aria-live for its bubbles, and honors prefers-reduced-motion. If the WebSocket is down it degrades safely to a calm disconnected state rather than erroring. You can toggle Tabby on or off in Settings (localized in English, Chinese, Vietnamese, Korean, and Spanish), and the implementation lives in client/src/components/Tabby/.


Connection Status Modal

Click the Live / Disconnected pill in the sidebar footer to open a small details panel about the dashboard's WebSocket transport. It shows the active ws:// endpoint, how long the current socket has been up, total events received, top event types as a horizontal bar chart, a 60-second throughput sparkline, and the last 8 events as a recent-activity list. Cumulative stats (totals, type breakdown, recent list) persist across reloads via localStorage under sidebar-connection-stats; the rolling sparkline and "connected since" timer are intentionally ephemeral. A Reset button in the footer clears everything on demand.

Connection details modal with throughput sparkline, top event types, and recent activity


VS Code Extension

The Claude Code Agent Monitor is available as a first-class VS Code extension, allowing you to monitor your AI agents without leaving your editor.

VS Code Extension Screenshot

🚀 Key Features

  • Live Sidebar: Dedicated Activity Bar view showing real-time Agent Health (Working, Waiting, Completed, etc.).
  • Usage Analytics: Track total tokens, live USD costs, and event counts directly in the sidebar.
  • Status Bar Integration: Quick-glance pulse monitor in the bottom bar showing active sessions and agents.
  • Deep Navigation: One-click access to specific dashboard views (Kanban, Analytics, Settings) or recent sessions.
  • Integrated Tab: Opens the full monitoring dashboard as a native VS Code webview tab.

📦 Installation & Setup

  1. Open the vscode-extension directory.
  2. Install the Marketplace extension or package it yourself using vsce package.
  3. Ensure your local dashboard server is running (npm run dev).
  4. Click the Radar icon in the VS Code Activity Bar to get started.

For detailed developer configuration, see the .vscode and vscode-extension directories.

Tip

Extension on VS Code Marketplace: Claude Code Agent Monitor


Desktop App (macOS & Windows)

The dashboard also ships as an optional native desktop application you install once and forget — a macOS .app (distributed as a .dmg) and a Windows .exe (an NSIS installer plus a no-install portable build). It lives in the desktop/ workspace, a sibling of client/, server/, mcp/, and vscode-extension/, and is built with Electron 35.

Claude Code Monitor running as a native desktop app
🍎🪟 Desktop App — native shell with a menu-bar / notification-area (tray) icon, Open-at-Login, and a single-instance lock. The same dashboard, in a real OS window (macOS shown).

Claude Code Monitor running as a native Windows desktop app, showing the Activity Feed with the Windows window menu bar and Tabby panel
🪟 The same dashboard as a native Windows app — notification-area (tray) icon, native window menu, and Open-at-Login.

Everything you see in the browser at localhost:4820 lives inside this window, with native OS lifecycle on top: a tray icon, a native application menu, auto-start integration, and a single quit button that cleanly shuts the server down.

How it works

Unlike running the dashboard from a terminal, the desktop app needs no npm start, no open shell, and no second copy of the server. The Electron main process hosts the Express server in-process — it require()s server/index.js directly in the same Node runtime, with no child process and no IPC — and points a Chromium BrowserWindow at the built React client.

flowchart LR
    subgraph electron["Claude Code Monitor.app — one Electron process"]
        main["Electron Main Process<br/>Node 22 / Electron 35"]
        host["server-host.ts<br/>port discovery · adoption · ABI patch"]
        express["server/index.js<br/>Express API · SQLite · WebSocket"]
        win["BrowserWindow<br/>built React client (client/dist)"]
        tray["tray.ts + menu.ts<br/>menu-bar icon · native app menu"]
        login["login-item.ts<br/>auto-start via SMAppService"]
        main -->|"startEmbeddedServer()"| host
        host -->|"require() in-process — no child process, no IPC"| express
        main --> tray
        main --> login
        express -->|"http + ws on 127.0.0.1:&lt;port&gt;"| win
    end

    hooks["Claude Code hooks<br/>(separate node processes)"] -->|"POST /api/hooks/event"| express
    sqlite[("data/dashboard.db<br/>SQLite — closed cleanly on quit")] <--> express

    style main fill:#47848F,stroke:#2f5a62,color:#fff
    style express fill:#339933,stroke:#5cb85c,color:#fff
    style win fill:#61DAFB,stroke:#3aa9c9,color:#000
    style host fill:#1f6feb,stroke:#1158c7,color:#fff
Loading

On launch the app:

  1. Picks a free port — preferring 4820, falling back to 4821–4829, then a random high port if all of those are taken.
  2. If a healthy dashboard server already answers /api/health on 4820 (e.g. you ran npm start in a terminal), it adopts that server instead of double-binding — no port collision, no SQLite contention. An adopted server keeps running after you quit the app.
  3. Otherwise it boots the embedded server, and on first owned-server boot auto-installs the Claude Code hooks and starts the background services (update scheduler, cc-watcher, orphaned-run reconciliation). A DMG-only user therefore gets events flowing with zero manual setup — no checkout, no npm run install-hooks.
  4. (macOS) Recovers your login-shell PATH so the Run Claude feature can find and spawn the claude CLI — a Finder/Dock-launched app otherwise inherits only launchd's minimal PATH and would miss CLIs in ~/.local/bin, /opt/homebrew/bin, version-manager bins, etc. (On Windows the process already inherits the user PATH.)
  5. Opens the dashboard window — unless the app was launched at login (on macOS via Login Items; on Windows via the tagged HKCU\…\Run entry), in which case it stays tray-only.
  6. On quit, shuts the embedded server down gracefully and closes SQLite cleanly (WAL checkpoint).

Features

  • Tray icon — always-on status surface (macOS menu bar / Windows notification area). Left-click toggles the dashboard window; right-click opens a context menu with Open Dashboard, Open in Browser, Restart Server, Show Logs, Open at Login (toggle), and Quit. macOS uses a tinted template glyph; Windows uses the colored icon.ico (a black template would vanish on the dark taskbar).
  • Window & taskbar icon — the BrowserWindow is wired to the colored app logo (icon.ico on Windows, icon.png elsewhere), so the title bar / taskbar show the real Claude Code Monitor icon — even an unpackaged npm run desktop:dev run no longer shows the generic Electron icon.
  • Native application menu — standard About / File / Edit / View / Window / Help menu with / Ctrl shortcuts. The File → Open Dashboard item (⌘1) is macOS-only: macOS keeps a global menu bar after the window hides, so it can reopen the window — on Windows/Linux the menu is attached to the window and can't fire while it's hidden, so reopen from the tray's Open Dashboard instead (which reliably raises the window even when minimized or behind other windows).
  • Auto-start at login — toggle Open at Login from the tray or app menu. On macOS it registers through the modern SMAppService API, so the entry appears under System Settings → General → Login Items; on Windows it writes a per-user HKCU\Software\Microsoft\Windows\CurrentVersion\Run entry, visible in Task Manager → Startup.
  • Window-close hides, server keeps running — closing the window just hides it; the server and tray stay up. Click the tray to bring the window back.
  • Single-instance lock — double-launching simply focuses the existing window; no second server, no port collision. (Applies on every platform.)
  • Data survives reinstalls and updates — the SQLite database and VAPID keys live in the per-user app-data directory outside the app bundle / install dir~/Library/Application Support/Claude Code Monitor/data/ on macOS, %APPDATA%\Claude Code Monitor\data\ on Windows. A packaged bundle is read-only, so writing the database inside it would break History Import and event persistence; keeping it in app-data fixes that and means your imported history is untouched when you replace or upgrade the app. (The Windows NSIS uninstaller keeps this data by default.)
  • claude CLI on PATH — on macOS the app recovers your login-shell PATH at startup, so the Run Claude feature works even though a Finder/Dock-launched app would otherwise only inherit launchd's minimal PATH. (On Windows the inherited user PATH already includes it.)
  • Logs — the main process writes to ~/Library/Logs/Claude Code Monitor/desktop.log (macOS) or %APPDATA%\Claude Code Monitor\logs\desktop.log (Windows); reach it from the tray menu's Show Logs.

Get it

Option A — download a pre-built installer (recommended). From Releases → latest (public, no GitHub sign-in). CI auto-publishes a new vX.Y.Z release whenever the version in package.json is bumped on master, so this link always serves the current build:

Platform Asset Notes
macOS (Apple Silicon) ClaudeCodeMonitor-<ver>-arm64.dmg drag into /Applications
macOS (Intel) ClaudeCodeMonitor-<ver>-x64.dmg drag into /Applications
Windows (installer) ClaudeCodeMonitor-Setup-<ver>-x64.exe per-user install, no admin
Windows (portable) ClaudeCodeMonitor-<ver>-x64-portable.exe run without installing

Per-commit fresh builds also live as CI artifacts (sign-in required, 14-day retention): ClaudeCodeMonitor-dmg from the 🍎 macOS Desktop (DMG) job and ClaudeCodeMonitor-win from the 🪟 Windows Desktop (EXE) job — useful for testing master before the next release tag.

Option B — build it yourself. From the repo root:

npm run setup                # install root + client deps, build client, install hooks
npm run build                # build the React client (client/dist)
npm run desktop:install      # install Electron + electron-builder into desktop/ (preflights native deps; prints setup help on failure)
npm run desktop:dmg:arm64    # macOS:   fast single-arch DMG → desktop/release/ClaudeCodeMonitor-<ver>-arm64.dmg
npm run desktop:win          # Windows: NSIS installer → desktop/release/ClaudeCodeMonitor-Setup-<ver>-x64.exe

Note

DMGs build on macOS; Windows .exes build on Windows — electron-builder packages for the host OS. The macOS npm run desktop:dmg build packages the app twice (once per architecture) and emits both per-arch DMGs (arm64 + x64) — the release build; it does not merge them into one universal binary. For your own Mac use the single-arch desktop:dmg:arm64 / desktop:dmg:x64. On Windows, better-sqlite3 is fetched as a prebuilt Electron binary by npm run desktop:install, so no Visual Studio C++ toolchain is needed in the common case. If the build does fail (no prebuilt binary, or a missing C++ toolchain), desktop:install prints the exact per-OS fix plus a no-toolchain alternative and fails loudly instead of leaving a broken install.

Install it

macOS:

  1. Double-click the .dmg to mount it.

  2. Drag Claude Code Monitor.app into your /Applications folder.

  3. The DMG is ad-hoc signed by default, so macOS Gatekeeper warns on first launch ("Apple could not verify…"). Clear the quarantine attribute:

    xattr -cr "/Applications/Claude Code Monitor.app"

    Or open System Settings → Privacy & Security and click Open Anyway.

  4. Launch the app. The tray icon appears and the dashboard window opens.

Windows:

  1. Run ClaudeCodeMonitor-Setup-<ver>-x64.exe. It installs per-user under %LOCALAPPDATA%\Programs\Claude Code Monitor (no administrator elevation) and lets you pick the install directory; or run the *-portable.exe to launch without installing.
  2. The installer is unsigned by default, so Windows SmartScreen may show "Windows protected your PC" on first launch — click More info → Run anyway.
  3. Launch from the Start menu / desktop shortcut. The notification-area (tray) icon appears and the dashboard window opens.

NSIS installer step 1 — Choose Installation Options, with per-user (Only for me) versus all-users selection
Windows installer · Step 1 — Choose Installation Options (per-user "Only for me" vs. all users).

NSIS installer step 2 — Choose Install Location, with the per-user %LOCALAPPDATA%\Programs destination folder
Windows installer · Step 2 — Choose Install Location (defaults to per-user %LOCALAPPDATA%\Programs).

NSIS installer step 3 — Completing Setup, with the option to finish and run the app
Windows installer · Step 3 — Completing Setup (Finish and launch the app).

Build commands

All commands run from the repo root:

Command What it does
npm run desktop:install Install Electron + electron-builder into desktop/; rebuild better-sqlite3 for Electron's ABI; preflights the native better-sqlite3 build and prints actionable setup help (incl. a no-toolchain alternative) on failure
npm run desktop:build Compile the desktop TypeScript sources into desktop/out/
npm run desktop:dev Build and launch the Electron app for local iteration
npm run desktop:test Run the smoke test (spawn Electron, probe /api/health, shut down)
npm run desktop:dmg macOS: build both DMGs (arm64 + x64) — correct for release, slower (packages each arch)
npm run desktop:dmg:arm64 macOS: build an Apple-Silicon-only DMG — fast (~1 min), recommended for your own Mac
npm run desktop:dmg:x64 macOS: build an Intel-only DMG — fast (~1 min)
npm run desktop:dmg:universal macOS: build one merged universal DMG (arm64 + x86_64) — optional, slowest, not what the release ships
npm run desktop:win Windows: build the NSIS installer .exe (x64)
npm run desktop:win:portable Windows: build the no-install portable .exe (x64)

The resulting macOS DMG is ~80 MB (≈ 250 MB on disk once installed) and the Windows installer is comparable — the standard Electron bundle tax.

Signing & notarization

The macOS DMG is ad-hoc signed by default so anyone can build a working .app without a paid Apple Developer account — the package script sets CSC_IDENTITY_AUTO_DISCOVERY=false so a code-signing certificate already in the contributor's keychain is never auto-picked. Real Developer ID signing is opt-in via CSC_LINK (a base64-encoded .p12) and CSC_KEY_PASSWORD; Apple notarization is opt-in via APPLE_ID, APPLE_TEAM_ID, and APPLE_APP_SPECIFIC_PASSWORD. The Windows build is unsigned by default (SmartScreen may prompt on first launch — More info → Run anyway); Authenticode signing activates only when an explicit certificate is provided via CSC_LINK + CSC_KEY_PASSWORD. CI picks all of these up automatically when provided — no code change required.

Implementation notes

  • better-sqlite3 is the only native module in the dependency tree, and a native module must be compiled against the exact Node ABI it runs on. The desktop/ workspace ships its own copy of better-sqlite3 rebuilt for Electron's ABI and uses a process-local require redirect to point server/db.js at it; the repo-root copy stays built for system Node (so npm run test:server keeps working).
  • Building a DMG rebuilds better-sqlite3 for the target architecture, which can leave the desktop copy built for the other CPU arch and break npm run desktop:dev / npm run desktop:test with ERR_DLOPEN_FAILED. The desktop prebuild step now auto-heals the native module for the local machine on the next build, so the dev and smoke-test flows keep working after an arch-specific DMG build. The prebuild step also fails fast with setup help when the better-sqlite3 native binary is missing entirely, turning a runtime crash into a copy-pasteable build-time error.
  • The only change outside desktop/ is a behavior-preserving refactor of server/index.js: its post-listen bootstrap (update scheduler, cc-watcher, orphaned-run reconciliation) was extracted into an exported startBackgroundServices() so the embedded server runs exactly what node server/index.js runs. The standalone node server/index.js path is functionally unchanged; client/, scripts/, mcp/, and vscode-extension/ are untouched.
  • Two path-filtered desktop CI jobs build, smoke-test, and package the app: 🍎 macOS Desktop (DMG) on macos-latest (uploads the ClaudeCodeMonitor-dmg artifact — two single-arch DMGs) and 🪟 Windows Desktop (EXE) on windows-latest (uploads the ClaudeCodeMonitor-win artifact — NSIS installer + portable). On a version-bump push to master, the release job attaches both the macOS DMGs and the Windows .exes to the published vX.Y.Z GitHub Release. The Windows icon (desktop/assets/icon.ico) is committed to the repo (regenerate it from icon.png with npm run build:win-icon, PowerShell + .NET, no extra tooling).

For the full user guide (download, install, Gatekeeper / SmartScreen, tray menu, auto-start) see DESKTOP.md; for the contributor / architecture reference (process model, boot lifecycle, port discovery, build pipeline — with Mermaid diagrams) see desktop/README.md.


Data Storage

  • Engine: SQLite 3 via better-sqlite3 (optional) or Node.js built-in node:sqlite
  • Location: data/dashboard.db
  • Journal mode: WAL (concurrent reads during writes)
  • Reset: Delete data/dashboard.db to clear all data

Entity Relationship Diagram

erDiagram
    sessions ||--o{ agents : has
    sessions ||--o{ events : has
    sessions ||--o{ token_usage : tracks
    agents ||--o{ events : generates
    agents ||--o{ agents : spawns

    sessions {
        TEXT id PK "UUID"
        TEXT name "Human-readable label"
        TEXT status "active|completed|error|abandoned"
        TEXT cwd "Working directory"
        TEXT model "Claude model ID"
        TEXT started_at "ISO 8601"
        TEXT ended_at "ISO 8601 or NULL"
        TEXT metadata "JSON blob"
        TEXT awaiting_input_since "ISO 8601 or NULL — set when Waiting"
        TEXT awaiting_reason "notification|stop|session_start|interrupted or NULL"
    }

    agents {
        TEXT id PK "UUID or session_id-main"
        TEXT session_id FK
        TEXT name "Main Agent — {session name} or subagent description"
        TEXT type "main|subagent"
        TEXT status "working|waiting|completed|error"
        TEXT current_tool "Active tool or NULL"
        TEXT awaiting_input_since "ISO 8601 or NULL — supplementary wait timestamp"
        TEXT awaiting_reason "notification|stop|session_start|interrupted or NULL"
    }

    events {
        INTEGER id PK "Auto-increment"
        TEXT session_id FK
        TEXT agent_id FK
        TEXT event_type "PreToolUse|PostToolUse|Stop|etc"
        TEXT tool_name "Tool that fired the event"
        TEXT created_at "ISO 8601"
    }

    token_usage {
        TEXT session_id PK "Composite PK with model"
        TEXT model PK "Model identifier"
        INTEGER input_tokens
        INTEGER output_tokens
        INTEGER cache_read_tokens
        INTEGER cache_write_tokens
    }

    model_pricing {
        TEXT model_pattern PK "SQL LIKE pattern"
        TEXT display_name "Human-readable name"
        REAL input_per_mtok "USD per M input tokens"
        REAL output_per_mtok "USD per M output tokens"
        REAL cache_read_per_mtok "USD per M cache reads"
        REAL cache_write_per_mtok "USD per M cache writes"
    }
Loading

Plugin Marketplace

CCAM ships 14 plugins from one shared source tree. Claude Code reads .claude-plugin/marketplace.json; Codex reads .agents/plugins/marketplace.json plus each plugin's .codex-plugin/plugin.json. The bundle contains 66 plugin skills, 18 Claude subagents, 34 Claude commands, 3 CLI helpers, 3 hook configurations, and 2 MCP-enabled plugins.

# Claude Code
claude plugin marketplace add hoangsonww/Claude-Code-Agent-Monitor
claude plugin install ccam-platform@claude-code-agent-monitor-plugins

# Codex
codex plugin marketplace add hoangsonww/Claude-Code-Agent-Monitor
codex plugin add ccam-platform@claude-code-agent-monitor-plugins

# Open Agent Skills / skills.sh-compatible CLI
npx skills add hoangsonww/Claude-Code-Agent-Monitor --list

# Install one skill for Claude Code and Codex in the current project
npx skills add hoangsonww/Claude-Code-Agent-Monitor \
  --skill mcp-server \
  --agent claude-code \
  --agent codex \
  --yes

# Verify, update, and remove the project-scoped skill
npx skills list --json
npx skills update --project --yes
npx skills remove mcp-server --yes

# Add --global to install at user scope, then manage that scope explicitly
npx skills add hoangsonww/Claude-Code-Agent-Monitor \
  --skill mcp-server \
  --agent claude-code \
  --agent codex \
  --global \
  --yes
npx skills list --global --json
npx skills update --global --yes
npx skills remove --global mcp-server --yes

The skills CLI discovers 75 total repository skills, including the 66 plugin skills and repository-maintenance skills. Project installs use .agents/skills/ plus agent-specific links. Global Claude Code skills default to ~/.claude/skills/, or $CLAUDE_CONFIG_DIR/skills/ when set. Global Codex skills default to ~/.codex/skills/, or $CODEX_HOME/skills/ when set. Multi-agent installs may deduplicate files through a shared store and link those destinations. Every plugin skill has canonical name/description frontmatter and agents/openai.yaml metadata. No upstream PR to vercel-labs/skills is needed for installation. The public GitHub repository is the source, and skills.sh visibility follows publication and real install telemetry.

New focused packs complement the existing analytics, productivity, quality, sessions, workflows, config, and dashboard plugins:

  • ccam-runner: monitored Claude Code/Codex launch, follow-up, stop, resume, and run history
  • ccam-integrations: alert rules, webhooks, push notifications, and SSH remote collection
  • ccam-platform: Claude/Codex Config Explorer, provider-aware import, backup restore, hook setup, updates, and MCP operations
  • ccam-reports: stakeholder-ready executive, cost, reliability, and workflow reports

Validate and regenerate the distribution metadata with:

npm run extensions:sync
npm run extensions:validate

Full catalog, installation commands, skills.sh behavior, public-submission boundary, and clean-install checks: docs/PLUGINS.md.


Statusline

A standalone CLI statusline utility for Claude Code that displays model name, user, working directory, git branch, context window usage bar, per-direction token counts, and session cost -- all color-coded with ANSI escape sequences.

nguyens6@host ~/agent-dashboard/client | Sonnet 4.6 | main | ████████░░ 79% | 3↑ 2↓ 156586c | $0.4231
Segment Color Example
Model Cyan Sonnet 4.6
User Green nguyens6
CWD Yellow ~/agent-dashboard
Git branch Magenta main
Context bar Green / Yellow / Red ████████░░ 79%
Tokens Green / Cyan / Dim 3↑ 2↓ 156586c (green in, cyan out, dim c cache)
Cost (USD) Green / Yellow / Red $0.4231 (session total — shown on API and subscription plans)

Cost color thresholds: green under $5, yellow $5–$20, red $20+.

See statusline/README.md for installation instructions.

Statusline Demo


Server Architecture

graph TD
    INDEX["server/index.js<br/>Express app + HTTP server"]
    DB["server/db.js<br/>SQLite + prepared statements"]
    WS["server/websocket.js<br/>WS server + broadcast"]
    HOOKS["routes/hooks.js<br/>Hook event processing"]
    SESSIONS["routes/sessions.js"]
    AGENTS["routes/agents.js"]
    EVENTS["routes/events.js"]
    STATS["routes/stats.js"]
    ANALYTICS["routes/analytics.js"]
    PRICING["routes/pricing.js<br/>Cost calculation"]
    SETTINGS["routes/settings.js<br/>System management"]
    WORKFLOWS["routes/workflows.js<br/>Workflow visualizations"]

    INDEX --> DB & WS
    INDEX --> HOOKS & SESSIONS & AGENTS & EVENTS & STATS & ANALYTICS & PRICING & SETTINGS & WORKFLOWS
    HOOKS --> DB & WS
    SESSIONS --> DB & WS
    AGENTS --> DB & WS
    EVENTS --> DB
    STATS --> DB
    ANALYTICS --> DB
    PRICING --> DB
    SETTINGS --> DB
    WORKFLOWS --> DB

    style INDEX fill:#6366f1,stroke:#818cf8,color:#fff
    style DB fill:#003B57,stroke:#005f8a,color:#fff
    style WS fill:#10b981,stroke:#34d399,color:#fff
Loading

Client Routing

graph LR
    ROOT["/ (index)"] --> DASH["Dashboard<br/>stats + agents + events"]
    K["/kanban"] --> KANBAN["KanbanBoard<br/>agents/sessions toggle"]
    S["/sessions"] --> SESS["Sessions<br/>server-paginated table"]
    D["/sessions/:id"] --> DETAIL["SessionDetail<br/>agents + timeline + cost"]
    A["/activity"] --> ACT["ActivityFeed<br/>streaming event log"]
    AN["/analytics"] --> ANALYTICS["Analytics<br/>tokens + heatmap + trends"]
    WF["/workflows"] --> WORKFLOWS["Workflows<br/>D3 visualizations + drill-in"]
    CC["/cc-config"] --> CCCONFIG["CcConfig<br/>12-tab Claude Code config inspector + editor"]
    RUN["/run"] --> RUNPAGE["Run<br/>spawn / resume / stream Claude subprocess"]
    ST["/settings"] --> SETTINGS["Settings<br/>pricing + notifications + hooks + export"]
    NF["/*"] --> NOTFOUND["NotFound<br/>404 catch-all"]

    ALL["All routes"] --> LAYOUT["Layout wrapper<br/>(Sidebar + Outlet)"]

    style ALL fill:#6366f1,stroke:#818cf8,color:#fff
    style LAYOUT fill:#1a1a28,stroke:#2a2a3d,color:#e4e4ed
Loading

Hook Handler Flow

flowchart TD
    START["Claude Code fires hook"] --> STDIN["Read stdin to EOF"]
    STDIN --> PARSE{"Parse JSON?"}
    PARSE -->|Success| POST["POST to 127.0.0.1:4820<br/>/api/hooks/event"]
    PARSE -->|Failure| WRAP["Wrap raw input as JSON"]
    WRAP --> POST
    POST --> RESP{"Response?"}
    RESP -->|200 OK| EXIT0["exit(0)"]
    RESP -->|Error| EXIT0
    RESP -->|Timeout 3s| DESTROY["Destroy request"] --> EXIT0
    SAFETY["Safety net: setTimeout 5s"] --> EXIT0

    style EXIT0 fill:#10b981,stroke:#34d399,color:#fff
    style START fill:#6366f1,stroke:#818cf8,color:#fff
Loading

Deployment Modes

We support both development and production deployment modes with different process architectures:

graph LR
    subgraph dev["Development — 2 processes"]
        D_CMD["npm run dev"] --> D_SRV["Express :4820<br/>node --watch"]
        D_CMD --> D_VITE["Vite :5173<br/>HMR"]
        D_BROWSER["Browser"] --> D_VITE
        D_VITE -->|"proxy /api + /ws"| D_SRV
    end

    subgraph prod["Production — 1 process"]
        P_BUILD["npm run build"] --> P_DIST["client/dist/"]
        P_START["npm start"] --> P_SRV["Express :4820<br/>serves static + API"]
        P_BROWSER["Browser"] --> P_SRV
    end

    style D_VITE fill:#646CFF,stroke:#818cf8,color:#fff
    style D_SRV fill:#339933,stroke:#5cb85c,color:#fff
    style P_SRV fill:#339933,stroke:#5cb85c,color:#fff
    style P_DIST fill:#646CFF,stroke:#818cf8,color:#fff
Loading

Optional local MCP sidecar (supports stdio, HTTP+SSE, and REPL transports):

graph LR
    subgraph "MCP Transport Options"
        M_STDIO["MCP Server (stdio)<br/>npm run mcp:start"]
        M_HTTP["MCP Server (HTTP)<br/>npm run mcp:start:http<br/>:8819"]
        M_REPL["MCP Server (REPL)<br/>npm run mcp:start:repl"]
    end

    H["MCP Host"] -->|"stdin/stdout"| M_STDIO
    RC["Remote Client"] -->|"POST /mcp · GET /sse"| M_HTTP
    OP["Operator"] -->|"interactive CLI"| M_REPL

    M_STDIO --> D["Dashboard Server<br/>:4820"]
    M_HTTP --> D
    M_REPL --> D

    style M_STDIO fill:#0f766e,stroke:#14b8a6,color:#fff
    style M_HTTP fill:#0f766e,stroke:#14b8a6,color:#fff
    style M_REPL fill:#0f766e,stroke:#14b8a6,color:#fff
Loading

Optional desktop app (macOS & Windows) — a single Electron process that hosts the Express server in-process (no terminal, no child process):

flowchart LR
    subgraph desktop["Desktop App (macOS & Windows) — 1 Electron process"]
        E_MAIN["Electron Main Process<br/>(Node 22 / Electron 35)"]
        E_HOST["server-host.ts<br/>require() server/index.js"]
        E_SRV["Embedded Express :4820<br/>API · SQLite · WebSocket"]
        E_WIN["BrowserWindow<br/>built React client"]
        E_TRAY["Menu-bar (tray) icon<br/>+ native app menu"]
        E_MAIN --> E_HOST
        E_HOST -->|"in-process require()"| E_SRV
        E_MAIN --> E_TRAY
        E_SRV -->|"http + ws on 127.0.0.1"| E_WIN
    end

    E_HOOKS["Claude Code hooks"] -->|"POST /api/hooks/event"| E_SRV

    style E_MAIN fill:#47848F,stroke:#2f5a62,color:#fff
    style E_SRV fill:#339933,stroke:#5cb85c,color:#fff
    style E_WIN fill:#61DAFB,stroke:#3aa9c9,color:#000
Loading

Cloud Deployment

The deployments/ stack targets any conformant Kubernetes service, including EKS, GKE, AKS, OKE, and self-managed clusters. CCAM uses SQLite, so every supported manifest enforces one active dashboard writer per persistent volume with a Recreate rollout. HPA, active-active replicas, blue-green, and canary are intentionally unsupported while SQLite remains the persistence backend.

flowchart LR
  CI["CI: test · validate · scan · attest · sign"] --> HELM["Helm / Kustomize / Terraform"]
  HELM --> APP["CCAM dashboard<br/>exactly 1 replica"]
  APP --> PVC[("Retained ReadWriteOnce PVC")]
  EDGE["Ingress or Gateway API<br/>TLS + WebSocket"] --> APP
  PROM["Prometheus Operator"] -->|"Bearer /api/metrics"| APP
  MCP["Authenticated MCP sidecar"] --> APP
Loading
  • Helm: schema-enforced one writer, digest support, retained PVC, Ingress or Gateway API, External-Secret-compatible token mount, NetworkPolicy, optional MCP and ServiceMonitor.
  • Kustomize: restricted-PSS base plus dev/staging/production overlays and optional MCP, monitoring, Gateway API, and CSI snapshot components.
  • Terraform: deploys the validated Helm chart to an existing Kubernetes cluster. Cloud networking, cluster identity, CSI, TLS, and secret synchronization remain provider-owned.
  • Operations: consistent SQLite online backup with SHA-256, verified restore with scale-to-zero, backup-first deploy/rollback/teardown, and authenticated health checks.
  • CI supply chain: app and MCP images are scanned, published for amd64/arm64 with SBOM and SLSA provenance, and keyless-signed with Cosign.
npm run deploy:validate
REGISTRY="ghcr.io/$(gh repo view --json owner -q .owner.login)"
IMAGE_TAG="$(git rev-parse --short HEAD)"

helm upgrade --install agent-monitor deployments/helm/agent-monitor \
  --namespace agent-monitor-production --create-namespace \
  --values deployments/helm/agent-monitor/values-production.yaml \
  --set image.registry= \
  --set image.repository=${REGISTRY}/claude-code-agent-monitor \
  --set image.tag=${IMAGE_TAG} \
  --atomic --wait --timeout 10m

Note

Full production setup, secrets, Docker/Podman stack, Gateway API, Terraform, backup, restore, and rollback are documented in DEPLOYMENT.md and deployments/README.md.


Project Structure

agent-dashboard/
|-- CLAUDE.md                   # Claude Code project memory and working agreements
|-- AGENTS.md                   # Codex project instructions
|-- package.json                # Root scripts (dashboard + MCP helpers) + server dependencies
|-- .claude/
|   +-- rules/                  # Path-scoped Claude rules
|   +-- skills/                 # Claude reusable project skills
|   +-- agents/                 # Claude custom subagents
|-- .claude-plugin/
|   +-- marketplace.json        # Claude Code marketplace manifest (14 plugins)
|-- .agents/plugins/
|   +-- marketplace.json        # Codex marketplace manifest (same 14 plugins)
|-- plugins/
|   |-- ccam-analytics/         # Analytics: session reports, cost breakdown, usage trends, productivity score
|   |   |-- .claude-plugin/plugin.json
|   |   |-- skills/ (4)         # session-report, cost-breakdown, usage-trends, productivity-score
|   |   |-- agents/             # analytics-advisor (Sonnet model)
|   |   |-- hooks/hooks.json    # Stop + SubagentStop event logging
|   |   +-- bin/ccam-stats      # Terminal dashboard CLI
|   |-- ccam-productivity/      # Productivity: standups, reports, sprints, workflow optimizer
|   |-- ccam-devtools/          # DevTools: debug, diagnostics, export, health checks
|   |   +-- bin/                # ccam-doctor + ccam-export CLIs
|   |-- ccam-insights/          # Insights: patterns, anomalies, optimization, comparison
|   |-- ccam-cost-guard/        # Cost guardrails: budgets, spend forecast, cost alerts, model savings
|   |-- ccam-sessions/          # Session forensics: search, timeline, transcript replay, cwd rollup, cleanup
|   |-- ccam-workflows/         # Workflow orchestration: DAG map, delegation audit, concurrency, fleet runs
|   |-- ccam-quality/           # Reliability & SLOs: error scan, API-error report, hook-failure audit, SLO check
|   |-- ccam-config/            # Config & memory governance: config audit, memory review, skill/MCP/hook inventory
|   +-- ccam-dashboard/         # Dashboard connector: status, quick stats, MCP integration
|       +-- .mcp.json           # MCP server configuration
|-- server/
|   |-- index.js                 # Express app, HTTP server, static serving
|   |-- db.js                    # SQLite schema, migrations, prepared statements
|   |-- websocket.js             # WebSocket server with heartbeat
|   +-- routes/
|       |-- hooks.js             # Hook event processing (transactional)
|       |-- sessions.js          # Session CRUD
|       |-- agents.js            # Agent CRUD
|       |-- events.js            # Event listing
|       |-- stats.js             # Aggregate statistics
|       |-- analytics.js         # Token, tool, and trend analytics
|       |-- workflows.js         # Aggregate workflow data and per-session drill-in
|       |-- pricing.js           # Model pricing CRUD and cost calculation
|       +-- settings.js          # System info, data management, export, cleanup
|   +-- lib/
|       +-- transcript-cache.js  # Stat-based JSONL transcript cache with chunked sync byte-stream reader (4 MiB chunks, line-by-line UTF-8 decode) so files larger than V8's max string length (~512 MiB) parse without aborting Node with "FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal". Extracts tokens, compactions, API errors, turn durations, thinking blocks, and usage extras (service_tier, speed, inference_geo)
|   +-- compat-sqlite.js         # node:sqlite compatibility wrapper (fallback for better-sqlite3)
|-- client/
|   |-- package.json             # Client dependencies
|   |-- index.html               # HTML entry point
|   |-- vite.config.ts           # Vite + proxy config
|   |-- tailwind.config.js       # Custom dark theme
|   |-- tsconfig.json            # Strict TypeScript
|   +-- src/
|       |-- main.tsx             # React entry
|       |-- App.tsx              # Router + WebSocket provider
|       |-- index.css            # Tailwind + custom utilities
|       |-- lib/
|       |   |-- types.ts         # Shared TypeScript interfaces
|       |   |-- api.ts           # Typed fetch client
|       |   |-- format.ts        # Date/time formatting utilities
|       |   +-- eventBus.ts      # Pub/sub for WebSocket distribution
|       |-- hooks/
|       |   |-- useWebSocket.ts      # Auto-reconnecting WebSocket hook
|       |   +-- useNotifications.ts  # Browser notification triggers from WebSocket events
|       |-- components/
|       |   |-- Layout.tsx       # Shell with sidebar + outlet
|       |   |-- Sidebar.tsx      # Navigation + connection indicator
|       |   |-- AgentCard.tsx    # Agent info card with status
|       |   |-- StatCard.tsx     # Metric card
|       |   |-- StatusBadge.tsx  # Color-coded status pills
|       |   |-- EmptyState.tsx   # Placeholder for empty lists
|       |   +-- workflows/       # D3.js workflow visualization components
|       |       |-- OrchestrationDAG.tsx            # Horizontal DAG of agent spawning patterns
|       |       |-- ToolExecutionFlow.tsx           # d3-sankey diagram of tool-to-tool transitions
|       |       |-- AgentCollaborationNetwork.tsx   # Force-directed agent pipeline graph
|       |       |-- SubagentEffectiveness.tsx       # Scorecard grid with SVG success rings
|       |       |-- WorkflowPatterns.tsx            # Auto-detected orchestration sequences
|       |       |-- ModelDelegationFlow.tsx         # Model routing through agent hierarchies
|       |       |-- ErrorPropagationMap.tsx         # Error clustering by hierarchy depth
|       |       |-- ConcurrencyTimeline.tsx         # Swim-lane parallel agent execution
|       |       |-- SessionComplexityScatter.tsx    # D3 bubble chart (duration vs agents vs tokens)
|       |       |-- CompactionImpact.tsx            # Token compression events and recovery
|       |       |-- WorkflowStats.tsx               # Aggregate workflow statistics
|       |       +-- SessionDrillIn.tsx              # Per-session agent tree, tool timeline, events
|       +-- pages/
|           |-- Dashboard.tsx      # Overview page
|           |-- KanbanBoard.tsx    # Agents/Sessions toggle, status columns
|           |-- Sessions.tsx       # Server-paginated sessions table
|           |-- SessionDetail.tsx  # Single session deep dive
|           |-- ActivityFeed.tsx   # Real-time event stream
|           |-- Analytics.tsx      # Token usage, heatmap, trends
|           |-- Workflows.tsx      # D3.js workflow visualizations and session drill-in
|           |-- Settings.tsx       # Model pricing, notifications, hooks, export, cleanup
|           +-- NotFound.tsx       # 404 catch-all page
|-- scripts/
|   |-- hook-handler.js          # Lightweight stdin-to-HTTP forwarder
|   |-- install-hooks.js         # Auto-configures ~/.claude/settings.json
|   |-- import-history.js        # Imports sessions from ~/.claude/ with enhanced JSONL extraction (API errors, turn durations, entrypoint, permission modes, thinking blocks, usage extras, tool errors, subagent JSONL files). Re-import is fully incremental: a per-event-type high-water mark (`MAX(created_at) GROUP BY event_type` per session) is computed up-front and only JSONL entries with `ts > cutoff[type]` are inserted, so long-running sessions whose transcripts grow across multiple days continue to receive Stop / PostToolUse / TurnDuration / ToolError events on every re-run. Also rolls `sessions.ended_at` forward when the JSONL advances past the stored value and refreshes message-count metadata on every pass
|   +-- seed.js                  # Sample data generator
|-- mcp/
|   |-- package.json             # MCP package scripts + dependencies
|   |-- README.md                # MCP setup, host config, tool catalog, safety model
|   |-- src/
|   |   |-- index.ts             # MCP runtime entrypoint (transport router)
|   |   |-- server.ts            # MCP server assembly
|   |   |-- clients/             # Dashboard API client with retry/backoff
|   |   |-- config/              # Environment/CLI config parsing
|   |   |-- core/                # Logger, tool registry, result helpers
|   |   |-- policy/              # Mutation/destructive guards
|   |   |-- tools/               # 16 domain modules registering 97 tools
|   |   |-- transports/          # HTTP+SSE server, REPL, tool collector
|   |   |-- ui/                  # ANSI banner, colors, formatter, tables
|   |   +-- types/               # Shared MCP type definitions
|   +-- build/                   # Built MCP runtime output
|-- desktop/
|   |-- package.json             # Electron + electron-builder dependencies and scripts
|   |-- electron-builder.yml     # DMG packaging config; signing/notarization hooks
|   |-- tsconfig.json            # Strict TypeScript (src/ -> out/)
|   |-- README.md                # Desktop app architecture reference (contributor docs)
|   |-- assets/                  # icon.svg + generated icon.icns + tray PNGs
|   |-- src/
|   |   |-- main.ts              # Electron main process entry — lifecycle, wiring
|   |   |-- server-host.ts       # In-process Express boot, port discovery, adoption, DB close
|   |   |-- window.ts            # BrowserWindow + persisted window geometry
|   |   |-- tray.ts              # Menu-bar (tray) icon + context menu
|   |   |-- menu.ts              # Native application menu (File ▸ Open Dashboard is macOS-only)
|   |   |-- login-item.ts        # macOS Login Items auto-start toggle (SMAppService)
|   |   |-- logger.ts            # File logger -> ~/Library/Logs/Claude Code Monitor/desktop.log
|   |   |-- constants.ts         # App name, ports, timeouts, window size
|   |   +-- preload.ts           # Intentionally empty (zero renderer privilege)
|   |-- scripts/
|   |   |-- install.js           # Preflights native deps for desktop:install; prints setup help + exits non-zero on failure
|   |   |-- preflight.js         # Shared better-sqlite3 binary check + actionable per-OS setup help (incl. no-toolchain alternative)
|   |   |-- prebuild.js          # Ensures root + client are built before tsc; fails fast with setup help if native binary missing
|   |   |-- build-icons.sh       # SVG -> PNG/ICNS via qlmanage/sips/iconutil
|   |   +-- notarize.js          # electron-builder afterSign hook (opt-in notarization)
|   +-- tests/
|       +-- smoke.test.mjs       # Spawn Electron + probe /api/health
|-- deployments/
|   |-- README.md                # Production deployment reference
|   |-- nginx/                   # Rootless Nginx edge and opt-in hook/MCP policies
|   |-- secrets/                 # Ignored Compose token/password files
|   |-- terraform/               # Helm deployment to an existing Kubernetes cluster
|   |-- kubernetes/              # One-writer Kustomize base + environment overlays
|   |   |-- base/                # Restricted PSS, Recreate Deployment, PVC, Service, Ingress
|   |   |-- overlays/            # Dev, staging, production namespaces and resources
|   |   +-- components/          # MCP, ServiceMonitor, Gateway API, VolumeSnapshot
|   |-- helm/agent-monitor/      # Schema-enforced Helm chart and environment values
|   +-- scripts/                 # Validate, deploy, backup, restore, rollback, health, teardown
|-- .codex/
|   |-- config.toml              # Codex runtime configuration
|   |-- README.md                # Codex setup guide for agents and skills
|   |-- rules/                   # Codex execution policy rules
|   |-- agents/                  # Codex custom agent templates
|   +-- skills/                  # Codex project skills
|-- statusline/
|   |-- README.md                # Statusline installation & usage guide
|   |-- statusline.py            # Python script that renders the statusline
|   +-- statusline-command.sh    # Shell wrapper for Claude Code's statusLine config
+-- data/
    +-- dashboard.db             # SQLite database (gitignored)

Troubleshooting

Problem Solution
better-sqlite3 fails to install This is non-fatal — the server falls back to Node.js built-in node:sqlite automatically (Node 22+). On older Node versions, install Python 3 and C++ build tools, then run npm rebuild better-sqlite3
Hooks not firing Run npm run install-hooks and restart Claude Code. Verify hooks exist in ~/.claude/settings.json
Dashboard shows no data Ensure the server is running (npm run dev) before starting a Claude Code session. Check http://localhost:4820/api/health
WebSocket disconnected The client auto-reconnects every 2 seconds. Check that port 4820 is not blocked by a firewall
Stale data after restart The database persists across restarts. Run npm run seed for fresh demo data, or delete data/dashboard.db to reset
MCP tools fail to connect Confirm dashboard API is up on MCP_DASHBOARD_BASE_URL and rebuild/start MCP (npm run mcp:build, npm run mcp:start)

Contributing

Contributions are welcome — see .github/CONTRIBUTING.md for the full guide.

All contributors must sign the Contributor License Agreement. This is enforced automatically on every pull request by the 🖋️ CLA Assistant GitHub Action: the first time you open a PR, a bot asks you to sign by commenting I have read the CLA Document and I hereby sign the CLA. The PR's CLA Assistant status check stays red until you do, and signing once covers all future contributions.


License

MIT. See LICENSE for details.