Real-time · Local-first · Zero-config

Claude Code Agent Monitor

A professional monitoring platform for Claude Code agent activity. Captures sessions, agents, and tool events via native hooks, persists them in SQLite, and streams updates to a React UI over WebSocket — with no external services required.

Node.js ≥ 18 React 18.3 TypeScript Strict SQLite WAL WebSocket Vite 6 Express D3.js Tailwind CSS React Router Zod Vitest MCP SDK Docker Local MCP Server Claude + Codex Extensions Python 3.6+ Lucide Icons PostCSS esbuild MIT License

System Overview

Claude Code Agent Monitor integrates with Claude Code through its native hook system. When Claude Code performs any action — tool use, session start, subagent orchestration, session end — it fires a hook that calls a small Node.js script bundled with this project. That script forwards the event over HTTP to the dashboard server, which stores it in SQLite and broadcasts it to the browser over WebSocket.

graph LR CC["Claude Code\nSession"]:::cc -->|"hooks fire on\ntool use / stop"| HH["hook-handler.js\n(stdin → HTTP)"]:::mid HH -->|"HTTP POST\n/api/hooks/event"| SRV["Express Server\n+ SQLite"]:::mid SRV -->|"WebSocket\nbroadcast"| UI["React Dashboard\n(browser)"]:::ui classDef cc fill:#6366f1,stroke:#818cf8,color:#fff classDef mid fill:#1a1a2b,stroke:#2e2e48,color:#e2e2f0 classDef ui fill:#10b981,stroke:#34d399,color:#fff

End-to-end data pipeline from Claude Code to the browser

< 50ms
Hook latency
7
Hook types captured
~30 MB
Server memory
1 M+
Events before slowdown
Local-first by design

The server binds to 0.0.0.0 but everything runs on your machine. No data leaves your system. No API keys. No external services.

What's Included

Every feature is driven by real hook events — nothing is hardcoded or simulated in production mode.

Screenshots

GitHub Star History

This chart tracks how interest in Claude Code Agent Monitor has grown over time. The curve keeps climbing as more developers discover the project, share it, and use it in real workflows. Each new star is a small vote of confidence from the community.

Hook Events Captured

Hook Type Trigger Dashboard Action
SessionStart Claude Code session begins Creates session and main agent. Reactivates resumed sessions. Abandons orphaned sessions with no activity for 5+ minutes.
PreToolUse Agent begins using a tool Agent → Working, current_tool set. If tool is Agent, subagent record created.
PostToolUse Tool execution completes current_tool cleared. Agent stays Working (no status change).
Stop Session/turn ended Main agent → Idle if subagents still running, else Completed. Running subagents are preserved. Token usage recorded.
SubagentStop Background agent finished Matched subagent → Completed. Auto-completes session when the last subagent finishes.
Notification Agent sends notification Event logged to activity feed. Compaction-related notifications tagged as Compaction events. Triggers persistent browser notification if enabled.
Compaction /compact detected in JSONL Creates a compaction subagent → Completed. Detected via isCompactSummary entries in the transcript. Token baselines preserve pre-compaction totals. Periodic scanner (every 2 min) catches compactions when no hooks fire.
APIError API error detected in transcript Extracted from JSONL during history import or real-time transcript scanning. Captures quota limits, rate limits, and other API failures.
TurnDuration Per-turn timing recorded Extracted from JSONL turn boundaries. Records the duration of each assistant turn for latency analysis.
SessionEnd Claude Code CLI process exits Marks all agents and the session as Completed.

Quick Start

1

Clone

Clone the repository to your machine

2

Install

Run npm run setup to install all dependencies

3

Start

Run npm run dev — server + client launch automatically

4

Use Claude

Start a new Claude Code session — events appear in real-time

bash
# 1. Clone
git clone https://github.com/hoangsonww/Claude-Code-Agent-Monitor.git
cd Claude-Code-Agent-Monitor

# 2. Install all dependencies (server + client)
npm run setup

# 3. Start in development mode
npm run dev
# → Express server on http://localhost:4820
# → Vite dev server on http://localhost:5173

# 4. (Optional) Build and run the local MCP server
npm run mcp:install
npm run mcp:build
npm run mcp:start              # stdio (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

# 5. Open the dashboard
# http://localhost:5173  (dev)
# http://localhost:4820  (prod after npm run build && npm start)

Alternative: Docker / Podman

A multi-stage Dockerfile and docker-compose.yml are included. You can run the monitor with either Docker or Podman and keep the SQLite database in a named volume.

bash
# Docker Compose
docker compose up -d --build

# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build

# Plain Docker / Podman
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
Hooks auto-install in local mode

When you run the server directly on the host with npm run dev or npm start, it automatically writes Claude Code hook entries to ~/.claude/settings.json. If you run the dashboard in Docker or Podman, install hooks from the host with npm run install-hooks after the container is up, then restart Claude Code.

Optional: Enable MCP and Agent Extensions

This repository also ships a local MCP server under mcp/ and extension scaffolding for both Claude Code and Codex. These are optional for the dashboard UI, but recommended for complete local-agent workflows. The MCP server supports stdio (for host integration), HTTP+SSE (for remote clients), and an interactive REPL (for operator debugging).

bash
# MCP lifecycle
npm run mcp:install
npm run mcp:build
npm run mcp:start              # stdio (default — for MCP hosts)
npm run mcp:start:http         # HTTP + SSE server on port 8819
npm run mcp:start:repl         # interactive CLI with tab completion

Verification

After starting a Claude Code session, you should see:

Page Expected
Sessions Your session listed with status Active
Kanban Board A Main Agent card in the Connected column
Activity Feed Events streaming in as Claude Code uses tools
Dashboard Stats updating in real-time
Start server before Claude Code

Hooks only fire to a running server. If Claude Code was already running when you started the dashboard, restart the Claude Code session.

Configuration

Environment Variables

