57dc91585d
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
1655 lines
62 KiB
JavaScript
1655 lines
62 KiB
JavaScript
/**
|
||
* @file ENRICHED OVERRIDE OpenAPI fragments for endpoints that are ALREADY
|
||
* documented in the base spec (server/openapi.js). This fragment does not add
|
||
* any new paths — instead, each entry here re-declares an already-existing path
|
||
* with the *identical* contract (same operationId, tags, parameters,
|
||
* requestBody schemas, and response `$ref`/media-type schemas) but layers on
|
||
* richer `description` text and realistic `example` values for parameters,
|
||
* request bodies, and responses. Because `server/openapi-extra.js` merges
|
||
* fragment `paths` with `Object.assign` (last-writer-wins) and the merge in
|
||
* `createOpenApiSpec()` spreads extras over the base, these override entries
|
||
* supersede the terser base definitions for the same path key.
|
||
*
|
||
* Covered paths (Webhooks / Settings / Import / Updates / Workflows):
|
||
* POST/GET /api/webhooks
|
||
* GET /api/webhooks/providers
|
||
* PATCH/DEL /api/webhooks/{id}
|
||
* POST /api/webhooks/{id}/test
|
||
* GET /api/webhooks/{id}/deliveries
|
||
* GET /api/settings/info
|
||
* POST /api/settings/clear-data ⚠ DESTRUCTIVE
|
||
* POST /api/settings/reimport
|
||
* POST /api/settings/reinstall-hooks
|
||
* POST /api/settings/reset-pricing ⚠ DESTRUCTIVE
|
||
* GET /api/settings/export
|
||
* POST /api/settings/import
|
||
* POST /api/settings/cleanup ⚠ DESTRUCTIVE (with purge_days)
|
||
* GET /api/import/guide
|
||
* POST /api/import/rescan
|
||
* POST /api/import/scan-path
|
||
* POST /api/import/upload (multipart/form-data)
|
||
* GET /api/updates/status
|
||
* POST /api/updates/check
|
||
* GET /api/workflows
|
||
* GET /api/workflows/session/{id}
|
||
*
|
||
* STRICTLY ADDITIVE: `tags` and `schemas` are intentionally empty — this module
|
||
* introduces no new tags and no new component schemas. Every schema reference
|
||
* reuses a base `$ref` (e.g. ErrorResponse, MessageErrorResponse, WorkflowAggregateResponse,
|
||
* SettingsInfoResponse, CleanupRequest/CleanupResponse, ImportGuideResponse,
|
||
* ImportResultResponse, ExportResponse, …) or an inline schema that matches the
|
||
* base byte-for-byte. The Webhooks/Settings/Import/Updates/Workflows tags are
|
||
* already declared in the base literal. Error bodies preserve the base's split:
|
||
* Webhooks / Settings / Import / Updates use `ErrorResponse` ({ error: { code,
|
||
* message } }) where the base had it (and keep the base's content-less 4xx
|
||
* descriptions where it had none); Workflows use the SHORT `MessageErrorResponse`
|
||
* ({ error: { message } }).
|
||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||
*/
|
||
|
||
const tags = [];
|
||
|
||
const schemas = {};
|
||
|
||
// ── Reusable realistic examples ──────────────────────────────────────────────
|
||
|
||
const WEBHOOK_TARGET_EXAMPLE = {
|
||
id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
|
||
name: "Eng on-call (Slack)",
|
||
type: "slack",
|
||
enabled: true,
|
||
url_preview: "https://hooks.slack.com/…BXqZ",
|
||
has_secret: false,
|
||
headers: null,
|
||
config: null,
|
||
rule_ids: ["rule_inactivity_30m"],
|
||
created_at: "2026-06-25T14:03:11.482Z",
|
||
updated_at: "2026-06-25T14:03:11.482Z",
|
||
last_delivery: {
|
||
status: "success",
|
||
status_code: 200,
|
||
attempts: 1,
|
||
error: null,
|
||
created_at: "2026-06-25T18:41:55.117Z",
|
||
},
|
||
};
|
||
|
||
const GENERIC_WEBHOOK_TARGET_EXAMPLE = {
|
||
id: "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
|
||
name: "Internal alert sink",
|
||
type: "generic",
|
||
enabled: true,
|
||
url_preview: "https://alerts.internal.example.com/…hook",
|
||
has_secret: true,
|
||
headers: { "X-Api-Key": "••••", Authorization: "••••" },
|
||
config: null,
|
||
rule_ids: null,
|
||
created_at: "2026-06-20T09:15:00.000Z",
|
||
updated_at: "2026-06-24T22:48:30.901Z",
|
||
last_delivery: {
|
||
status: "failed",
|
||
status_code: 503,
|
||
attempts: 3,
|
||
error: "Service Unavailable",
|
||
created_at: "2026-06-24T22:48:30.901Z",
|
||
},
|
||
};
|
||
|
||
const SETTINGS_INFO_EXAMPLE = {
|
||
db: {
|
||
path: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor/server/data/agent-monitor.db",
|
||
size: 48922624,
|
||
counts: {
|
||
sessions: 412,
|
||
agents: 1893,
|
||
events: 58117,
|
||
model_pricing: 11,
|
||
token_usage: 401,
|
||
},
|
||
pragmas: {
|
||
journal_mode: "wal",
|
||
synchronous: 1,
|
||
auto_vacuum: 0,
|
||
encoding: "UTF-8",
|
||
foreign_keys: 1,
|
||
busy_timeout: 5000,
|
||
},
|
||
load_stats: { m5: 23, m15: 88, h1: 511 },
|
||
},
|
||
hooks: {
|
||
installed: true,
|
||
path: "/Users/son/.claude/settings.json",
|
||
hooks: {
|
||
PreToolUse: true,
|
||
PostToolUse: true,
|
||
Stop: true,
|
||
SubagentStop: true,
|
||
Notification: true,
|
||
SessionStart: true,
|
||
SessionEnd: true,
|
||
},
|
||
},
|
||
server: {
|
||
uptime: 14523.91,
|
||
node_version: "v22.14.0",
|
||
platform: "darwin",
|
||
ws_connections: 2,
|
||
memory: {
|
||
rss: 142802944,
|
||
heapTotal: 71303168,
|
||
heapUsed: 58392104,
|
||
external: 3211884,
|
||
arrayBuffers: 1048576,
|
||
},
|
||
cpu_load: [2.13, 2.45, 2.31],
|
||
arch: "arm64",
|
||
total_mem: 17179869184,
|
||
free_mem: 2147483648,
|
||
cpus: 10,
|
||
},
|
||
transcript_cache: {
|
||
entries: 37,
|
||
paths: [
|
||
"/Users/son/.claude/projects/-Users-son-code-foo/abc123.jsonl",
|
||
"/Users/son/.claude/projects/-Users-son-code-bar/def456.jsonl",
|
||
],
|
||
},
|
||
};
|
||
|
||
const CLEAR_DATA_EXAMPLE = {
|
||
ok: true,
|
||
cleared: { sessions: 412, agents: 1893, events: 58117, model_pricing: 11, token_usage: 401 },
|
||
};
|
||
|
||
const REIMPORT_EXAMPLE = { ok: true, imported: 38, skipped: 374, errors: 0 };
|
||
|
||
const REINSTALL_HOOKS_EXAMPLE = {
|
||
ok: true,
|
||
hooks: {
|
||
installed: true,
|
||
path: "/Users/son/.claude/settings.json",
|
||
hooks: {
|
||
PreToolUse: true,
|
||
PostToolUse: true,
|
||
Stop: true,
|
||
SubagentStop: true,
|
||
Notification: true,
|
||
SessionStart: true,
|
||
SessionEnd: true,
|
||
},
|
||
},
|
||
};
|
||
|
||
const RESET_PRICING_EXAMPLE = {
|
||
ok: true,
|
||
pricing: [
|
||
{
|
||
model_pattern: "claude-opus-4*",
|
||
display_name: "Claude Opus 4",
|
||
input_per_mtok: 15,
|
||
output_per_mtok: 75,
|
||
cache_read_per_mtok: 1.5,
|
||
cache_write_per_mtok: 18.75,
|
||
cache_write_1h_per_mtok: 30,
|
||
fast_input_per_mtok: 0,
|
||
fast_output_per_mtok: 0,
|
||
updated_at: "2026-06-26T00:00:00.000Z",
|
||
},
|
||
{
|
||
model_pattern: "claude-sonnet-4*",
|
||
display_name: "Claude Sonnet 4",
|
||
input_per_mtok: 3,
|
||
output_per_mtok: 15,
|
||
cache_read_per_mtok: 0.3,
|
||
cache_write_per_mtok: 3.75,
|
||
cache_write_1h_per_mtok: 6,
|
||
fast_input_per_mtok: 0,
|
||
fast_output_per_mtok: 0,
|
||
updated_at: "2026-06-26T00:00:00.000Z",
|
||
},
|
||
],
|
||
};
|
||
|
||
const EXPORT_EXAMPLE = {
|
||
format: "ccam-export",
|
||
version: 1,
|
||
exported_at: "2026-06-26T01:12:44.913Z",
|
||
sessions: [
|
||
{
|
||
id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
name: "Refactor auth flow",
|
||
status: "completed",
|
||
cwd: "/Users/son/code/foo",
|
||
model: "claude-opus-4-8",
|
||
started_at: "2026-06-25T13:00:00.000Z",
|
||
ended_at: "2026-06-25T13:42:18.220Z",
|
||
metadata: null,
|
||
updated_at: "2026-06-25T13:42:18.220Z",
|
||
},
|
||
],
|
||
agents: [
|
||
{
|
||
id: "agent_main_5f3c0e2a",
|
||
session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
name: "Main agent",
|
||
type: "main",
|
||
subagent_type: null,
|
||
status: "completed",
|
||
task: null,
|
||
current_tool: null,
|
||
started_at: "2026-06-25T13:00:00.000Z",
|
||
ended_at: "2026-06-25T13:42:18.220Z",
|
||
parent_agent_id: null,
|
||
metadata: null,
|
||
updated_at: "2026-06-25T13:42:18.220Z",
|
||
},
|
||
],
|
||
events: [
|
||
{
|
||
id: 91021,
|
||
session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
agent_id: "agent_main_5f3c0e2a",
|
||
event_type: "PreToolUse",
|
||
tool_name: "Edit",
|
||
summary: "Edit server/auth.js",
|
||
data: null,
|
||
created_at: "2026-06-25T13:05:42.001Z",
|
||
},
|
||
],
|
||
token_usage: [
|
||
{
|
||
session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
model: "claude-opus-4-8",
|
||
input_tokens: 18422,
|
||
output_tokens: 9120,
|
||
cache_read_tokens: 220184,
|
||
cache_write_tokens: 41002,
|
||
},
|
||
],
|
||
model_pricing: RESET_PRICING_EXAMPLE.pricing,
|
||
};
|
||
|
||
const CLEANUP_EXAMPLE = {
|
||
ok: true,
|
||
abandoned: 3,
|
||
purged_sessions: 57,
|
||
purged_events: 14820,
|
||
purged_agents: 241,
|
||
};
|
||
|
||
const IMPORT_GUIDE_EXAMPLE = {
|
||
platform: "darwin",
|
||
default_projects_dir: "/Users/son/.claude/projects",
|
||
default_projects_dir_display: "~/.claude/projects",
|
||
default_projects_dir_exists: true,
|
||
default_projects_dir_stats: { projects: 24, jsonl_files: 312 },
|
||
archive_command: "tar -czf claude-history.tar.gz -C ~/.claude projects",
|
||
supported_extensions: [".jsonl", ".meta.json", ".zip", ".tar", ".tar.gz", ".tgz", ".gz"],
|
||
max_upload_bytes: 1073741824,
|
||
max_upload_files: 2000,
|
||
steps: [
|
||
{
|
||
id: "locate",
|
||
title: "Locate your Claude Code history",
|
||
body: "Claude Code stores every session as a JSONL transcript under ~/.claude/projects. Each subdirectory is named after the working directory where the session started (with slashes replaced by dashes).",
|
||
},
|
||
{
|
||
id: "archive",
|
||
title: "Bundle it for transfer (optional)",
|
||
body: "If you're importing from another machine, archive the whole projects folder first:\n\n tar -czf claude-history.tar.gz -C ~/.claude projects\n\nMove claude-history.tar.gz to this machine however you like (AirDrop, scp, USB, cloud storage).",
|
||
},
|
||
{
|
||
id: "choose",
|
||
title: "Pick an import mode",
|
||
body: "Rescan default: re-read ~/.claude/projects on this machine and import anything new. From folder: point the dashboard at any directory you've extracted history into. Upload: drag-drop JSONL files or an archive directly into the browser.",
|
||
},
|
||
{
|
||
id: "verify",
|
||
title: "Verify tokens and cost",
|
||
body: "Imports are idempotent: re-running is always safe. Token counts are deduplicated per session ID, with compaction baselines preserved so cost never double-counts. After import, open Analytics → Cost to confirm the breakdown.",
|
||
},
|
||
],
|
||
};
|
||
|
||
const IMPORT_RESCAN_EXAMPLE = {
|
||
ok: true,
|
||
source: "default",
|
||
imported: 14,
|
||
skipped: 298,
|
||
backfilled: 2,
|
||
errors: 0,
|
||
sessions_seen: 312,
|
||
files_scanned: 312,
|
||
};
|
||
|
||
const IMPORT_SCAN_PATH_EXAMPLE = {
|
||
ok: true,
|
||
source: "path",
|
||
path: "/Users/son/Downloads/claude-history/projects",
|
||
imported: 9,
|
||
skipped: 41,
|
||
backfilled: 0,
|
||
errors: 0,
|
||
sessions_seen: 50,
|
||
files_scanned: 50,
|
||
};
|
||
|
||
const IMPORT_UPLOAD_EXAMPLE = {
|
||
ok: true,
|
||
source: "upload",
|
||
files_received: 3,
|
||
rejected_files: ["notes.txt"],
|
||
entries_extracted: 128,
|
||
entries_skipped: 2,
|
||
imported: 12,
|
||
skipped: 116,
|
||
backfilled: 0,
|
||
errors: 0,
|
||
sessions_seen: 128,
|
||
files_scanned: 128,
|
||
};
|
||
|
||
const UPDATE_STATUS_AVAILABLE_EXAMPLE = {
|
||
git_repo: true,
|
||
repo_root: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor",
|
||
update_available: true,
|
||
commits_behind: 4,
|
||
remote_ref: "origin/master",
|
||
local_sha: "de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7",
|
||
remote_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
|
||
manual_command: "npm run self-update",
|
||
checked_at: "2026-06-26T01:20:09.553Z",
|
||
};
|
||
|
||
const UPDATE_STATUS_UP_TO_DATE_EXAMPLE = {
|
||
git_repo: true,
|
||
repo_root: "/Users/son/WebstormProjects/Claude-Code-Agent-Monitor",
|
||
update_available: false,
|
||
commits_behind: 0,
|
||
remote_ref: "origin/master",
|
||
local_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
|
||
remote_sha: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
|
||
manual_command: "npm run self-update",
|
||
checked_at: "2026-06-26T01:25:41.002Z",
|
||
};
|
||
|
||
const UPDATE_STATUS_NON_REPO_EXAMPLE = {
|
||
git_repo: false,
|
||
update_available: false,
|
||
message: "Not a git checkout — update detection is unavailable for this install.",
|
||
};
|
||
|
||
const WORKFLOW_AGGREGATE_EXAMPLE = {
|
||
stats: {
|
||
totalSessions: 412,
|
||
totalAgents: 1893,
|
||
totalSubagents: 1481,
|
||
avgSubagents: 3.6,
|
||
successRate: 94.2,
|
||
avgDepth: 1.4,
|
||
avgDurationSec: 1187,
|
||
totalCompactions: 96,
|
||
avgCompactions: 0.2,
|
||
topFlow: { source: "Read", target: "Edit", count: 3120 },
|
||
},
|
||
orchestration: {
|
||
sessionCount: 412,
|
||
mainCount: 412,
|
||
subagentTypes: [
|
||
{ subagent_type: "general-purpose", count: 612, completed: 590, errors: 8 },
|
||
{ subagent_type: "Explore", count: 388, completed: 380, errors: 2 },
|
||
],
|
||
edges: [{ source: "main", target: "general-purpose", weight: 612 }],
|
||
outcomes: [
|
||
{ status: "completed", count: 1402 },
|
||
{ status: "error", count: 79 },
|
||
],
|
||
compactions: { total: 96, sessions: 71 },
|
||
},
|
||
toolFlow: {
|
||
transitions: [{ source: "Read", target: "Edit", value: 3120 }],
|
||
toolCounts: [
|
||
{ tool_name: "Read", count: 9821 },
|
||
{ tool_name: "Edit", count: 5402 },
|
||
{ tool_name: "Bash", count: 4810 },
|
||
],
|
||
},
|
||
effectiveness: [
|
||
{
|
||
subagent_type: "general-purpose",
|
||
total: 612,
|
||
completed: 590,
|
||
errors: 8,
|
||
sessions: 281,
|
||
successRate: 98.7,
|
||
avgDuration: 142,
|
||
trend: [88, 91, 102, 97, 110, 24, 12],
|
||
},
|
||
],
|
||
patterns: {
|
||
patterns: [{ steps: ["Explore", "general-purpose"], count: 142, percentage: 34.5 }],
|
||
soloSessionCount: 120,
|
||
soloPercentage: 29.1,
|
||
},
|
||
modelDelegation: {
|
||
mainModels: [{ model: "claude-opus-4-8", agent_count: 290, session_count: 290 }],
|
||
subagentModels: [{ model: "claude-opus-4-8", agent_count: 980 }],
|
||
tokensByModel: [
|
||
{
|
||
model: "claude-opus-4-8",
|
||
input_tokens: 4820112,
|
||
output_tokens: 1920334,
|
||
cache_read_tokens: 88201442,
|
||
cache_write_tokens: 12044210,
|
||
},
|
||
],
|
||
},
|
||
errorPropagation: {
|
||
byDepth: [
|
||
{ depth: 0, count: 41 },
|
||
{ depth: 1, count: 38 },
|
||
],
|
||
byType: [{ subagent_type: "general-purpose", count: 8 }],
|
||
eventErrors: [{ summary: "Error in Bash: command not found", count: 5 }],
|
||
sessionsWithErrors: 33,
|
||
totalSessions: 412,
|
||
errorRate: 8,
|
||
},
|
||
concurrency: {
|
||
aggregateLanes: [
|
||
{ name: "Main Agent", avgStart: 0, avgEnd: 1, count: 412 },
|
||
{ name: "Explore", avgStart: 0.12, avgEnd: 0.31, count: 388 },
|
||
],
|
||
},
|
||
complexity: [
|
||
{
|
||
id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
name: "Refactor auth flow",
|
||
status: "completed",
|
||
duration: 2538,
|
||
agentCount: 6,
|
||
subagentCount: 5,
|
||
totalTokens: 940212,
|
||
model: "claude-opus-4-8",
|
||
},
|
||
],
|
||
compaction: {
|
||
totalCompactions: 96,
|
||
tokensRecovered: 18402991,
|
||
perSession: [{ session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11", compactions: 3 }],
|
||
sessionsWithCompactions: 71,
|
||
totalSessions: 412,
|
||
},
|
||
cooccurrence: [{ source: "Explore", target: "general-purpose", weight: 142 }],
|
||
};
|
||
|
||
const WORKFLOW_SESSION_EXAMPLE = {
|
||
session: {
|
||
id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
name: "Refactor auth flow",
|
||
status: "completed",
|
||
cwd: "/Users/son/code/foo",
|
||
model: "claude-opus-4-8",
|
||
started_at: "2026-06-25T13:00:00.000Z",
|
||
ended_at: "2026-06-25T13:42:18.220Z",
|
||
metadata: null,
|
||
updated_at: "2026-06-25T13:42:18.220Z",
|
||
},
|
||
tree: [
|
||
{
|
||
id: "agent_main_5f3c0e2a",
|
||
name: "Main agent",
|
||
type: "main",
|
||
subagent_type: null,
|
||
status: "completed",
|
||
task: null,
|
||
started_at: "2026-06-25T13:00:00.000Z",
|
||
ended_at: "2026-06-25T13:42:18.220Z",
|
||
children: [
|
||
{
|
||
id: "agent_sub_a1",
|
||
name: "Explore the auth module",
|
||
type: "subagent",
|
||
subagent_type: "Explore",
|
||
status: "completed",
|
||
task: "Map auth call sites",
|
||
started_at: "2026-06-25T13:02:10.000Z",
|
||
ended_at: "2026-06-25T13:06:55.000Z",
|
||
children: [],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
toolTimeline: [
|
||
{
|
||
id: 91021,
|
||
tool_name: "Read",
|
||
event_type: "PreToolUse",
|
||
agent_id: "agent_main_5f3c0e2a",
|
||
created_at: "2026-06-25T13:01:02.500Z",
|
||
summary: "Read server/auth.js",
|
||
},
|
||
],
|
||
swimLanes: [
|
||
{
|
||
id: "agent_main_5f3c0e2a",
|
||
name: "Main agent",
|
||
type: "main",
|
||
subagent_type: null,
|
||
status: "completed",
|
||
started_at: "2026-06-25T13:00:00.000Z",
|
||
ended_at: "2026-06-25T13:42:18.220Z",
|
||
parent_agent_id: null,
|
||
},
|
||
],
|
||
events: [
|
||
{
|
||
id: 91021,
|
||
session_id: "5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11",
|
||
agent_id: "agent_main_5f3c0e2a",
|
||
event_type: "PreToolUse",
|
||
tool_name: "Read",
|
||
summary: "Read server/auth.js",
|
||
data: null,
|
||
created_at: "2026-06-25T13:01:02.500Z",
|
||
},
|
||
],
|
||
};
|
||
|
||
const WEBHOOK_ID_PARAM = {
|
||
name: "id",
|
||
in: "path",
|
||
required: true,
|
||
schema: { type: "string" },
|
||
example: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
|
||
};
|
||
|
||
// ── Paths (enriched overrides — contract preserved) ──────────────────────────
|
||
|
||
const paths = {
|
||
"/api/webhooks/providers": {
|
||
get: {
|
||
tags: ["Webhooks"],
|
||
summary: "List supported providers + their config fields (for the UI)",
|
||
description:
|
||
"Returns the redacted provider catalog the webhook-target editor renders. For each of the 14 supported providers (Slack, Discord, Teams, Google Chat, Mattermost, Rocket.Chat, Telegram, PagerDuty, Opsgenie, Splunk On-Call, Zapier, Make, n8n, Pipedream) plus the `generic` family, it lists: a human label, the provider family, whether the URL must be https / is user-supplied, and the per-provider config field definitions (key, label, type, required, options, and whether the field is secret). No secret values are ever included — this is purely the *shape* of the form, not stored credentials.",
|
||
operationId: "listWebhookProviders",
|
||
responses: {
|
||
200: {
|
||
description: "Provider catalog: label, family, url requirements, fields",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
example: {
|
||
providers: {
|
||
slack: {
|
||
label: "Slack",
|
||
family: "chat",
|
||
https: true,
|
||
urlRequired: true,
|
||
fields: [],
|
||
},
|
||
telegram: {
|
||
label: "Telegram",
|
||
family: "chat",
|
||
https: true,
|
||
urlRequired: false,
|
||
fields: [
|
||
{
|
||
key: "bot_token",
|
||
label: "Bot token",
|
||
type: "string",
|
||
required: true,
|
||
secret: true,
|
||
},
|
||
{
|
||
key: "chat_id",
|
||
label: "Chat ID",
|
||
type: "string",
|
||
required: true,
|
||
secret: false,
|
||
},
|
||
],
|
||
},
|
||
pagerduty: {
|
||
label: "PagerDuty",
|
||
family: "incident",
|
||
https: true,
|
||
urlRequired: false,
|
||
fields: [
|
||
{
|
||
key: "routing_key",
|
||
label: "Routing key",
|
||
type: "string",
|
||
required: true,
|
||
secret: true,
|
||
},
|
||
{
|
||
key: "severity",
|
||
label: "Severity",
|
||
type: "enum",
|
||
required: false,
|
||
options: ["critical", "error", "warning", "info"],
|
||
secret: false,
|
||
},
|
||
],
|
||
},
|
||
generic: {
|
||
label: "Generic webhook",
|
||
family: "generic",
|
||
https: false,
|
||
urlRequired: true,
|
||
fields: [],
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/webhooks": {
|
||
get: {
|
||
tags: ["Webhooks"],
|
||
summary: "List webhook targets (URLs masked, secrets redacted)",
|
||
description:
|
||
"Lists every configured webhook target. Responses are always redacted: the full URL is never returned — only `url_preview` (protocol + host + a `…` + the last 4 chars), `has_secret` is a boolean (never the secret itself), and any secret-flagged provider config fields (routing keys, API keys, bot tokens) and custom header values are masked to `••••`. Each target also carries its most recent delivery outcome in `last_delivery` (or null if it has never fired).",
|
||
operationId: "listWebhooks",
|
||
responses: {
|
||
200: {
|
||
description: "All configured webhook targets",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
example: { targets: [WEBHOOK_TARGET_EXAMPLE, GENERIC_WEBHOOK_TARGET_EXAMPLE] },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
post: {
|
||
tags: ["Webhooks"],
|
||
summary: "Create a webhook target",
|
||
description:
|
||
"Creates a webhook target that fires when alerts match. `name` and `type` are required. `url` is required for most providers but is derived or defaulted for a few (Telegram and Opsgenie derive it from config; PagerDuty defaults it) — consult GET /api/webhooks/providers for which fields each provider needs. `config` carries provider-specific params (e.g. `{ chat_id }` for Telegram, `{ routing_key, severity }` for PagerDuty, `{ api_key, region }` for Opsgenie). `secret` (HMAC-SHA256 signing) and custom `headers` apply only to the generic family and are silently ignored for other providers. `rule_ids` optionally scopes the target to specific alert rules; omit it to fire for all rules. The response is the created target, REDACTED the same way as the list endpoint (URL masked, secrets shown only as `has_secret`/`••••`).",
|
||
operationId: "createWebhook",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
required: ["name", "type"],
|
||
properties: {
|
||
name: { type: "string" },
|
||
type: {
|
||
type: "string",
|
||
enum: [
|
||
"slack",
|
||
"discord",
|
||
"teams",
|
||
"google_chat",
|
||
"mattermost",
|
||
"rocketchat",
|
||
"telegram",
|
||
"pagerduty",
|
||
"opsgenie",
|
||
"splunk_oncall",
|
||
"zapier",
|
||
"make",
|
||
"n8n",
|
||
"pipedream",
|
||
"generic",
|
||
],
|
||
},
|
||
url: {
|
||
type: "string",
|
||
format: "uri",
|
||
description:
|
||
"Required for most providers; omit for those that derive their URL (Telegram, Opsgenie) or default it (PagerDuty). See GET /api/webhooks/providers.",
|
||
},
|
||
enabled: { type: "boolean", default: true },
|
||
config: {
|
||
type: "object",
|
||
additionalProperties: true,
|
||
description:
|
||
"Provider-specific params, e.g. { chat_id } (Telegram), { routing_key, severity } (PagerDuty), { api_key, region } (Opsgenie).",
|
||
},
|
||
secret: {
|
||
type: "string",
|
||
description: "Generic family only: HMAC-SHA256 signing secret",
|
||
},
|
||
headers: {
|
||
type: "object",
|
||
additionalProperties: { type: "string" },
|
||
description: "Generic family only: extra request headers",
|
||
},
|
||
rule_ids: {
|
||
type: "array",
|
||
items: { type: "string" },
|
||
description: "Optional: scope to specific alert rules (omit for all)",
|
||
},
|
||
},
|
||
},
|
||
examples: {
|
||
slack: {
|
||
summary: "Slack incoming webhook scoped to one rule",
|
||
value: {
|
||
name: "Eng on-call (Slack)",
|
||
type: "slack",
|
||
url: "https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXXXXXXXXXXXXBXqZ",
|
||
enabled: true,
|
||
rule_ids: ["rule_inactivity_30m"],
|
||
},
|
||
},
|
||
pagerduty: {
|
||
summary: "PagerDuty (URL defaulted, routing key in config)",
|
||
value: {
|
||
name: "Sev1 pager",
|
||
type: "pagerduty",
|
||
config: { routing_key: "R0ABCDEF0123456789ABCDEF01", severity: "critical" },
|
||
},
|
||
},
|
||
generic: {
|
||
summary: "Generic webhook with HMAC secret and custom headers",
|
||
value: {
|
||
name: "Internal alert sink",
|
||
type: "generic",
|
||
url: "https://alerts.internal.example.com/cc-monitor/hook",
|
||
secret: "whsec_4f8c2a9e1b6d7f0a3c5e8b1d2f4a6c8e",
|
||
headers: { "X-Api-Key": "k_live_abc123", Authorization: "Bearer t0ken" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
201: {
|
||
description: "Created target (redacted)",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
example: { target: WEBHOOK_TARGET_EXAMPLE },
|
||
},
|
||
},
|
||
},
|
||
400: { description: "Validation error" },
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/webhooks/{id}": {
|
||
patch: {
|
||
tags: ["Webhooks"],
|
||
summary: "Update a webhook target (partial; type is immutable)",
|
||
description:
|
||
"Partially updates a webhook target. Only the keys present in the body are changed; omitted keys are left as-is. The provider `type` is immutable. `config` is MERGED over the existing config and re-validated, so a single field (e.g. `severity`, `region`) can change without re-sending the secret fields. For the generic family, `secret` may be omitted (keep current) or sent as `null` (clear it); `headers` and `rule_ids` replace their stored value when present. The response is the updated target, REDACTED (URL masked, secrets shown only as `has_secret`/`••••`).",
|
||
operationId: "updateWebhook",
|
||
parameters: [WEBHOOK_ID_PARAM],
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string" },
|
||
url: { type: "string", format: "uri", description: "Omit to keep current" },
|
||
enabled: { type: "boolean" },
|
||
config: {
|
||
type: "object",
|
||
additionalProperties: true,
|
||
description: "Provider params; merged over existing (secrets kept if omitted)",
|
||
},
|
||
secret: {
|
||
type: ["string", "null"],
|
||
description: "Generic family only: omit to keep, null to clear",
|
||
},
|
||
headers: { type: "object", additionalProperties: { type: "string" } },
|
||
rule_ids: { type: "array", items: { type: "string" } },
|
||
},
|
||
},
|
||
examples: {
|
||
disable: {
|
||
summary: "Disable a target without touching anything else",
|
||
value: { enabled: false },
|
||
},
|
||
renameAndRescope: {
|
||
summary: "Rename and re-scope to different rules",
|
||
value: {
|
||
name: "Eng on-call (Slack) — muted weekends",
|
||
rule_ids: ["rule_token_threshold_5m"],
|
||
},
|
||
},
|
||
mergeConfig: {
|
||
summary: "Change one PagerDuty config field (routing key kept)",
|
||
value: { config: { severity: "warning" } },
|
||
},
|
||
clearSecret: {
|
||
summary: "Generic family: clear the signing secret",
|
||
value: { secret: null },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: "Updated target (redacted)",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
example: {
|
||
target: { ...WEBHOOK_TARGET_EXAMPLE, name: "Eng on-call (Slack) — muted weekends" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
400: { description: "Validation error" },
|
||
404: { description: "Target not found" },
|
||
},
|
||
},
|
||
delete: {
|
||
tags: ["Webhooks"],
|
||
summary: "Delete a webhook target and its delivery log",
|
||
description:
|
||
"Permanently deletes a webhook target. Its delivery-log history cascades away with it. Returns `{ ok: true }` on success. This only removes the delivery channel — alert *rules* and the fired-alert feed are untouched.",
|
||
operationId: "deleteWebhook",
|
||
parameters: [WEBHOOK_ID_PARAM],
|
||
responses: {
|
||
200: { description: "Deleted" },
|
||
404: { description: "Target not found" },
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/webhooks/{id}/test": {
|
||
post: {
|
||
tags: ["Webhooks"],
|
||
summary: "Send a synthetic test alert to a target",
|
||
description:
|
||
"Sends a synthetic test alert to the target and reports the delivery outcome synchronously. The HTTP status is always 200 when the target exists — the *request itself* succeeded — and the `ok` flag carries the downstream delivery result. `status` is the HTTP status code returned by the provider (or null if the request never completed), `attempts` is how many tries were made (the delivery layer retries transient failures), and `error` is a human-readable failure reason or null on success.",
|
||
operationId: "testWebhook",
|
||
parameters: [WEBHOOK_ID_PARAM],
|
||
responses: {
|
||
200: {
|
||
description: "Delivery result (ok flag carries the downstream outcome)",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
examples: {
|
||
success: {
|
||
summary: "Provider accepted the test payload",
|
||
value: { ok: true, status: 200, attempts: 1, error: null },
|
||
},
|
||
failure: {
|
||
summary: "Provider rejected or was unreachable after retries",
|
||
value: { ok: false, status: 503, attempts: 3, error: "Service Unavailable" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
404: { description: "Target not found" },
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/webhooks/{id}/deliveries": {
|
||
get: {
|
||
tags: ["Webhooks"],
|
||
summary: "Recent delivery log for a target",
|
||
description:
|
||
"Returns the recent delivery log for a target, newest first. Each row records the alert that fired, the resulting HTTP status, the number of attempts, any error text, and a timestamp. `limit` is clamped to 1–200 (default 20) and `offset` to ≥0 (default 0). This is an audit trail of past sends — it does not trigger a new delivery (use POST /{id}/test for that).",
|
||
operationId: "listWebhookDeliveries",
|
||
parameters: [
|
||
WEBHOOK_ID_PARAM,
|
||
{
|
||
name: "limit",
|
||
in: "query",
|
||
schema: { type: "integer", default: 20 },
|
||
description: "Max rows to return (clamped to 1–200).",
|
||
example: 20,
|
||
},
|
||
{
|
||
name: "offset",
|
||
in: "query",
|
||
schema: { type: "integer", default: 0 },
|
||
description: "Pagination offset (clamped to ≥0).",
|
||
example: 0,
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Delivery rows, newest first",
|
||
content: {
|
||
"application/json": {
|
||
schema: { type: "object", additionalProperties: true },
|
||
example: {
|
||
deliveries: [
|
||
{
|
||
id: 5012,
|
||
target_id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
|
||
alert_event_id: 8841,
|
||
status: "success",
|
||
status_code: 200,
|
||
attempts: 1,
|
||
error: null,
|
||
created_at: "2026-06-25T18:41:55.117Z",
|
||
},
|
||
{
|
||
id: 5008,
|
||
target_id: "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
|
||
alert_event_id: 8839,
|
||
status: "failed",
|
||
status_code: 429,
|
||
attempts: 3,
|
||
error: "Too Many Requests",
|
||
created_at: "2026-06-25T18:30:02.004Z",
|
||
},
|
||
],
|
||
limit: 20,
|
||
offset: 0,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
404: { description: "Target not found" },
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/info": {
|
||
get: {
|
||
tags: ["Settings"],
|
||
summary: "Get system/database/hook diagnostics",
|
||
description:
|
||
"Returns a diagnostics snapshot used by the Settings page: `db` (database file path, on-disk size in bytes, per-table row counts, SQLite pragmas, and recent event load over the last 5/15/60 minutes), `hooks` (whether the Claude Code hook-handler is installed in `settings.json`, the settings path, and a per-hook-type installed map), `server` (process uptime, Node version, platform, live WebSocket connection count, memory/CPU/host stats), and `transcript_cache` (number of cached transcript entries and the cached paths). Read-only and cheap to poll.",
|
||
operationId: "getSettingsInfo",
|
||
responses: {
|
||
200: {
|
||
description: "Settings and diagnostics",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/SettingsInfoResponse" },
|
||
example: SETTINGS_INFO_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/clear-data": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Delete all dashboard data",
|
||
description:
|
||
"⚠ DESTRUCTIVE — IRREVERSIBLE. Deletes ALL sessions, agents, events, token_usage rows, the fired-alert feed (alert_events), and the webhook delivery log. There is no confirmation step and no undo — export first via GET /api/settings/export if you need a backup. User CONFIGURATION survives: alert *rules*, webhook *targets*, and model_pricing are preserved (they're settings, not captured data). The response echoes the row counts that existed BEFORE the wipe so the UI can report what was removed.",
|
||
operationId: "clearData",
|
||
responses: {
|
||
200: {
|
||
description: "Data cleared",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ClearDataResponse" },
|
||
example: CLEAR_DATA_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/reimport": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Re-import legacy sessions from ~/.claude",
|
||
description:
|
||
"Re-runs the legacy history importer against the default `~/.claude` projects directory, funneling every transcript through the same parser + importSession pipeline the live server uses. This is IDEMPOTENT and ADDITIVE — already-imported sessions are deduplicated (counted under `skipped`), token counts and compaction baselines are preserved so cost never double-counts, and nothing existing is deleted. The response reports how many sessions were `imported` vs `skipped`, plus an `errors` count for transcripts that failed to parse.",
|
||
operationId: "reimportLegacySessions",
|
||
responses: {
|
||
200: {
|
||
description: "Import completed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ReimportResponse" },
|
||
example: REIMPORT_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Import failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: {
|
||
code: "IMPORT_FAILED",
|
||
message:
|
||
"ENOENT: no such file or directory, scandir '/Users/son/.claude/projects'",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/reinstall-hooks": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Reinstall Claude Code hooks",
|
||
description:
|
||
"Re-runs the hook installer to (re)wire the dashboard's hook-handler into Claude Code's `settings.json` for all seven hook types (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd). Safe to re-run — it overwrites/repairs the dashboard's own hook entries without touching unrelated user hooks. The response returns the post-install hook status so the UI can confirm every hook type is now `installed: true`.",
|
||
operationId: "reinstallHooks",
|
||
responses: {
|
||
200: {
|
||
description: "Hooks reinstall result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ReinstallHooksResponse" },
|
||
example: REINSTALL_HOOKS_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Hook installation failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: {
|
||
code: "HOOK_INSTALL_FAILED",
|
||
message: "EACCES: permission denied, open '/Users/son/.claude/settings.json'",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/reset-pricing": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Reset pricing table to defaults",
|
||
description:
|
||
"⚠ DESTRUCTIVE to pricing customizations. Deletes EVERY row in the model_pricing table and re-seeds it from the dashboard's built-in DEFAULT_PRICING list. Any custom rates or custom model patterns you added are permanently lost — there is no undo. Captured session/token data is untouched (only the pricing rules used to *compute* cost change). The response returns the full freshly-seeded pricing table.",
|
||
operationId: "resetPricing",
|
||
responses: {
|
||
200: {
|
||
description: "Pricing defaults restored",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ResetPricingResponse" },
|
||
example: RESET_PRICING_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/export": {
|
||
get: {
|
||
tags: ["Settings"],
|
||
summary: "Export all dashboard data as JSON",
|
||
description:
|
||
'Exports the entire dataset as a single versioned JSON document — all sessions, agents, events, token_usage rows, workflows, dashboard_runs, alert_rules, and model_pricing — stamped with `format: "ccam-export"`, `version`, and `exported_at`. Served with a `Content-Disposition: attachment` header (filename `agent-monitor-export-YYYY-MM-DD.json`) so browsers download it. Use it to back up before a destructive operation (clear-data / cleanup with purge_days) or to migrate/consolidate data across machines — the bundle is re-importable via POST /api/settings/import. Read-only; nothing is modified.',
|
||
operationId: "exportData",
|
||
responses: {
|
||
200: {
|
||
description: "Export payload (served as attachment)",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ExportResponse" },
|
||
example: EXPORT_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/import": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Restore (import) a previously exported data bundle",
|
||
description:
|
||
'Restores a bundle produced by GET /api/settings/export. Supply it either as `multipart/form-data` with a single `file` field (browser upload) or as a JSON body `{ "path": "<absolute path>" }` (the server reads the file from disk — used by the `ccam import-data` CLI, and it also sidesteps the global 1 MB JSON body cap for large bundles). The restore is idempotent and NON-DESTRUCTIVE: it is session-atomic, so a session already present (matched by its UUID) is skipped whole together with its agents/events/token_usage/workflows, and independent config rows (dashboard_runs, alert_rules, model_pricing) are inserted only when absent. Nothing existing is overwritten — ideal for consolidating several machines into one dashboard. The response reports per-table counts.',
|
||
operationId: "importData",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"multipart/form-data": {
|
||
schema: {
|
||
type: "object",
|
||
properties: {
|
||
file: { type: "string", format: "binary", description: "The export .json file." },
|
||
},
|
||
},
|
||
},
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
required: ["path"],
|
||
properties: {
|
||
path: {
|
||
type: "string",
|
||
description:
|
||
"Absolute path (server-side) to an export .json file. `~` is expanded.",
|
||
},
|
||
},
|
||
},
|
||
example: { path: "/Users/you/ccam-export-2026-06-26.json" },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: "Per-table restore counts",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ImportResponse" },
|
||
example: {
|
||
ok: true,
|
||
source: "agent-monitor-export-2026-06-26.json",
|
||
format: "ccam-export",
|
||
sessions_imported: 42,
|
||
sessions_skipped: 3,
|
||
agents: 96,
|
||
events: 5120,
|
||
token_usage: 42,
|
||
workflows: 4,
|
||
dashboard_runs: 11,
|
||
alert_rules: 2,
|
||
model_pricing: 0,
|
||
errors: 0,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: "Missing file, invalid JSON, or unrecognized export format",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/settings/cleanup": {
|
||
post: {
|
||
tags: ["Settings"],
|
||
summary: "Abandon stale sessions and optionally purge old history",
|
||
description:
|
||
"Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. The response reports counts for each phase.",
|
||
operationId: "cleanupData",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/CleanupRequest" },
|
||
examples: {
|
||
tidyOnly: {
|
||
summary: "Safe: abandon stale sessions only (no deletion)",
|
||
value: { abandon_hours: 12 },
|
||
},
|
||
abandonAndPurge: {
|
||
summary:
|
||
"Abandon stale sessions, then purge history older than 90 days (destructive)",
|
||
value: { abandon_hours: 12, purge_days: 90 },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: "Cleanup result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/CleanupResponse" },
|
||
example: CLEANUP_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/import/guide": {
|
||
get: {
|
||
tags: ["Import"],
|
||
summary: "Import guide with OS-aware defaults and step-by-step instructions",
|
||
description:
|
||
"Returns the OS-aware import guide the Import page renders verbatim: the detected `platform`, the default `~/.claude/projects` directory (raw + display form), whether it exists and how many projects/JSONL files it holds, an OS-specific `archive_command` for bundling history off another machine, the supported file extensions, the upload size/count limits, and four ordered `steps` (locate → archive → choose mode → verify). Read-only; performs no import.",
|
||
operationId: "importGuide",
|
||
responses: {
|
||
200: {
|
||
description: "Guide payload",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ImportGuideResponse" },
|
||
example: IMPORT_GUIDE_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/import/rescan": {
|
||
post: {
|
||
tags: ["Import"],
|
||
summary: "Rescan the default ~/.claude/projects directory",
|
||
description:
|
||
'Re-scans the default `~/.claude/projects` directory and imports anything new through the live ingestion pipeline. IDEMPOTENT and ADDITIVE — re-running is always safe, already-imported sessions are deduplicated (`skipped`), and token/compaction baselines are preserved so cost never double-counts. Progress is broadcast over the WebSocket as `import.progress` frames while it runs. The response reports `imported` / `skipped` / `backfilled` / `errors` plus `sessions_seen` and `files_scanned`, with `source: "default"`.',
|
||
operationId: "importRescan",
|
||
responses: {
|
||
200: {
|
||
description: "Import result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
||
example: IMPORT_RESCAN_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Import failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: { code: "IMPORT_FAILED", message: "Failed to read projects directory" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/import/scan-path": {
|
||
post: {
|
||
tags: ["Import"],
|
||
summary: "Import transcripts from an arbitrary absolute directory",
|
||
description:
|
||
'Imports transcripts from an arbitrary directory you point the dashboard at (e.g. history extracted from another machine). The `path` must resolve to an existing directory: a leading `~` is expanded to the home directory, the result must be absolute, and subdirectories are walked recursively for `.jsonl` files. Same idempotent, baseline-preserving pipeline as the default rescan; progress is broadcast as `import.progress`. The response echoes the resolved `path` and the per-run counters with `source: "path"`.',
|
||
operationId: "importScanPath",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
required: ["path"],
|
||
properties: {
|
||
path: {
|
||
type: "string",
|
||
description:
|
||
"Absolute directory path. Tilde (~) is expanded. Walks subdirectories recursively.",
|
||
},
|
||
},
|
||
},
|
||
examples: {
|
||
absolute: {
|
||
summary: "Absolute directory",
|
||
value: { path: "/Users/son/Downloads/claude-history/projects" },
|
||
},
|
||
tilde: {
|
||
summary: "Tilde expanded to home directory",
|
||
value: { path: "~/Downloads/claude-history/projects" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: "Import result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
||
example: IMPORT_SCAN_PATH_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: "Path validation failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
examples: {
|
||
missing: {
|
||
summary: "path omitted",
|
||
value: { error: { code: "INVALID_INPUT", message: "`path` is required" } },
|
||
},
|
||
notAbsolute: {
|
||
summary: "path is not absolute",
|
||
value: {
|
||
error: { code: "INVALID_INPUT", message: "`path` must be an absolute path" },
|
||
},
|
||
},
|
||
notFound: {
|
||
summary: "path does not exist",
|
||
value: {
|
||
error: { code: "PATH_NOT_FOUND", message: "Path does not exist: /tmp/nope" },
|
||
},
|
||
},
|
||
notDir: {
|
||
summary: "path is a file, not a directory",
|
||
value: {
|
||
error: {
|
||
code: "NOT_A_DIRECTORY",
|
||
message: "Path is not a directory: /tmp/foo.jsonl",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Import failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: { code: "IMPORT_FAILED", message: "Unexpected end of JSON input" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/import/upload": {
|
||
post: {
|
||
tags: ["Import"],
|
||
summary: "Upload JSONL files or archives (.zip, .tar, .tar.gz, .tgz, .gz)",
|
||
description:
|
||
'Imports history uploaded directly from the browser as `multipart/form-data` under the `files` field. Accepts raw `.jsonl` / `.meta.json` transcripts and/or archives (`.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`), which are extracted into a temp dir and walked for JSONL content. Unsupported extensions are silently rejected and reported in `rejected_files`. Extraction is bounded to defend against zip bombs — exceeding the limit returns 413. Same idempotent import pipeline; progress is broadcast as `import.progress`. On success the response carries `source: "upload"` plus `files_received`, `rejected_files`, `entries_extracted`, `entries_skipped`, and the standard import counters. (Requires the optional `multer` dependency; a missing install yields a 500.)',
|
||
operationId: "importUpload",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"multipart/form-data": {
|
||
schema: {
|
||
type: "object",
|
||
properties: {
|
||
files: {
|
||
type: "array",
|
||
items: { type: "string", format: "binary" },
|
||
description:
|
||
"Files to import. Supports .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.",
|
||
},
|
||
},
|
||
},
|
||
example: { files: ["claude-history.tar.gz", "session-extra.jsonl"] },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: "Import result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
||
example: IMPORT_UPLOAD_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: "No files or no JSONL content",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
examples: {
|
||
noFiles: {
|
||
summary: "No supported files in the upload",
|
||
value: {
|
||
error: {
|
||
code: "NO_FILES",
|
||
message:
|
||
"No supported files in upload. 1 file(s) rejected (unsupported extension).",
|
||
},
|
||
},
|
||
},
|
||
noJsonl: {
|
||
summary: "Archive extracted but contained no .jsonl",
|
||
value: {
|
||
error: {
|
||
code: "NO_JSONL",
|
||
message:
|
||
"No .jsonl files were found in the uploaded content. Supported inputs: .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
413: {
|
||
description: "Extraction limit exceeded (possible zip bomb)",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: {
|
||
code: "EXTRACTION_LIMIT_EXCEEDED",
|
||
message: "Archive exceeded the extraction size limit (possible zip bomb).",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Upload or import failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
examples: {
|
||
noMulter: {
|
||
summary: "Optional multer dependency not installed",
|
||
value: {
|
||
error: {
|
||
code: "UPLOADER_UNAVAILABLE",
|
||
message:
|
||
"File upload requires `multer`. Run `npm install` to pick up new deps.",
|
||
},
|
||
},
|
||
},
|
||
importFailed: {
|
||
summary: "Import pipeline error",
|
||
value: {
|
||
error: { code: "IMPORT_FAILED", message: "Unexpected end of JSON input" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/updates/status": {
|
||
get: {
|
||
tags: ["Updates"],
|
||
summary: "Check whether the dashboard git checkout is behind origin",
|
||
description:
|
||
"Reports whether the dashboard's own git checkout is behind its upstream remote, so a user can pull and restart manually (the dashboard never self-restarts). The response is a variant object: when it IS a git checkout it includes `git_repo: true`, `repo_root`, `update_available`, `commits_behind`, `remote_ref`, `local_sha`, `remote_sha`, and a copy-pasteable `manual_command`; when it is NOT a git checkout (e.g. an npm/tarball install) it returns `git_repo: false` with a `message` and no diff fields. Read-only — this only inspects git, it does not fetch destructively or modify the working tree.",
|
||
operationId: "getUpdatesStatus",
|
||
responses: {
|
||
200: {
|
||
description: "Update check result",
|
||
content: {
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
additionalProperties: true,
|
||
description:
|
||
"Includes git_repo, update_available, commits_behind, remote_ref, local_sha, remote_sha, manual_command, and optional error/message fields.",
|
||
},
|
||
examples: {
|
||
updateAvailable: {
|
||
summary: "Behind origin — update available",
|
||
value: UPDATE_STATUS_AVAILABLE_EXAMPLE,
|
||
},
|
||
upToDate: {
|
||
summary: "Up to date with origin",
|
||
value: UPDATE_STATUS_UP_TO_DATE_EXAMPLE,
|
||
},
|
||
notARepo: {
|
||
summary: "Not a git checkout — detection unavailable",
|
||
value: UPDATE_STATUS_NON_REPO_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Update status query failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: { code: "UPDATE_STATUS_FAILED", message: "git: command not found" },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/updates/check": {
|
||
post: {
|
||
tags: ["Updates"],
|
||
summary: "Run an update check immediately and broadcast the result",
|
||
description:
|
||
"Runs the same upstream check as GET /api/updates/status immediately and, in addition to returning the result, broadcasts it to every connected client as an `update_status` WebSocket message — so all open dashboard tabs refresh their update banner at once. The response shape is identical to GET /api/updates/status (the same git_repo / update_available / commits_behind / manual_command variant object). Read-only with respect to the working tree.",
|
||
operationId: "triggerUpdatesCheck",
|
||
responses: {
|
||
200: {
|
||
description: "Fresh update status payload (also broadcast over WebSocket)",
|
||
content: {
|
||
"application/json": {
|
||
schema: {
|
||
type: "object",
|
||
additionalProperties: true,
|
||
description:
|
||
"Same shape as GET /api/updates/status. Also sent as an update_status WebSocket message to all connected clients.",
|
||
},
|
||
examples: {
|
||
updateAvailable: {
|
||
summary: "Behind origin — update available",
|
||
value: UPDATE_STATUS_AVAILABLE_EXAMPLE,
|
||
},
|
||
upToDate: {
|
||
summary: "Up to date with origin",
|
||
value: UPDATE_STATUS_UP_TO_DATE_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Update check failed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: {
|
||
code: "UPDATE_CHECK_FAILED",
|
||
message: "git fetch failed: network unreachable",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/workflows": {
|
||
get: {
|
||
tags: ["Workflows"],
|
||
summary: "Get workflow intelligence aggregates",
|
||
description:
|
||
"Returns the full workflow-intelligence aggregate powering the Workflows analytics page — 11 sections in one payload: `stats` (headline counters: sessions, agents, subagents, success rate, avg depth/duration, compactions, top tool flow), `orchestration` (subagent-type breakdown + delegation edges + outcomes), `toolFlow` (tool-to-tool transitions + tool counts), `effectiveness` (per-subagent-type success rate, avg duration, weekly trend), `patterns` (frequent subagent sequences + solo-session share), `modelDelegation` (model usage for main/sub agents + tokens by model), `errorPropagation` (errors by depth/type + error rate), `concurrency` (averaged agent swim-lane start/end), `complexity` (per-session agent/token/duration rows), `compaction` (compaction counts + tokens recovered), and `cooccurrence` (directed subagent-after-subagent pairs). The optional `status` query filter scopes every section to sessions of one status. Errors use the SHORT `{ error: { message } }` shape.",
|
||
operationId: "getWorkflowIntelligence",
|
||
parameters: [{ $ref: "#/components/parameters/WorkflowStatusQuery" }],
|
||
responses: {
|
||
200: {
|
||
description: "Workflow aggregate data",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/WorkflowAggregateResponse" },
|
||
example: WORKFLOW_AGGREGATE_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Internal server error",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
||
example: { error: { message: "no such table: agents" } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/workflows/session/{id}": {
|
||
get: {
|
||
tags: ["Workflows"],
|
||
summary: "Get workflow drill-in for one session",
|
||
description:
|
||
"Returns the workflow drill-in for a single session, used by the session-level Workflow view: `session` (the session row), `tree` (the recursive parent→child agent tree rooted at the main agent), `toolTimeline` (chronological tool events with tool_name/event_type/agent_id/summary), `swimLanes` (a flat per-agent start/end lane list for the Gantt-style view), and `events` (the chronological event stream, capped at the first 500 rows). Returns 404 with the SHORT `{ error: { message } }` shape when the session id is unknown.",
|
||
operationId: "getWorkflowSession",
|
||
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
||
responses: {
|
||
200: {
|
||
description: "Workflow session detail",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/WorkflowSessionResponse" },
|
||
example: WORKFLOW_SESSION_EXAMPLE,
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: "Session not found",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
||
example: { error: { message: "Session not found" } },
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: "Internal server error",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
||
example: { error: { message: "no such table: agents" } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
module.exports = { tags, schemas, paths };
|