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

[!TIP]
See also: README-CN.md (中文版本) and README-VN.md (Phiên bản tiếng Việt) for localized documentation with region-specific tips and best practices.
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 via its native hook system.
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
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 workflows. There is also an agent extension layer, which provides Claude Code 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), and Korean (ko). 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"] --> 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"]
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:
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:
| Feature | Description |
|---|
| 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. |
| Sessions |
Quick Start
Prerequisites
- Node.js >= 18.0.0 (22+ 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
This adds hook entries to ~/.claude/settings.json that forward events to the dashboard. Existing hooks are preserved.
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: Build and run the local MCP server
npm run mcp:install
npm run mcp:build
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
For stdio mode, configure your MCP host (Claude Code / Claude Desktop / other MCP clients):
- command:
node
- args:
["<ABSOLUTE_PATH>/mcp/build/index.js"]
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
A Dockerfile and docker-compose.yml are included. Both Docker and Podman are supported.
With Docker Compose:
docker compose up -d --build
With Podman Compose:
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
With plain Docker or Podman (no Compose):
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
The dashboard is then available at http://localhost:4820. The server binds 127.0.0.1 by default, so to make it reachable beyond the container's own loopback set DASHBOARD_HOST=0.0.0.0 and DASHBOARD_TOKEN (see Configuration and .github/SECURITY.md).
Volume mounts:
| Mount | Purpose |
|---|
~/.claude:/root/.claude:ro | Read legacy session history for import |
agent-monitor-data:/app/data | Persist the SQLite database across restarts |
[!IMPORTANT]
Note: Claude Code hooks must still point to a running hook-handler process on the host. The container itself does not receive hooks — run npm run install-hooks on the host to configure hooks that POST to http://localhost:4820. Running the installer inside a container is refused (issue #193) so it can't write a container-internal handler path into a bind-mounted host ~/.claude; override with CCAM_ALLOW_CONTAINER_HOOKS=1 only if you actually run Claude Code inside the same container.
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
[!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
- Claude Code fires a hook on session start, tool use, turn end, subagent completion, and session exit
- Hook Handler (
scripts/hook-handler.js) reads the JSON event from stdin, resolves every live dashboard via ~/.claude/.agent-dashboard.json (or CLAUDE_DASHBOARD_PORT if set), and POSTs the same payload to each one in parallel. 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. Multiple dashboards running together (e.g. npm run dev + the macOS desktop app) all receive every event.
- 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 and the session
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
working --> working: PostToolUse (tool completed)
working --> waiting: Stop, non-error
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
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 (status=active + flag)
waiting --> active: UserPromptSubmit / PreToolUse / PostToolUse
active --> waiting: Stop, non-error (flag re-stamped)
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 --> [*]
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
[!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. ) for a shorter idle timeout |
[!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)
# 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 # total estimated cost with per-model breakdown
# 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
ccam pricing set <pattern> --input N --output N [--cache-read N --cache-write N]
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 cleanup --hours N --days M # abandon stale / purge old sessions
ccam reinstall-hooks # reinstall Claude Code hooks
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. 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, safety model, scripting/exit codes, troubleshooting — in .
npm Scripts
| Command | Description |
|---|
npm run setup | Install server and client dependencies |
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 |
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/
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
Claude Code Layer
- Persistent context:
- Path-scoped rules:
- Skills:
repo-onboarding
ship-feature
mcp-operations
debug-live-issue
- Subagents:
backend-reviewer
frontend-reviewer
mcp-reviewer
Codex Layer
- Persistent context:
- Execution policy:
- Custom subagent templates:
- Skills:
- Setup:
MCP Integration
This project includes a local, production-grade MCP server at mcp/ that exposes dashboard operations as tools for AI agents. It supports three transport modes to suit different integration scenarios.
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
| 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 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
MCP Tool Surface
graph TD
ROOT["MCP Tools"]
OBS["Observability<br/>health, stats, analytics,<br/>system info, export, snapshot"]
SES["Sessions<br/>list/get/create/update"]
AGT["Agents<br/>list/get/create/update"]
EVT["Events & Hooks<br/>list events, ingest hook events"]
PRC["Pricing & Cost<br/>rules CRUD, total/session cost, reset defaults"]
MNT["Maintenance<br/>cleanup, reimport, reinstall hooks, clear-all (guarded)"]
ROOT --> OBS
ROOT --> SES
ROOT --> AGT
ROOT --> EVT
ROOT --> PRC
ROOT --> MNT
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
MCP Operational Modes
- Read-only mode (default):
MCP_DASHBOARD_ALLOW_MUTATIONS=false
- Admin mode:
MCP_DASHBOARD_ALLOW_MUTATIONS=true
- 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 (75 path entries) across these tags — Health, Sessions, Agents, Events, Stats, 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
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 | |
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 |
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 | Aggregate workflow data (orchestration, tools, patterns). Optional ?status=active|completed query param filters all 11 data sections by session status |
GET | /api/workflows/session/:id | Per-session drill-in (agent tree, 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 |
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) |
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 pricing to defaults |
GET | /api/settings/export | Export all data as JSON download |
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. 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 metadata |
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.
| 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 |
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
Bring existing Claude Code sessions into the dashboard from three
different sources, all funneled through the same parser the server uses
for live ingestion so imported tokens, per-model cost, compactions,
subagents, tool use, and turn durations match real-time capture
bit-for-bit. Re-imports are idempotent: sessions are keyed by ID and
compaction baselines preserve pre-compaction token totals, so running
the importer twice never double-counts usage or cost.
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
Routes
| Method | Path | Description |
|---|
GET | /api/import/guide | OS-aware paths, archive command, supported extensions, step instructions |
POST | /api/import/rescan | Rescan the default ~/.claude/projects directory |
POST | /api/import/scan-path | Scan an absolute directory (body { path }); walks recursively |
POST | /api/import/upload | Multipart upload of .jsonl, .meta.json, .zip, .tar(.gz), .gz |
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).
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.
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
stateDiagram-v2
[*] --> Connecting: Component mounts
Connecting --> Connected: onopen
Connected --> Closed: onclose / onerror
Closed --> Connecting: setTimeout(2000ms)
Connected --> [*]: Component unmounts
Closed --> [*]: Component unmounts
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 so a fresh session lands in Waiting. 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 |
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
- Enable notifications in the Settings page via the master toggle
- Grant browser permission when prompted — this registers a Service Worker and creates a push subscription
- 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 |
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.
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
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.
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 line —
N 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, and Korean), 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.
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.
🚀 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
- Open the vscode-extension directory.
- Install the Marketplace extension or package it yourself using
vsce package.
- Ensure your local dashboard server is running (
npm run dev).
- 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.
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:<port>"| 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
On launch the app:
- Picks a free port — preferring 4820, falling back to 4821–4829, then a random high port if all of those are taken.
- 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.
- 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.
- (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.)
- 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.
- 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 ; on Windows it writes a per-user entry, visible in .
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 universal npm run desktop:dmg build is intentionally slow (it builds the app twice and merges with @electron/universal); 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:
-
Double-click the .dmg to mount it.
-
Drag Claude Code Monitor.app into your /Applications folder.
-
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.
-
Launch the app. The tray icon appears and the dashboard window opens.
Windows:
- 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.
- The installer is unsigned by default, so Windows SmartScreen may show "Windows protected your PC" on first launch — click More info → Run anyway.
- Launch from the Start menu / desktop shortcut. The notification-area (tray) icon appears and the dashboard window opens.
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 the universal (x64 + arm64) DMG — correct for release, slow |
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:win | Windows: build the NSIS installer .exe (x64) |
npm run desktop:win:portable |
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 runs. The standalone path is functionally unchanged; , , , and are untouched.
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"
}
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"
}
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"
}
Plugin Marketplace
Extend Claude Code with official Agent Monitor plugins — analytics, productivity tools, developer utilities, AI-powered insights, dashboard connectivity, cost guardrails, session forensics, workflow orchestration, reliability/SLOs, and config governance. 10 plugins, 53 skills, 14 agents, 30 slash commands, 3 CLI tools, 3 hook configs, 1 MCP server.
Add the marketplace
claude plugin marketplace add hoangsonww/Claude-Code-Agent-Monitor
Available plugins
| Plugin | Install command | Skills |
|---|
| ccam-analytics | claude plugin install ccam-analytics@hoangsonww-claude-code-agent-monitor | session-report, cost-breakdown, usage-trends, productivity-score |
| ccam-cost-guard | claude plugin install ccam-cost-guard@hoangsonww-claude-code-agent-monitor | budget-set, spend-forecast, cost-alert, model-savings, daily-budget-check |
| ccam-productivity | claude plugin install ccam-productivity@hoangsonww-claude-code-agent-monitor | daily-standup, weekly-report, sprint-summary, workflow-optimizer |
| ccam-devtools | claude plugin install ccam-devtools@hoangsonww-claude-code-agent-monitor | session-debug, hook-diagnostics, data-export, |
CLI tools included
ccam-stats — Terminal dashboard (sessions, costs, tokens with compaction baselines)
ccam-doctor — System diagnostics (API, database, hooks, data freshness)
ccam-export — Data export (JSON, CSV) for sessions, events, analytics, costs
Example usage
# In Claude Code, after installing a plugin:
/ccam-analytics:session-report latest
/ccam-analytics:cost-breakdown this week
/ccam-productivity:daily-standup today
/ccam-insights:pattern-detect tools
/ccam-dashboard:quick-stats
A server test (server/__tests__/plugins-marketplace.test.js) validates the marketplace ↔ plugins/ directory bijection and every plugin's plugin.json, agents, skills, and commands.
📖 Full documentation: 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.
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
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
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
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
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
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
Cloud Deployment
The deployments/ directory provides cloud-agnostic, enterprise-grade infrastructure for deploying the dashboard to production. Supports Helm, Kustomize, and Terraform across AWS, GCP, Azure, and OCI with blue-green, canary, and rolling release strategies.
graph TB
subgraph "Deployment Methods"
HELM["⎈ Helm Chart<br/>Parameterized installs"]
KUST["📦 Kustomize<br/>Overlay-based patching"]
TF["🏗️ Terraform<br/>Full cloud provisioning"]
end
subgraph "Cloud Providers"
AWS["☁️ AWS<br/>ECS Fargate + ALB"]
GCP["☁️ GCP<br/>Cloud Run + GCLB"]
AZ["☁️ Azure<br/>ACI + App Gateway"]
OCI["☁️ OCI<br/>OKE + LBaaS"]
end
subgraph "Release Strategies"
ROLL["Rolling Update"]
BG["Blue-Green"]
CAN["Canary + Analysis"]
end
subgraph "Observability"
PROM["📊 Prometheus + Grafana"]
CX["📡 Coralogix<br/>Logs · Metrics · Traces · SLOs"]
end
HELM & KUST --> ROLL & BG & CAN
TF --> AWS & GCP & AZ & OCI
ROLL & BG & CAN --> PROM & CX
style HELM fill:#0f1689,color:#fff
style KUST fill:#326ce5,color:#fff
style TF fill:#7b42bc,color:#fff
style AWS fill:#ff9900,color:#fff
style GCP fill:#4285f4,color:#fff
style AZ fill:#0078d4,color:#fff
style OCI fill:#f80000,color:#fff
style PROM fill:#e6522c,color:#fff
style CX fill:#1a1a2e,color:#fff
# Helm (recommended for Kubernetes)
helm install agent-monitor deployments/helm/agent-monitor \
-f deployments/helm/agent-monitor/values-production.yaml \
-n agent-monitor --create-namespace
# Kustomize
kubectl apply -k deployments/kubernetes/overlays/production
# Terraform (full infra + app)
cd deployments/terraform/providers/aws
terraform init && terraform apply -var-file=../../environments/production/terraform.tfvars
# Script orchestrator
./deployments/scripts/deploy.sh --env production --method helm --strategy blue-green
The deployment stack includes CI/CD pipelines (GitHub Actions + GitLab CI), comprehensive monitoring (Prometheus, Grafana, Alertmanager with 13 alert rules, Coralogix full-stack observability with OpenTelemetry Collector for logs, metrics, traces, and SLO tracking), operational scripts (deploy, rollback, blue-green switch, backup/restore, teardown), and a full security posture (Restricted Pod Security Standard, TLS 1.3, network policies, Trivy scanning).
[!NOTE]
📘 Full deployment guide: See DEPLOYMENT.md for step-by-step instructions, architecture diagrams, and operational workflows.
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 # Plugin marketplace manifest (10 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/ # Domain-specific tool modules (6 domains)
| | |-- 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 # Deployment infrastructure reference
| |-- terraform/ # Cloud provisioning (AWS, GCP, Azure, OCI)
| | |-- modules/ # Reusable modules (networking, compute, db, lb, monitoring)
| | |-- providers/ # Cloud-specific implementations
| | +-- environments/ # Per-env tfvars (dev, staging, production)
| |-- kubernetes/ # Kustomize manifests
| | |-- base/ # 11 base resources (deployment, service, ingress, hpa, etc.)
| | |-- overlays/ # Environment overlays (dev, staging, production)
| | |-- components/ # Optional add-ons (mcp-sidecar, monitoring)
| | +-- strategies/ # Blue-green and canary deployment strategies
| |-- helm/agent-monitor/ # Helm chart with 12 templates and 4 value sets
| |-- scripts/ # Operational scripts (deploy, rollback, backup, teardown)
| |-- monitoring/ # Prometheus, Grafana, Alertmanager, Coralogix (OTel Collector)
| +-- ci/ # CI/CD pipelines (GitHub Actions, GitLab CI)
|-- .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.