Variable Default Description
DASHBOARD_PORT 4820 Port the Express server listens on
CLAUDE_DASHBOARD_PORT 4820 Port used by the hook handler to reach the server (for custom port setups)
MCP_DASHBOARD_BASE_URL http://127.0.0.1:4820 Base URL used by the local MCP server when calling dashboard APIs
MCP_TRANSPORT stdio MCP transport mode: stdio, http, repl
MCP_HTTP_PORT 8819 Port for the MCP HTTP+SSE server (only when MCP_TRANSPORT=http)
MCP_HTTP_HOST 127.0.0.1 Bind address for the MCP HTTP server
DASHBOARD_DB_PATH data/dashboard.db Path to the SQLite database file
NODE_ENV development Set to production to serve built client from client/dist/

Hook Configuration

The server writes the following to ~/.claude/settings.json on every startup:

json
{
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SessionStart" }] }
    ],
    "PreToolUse": [
      { "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" PreToolUse" }] }
    ],
    "PostToolUse": [
      { "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" PostToolUse" }] }
    ],
    "Stop":         [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" Stop" }] }],
    "SubagentStop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SubagentStop" }] }],
    "Notification": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" Notification" }] }],
    "SessionEnd": [
      { "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SessionEnd" }] }
    ]
  }
}

Existing hooks are preserved. The installer only adds or updates entries containing hook-handler.js.

Scripts Reference

Script Command Description
setup npm run setup Install all dependencies (server + client)
dev npm run dev Start server + client in development mode with hot reload
dev:server npm run dev:server Start only the Express server with --watch
dev:client npm run dev:client Start only the Vite dev server
build npm run build TypeScript check + Vite production build to client/dist/
start npm start Start Express in production mode serving built client
install-hooks npm run install-hooks Manually write Claude Code hooks to ~/.claude/settings.json
seed npm run seed Insert demo sessions, agents, and events (8 sessions / 23 agents / 106 events)
import-history npm run import-history Import historical Claude Code sessions from ~/.claude with deep JSONL extraction (API errors, turn durations, thinking blocks, subagent data)
clear-data npm run clear-data Delete all data from the database (keeps schema)
test npm test Run all server and client tests
test:server npm run test:server Server integration tests only (Node built-in test runner)
test:client npm run test:client Client unit tests only (Vitest + Testing Library)
mcp:install npm run mcp:install Install dependencies for the local MCP package under mcp/
mcp:typecheck npm run mcp:typecheck Type-check MCP source without emitting build output
mcp:build npm run mcp:build Compile MCP server into mcp/build/
mcp:start npm run mcp:start Start MCP server (stdio transport — for MCP hosts)
mcp:start:http npm run mcp:start:http Start MCP HTTP+SSE server on port 8819 (Streamable HTTP + legacy SSE)
mcp:start:repl npm run mcp:start:repl Start interactive MCP REPL with tab completion and colored output
mcp:dev npm run mcp:dev Run MCP server in dev mode with tsx (stdio)
mcp:dev:http npm run mcp:dev:http Run MCP HTTP server in dev mode with tsx
mcp:dev:repl npm run mcp:dev:repl Run MCP REPL in dev mode with tsx
mcp:docker:build npm run mcp:docker:build Build MCP container image with Docker (agent-dashboard-mcp:local)
mcp:podman:build npm run mcp:podman:build Build MCP container image with Podman (localhost/agent-dashboard-mcp:local)
test:mcp npm run test:mcp Run MCP server unit tests
format npm run format Format all files with Prettier
format:check npm run format:check Check formatting without writing

System Architecture

Core dashboard telemetry is composed of three processes (Claude hook source, dashboard server, browser UI). When the local MCP sidecar is enabled, it integrates with the same dashboard API via stdio, HTTP+SSE, or interactive REPL transport.

graph TB subgraph Claude["Claude Code Process"] CC["Claude Code CLI"] H0["SessionStart Hook"] H1["PreToolUse Hook"] H2["PostToolUse Hook"] H3["Stop Hook"] H4["SubagentStop Hook"] H5["Notification Hook"] H6["SessionEnd Hook"] CC --> H0 CC --> H1 CC --> H2 CC --> H3 CC --> H4 CC --> H5 CC --> H6 end subgraph Hook["Hook Layer"] HH["hook-handler.js"] end subgraph Server["Server Process — port 4820"] direction TB EX["Express App"] HR["/api/hooks"] SR["/api/sessions"] AR["/api/agents"] AN["/api/analytics"] DB[("SQLite WAL")] WSS["WebSocket Server"] EX --> HR EX --> SR EX --> AR EX --> AN HR -->|transaction| DB SR --> DB AR --> DB AN --> DB HR -->|broadcast| WSS end subgraph Client["Browser"] direction TB APP["React App"] WS_C["WS Client"] EB["Event Bus"] PAGES["Dashboard / Kanban /\nSessions / Analytics /\nWorkflows"] APP --> WS_C WS_C --> EB EB --> PAGES end H0 -->|stdin JSON| HH H1 -->|stdin JSON| HH H2 -->|stdin JSON| HH H3 -->|stdin JSON| HH H4 -->|stdin JSON| HH H5 -->|stdin JSON| HH H6 -->|stdin JSON| HH HH -->|"POST /api/hooks/event"| HR WSS -->|push| WS_C PAGES -->|fetch| EX

Full system architecture — Claude Code process → Hook Layer → Server → Browser

Agent State Machine

stateDiagram-v2 [*] --> connected: Agent created connected --> working: PreToolUse working --> working: PreToolUse (different tool) working --> idle: Stop (subagents still running) working --> completed: Stop (no subagents) idle --> idle: Tool events from subagents idle --> completed: Last SubagentStop working --> error: Error occurred completed --> [*] error --> [*]

Agent status transitions driven by hook events

Session State Machine

stateDiagram-v2 [*] --> active: First hook event received active --> active: Stop (subagents still running) active --> completed: Stop (no subagents) / last SubagentStop active --> error: Stop hook (error) active --> abandoned: No activity timeout completed --> [*] error --> [*] abandoned --> [*]

Session status lifecycle

Data Flow

Event Ingestion Pipeline

sequenceDiagram participant CC as Claude Code participant HH as hook-handler.js participant API as POST /api/hooks/event participant TX as SQLite Transaction participant WS as WebSocket.broadcast() participant UI as React Client CC->>HH: stdin JSON payload Note over HH: Reads stdin to EOF
Parses JSON and adds hook_type HH->>API: POST {"hook_type":"PreToolUse","data":{...}} API->>TX: BEGIN TRANSACTION TX->>TX: ensureSession(session_id) Note over TX: Creates session + main agent
on first contact TX->>TX: process by hook_type Note over TX: PreToolUse -> agent working
PostToolUse -> clear current_tool
Stop -> idle if subagents, else completed
SubagentStop -> complete matched subagent TX->>TX: insertEvent(...) TX->>TX: COMMIT API->>WS: broadcast("agent_updated", agent) API->>WS: broadcast("new_event", event) WS->>UI: {"type":"agent_updated","data":{...}} UI->>UI: eventBus.publish -> page re-renders

Complete event ingestion from hook fire to browser re-render

Client Data Loading Pattern

sequenceDiagram participant Page as React Page participant API as api.ts participant Server as Express participant EB as eventBus participant WS as WebSocket Note over Page: Component mounts Page->>API: load() via useEffect API->>Server: GET /api/sessions (or agents, events, stats) Server-->>API: JSON response API-->>Page: setState(data) Note over Page: Subscribes to live updates Page->>EB: eventBus.subscribe(handler) loop Real-time updates WS->>EB: eventBus.publish(msg) EB->>Page: handler(msg) Page->>Page: reload or optimistic update end Note over Page: Component unmounts Page->>EB: unsubscribe()

Initial load + WebSocket subscription lifecycle

Server Architecture

graph TD INDEX["server/index.js
Express app + HTTP server"] DB["server/db.js
SQLite + prepared statements
better-sqlite3 / node:sqlite fallback"] WS["server/websocket.js
WS server + heartbeat"] HOOKS["routes/hooks.js
Hook event processing"] SESSIONS["routes/sessions.js
Session CRUD"] AGENTS["routes/agents.js
Agent CRUD"] EVENTS["routes/events.js
Event listing"] STATS["routes/stats.js
Aggregate queries"] ANALYTICS["routes/analytics.js
Analytics metrics"] PRICING["routes/pricing.js
Pricing CRUD + cost calc"] SETTINGS_R["routes/settings.js
System info + data mgmt"] WORKFLOWS_R["routes/workflows.js
Workflow visualizations"] INDEX --> DB INDEX --> WS INDEX --> HOOKS INDEX --> SESSIONS INDEX --> AGENTS INDEX --> EVENTS INDEX --> STATS INDEX --> ANALYTICS INDEX --> PRICING INDEX --> SETTINGS_R INDEX --> WORKFLOWS_R HOOKS --> DB HOOKS --> WS SESSIONS --> DB SESSIONS --> WS AGENTS --> DB AGENTS --> WS EVENTS --> DB STATS --> DB ANALYTICS --> DB PRICING --> DB SETTINGS_R --> DB WORKFLOWS_R --> DB

Server module dependency graph

Server Modules

Module Responsibility
server/index.js Express app setup, middleware (CORS, JSON 1MB limit), route mounting, static serving in production, HTTP server, auto-hook installation on startup
server/db.js SQLite connection, WAL/FK pragmas, schema migrations (CREATE TABLE IF NOT EXISTS), all prepared statements as a reusable stmts object. Tries better-sqlite3 first, falls back to built-in node:sqlite via compat-sqlite.js
server/compat-sqlite.js Compatibility wrapper giving Node.js built-in node:sqlite (DatabaseSync) the same API as better-sqlite3 — pragma, transaction, prepare. Used as automatic fallback on Node 22+
server/websocket.js WebSocket server on /ws path, 30s ping/pong heartbeat, typed broadcast(type, data) function
routes/hooks.js Core event processing inside SQLite transactions. Auto-creates sessions/agents. Switch-case dispatch by hook type. Extracts token usage from Stop events.
routes/sessions.js CRUD with pagination. GET includes agent count via LEFT JOIN. POST is idempotent on session ID.
routes/agents.js CRUD with status/session_id filtering. PATCH broadcasts agent_updated.
routes/events.js Read-only event listing with session_id filter and pagination.
routes/stats.js Single aggregate query — total/active counts, status distributions, WS connection count.
routes/analytics.js Extended analytics — token totals, tool usage counts, daily event/session trends, agent type distribution, event type breakdown, average events per session.
routes/pricing.js Model pricing CRUD (list, upsert, delete). Per-session and global cost calculation with pattern-based model matching and specificity sorting.
routes/settings.js System info (DB size, row counts, hook status, server uptime). Data export as JSON. Session cleanup (abandon stale active sessions, purge old completed sessions). Clear all data. Reset pricing to defaults. Reinstall hooks.
routes/workflows.js Aggregate workflow visualization data (agent orchestration, tool transitions, collaboration networks, workflow patterns, model delegation, error propagation, concurrency, session complexity, compaction impact). Accepts ?status=active|completed filter. Per-session drill-in with agent tree, tool timeline, and events.

Client Architecture

graph TD APP["App.tsx — Router + WebSocket"]:::root LAYOUT["Layout.tsx — Sidebar + Outlet"] SIDEBAR["Sidebar.tsx — Nav + Connection Status"] DASH["Dashboard.tsx"] KANBAN["KanbanBoard.tsx"] SESS["Sessions.tsx"] DETAIL["SessionDetail.tsx"] ACTIVITY["ActivityFeed.tsx"] ANALYTICS["Analytics.tsx"] WORKFLOWS["Workflows.tsx"] SETTINGS_P["Settings.tsx"] SC["StatCard x4"] AC["AgentCard list"] COL["Column x5\n(idle/connected/working/completed/error)"] AC2["AgentCard list"] APP --> LAYOUT LAYOUT --> SIDEBAR LAYOUT --> DASH LAYOUT --> KANBAN LAYOUT --> SESS LAYOUT --> DETAIL LAYOUT --> ACTIVITY LAYOUT --> ANALYTICS LAYOUT --> WORKFLOWS LAYOUT --> SETTINGS_P DASH --> SC DASH --> AC KANBAN --> COL COL --> AC2 classDef root fill:#6366f1,stroke:#818cf8,color:#fff

React component tree

Client Routes

/ Dashboard — stats, active agents, recent events
/kanban KanbanBoard — agent status columns
/sessions Sessions — searchable, filterable table
/sessions/:id SessionDetail — agents + full event timeline
/activity ActivityFeed — real-time streaming event log
/analytics Analytics — token usage, heatmap (day-of-week aligned), tool charts, donut charts
/workflows Workflows — D3.js visualizations, cross-filtering, status filter, session drill-in
/settings Settings — model pricing, hook status, data export, session cleanup

Key Client Modules

Module Purpose
lib/api.ts Typed fetch wrapper — one method per REST endpoint. All return typed promises.
lib/types.ts TypeScript interfaces: Session, Agent, DashboardEvent, Stats, Analytics, WSMessage, plus all workflow-related types (WorkflowData, SessionDrillIn, etc). Status config maps.
lib/eventBus.ts Set-based pub/sub. subscribe(fn) returns an unsubscribe function for clean useEffect teardown.
lib/format.ts Date/time formatting helpers — relative time, duration, ISO display.
hooks/useWebSocket.ts Auto-reconnecting WebSocket React hook. 2-second reconnect interval. Publishes messages to eventBus.

State Management

The client deliberately avoids Redux / Zustand / Context. Each page owns its data and lifecycle. WebSocket events trigger a reload or append — no complex state merging.

graph TD REST["REST API\n(initial load)"]:::source WSM["WebSocket Messages\n(real-time updates)"]:::source EB["eventBus\n(Set-based pub/sub)"]:::bus US1["useState\nDashboard"] US2["useState\nKanbanBoard"] US3["useState\nSessions"] US4["useState\nSessionDetail"] US5["useState\nActivityFeed"] US6["useState\nAnalytics"] US8["useState\nWorkflows"] US7["useState\nSettings"] REST --> US1 REST --> US2 REST --> US3 REST --> US4 REST --> US5 REST --> US6 REST --> US8 REST --> US7 WSM --> EB EB --> US1 EB --> US2 EB --> US3 EB --> US4 EB --> US5 EB --> US6 EB --> US8 EB --> US7 classDef source fill:#6366f1,stroke:#818cf8,color:#fff classDef bus fill:#f59e0b,stroke:#fbbf24,color:#000

Each page pulls initial data from REST then subscribes to eventBus for live updates

No global store — by design

There is no cross-page shared state. Each page fetches and owns exactly the data it displays. This simplifies debugging and avoids stale-closure hazards that are common with global stores in long-running WebSocket apps.

Database Design

erDiagram sessions ||--o{ agents : has sessions ||--o{ events : has sessions ||--o{ token_usage : tracks agents ||--o{ events : generates agents ||--o{ agents : spawns_subagents sessions { TEXT id PK "UUID" TEXT name "Human-readable label" TEXT status "active|completed|error|abandoned" TEXT cwd "Working directory path" TEXT model "Claude model ID" TEXT started_at "ISO 8601" TEXT ended_at "ISO 8601 or NULL" TEXT metadata "JSON blob" } agents { TEXT id PK "UUID or session_id-main" TEXT session_id FK TEXT name "Display name" TEXT type "main|subagent" TEXT subagent_type "Explore|general-purpose|etc" TEXT status "idle|connected|working|completed|error" TEXT task "Current task description" TEXT current_tool "Active tool or NULL" TEXT started_at "ISO 8601" TEXT ended_at "ISO 8601 or NULL" TEXT parent_agent_id FK "References agents.id" TEXT metadata "JSON blob" } 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 summary "Human-readable summary" TEXT data "Full event JSON" 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 million input tokens" REAL output_per_mtok "USD per million output tokens" REAL cache_read_per_mtok "USD per million cache read tokens" REAL cache_write_per_mtok "USD per million cache write tokens" TEXT updated_at "ISO 8601" }

Entity Relationship Diagram — SQLite schema

Indexes

Index Table Column(s) Purpose
idx_agents_session agents session_id Fast agent lookup by session
idx_agents_status agents status Kanban column queries
idx_events_session events session_id Session detail event list
idx_events_type events event_type Filter events by type
idx_events_created events created_at DESC Activity feed ordering
idx_sessions_status sessions status Status filter on sessions page
idx_sessions_started sessions started_at DESC Default sort order

SQLite Configuration

Pragma Value Rationale
journal_mode WAL Concurrent reads during writes. Far better for read-heavy dashboards.
foreign_keys ON Referential integrity — prevents orphaned agents/events.
busy_timeout 5000 Wait up to 5s for write lock instead of failing immediately under load.

API Reference

All endpoints return JSON. Errors follow { "error": { "code", "message" } }.

Health

GET /api/health Returns { "status": "ok", "timestamp": "..." }

Sessions

GET /api/sessions List sessions — params: status, limit, offset
GET /api/sessions/:id Session detail with agents and events
POST /api/sessions Create session (idempotent on id)
PATCH /api/sessions/:id Update session status / metadata

Agents

GET /api/agents List agents — params: status, session_id, limit, offset
GET /api/agents/:id Single agent detail
POST /api/agents Create agent
PATCH /api/agents/:id Update agent status / task / current_tool

Events, Stats, Analytics

GET /api/events List events newest-first — params: session_id, limit, offset
GET /api/stats Aggregate counts + status distributions + WS connections
GET /api/analytics Token totals, tool usage, daily trends, agent types, event types, averages

Hooks Ingestion

POST /api/hooks/event Receive and process a Claude Code hook event (called by hook-handler.js)

Pricing

GET /api/pricing List all model 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/:sessionId Cost breakdown for a specific session

Settings

GET /api/settings/info System info, DB stats, hook installation status
POST /api/settings/clear-data Delete all sessions, agents, events, token usage
POST /api/settings/reinstall-hooks Reinstall Claude Code hooks
POST /api/settings/reset-pricing Reset pricing rules to defaults
GET /api/settings/export Export all data as JSON download
POST /api/settings/cleanup Abandon stale sessions (by hours), purge old data (by days)

Workflows

GET /api/workflows Aggregate workflow data — orchestration graphs, tool flows, effectiveness, patterns, model delegation, error propagation, concurrency, complexity, compaction impact. Accepts ?status=active|completed query param to filter by workflow status
GET /api/workflows/session/:id Per-session drill-in — agent tree, tool timeline, event details
json — Hook event payload
{
  "hook_type": "PreToolUse",
  "data": {
    "session_id": "abc-123",
    "tool_name":  "Bash",
    "tool_input": { "command": "ls -la" }
  }
}

WebSocket Protocol

Property Value
Path ws://localhost:4820/ws
Protocol Standard WebSocket (RFC 6455)
Heartbeat Server pings every 30s — clients that don't pong are terminated
Reconnect Client retries every 2 seconds on disconnect

Message Envelope

typescript
// All messages use this shape
{
  type:      "session_created" | "session_updated" |
             "agent_created"   | "agent_updated"   | "new_event";
  data:      Session | Agent | DashboardEvent;
  timestamp: string;  // ISO 8601
}
stateDiagram-v2 [*] --> Connecting: Component mounts Connecting --> Connected: onopen Connected --> Closed: onclose / onerror Closed --> Connecting: setTimeout 2000ms Connected --> [*]: Component unmounts Closed --> [*]: Component unmounts

Client WebSocket auto-reconnect state machine

Hook Integration

Hook Handler Design

scripts/hook-handler.js is a minimal, fail-safe forwarder. It always exits 0 so it can never block Claude Code regardless of server state.

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\n/api/hooks/event"] PARSE -->|Failure| WRAP["Wrap raw input payload"] WRAP --> POST POST --> RESP{Response?} RESP -->|200| EXIT0[exit 0] RESP -->|Error| EXIT0 RESP -->|Timeout 3s| DESTROY[Destroy request] DESTROY --> EXIT0 SAFETY["Safety net: setTimeout 5s"] --> EXIT0

hook-handler.js flow — always exits 0, never blocks Claude Code

Hook Installation Flow

flowchart TD START[Server startup or npm run install-hooks] --> READ{"~/.claude/settings.json\nexists?"} READ -->|Yes| PARSE[Parse JSON] READ -->|No| EMPTY[Start with empty object] PARSE --> CHECK[Ensure hooks section exists] EMPTY --> CHECK CHECK --> LOOP["For each hook type:\nSessionStart, PreToolUse, PostToolUse,\nStop, SubagentStop, Notification, SessionEnd"] LOOP --> EXISTS{"Our hook\nalready installed?"} EXISTS -->|Yes| UPDATE[Update command path] EXISTS -->|No| APPEND[Append to array] UPDATE --> NEXT{More hook types?} APPEND --> NEXT NEXT -->|Yes| LOOP NEXT -->|No| WRITE["Write settings.json\n(preserves all other hooks)"] WRITE --> DONE[Done]

Hook installation is idempotent — safe to run multiple times

MCP & Agent Extensions

In addition to dashboard telemetry, this project includes a production-grade local MCP server and complete extension scaffolding for both Claude Code and Codex. This gives agents a richer local tool surface while keeping all execution local-first. The MCP server supports three transport modes: stdio for host integration, HTTP+SSE for remote clients, and an interactive REPL for operator debugging.

graph LR subgraph Hosts["Agent Hosts"] CC["Claude Code"] CX["Codex"] RC["Remote Client"] OP["Operator"] end subgraph Layers["Instruction + Skill Layers"] CL["CLAUDE.md + .claude/rules + .claude/skills + .claude/agents"] AG["AGENTS.md + .codex/rules + .codex/skills + .codex/agents"] end subgraph Runtime["Local MCP Runtime"] MCP_STDIO["MCP Server (stdio)"] MCP_HTTP["MCP Server (HTTP+SSE :8819)"] MCP_REPL["MCP Server (REPL)"] API["Dashboard API\nhttp://127.0.0.1:4820"] end CC --> CL CX --> AG CC -->|"stdin/stdout"| MCP_STDIO RC -->|"POST /mcp · GET /sse"| MCP_HTTP OP -->|"interactive CLI"| MCP_REPL MCP_STDIO -->|"REST"| API MCP_HTTP -->|"REST"| API MCP_REPL -->|"REST"| API

Local extension architecture: host instructions + skills + multi-transport MCP sidecar

Local MCP Server Runtime

The mcp/ package exposes dashboard-oriented tools for AI agents across three transport modes. Mutation and destructive operations are policy-gated by environment variables and disabled by default. HTTP mode serves both Streamable HTTP (protocol 2025-11-25) and legacy SSE (protocol 2024-11-05). REPL mode provides tab-completed interactive tool invocation with colored output and JSON syntax highlighting.

Component Location Notes
MCP source mcp/src/ TypeScript server, tools, policy guards, transport layer, CLI UI
MCP build output mcp/build/ Compiled JavaScript runtime for all transport modes
MCP docs mcp/README.md Tool catalog, architecture diagrams, host integration examples, REPL guide
Transport layer mcp/src/transports/ HTTP+SSE server, interactive REPL, tool handler collector
CLI UI mcp/src/ui/ ANSI banner, colors, formatter with tables, boxes, JSON highlighting
Runtime commands npm run mcp:start|start:http|start:repl|dev|dev:http|dev:repl Start MCP in stdio, HTTP+SSE, or REPL mode (production or dev)

Agent Extension Layout

Target Files Purpose
Claude Code CLAUDE.md, .claude/rules/ Persistent project instructions + path-scoped coding rules
Claude Code Skills .claude/skills/ Reusable workflows (onboarding, shipping, MCP ops, live debugging)
Claude Code Subagents .claude/agents/ Specialized reviewers for backend, frontend, and MCP code paths
Codex Base Instructions AGENTS.md, .codex/rules/default.rules Project-wide guidance + execution policy defaults
Codex Skills .codex/skills/ Task-specific skills aligned to this repository
Codex Agents .codex/agents/ Reusable custom-agent templates for implementation and review

Root Helper Scripts

Script Role
scripts/hook-handler.js Receives Claude hook payloads over stdin and forwards them to dashboard API
scripts/install-hooks.js Writes/updates hook registration in ~/.claude/settings.json
scripts/import-history.js Imports historical session data into local database with deep JSONL extraction (API errors, turn durations, thinking blocks, usage extras, subagent breakdowns)
scripts/seed.js Loads deterministic demo data for testing and demos
scripts/clear-data.js Removes persisted rows while preserving schema

Plugin Marketplace

The Agent Monitor ships with an official Claude Code plugin marketplace containing five production-ready plugins. These extend Claude Code with skills, agents, hooks, CLI tools, and MCP integration — all grounded in the real data model (token tracking with compaction baselines, cost calculation via pattern-matched pricing rules, workflow intelligence with 11 datasets per session, and session metadata including thinking blocks, turn counts, and inference geography).

Installation

bash
# Add the marketplace
claude plugin marketplace add hoangsonww/Claude-Code-Agent-Monitor

# Install individual plugins
claude plugin install ccam-analytics@hoangsonww-claude-code-agent-monitor
claude plugin install ccam-productivity@hoangsonww-claude-code-agent-monitor
claude plugin install ccam-devtools@hoangsonww-claude-code-agent-monitor
claude plugin install ccam-insights@hoangsonww-claude-code-agent-monitor
claude plugin install ccam-dashboard@hoangsonww-claude-code-agent-monitor

Available Plugins

Plugin Skills Agent CLI Tools Focus
ccam-analytics session-report, cost-breakdown, usage-trends, productivity-score analytics-advisor ccam-stats Token usage (4 types + baselines), cost via pricing engine, daily trends, productivity scoring
ccam-productivity daily-standup, weekly-report, sprint-summary, workflow-optimizer productivity-coach Standup reports, sprint tracking, workflow optimization via 11 workflow intelligence datasets
ccam-devtools session-debug, hook-diagnostics, data-export, health-check issue-triager ccam-doctor, ccam-export Session debugging, hook diagnostics, data export (JSON/CSV), system health
ccam-insights pattern-detect, anomaly-alert, optimization-suggest, session-compare insights-advisor Pattern detection via tool flow transitions, anomaly alerting, optimization, session comparison
ccam-dashboard dashboard-status, quick-stats Dashboard connector with MCP integration and one-line metric summaries

Skill Usage Examples

claude code
# Analytics — session report with per-model token breakdown + cost
/ccam-analytics:session-report latest

# Analytics — cost breakdown with cache efficiency analysis
/ccam-analytics:cost-breakdown this week

# Productivity — daily standup grouped by project
/ccam-productivity:daily-standup today

# Productivity — workflow optimization using workflow intelligence API
/ccam-productivity:workflow-optimizer analyze

# DevTools — debug a session's full event chain
/ccam-devtools:session-debug errors

# Insights — detect tool flow patterns and anti-patterns
/ccam-insights:pattern-detect tools

# Dashboard — one-line metrics summary
/ccam-dashboard:quick-stats

CLI Tools

bash
# Quick terminal dashboard
ccam-stats                  # Sessions, costs (per-model), tokens (with baselines)
ccam-stats --cost           # Cost summary with matched pricing rules
ccam-stats --tokens         # Token usage including compaction baselines
ccam-stats --json           # Raw JSON output

# System diagnostics
ccam-doctor                 # Full diagnostic: API, endpoints, database, hooks, freshness
ccam-doctor --quick         # Basic connectivity check

# Data export
ccam-export sessions --format csv --limit 500
ccam-export all --output backup.json

Plugin Architecture

Each plugin follows the official Claude Code plugin specification. The marketplace manifest at .claude-plugin/marketplace.json catalogs all five plugins. Each plugin directory contains:

text
plugins/ccam-{name}/
├── .claude-plugin/plugin.json    # Plugin manifest (name, version, description)
├── skills/{skill-name}/SKILL.md  # Skill definitions with $ARGUMENTS
├── agents/{agent-name}.md        # Agent definitions (model, tools, instructions)
├── hooks/hooks.json              # Event hooks (fail-safe, non-blocking)
├── bin/{cli-tool}                # CLI scripts (added to PATH)
├── .mcp.json                     # MCP server configuration (dashboard plugin)
└── settings.json                 # Plugin settings (dashboard plugin)

Data Model Reference

All plugins query the Agent Monitor API at http://localhost:4820. Key capabilities they leverage:

Capability Details
Token tracking 4 types (input, output, cache_read, cache_write) + 4 compaction baselines per model per session
Cost calculation (tokens / 1M) × rate_per_mtok for each type; longest pattern match wins
Session metadata thinking_blocks, turn_count, total_turn_duration_ms, usage_extras (service_tier, speed, inference_geo)
Event types PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, Notification, Compaction, APIError, TurnDuration
Workflow intelligence 11 datasets: stats, orchestration (DAG), toolFlow, effectiveness, patterns, modelDelegation, errorPropagation, concurrency, complexity, compaction, cooccurrence
Agent hierarchy Recursive parent/child tree with subagent_type, depth tracking via recursive CTE

📖 Full documentation: docs/plugins.md

Statusline Utility

The statusline/ directory contains a standalone CLI statusline for Claude Code — completely independent of the web dashboard. It renders a color-coded bar at the bottom of the Claude Code terminal showing context window usage, token counts, and git branch.

terminal output
Sonnet 4.6 | nguyens6 | ~/agent-dashboard/client | main | ████████░░ 79% | 3↑ 2↓ 156586c
Segment Source Color Logic
Model data.model.display_name Always cyan
User $USERNAME / $USER Always green
Working Dir data.workspace.current_dir Always yellow, ~ prefix for home
Git Branch git symbolic-ref --short HEAD Always magenta, hidden outside git repos
Context Bar data.context_window.used_percentage Green < 50%, Yellow 50–79%, Red ≥ 80%
Token Counts data.context_window.current_usage Dim — input, output, c cache reads
sequenceDiagram participant CC as Claude Code participant SH as statusline-command.sh participant PY as statusline.py participant GIT as git CLI CC->>SH: stdin (JSON payload) SH->>PY: Pipe stdin through (PYTHONUTF8=1) PY->>PY: Parse JSON (model, cwd, context_window) PY->>GIT: git symbolic-ref --short HEAD GIT-->>PY: Branch name PY->>PY: Build ANSI-colored segments PY-->>CC: stdout (formatted statusline)

Statusline rendering pipeline — invoked on each Claude Code update

Installation

Add this to ~/.claude/settings.json:

json
{
  "statusLine": {
    "type": "command",
    "command": "bash \"/absolute/path/to/statusline/statusline-command.sh\""
  }
}
No dependencies required

The statusline uses only Python 3.6+ stdlib (sys, json, os, subprocess). It fails silently on empty input or JSON errors and never blocks Claude Code.

Settings Page

The /settings route provides a comprehensive management interface with six sections:

💲

Model Pricing

Editable table of per-model pricing rules. Each Claude model variant has its own explicit pattern (e.g., claude-opus-4-6%). Rates cover input, output, cache read, and cache write tokens. Reset to defaults or add custom models.

🪝

Hook Configuration

Shows per-hook installation status (SessionStart, PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionEnd). One-click reinstall if hooks are missing or outdated. Validates paths and permissions automatically.

🗄️

Data Management

View database row counts and size. Session cleanup: abandon stale active sessions after N hours, purge old completed sessions after N days. Danger zone: clear all data with confirmation dialog to prevent accidental loss.

📤

Data Export

Download all sessions, agents, events, token usage, and pricing rules as a single JSON file for backup or analysis. Includes full event history, model metadata, and cost breakdowns in one portable archive.

🖧

System Info

Server uptime, Node.js version, platform, and active WebSocket connections. At-a-glance health monitoring for the dashboard server itself, with database statistics and memory usage displayed in real-time.

🔔

Notification Preferences

Configure native browser notifications with per-event toggles for session starts, completions, errors, and subagent spawns. Automatic permission management with test-send button and graceful fallback when denied.

Per-model pricing — no catch-all grouping

Each Claude model variant (e.g., Opus 4.6 vs Opus 4.1) has its own explicit pricing pattern because different model versions have different rates. The cost engine uses specificity sorting — longer patterns match before shorter ones.

Deployment Modes

graph LR subgraph dev["Development — 2 processes"] D_CMD["npm run dev\n(concurrently)"] D_SRV["node --watch server/index.js\nport 4820 — auto-restart"] D_VITE["vite dev server\nport 5173 — HMR"] D_BROWSER["Browser"] D_CMD --> D_SRV D_CMD --> D_VITE D_BROWSER --> D_VITE D_VITE -->|"proxy /api + /ws"| D_SRV end subgraph prod["Production — 1 process"] P_BUILD["npm run build\n(tsc + vite build)"] P_DIST["client/dist/\nstatic files"] P_START["npm start"] P_SRV["node server/index.js\nport 4820 — serves dist"] P_BROWSER["Browser"] P_BUILD --> P_DIST P_START --> P_SRV P_BROWSER --> P_SRV end

Development vs production deployment topology

Aspect Development Production
Processes 2 (Express + Vite) 1 (Express only)
Client URL http://localhost:5173 http://localhost:4820
API proxy Vite proxies /api + /ws to :4820 Same origin, no proxy
File watching node --watch + Vite HMR None
Source maps Inline External files

Container Runtime (Docker / Podman)

The production image is OCI-compatible and works with both Docker and Podman. The server listens on 4820, reads legacy Claude history from a read-only mount, and persists SQLite data under /app/data.

graph LR subgraph build["Multi-stage image build"] C1["server-deps\nnode:22-alpine\nnpm ci --omit=dev"] C2["client-build\nvite build"] C3["runtime image\nnode server/index.js"] C1 --> C3 C2 --> C3 end subgraph runtime["Container runtime"] V1["~/.claude\nmounted read-only"] V2["agent-monitor-data\npersistent volume"] PORT["localhost:4820"] C3 --> PORT V1 --> C3 V2 --> C3 end

Container image build and runtime mounts

bash
# Docker Compose
docker compose up -d --build

# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build

# Stop the stack
docker compose down
# or
podman compose down
Mount Purpose
~/.claude:/root/.claude:ro Read historical Claude session files for import without modifying them
agent-monitor-data:/app/data Persist the SQLite database across rebuilds and container restarts
Hooks still run on the host

Claude Code fires hooks from the host machine, not from inside the container. After the container is healthy on http://localhost:4820, run npm run install-hooks on the host so hook events post back to the containerized server.

Docker / Podman

A multi-stage Dockerfile and docker-compose.yml are included. Both Docker and Podman are fully supported — the image is OCI-compliant.

Quick Start

# Docker Compose
docker compose up -d --build

# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build

Plain Docker / Podman (no Compose)

# Build the image
docker build -t agent-monitor .
# — or —
podman build -t agent-monitor .

# Run the container
docker run -d --name agent-monitor \
  -p 4820:4820 \
  -v "$HOME/.claude:/root/.claude:ro" \
  -v agent-monitor-data:/app/data \
  agent-monitor

Volume Mounts

MountPurpose
~/.claude:/root/.claude:ro Read-only access to legacy session history for automatic import on startup
agent-monitor-data:/app/data Persists the SQLite database across container restarts

Multi-Stage Build

The Dockerfile uses three stages to minimize the final image size:

StagePurpose
server-deps Installs production node_modules on node:22-alpine. better-sqlite3 is optional — if prebuilds are unavailable, the server falls back to built-in node:sqlite
client-build Runs npm ci + vite build to produce optimized static assets
runtime Clean node:22-alpine with only node_modules, server code, and client/dist
Hook note

Claude Code hooks run on the host, not inside the container. The containerized server receives hook events via HTTP on localhost:4820. Run npm run install-hooks on the host after starting the container.

Performance Characteristics

<200ms
Server startup
<50ms
Hook-to-broadcast latency
200 KB
JS bundle (63 KB gzipped)
50k/s
SQLite inserts/sec (WAL)
Metric Value Notes
Server startup < 200ms SQLite opens instantly; schema migration is idempotent
Hook latency < 50ms Transaction + broadcast, no async I/O beyond SQLite
Client JS bundle 200 KB / 63 KB gzip CSS: 17 KB / 4 KB gzip
WebSocket latency < 5ms Local loopback, JSON serialization only
SQLite write throughput ~50,000 inserts/sec WAL mode on SSD; far exceeds any hook event rate
Max events before slowdown ~1M rows Pagination prevents full-table scans
Server memory ~30 MB SQLite in-process, no ORM overhead
Client memory ~15 MB React + Tailwind, minimal runtime deps

Security Considerations

Area Approach
SQL injection All queries use prepared statements with parameterized values — no string interpolation
Request size Express JSON body parser limited to 1MB
Input validation Required fields checked before DB operations; CHECK constraints on status enums
Hook safety Hook handler always exits 0; 5s max lifetime; uses 127.0.0.1 not external hosts
CORS Enabled for development; in production same-origin (Express serves the client)
Authentication Intentionally none — local dev tool. Restrict via firewall if exposing on LAN.
Secrets No API keys, tokens, or credentials stored or transmitted anywhere
Dependency surface 5 runtime server deps, 6 runtime client deps (includes D3.js for Workflows) — minimal attack surface

Troubleshooting

No sessions appearing after starting Claude Code

Check 1 — Is the server running?

bash
curl http://localhost:4820/api/health
# Expected: {"status":"ok","timestamp":"..."}

Check 2 — Are hooks installed?

bash
# Open ~/.claude/settings.json and confirm it contains "hook-handler.js"
# If not, re-run:
npm run install-hooks

Check 3 — Start a new Claude Code session

Hooks only apply to sessions started after installation. Restart Claude Code after starting the dashboard.

Check 4 — Is Node.js in PATH?

On some systems the shell environment when Claude Code fires hooks may not include the full PATH. Test with node --version. If not found, use the absolute path to node in the hook command.

Common Issues

Problem Solution
better-sqlite3 errors during install This is non-fatal — better-sqlite3 is an optional dependency. On Node 22+ the server automatically falls back to built-in node:sqlite. On older Node versions, install Python 3 + C++ build tools, then run npm rebuild better-sqlite3.
Dashboard shows "Disconnected" Server is not running. Start it with npm run dev. Client auto-reconnects every 2s.
Events Today shows 0 Ensure you are on the latest version (timezone bug was fixed). Restart the server.
Port 4820 already in use Run DASHBOARD_PORT=4821 npm run dev, update Vite proxy in client/vite.config.ts, and re-run npm run install-hooks.
Stale seed data shown Run npm run clear-data to wipe all rows, then restart.
Hooks show validation error about matcher Ensure you're on the latest version — the hook format was updated to use matcher: "*" string (not object).
"SQLite backend not available" on startup Neither better-sqlite3 nor node:sqlite could load. Upgrade to Node.js 22+ (recommended), or install Python 3 + C++ build tools and run npm rebuild better-sqlite3.
Docker container runs but no sessions appear Hooks run on the host, not inside the container. Run npm run install-hooks on the host after the container starts. Verify hooks in ~/.claude/settings.json point to localhost:4820.

Technology Choices

Technology Why This Over Alternatives
SQLite (better-sqlite3 / node:sqlite) Zero-config, embedded, no server process. WAL mode gives concurrent reads. Synchronous API is simpler than async for this use case. better-sqlite3 is preferred when prebuilds are available; falls back to Node.js built-in node:sqlite on Node 22+ when the native module cannot be compiled.
Express Battle-tested, minimal, well-understood. Fastify would be overkill; raw http module would require too much boilerplate for routing.
ws Fastest, most lightweight WebSocket library for Node. No Socket.IO overhead needed — we only push typed JSON messages one-way.
React 18 Stable, widely known, strong TypeScript support. No Server Components or RSC needed for a client-rendered local SPA.
Vite 6 Fast builds, native ESM, excellent dev experience. Proxy config handles the dev server split cleanly with no ejection.
Tailwind CSS Utility-first approach keeps styles colocated with markup. No CSS module boilerplate. Custom dark theme config for the dark UI.
React Router 6 Standard routing for React SPAs. Layout routes with <Outlet> give clean shell composition without prop drilling.
Lucide React Tree-shakeable icon library — only imports what's used (~20 icons). No heavy icon font.
TypeScript Strict Catches null/undefined bugs at compile time. noUncheckedIndexedAccess prevents array bounds issues in analytics aggregations.
D3.js + d3-sankey Industry-standard data visualization library. Powers the Workflows page's 11 interactive sections — DAG layouts, Sankey diagrams, force-directed graphs, bubble charts, and swim-lane timelines. No wrapper libraries needed; direct SVG rendering keeps bundle impact minimal.
Python (statusline) Available on virtually all systems. Handles ANSI and JSON natively with stdlib only. No install step required.