f78c7f9a2e
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.
2931 lines
101 KiB
JavaScript
2931 lines
101 KiB
JavaScript
/**
|
|
* @file Central OpenAPI 3.0 specification for the dashboard HTTP API.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const pkg = require("../package.json");
|
|
const extraSpec = require("./openapi-extra");
|
|
|
|
function normalizeRepositoryUrl(url) {
|
|
if (!url || typeof url !== "string") return null;
|
|
return url.replace(/^git\+/, "").replace(/\.git$/, "");
|
|
}
|
|
|
|
/**
|
|
* Merge supplementary tags/schemas/paths (from server/openapi-extra) into the
|
|
* base spec. Extra `paths` and `schemas` override base entries with the same
|
|
* key; `tags` are appended only when a tag with that `name` isn't already
|
|
* present. Mutates and returns `spec`.
|
|
*/
|
|
function mergeExtraSpec(spec, extra) {
|
|
if (!extra) return spec;
|
|
if (Array.isArray(extra.tags) && extra.tags.length > 0) {
|
|
const have = new Set((spec.tags || []).map((t) => t && t.name));
|
|
for (const tag of extra.tags) {
|
|
if (tag && !have.has(tag.name)) spec.tags.push(tag);
|
|
}
|
|
}
|
|
if (extra.schemas) {
|
|
spec.components.schemas = { ...spec.components.schemas, ...extra.schemas };
|
|
}
|
|
if (extra.paths) {
|
|
spec.paths = { ...spec.paths, ...extra.paths };
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
function createOpenApiSpec() {
|
|
const repositoryUrl = normalizeRepositoryUrl(pkg.repository?.url);
|
|
const issuesUrl =
|
|
typeof pkg.bugs?.url === "string" && pkg.bugs.url.length > 0
|
|
? pkg.bugs.url
|
|
: repositoryUrl
|
|
? `${repositoryUrl}/issues`
|
|
: null;
|
|
const defaultPort = Number.parseInt(process.env.DASHBOARD_PORT || "4820", 10) || 4820;
|
|
|
|
const spec = {
|
|
openapi: "3.0.3",
|
|
info: {
|
|
title: "Agent Dashboard for Claude Code API",
|
|
version: pkg.version || "1.0.0",
|
|
description:
|
|
"HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.",
|
|
contact: {
|
|
name: "Nguyễn Ngọc Trí Vĩ",
|
|
email: "vinnt@smartgift.vn",
|
|
...(repositoryUrl ? { url: repositoryUrl } : {}),
|
|
},
|
|
// No license block: this is an internal build with no public licence
|
|
// file, and pointing at a LICENSE that does not exist would 404.
|
|
},
|
|
externalDocs: repositoryUrl
|
|
? {
|
|
description: "Project documentation",
|
|
url: `${repositoryUrl}#readme`,
|
|
}
|
|
: undefined,
|
|
servers: [
|
|
{
|
|
url: `http://localhost:${defaultPort}`,
|
|
description: "Local dashboard server (default)",
|
|
},
|
|
{
|
|
url: "http://127.0.0.1:4820",
|
|
description: "Local loopback endpoint used by hook-handler",
|
|
},
|
|
],
|
|
tags: [
|
|
{ name: "Health", description: "Service liveness checks" },
|
|
{ name: "Metrics", description: "Prometheus / OpenMetrics scrape endpoint" },
|
|
{ name: "Sessions", description: "Claude Code session lifecycle" },
|
|
{ name: "Agents", description: "Main/subagent records and status" },
|
|
{ name: "Events", description: "Event stream persistence" },
|
|
{ name: "Stats", description: "High-level dashboard counters" },
|
|
{ name: "Analytics", description: "Aggregated analytics views" },
|
|
{ name: "Hooks", description: "Claude hook ingestion endpoint" },
|
|
{ name: "Pricing", description: "Model pricing and token cost calculations" },
|
|
{ name: "Workflows", description: "Workflow intelligence and session drill-in" },
|
|
{ name: "Settings", description: "Operational maintenance endpoints" },
|
|
{
|
|
name: "Updates",
|
|
description:
|
|
"Detect upstream git changes so users can pull and restart manually (local dashboard installs)",
|
|
},
|
|
{
|
|
name: "Alerts",
|
|
description: "Rules-based alerting: rule CRUD, fired-alert feed, acknowledgement",
|
|
},
|
|
{
|
|
name: "Webhooks",
|
|
description:
|
|
"Universal webhook delivery for fired alerts: target CRUD (Slack/Discord/Teams/generic), test probe, and delivery log. Secrets are never returned.",
|
|
},
|
|
{
|
|
name: "Remote Sources",
|
|
description:
|
|
"Config for remote SSH machines the dashboard pulls Claude Code history from. No secrets are stored — SSH auth defers to the host's SSH stack (agent, ~/.ssh/config, keys).",
|
|
},
|
|
{ name: "Documentation", description: "OpenAPI/Swagger endpoints" },
|
|
],
|
|
components: {
|
|
parameters: {
|
|
SessionIdPath: {
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
description: "Session ID",
|
|
},
|
|
AgentIdPath: {
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
description: "Agent ID",
|
|
},
|
|
PatternPath: {
|
|
name: "pattern",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
description: "Model pattern (URL-encoded)",
|
|
},
|
|
LimitQuery: {
|
|
name: "limit",
|
|
in: "query",
|
|
required: false,
|
|
schema: { type: "integer", minimum: 0 },
|
|
description: "Page size",
|
|
},
|
|
OffsetQuery: {
|
|
name: "offset",
|
|
in: "query",
|
|
required: false,
|
|
schema: { type: "integer", minimum: 0 },
|
|
description: "Pagination offset",
|
|
},
|
|
SessionStatusQuery: {
|
|
name: "status",
|
|
in: "query",
|
|
required: false,
|
|
schema: {
|
|
type: "string",
|
|
enum: ["active", "completed", "error", "abandoned"],
|
|
},
|
|
description: "Filter by session status",
|
|
},
|
|
AgentStatusQuery: {
|
|
name: "status",
|
|
in: "query",
|
|
required: false,
|
|
schema: {
|
|
type: "string",
|
|
enum: ["working", "waiting", "completed", "error"],
|
|
},
|
|
description: "Filter by agent status",
|
|
},
|
|
SessionFilterQuery: {
|
|
name: "session_id",
|
|
in: "query",
|
|
required: false,
|
|
schema: { type: "string" },
|
|
description: "Filter by session ID",
|
|
},
|
|
WorkflowStatusQuery: {
|
|
name: "status",
|
|
in: "query",
|
|
required: false,
|
|
schema: {
|
|
type: "string",
|
|
enum: ["all", "active", "completed", "error", "abandoned"],
|
|
},
|
|
description: "Filter workflow aggregates by session status",
|
|
},
|
|
SourcesQuery: {
|
|
name: "sources",
|
|
in: "query",
|
|
required: false,
|
|
schema: { type: "string" },
|
|
description:
|
|
"Comma-separated list of data-source ids to include. The built-in local history is the id `local`; remote SSH machines use their `remote_sources.id`. Omit to include every source. Filters on the `sessions.source` column.",
|
|
},
|
|
},
|
|
schemas: {
|
|
ErrorObject: {
|
|
type: "object",
|
|
required: ["code", "message"],
|
|
properties: {
|
|
code: { type: "string" },
|
|
message: { type: "string" },
|
|
},
|
|
},
|
|
ErrorResponse: {
|
|
type: "object",
|
|
required: ["error"],
|
|
properties: {
|
|
error: { $ref: "#/components/schemas/ErrorObject" },
|
|
},
|
|
},
|
|
MessageErrorObject: {
|
|
type: "object",
|
|
required: ["message"],
|
|
properties: { message: { type: "string" } },
|
|
},
|
|
MessageErrorResponse: {
|
|
type: "object",
|
|
required: ["error"],
|
|
properties: { error: { $ref: "#/components/schemas/MessageErrorObject" } },
|
|
},
|
|
CountMap: {
|
|
type: "object",
|
|
additionalProperties: { type: "integer" },
|
|
},
|
|
Session: {
|
|
type: "object",
|
|
required: ["id", "status", "started_at", "updated_at"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
name: { type: "string", nullable: true },
|
|
status: {
|
|
type: "string",
|
|
enum: ["active", "completed", "error", "abandoned"],
|
|
},
|
|
cwd: { type: "string", nullable: true },
|
|
model: { type: "string", nullable: true },
|
|
started_at: { type: "string", format: "date-time" },
|
|
ended_at: { type: "string", format: "date-time", nullable: true },
|
|
metadata: {
|
|
type: "string",
|
|
nullable: true,
|
|
description: "JSON-encoded session metadata",
|
|
},
|
|
updated_at: { type: "string", format: "date-time" },
|
|
agent_count: { type: "integer", nullable: true },
|
|
last_activity: { type: "string", format: "date-time", nullable: true },
|
|
cost: { type: "number", nullable: true },
|
|
awaiting_input_since: {
|
|
type: "string",
|
|
format: "date-time",
|
|
nullable: true,
|
|
description:
|
|
"ISO timestamp set when Claude Code is blocked waiting for the user (permission prompt or input request). Null when not waiting; cleared on the next non-Notification hook event.",
|
|
},
|
|
awaiting_reason: {
|
|
type: "string",
|
|
enum: ["notification", "stop", "session_start", "interrupted"],
|
|
nullable: true,
|
|
description:
|
|
"Why the session is awaiting input: 'notification' (Claude asked the user something), 'stop' (turn completed, waiting for the next prompt), 'session_start' (new/resumed session waiting for the first prompt), or 'interrupted' (watchdog/Esc recovery). Null when not waiting; cleared alongside awaiting_input_since.",
|
|
},
|
|
},
|
|
},
|
|
Agent: {
|
|
type: "object",
|
|
required: ["id", "session_id", "name", "type", "status", "started_at", "updated_at"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
session_id: { type: "string" },
|
|
name: { type: "string" },
|
|
type: { type: "string", enum: ["main", "subagent"] },
|
|
subagent_type: { type: "string", nullable: true },
|
|
status: {
|
|
type: "string",
|
|
enum: ["working", "waiting", "completed", "error"],
|
|
},
|
|
task: { type: "string", nullable: true },
|
|
current_tool: { type: "string", nullable: true },
|
|
started_at: { type: "string", format: "date-time" },
|
|
ended_at: { type: "string", format: "date-time", nullable: true },
|
|
parent_agent_id: { type: "string", nullable: true },
|
|
metadata: {
|
|
type: "string",
|
|
nullable: true,
|
|
description: "JSON-encoded agent metadata",
|
|
},
|
|
updated_at: { type: "string", format: "date-time" },
|
|
awaiting_input_since: {
|
|
type: "string",
|
|
format: "date-time",
|
|
nullable: true,
|
|
description:
|
|
"ISO timestamp set when this agent is blocked waiting for user input. Cleared on the next non-Notification hook event for the session.",
|
|
},
|
|
awaiting_reason: {
|
|
type: "string",
|
|
enum: ["notification", "stop", "session_start", "interrupted"],
|
|
nullable: true,
|
|
description:
|
|
"Why this agent is awaiting input: 'notification' (Claude asked the user something), 'stop' (turn completed, waiting for the next prompt), 'session_start' (new/resumed session waiting for the first prompt), or 'interrupted' (watchdog/Esc recovery). Null when not waiting; cleared alongside awaiting_input_since.",
|
|
},
|
|
},
|
|
},
|
|
DashboardEvent: {
|
|
type: "object",
|
|
required: ["session_id", "event_type", "created_at"],
|
|
properties: {
|
|
id: { type: "integer", nullable: true },
|
|
session_id: { type: "string" },
|
|
agent_id: { type: "string", nullable: true },
|
|
event_type: { type: "string" },
|
|
tool_name: { type: "string", nullable: true },
|
|
summary: { type: "string", nullable: true },
|
|
data: {
|
|
type: "string",
|
|
nullable: true,
|
|
description: "JSON-encoded event payload",
|
|
},
|
|
created_at: { type: "string", format: "date-time" },
|
|
},
|
|
},
|
|
HealthResponse: {
|
|
type: "object",
|
|
required: ["status", "timestamp", "version"],
|
|
properties: {
|
|
status: { type: "string", enum: ["ok"] },
|
|
version: {
|
|
type: "string",
|
|
description: "Dashboard release version from package.json",
|
|
example: "1.4.6",
|
|
},
|
|
timestamp: { type: "string", format: "date-time" },
|
|
},
|
|
},
|
|
SessionsListResponse: {
|
|
type: "object",
|
|
required: ["sessions", "limit", "offset", "total"],
|
|
properties: {
|
|
sessions: { type: "array", items: { $ref: "#/components/schemas/Session" } },
|
|
limit: { type: "integer" },
|
|
offset: { type: "integer" },
|
|
total: {
|
|
type: "integer",
|
|
description:
|
|
"Total sessions matching the filters (independent of limit/offset). Used by paginators.",
|
|
},
|
|
},
|
|
},
|
|
SessionCreateRequest: {
|
|
type: "object",
|
|
required: ["id"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
name: { type: "string" },
|
|
cwd: { type: "string" },
|
|
model: { type: "string" },
|
|
metadata: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
},
|
|
},
|
|
},
|
|
SessionCreateResponse: {
|
|
type: "object",
|
|
required: ["session", "created"],
|
|
properties: {
|
|
session: { $ref: "#/components/schemas/Session" },
|
|
created: { type: "boolean" },
|
|
},
|
|
},
|
|
SessionDetailResponse: {
|
|
type: "object",
|
|
required: ["session", "agents", "events"],
|
|
properties: {
|
|
session: { $ref: "#/components/schemas/Session" },
|
|
agents: { type: "array", items: { $ref: "#/components/schemas/Agent" } },
|
|
events: { type: "array", items: { $ref: "#/components/schemas/DashboardEvent" } },
|
|
},
|
|
},
|
|
TranscriptInfo: {
|
|
type: "object",
|
|
required: ["id", "name", "type", "has_transcript"],
|
|
properties: {
|
|
id: {
|
|
type: "string",
|
|
description:
|
|
"'main' for the session's main transcript, otherwise a subagent or compaction id.",
|
|
},
|
|
name: { type: "string" },
|
|
type: { type: "string", enum: ["main", "subagent", "compaction"] },
|
|
subagent_type: { type: "string", nullable: true },
|
|
has_transcript: { type: "boolean" },
|
|
db_agent_id: {
|
|
type: "string",
|
|
nullable: true,
|
|
description:
|
|
"Foreign key into agents.id when this transcript belongs to a tracked subagent.",
|
|
},
|
|
},
|
|
},
|
|
TranscriptListResponse: {
|
|
type: "object",
|
|
required: ["transcripts"],
|
|
properties: {
|
|
transcripts: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/TranscriptInfo" },
|
|
},
|
|
},
|
|
},
|
|
TranscriptContent: {
|
|
type: "object",
|
|
required: ["type"],
|
|
properties: {
|
|
type: {
|
|
type: "string",
|
|
enum: ["text", "tool_use", "tool_result", "thinking"],
|
|
},
|
|
text: { type: "string" },
|
|
name: { type: "string", description: "Tool name when type === tool_use." },
|
|
id: {
|
|
type: "string",
|
|
description: "Tool-use id used to pair tool_use with tool_result.",
|
|
},
|
|
input: {
|
|
description: "Tool input payload (object) or { _truncated: string } when oversized.",
|
|
oneOf: [{ type: "object", additionalProperties: true }, { type: "string" }],
|
|
},
|
|
output: { type: "string", description: "Tool output text when type === tool_result." },
|
|
is_error: { type: "boolean" },
|
|
},
|
|
},
|
|
TranscriptMessage: {
|
|
type: "object",
|
|
required: ["type", "content"],
|
|
properties: {
|
|
type: {
|
|
type: "string",
|
|
enum: ["user", "assistant", "session_event"],
|
|
description:
|
|
"Raw line type. session_event is a synthetic marker (e.g. a /rename) injected by the server, not a real transcript line. Mid-turn queued messages (queued_command attachments) surface as type user.",
|
|
},
|
|
sender: {
|
|
type: "string",
|
|
enum: ["user", "assistant", "orchestrator", "system", "tool"],
|
|
description:
|
|
"TRUE sender of the line: the human (user), the agent (assistant), a subagent's task assigned by its parent (orchestrator), a harness injection such as a task-notification or [SYSTEM NOTIFICATION …] banner (system), or a tool_result echoed on a user line (tool).",
|
|
},
|
|
timestamp: { type: "string", format: "date-time", nullable: true },
|
|
content: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/TranscriptContent" },
|
|
},
|
|
line: {
|
|
type: "integer",
|
|
minimum: 1,
|
|
description: "JSONL line number the message was parsed from (pagination cursor).",
|
|
},
|
|
model: { type: "string" },
|
|
usage: {
|
|
type: "object",
|
|
properties: {
|
|
input_tokens: { type: "integer", minimum: 0 },
|
|
output_tokens: { type: "integer", minimum: 0 },
|
|
cache_read_input_tokens: { type: "integer", minimum: 0 },
|
|
cache_creation_input_tokens: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
event_kind: {
|
|
type: "string",
|
|
enum: ["rename"],
|
|
description: "For type === session_event: the TUI action this marker represents.",
|
|
},
|
|
title: {
|
|
type: "string",
|
|
description: "For type === session_event: the new session title.",
|
|
},
|
|
},
|
|
},
|
|
TranscriptResponse: {
|
|
type: "object",
|
|
required: ["messages", "total", "has_more", "last_line", "first_line"],
|
|
properties: {
|
|
messages: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/TranscriptMessage" },
|
|
},
|
|
total: {
|
|
type: "integer",
|
|
minimum: 0,
|
|
description: "Total messages available in the transcript.",
|
|
},
|
|
has_more: {
|
|
type: "boolean",
|
|
description: "True when older messages remain (use `before` to load them).",
|
|
},
|
|
last_line: {
|
|
type: "integer",
|
|
minimum: 0,
|
|
description:
|
|
"JSONL line number of the newest returned message — pass back as `after` for incremental fetches.",
|
|
},
|
|
first_line: {
|
|
type: "integer",
|
|
minimum: 0,
|
|
description:
|
|
"JSONL line number of the oldest returned message — pass back as `before` to page backwards.",
|
|
},
|
|
},
|
|
},
|
|
SessionStatsResponse: {
|
|
type: "object",
|
|
description:
|
|
"Aggregated counts powering the SessionOverview panel on the Session Detail page. All aggregation runs in SQL.",
|
|
required: [
|
|
"session_id",
|
|
"total_events",
|
|
"events_by_type",
|
|
"tools_used",
|
|
"error_count",
|
|
"first_event_at",
|
|
"last_event_at",
|
|
"agents",
|
|
"subagent_types",
|
|
"tokens",
|
|
],
|
|
properties: {
|
|
session_id: { type: "string" },
|
|
total_events: { type: "integer", minimum: 0 },
|
|
events_by_type: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["event_type", "count"],
|
|
properties: {
|
|
event_type: { type: "string" },
|
|
count: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
tools_used: {
|
|
type: "array",
|
|
description: "Top 15 tools used in this session, sorted by count descending.",
|
|
items: {
|
|
type: "object",
|
|
required: ["tool_name", "count"],
|
|
properties: {
|
|
tool_name: { type: "string" },
|
|
count: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
error_count: {
|
|
type: "integer",
|
|
minimum: 0,
|
|
description:
|
|
"Events whose event_type or summary matches /error/i or /failed/i (case-insensitive).",
|
|
},
|
|
first_event_at: { type: "string", format: "date-time", nullable: true },
|
|
last_event_at: { type: "string", format: "date-time", nullable: true },
|
|
agents: {
|
|
type: "object",
|
|
required: ["total", "main", "subagent", "compaction", "by_status"],
|
|
properties: {
|
|
total: { type: "integer", minimum: 0 },
|
|
main: { type: "integer", minimum: 0 },
|
|
subagent: { type: "integer", minimum: 0 },
|
|
compaction: { type: "integer", minimum: 0 },
|
|
by_status: {
|
|
type: "object",
|
|
additionalProperties: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
subagent_types: {
|
|
type: "array",
|
|
description:
|
|
"Subagent types in this session with counts. Excludes the special 'compaction' type which is surfaced via agents.compaction.",
|
|
items: {
|
|
type: "object",
|
|
required: ["subagent_type", "count"],
|
|
properties: {
|
|
subagent_type: { type: "string" },
|
|
count: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
tokens: {
|
|
type: "object",
|
|
required: [
|
|
"input_tokens",
|
|
"output_tokens",
|
|
"cache_read_tokens",
|
|
"cache_write_tokens",
|
|
],
|
|
properties: {
|
|
input_tokens: { type: "integer", minimum: 0 },
|
|
output_tokens: { type: "integer", minimum: 0 },
|
|
cache_read_tokens: { type: "integer", minimum: 0 },
|
|
cache_write_tokens: { type: "integer", minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
SessionUpdateRequest: {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
status: { type: "string", enum: ["active", "completed", "error", "abandoned"] },
|
|
ended_at: { type: "string", format: "date-time" },
|
|
metadata: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
SessionUpdateResponse: {
|
|
type: "object",
|
|
required: ["session"],
|
|
properties: { session: { $ref: "#/components/schemas/Session" } },
|
|
},
|
|
AgentsListResponse: {
|
|
type: "object",
|
|
required: ["agents", "limit", "offset"],
|
|
properties: {
|
|
agents: { type: "array", items: { $ref: "#/components/schemas/Agent" } },
|
|
limit: { type: "integer" },
|
|
offset: { type: "integer" },
|
|
},
|
|
},
|
|
AgentCreateRequest: {
|
|
type: "object",
|
|
required: ["id", "session_id", "name"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
session_id: { type: "string" },
|
|
name: { type: "string" },
|
|
type: { type: "string", enum: ["main", "subagent"] },
|
|
subagent_type: { type: "string" },
|
|
status: {
|
|
type: "string",
|
|
enum: ["working", "waiting", "completed", "error"],
|
|
},
|
|
task: { type: "string" },
|
|
parent_agent_id: { type: "string" },
|
|
metadata: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
AgentCreateResponse: {
|
|
type: "object",
|
|
required: ["agent", "created"],
|
|
properties: {
|
|
agent: { $ref: "#/components/schemas/Agent" },
|
|
created: { type: "boolean" },
|
|
},
|
|
},
|
|
AgentDetailResponse: {
|
|
type: "object",
|
|
required: ["agent"],
|
|
properties: { agent: { $ref: "#/components/schemas/Agent" } },
|
|
},
|
|
AgentUpdateRequest: {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
status: {
|
|
type: "string",
|
|
enum: ["working", "waiting", "completed", "error"],
|
|
},
|
|
task: { type: "string" },
|
|
current_tool: { type: "string", nullable: true },
|
|
ended_at: { type: "string", format: "date-time" },
|
|
metadata: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
AgentUpdateResponse: {
|
|
type: "object",
|
|
required: ["agent"],
|
|
properties: { agent: { $ref: "#/components/schemas/Agent" } },
|
|
},
|
|
EventsListResponse: {
|
|
type: "object",
|
|
required: ["events", "limit", "offset", "total"],
|
|
properties: {
|
|
events: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/DashboardEvent" },
|
|
},
|
|
limit: { type: "integer" },
|
|
offset: { type: "integer" },
|
|
total: {
|
|
type: "integer",
|
|
description: "Total rows matching the current filter (for UI pagination)",
|
|
},
|
|
},
|
|
},
|
|
EventsFacetsResponse: {
|
|
type: "object",
|
|
required: ["event_types", "tool_names"],
|
|
properties: {
|
|
event_types: { type: "array", items: { type: "string" } },
|
|
tool_names: { type: "array", items: { type: "string" } },
|
|
},
|
|
},
|
|
StatsResponse: {
|
|
type: "object",
|
|
required: [
|
|
"total_sessions",
|
|
"active_sessions",
|
|
"active_agents",
|
|
"total_agents",
|
|
"total_events",
|
|
"events_today",
|
|
"ws_connections",
|
|
"agents_by_status",
|
|
"sessions_by_status",
|
|
],
|
|
properties: {
|
|
total_sessions: { type: "integer" },
|
|
active_sessions: { type: "integer" },
|
|
active_agents: { type: "integer" },
|
|
total_agents: { type: "integer" },
|
|
total_events: { type: "integer" },
|
|
events_today: { type: "integer" },
|
|
ws_connections: { type: "integer" },
|
|
agents_by_status: { $ref: "#/components/schemas/CountMap" },
|
|
sessions_by_status: { $ref: "#/components/schemas/CountMap" },
|
|
},
|
|
},
|
|
AnalyticsResponse: {
|
|
type: "object",
|
|
required: [
|
|
"tokens",
|
|
"tool_usage",
|
|
"daily_events",
|
|
"daily_sessions",
|
|
"agent_types",
|
|
"event_types",
|
|
"avg_events_per_session",
|
|
"total_subagents",
|
|
"overview",
|
|
"agents_by_status",
|
|
"sessions_by_status",
|
|
],
|
|
properties: {
|
|
tokens: {
|
|
type: "object",
|
|
required: ["total_input", "total_output", "total_cache_read", "total_cache_write"],
|
|
properties: {
|
|
total_input: { type: "integer" },
|
|
total_output: { type: "integer" },
|
|
total_cache_read: { type: "integer" },
|
|
total_cache_write: { type: "integer" },
|
|
},
|
|
},
|
|
tool_usage: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["tool_name", "count"],
|
|
properties: { tool_name: { type: "string" }, count: { type: "integer" } },
|
|
},
|
|
},
|
|
daily_events: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["date", "count"],
|
|
properties: { date: { type: "string" }, count: { type: "integer" } },
|
|
},
|
|
},
|
|
daily_sessions: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["date", "count"],
|
|
properties: { date: { type: "string" }, count: { type: "integer" } },
|
|
},
|
|
},
|
|
agent_types: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["subagent_type", "count"],
|
|
properties: {
|
|
subagent_type: { type: "string", nullable: true },
|
|
count: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
event_types: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
required: ["event_type", "count"],
|
|
properties: { event_type: { type: "string" }, count: { type: "integer" } },
|
|
},
|
|
},
|
|
avg_events_per_session: { type: "number" },
|
|
total_subagents: { type: "integer" },
|
|
overview: {
|
|
type: "object",
|
|
required: [
|
|
"total_sessions",
|
|
"active_sessions",
|
|
"active_agents",
|
|
"total_agents",
|
|
"total_events",
|
|
],
|
|
properties: {
|
|
total_sessions: { type: "integer" },
|
|
active_sessions: { type: "integer" },
|
|
active_agents: { type: "integer" },
|
|
total_agents: { type: "integer" },
|
|
total_events: { type: "integer" },
|
|
},
|
|
},
|
|
agents_by_status: { $ref: "#/components/schemas/CountMap" },
|
|
sessions_by_status: { $ref: "#/components/schemas/CountMap" },
|
|
},
|
|
},
|
|
HookEventRequest: {
|
|
type: "object",
|
|
required: ["hook_type", "data"],
|
|
properties: {
|
|
hook_type: {
|
|
type: "string",
|
|
description:
|
|
"Hook type from Claude Code (common values: PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd)",
|
|
},
|
|
data: {
|
|
type: "object",
|
|
required: ["session_id"],
|
|
properties: {
|
|
session_id: { type: "string" },
|
|
tool_name: { type: "string" },
|
|
transcript_path: { type: "string" },
|
|
},
|
|
additionalProperties: true,
|
|
},
|
|
},
|
|
},
|
|
HookEventResponse: {
|
|
type: "object",
|
|
required: ["ok", "event"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
event: { $ref: "#/components/schemas/DashboardEvent" },
|
|
},
|
|
},
|
|
PricingRule: {
|
|
type: "object",
|
|
required: [
|
|
"model_pattern",
|
|
"display_name",
|
|
"input_per_mtok",
|
|
"output_per_mtok",
|
|
"cache_read_per_mtok",
|
|
"cache_write_per_mtok",
|
|
"cache_write_1h_per_mtok",
|
|
"updated_at",
|
|
],
|
|
properties: {
|
|
model_pattern: { type: "string" },
|
|
display_name: { type: "string" },
|
|
input_per_mtok: { type: "number" },
|
|
output_per_mtok: { type: "number" },
|
|
cache_read_per_mtok: { type: "number" },
|
|
cache_write_per_mtok: { type: "number", description: "5m ephemeral cache-write rate" },
|
|
cache_write_1h_per_mtok: {
|
|
type: "number",
|
|
description: "1h ephemeral cache-write rate",
|
|
},
|
|
fast_input_per_mtok: { type: "number", description: "Fast-mode input rate (0 = none)" },
|
|
fast_output_per_mtok: {
|
|
type: "number",
|
|
description: "Fast-mode output rate (0 = none)",
|
|
},
|
|
updated_at: { type: "string", format: "date-time" },
|
|
},
|
|
},
|
|
PricingUpsertRequest: {
|
|
type: "object",
|
|
required: ["model_pattern", "display_name"],
|
|
properties: {
|
|
model_pattern: { type: "string" },
|
|
display_name: { type: "string" },
|
|
input_per_mtok: { type: "number" },
|
|
output_per_mtok: { type: "number" },
|
|
cache_read_per_mtok: { type: "number" },
|
|
cache_write_per_mtok: { type: "number", description: "5m ephemeral cache-write rate" },
|
|
cache_write_1h_per_mtok: {
|
|
type: "number",
|
|
description: "1h ephemeral cache-write rate",
|
|
},
|
|
fast_input_per_mtok: { type: "number", description: "Fast-mode input rate (0 = none)" },
|
|
fast_output_per_mtok: {
|
|
type: "number",
|
|
description: "Fast-mode output rate (0 = none)",
|
|
},
|
|
},
|
|
},
|
|
PricingListResponse: {
|
|
type: "object",
|
|
required: ["pricing"],
|
|
properties: {
|
|
pricing: { type: "array", items: { $ref: "#/components/schemas/PricingRule" } },
|
|
},
|
|
},
|
|
PricingUpsertResponse: {
|
|
type: "object",
|
|
required: ["pricing"],
|
|
properties: { pricing: { $ref: "#/components/schemas/PricingRule" } },
|
|
},
|
|
CostBreakdownItem: {
|
|
type: "object",
|
|
required: [
|
|
"model",
|
|
"input_tokens",
|
|
"output_tokens",
|
|
"cache_read_tokens",
|
|
"cache_write_tokens",
|
|
"cost",
|
|
"matched_rule",
|
|
],
|
|
properties: {
|
|
model: { type: "string" },
|
|
speed: { type: "string" },
|
|
inference_geo: { type: "string" },
|
|
service_tier: { type: "string" },
|
|
input_tokens: { type: "integer" },
|
|
output_tokens: { type: "integer" },
|
|
cache_read_tokens: { type: "integer" },
|
|
cache_write_tokens: { type: "integer" },
|
|
cache_write_1h_tokens: { type: "integer" },
|
|
web_search_requests: { type: "integer" },
|
|
web_fetch_requests: { type: "integer" },
|
|
code_execution_requests: { type: "integer" },
|
|
cost: { type: "number" },
|
|
matched_rule: { type: "string", nullable: true },
|
|
},
|
|
},
|
|
DailyCostItem: {
|
|
type: "object",
|
|
required: ["date", "cost"],
|
|
properties: {
|
|
date: { type: "string", format: "date" },
|
|
cost: { type: "number" },
|
|
},
|
|
},
|
|
CostResult: {
|
|
type: "object",
|
|
required: ["total_cost", "breakdown", "daily_costs"],
|
|
properties: {
|
|
total_cost: { type: "number" },
|
|
breakdown: { type: "array", items: { $ref: "#/components/schemas/CostBreakdownItem" } },
|
|
daily_costs: { type: "array", items: { $ref: "#/components/schemas/DailyCostItem" } },
|
|
feature_costs: {
|
|
type: "object",
|
|
description: "Server-tool surcharges separate from token cost",
|
|
properties: {
|
|
web_search_cost: { type: "number" },
|
|
web_fetch_cost: { type: "number" },
|
|
code_execution_cost: { type: "number" },
|
|
code_execution_hours_estimated: { type: "number" },
|
|
code_execution_free_hours: { type: "number" },
|
|
},
|
|
},
|
|
unpriced_models: {
|
|
type: "array",
|
|
description: "Models with usage but no matching pricing rule (cost not counted)",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
model: { type: "string" },
|
|
input_tokens: { type: "integer" },
|
|
output_tokens: { type: "integer" },
|
|
cache_read_tokens: { type: "integer" },
|
|
cache_write_tokens: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DeleteOkResponse: {
|
|
type: "object",
|
|
required: ["ok"],
|
|
properties: { ok: { type: "boolean", enum: [true] } },
|
|
},
|
|
WorkflowAggregateResponse: {
|
|
type: "object",
|
|
required: [
|
|
"stats",
|
|
"orchestration",
|
|
"toolFlow",
|
|
"effectiveness",
|
|
"patterns",
|
|
"modelDelegation",
|
|
"errorPropagation",
|
|
"concurrency",
|
|
"complexity",
|
|
"compaction",
|
|
"cooccurrence",
|
|
],
|
|
properties: {
|
|
stats: {
|
|
type: "object",
|
|
required: [
|
|
"totalSessions",
|
|
"totalAgents",
|
|
"totalSubagents",
|
|
"avgSubagents",
|
|
"successRate",
|
|
"avgDepth",
|
|
"avgDurationSec",
|
|
"totalCompactions",
|
|
"avgCompactions",
|
|
],
|
|
properties: {
|
|
totalSessions: { type: "integer" },
|
|
totalAgents: { type: "integer" },
|
|
totalSubagents: { type: "integer" },
|
|
avgSubagents: { type: "number" },
|
|
successRate: { type: "number" },
|
|
avgDepth: { type: "number" },
|
|
avgDurationSec: { type: "integer" },
|
|
totalCompactions: { type: "integer" },
|
|
avgCompactions: { type: "number" },
|
|
topFlow: {
|
|
type: "object",
|
|
nullable: true,
|
|
properties: {
|
|
source: { type: "string" },
|
|
target: { type: "string" },
|
|
count: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orchestration: { type: "object", additionalProperties: true },
|
|
toolFlow: { type: "object", additionalProperties: true },
|
|
effectiveness: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
patterns: { type: "object", additionalProperties: true },
|
|
modelDelegation: { type: "object", additionalProperties: true },
|
|
errorPropagation: { type: "object", additionalProperties: true },
|
|
concurrency: { type: "object", additionalProperties: true },
|
|
complexity: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
compaction: { type: "object", additionalProperties: true },
|
|
cooccurrence: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
AgentTreeNode: {
|
|
type: "object",
|
|
required: ["id", "name", "type", "status", "children"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
name: { type: "string" },
|
|
type: { type: "string", enum: ["main", "subagent"] },
|
|
subagent_type: { type: "string", nullable: true },
|
|
status: { type: "string" },
|
|
task: { type: "string", nullable: true },
|
|
started_at: { type: "string", format: "date-time" },
|
|
ended_at: { type: "string", format: "date-time", nullable: true },
|
|
children: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/AgentTreeNode" },
|
|
},
|
|
},
|
|
},
|
|
WorkflowSessionResponse: {
|
|
type: "object",
|
|
required: ["session", "tree", "toolTimeline", "swimLanes", "events"],
|
|
properties: {
|
|
session: { $ref: "#/components/schemas/Session" },
|
|
tree: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/AgentTreeNode" },
|
|
},
|
|
toolTimeline: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "integer" },
|
|
tool_name: { type: "string" },
|
|
event_type: { type: "string" },
|
|
agent_id: { type: "string", nullable: true },
|
|
created_at: { type: "string", format: "date-time" },
|
|
summary: { type: "string", nullable: true },
|
|
},
|
|
},
|
|
},
|
|
swimLanes: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string" },
|
|
name: { type: "string" },
|
|
type: { type: "string" },
|
|
subagent_type: { type: "string", nullable: true },
|
|
status: { type: "string" },
|
|
started_at: { type: "string", format: "date-time" },
|
|
ended_at: { type: "string", format: "date-time", nullable: true },
|
|
parent_agent_id: { type: "string", nullable: true },
|
|
},
|
|
},
|
|
},
|
|
events: {
|
|
type: "array",
|
|
items: { $ref: "#/components/schemas/DashboardEvent" },
|
|
},
|
|
},
|
|
},
|
|
SettingsInfoResponse: {
|
|
type: "object",
|
|
required: ["db", "hooks", "server", "transcript_cache"],
|
|
properties: {
|
|
db: {
|
|
type: "object",
|
|
required: ["path", "size", "counts"],
|
|
properties: {
|
|
path: { type: "string" },
|
|
size: { type: "integer" },
|
|
counts: {
|
|
type: "object",
|
|
additionalProperties: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
hooks: {
|
|
type: "object",
|
|
required: ["installed", "path", "hooks"],
|
|
properties: {
|
|
installed: { type: "boolean" },
|
|
path: { type: "string" },
|
|
hooks: {
|
|
type: "object",
|
|
additionalProperties: { type: "boolean" },
|
|
},
|
|
},
|
|
},
|
|
server: {
|
|
type: "object",
|
|
required: ["version", "uptime", "node_version", "platform", "ws_connections"],
|
|
properties: {
|
|
version: {
|
|
type: "string",
|
|
description: "Dashboard release version from package.json",
|
|
},
|
|
uptime: { type: "number" },
|
|
node_version: { type: "string" },
|
|
platform: { type: "string" },
|
|
ws_connections: { type: "integer" },
|
|
},
|
|
},
|
|
transcript_cache: {
|
|
type: "object",
|
|
required: ["entries", "paths"],
|
|
properties: {
|
|
entries: { type: "integer" },
|
|
paths: { type: "array", items: { type: "string" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ClearDataResponse: {
|
|
type: "object",
|
|
required: ["ok", "cleared"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
cleared: {
|
|
type: "object",
|
|
additionalProperties: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
ReimportResponse: {
|
|
type: "object",
|
|
required: ["ok", "imported", "skipped", "errors"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
imported: { type: "integer" },
|
|
skipped: { type: "integer" },
|
|
errors: { type: "integer" },
|
|
},
|
|
},
|
|
ImportGuideResponse: {
|
|
type: "object",
|
|
properties: {
|
|
platform: { type: "string" },
|
|
default_projects_dir: { type: "string" },
|
|
default_projects_dir_display: { type: "string" },
|
|
default_projects_dir_exists: { type: "boolean" },
|
|
default_projects_dir_stats: {
|
|
type: "object",
|
|
properties: {
|
|
projects: { type: "integer" },
|
|
jsonl_files: { type: "integer" },
|
|
},
|
|
},
|
|
archive_command: { type: "string" },
|
|
supported_extensions: { type: "array", items: { type: "string" } },
|
|
max_upload_bytes: { type: "integer" },
|
|
max_upload_files: { type: "integer" },
|
|
steps: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string" },
|
|
title: { type: "string" },
|
|
body: { type: "string" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ImportResultResponse: {
|
|
type: "object",
|
|
required: ["ok", "source", "imported", "skipped", "errors"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
source: { type: "string", enum: ["default", "path", "upload"] },
|
|
path: { type: "string", nullable: true },
|
|
imported: { type: "integer" },
|
|
backfilled: { type: "integer" },
|
|
skipped: { type: "integer" },
|
|
errors: { type: "integer" },
|
|
sessions_seen: { type: "integer" },
|
|
files_scanned: { type: "integer" },
|
|
files_received: { type: "integer" },
|
|
rejected_files: { type: "array", items: { type: "string" } },
|
|
entries_extracted: { type: "integer" },
|
|
entries_skipped: { type: "integer" },
|
|
},
|
|
},
|
|
ReinstallHooksResponse: {
|
|
type: "object",
|
|
required: ["ok", "hooks"],
|
|
properties: {
|
|
ok: { type: "boolean" },
|
|
hooks: {
|
|
type: "object",
|
|
required: ["installed", "path", "hooks"],
|
|
properties: {
|
|
installed: { type: "boolean" },
|
|
path: { type: "string" },
|
|
hooks: {
|
|
type: "object",
|
|
additionalProperties: { type: "boolean" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResetPricingResponse: {
|
|
type: "object",
|
|
required: ["ok", "pricing"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
pricing: { type: "array", items: { $ref: "#/components/schemas/PricingRule" } },
|
|
},
|
|
},
|
|
ExportResponse: {
|
|
type: "object",
|
|
required: ["exported_at", "sessions", "agents", "events", "token_usage", "model_pricing"],
|
|
properties: {
|
|
format: {
|
|
type: "string",
|
|
description:
|
|
'Bundle format marker (always "ccam-export" for exports from this version).',
|
|
example: "ccam-export",
|
|
},
|
|
version: { type: "integer", description: "Bundle schema version.", example: 1 },
|
|
exported_at: { type: "string", format: "date-time" },
|
|
sessions: { type: "array", items: { $ref: "#/components/schemas/Session" } },
|
|
agents: { type: "array", items: { $ref: "#/components/schemas/Agent" } },
|
|
events: { type: "array", items: { $ref: "#/components/schemas/DashboardEvent" } },
|
|
token_usage: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
},
|
|
},
|
|
workflows: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
dashboard_runs: {
|
|
type: "array",
|
|
items: { type: "object", additionalProperties: true },
|
|
},
|
|
alert_rules: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
model_pricing: { type: "array", items: { $ref: "#/components/schemas/PricingRule" } },
|
|
},
|
|
},
|
|
ImportResponse: {
|
|
type: "object",
|
|
required: [
|
|
"ok",
|
|
"sessions_imported",
|
|
"sessions_skipped",
|
|
"agents",
|
|
"events",
|
|
"token_usage",
|
|
"workflows",
|
|
"dashboard_runs",
|
|
"alert_rules",
|
|
"model_pricing",
|
|
"errors",
|
|
],
|
|
description:
|
|
"Per-table counts from restoring an export bundle. Session-scoped counts are rows newly inserted; sessions_skipped counts sessions already present (skipped whole to stay idempotent).",
|
|
properties: {
|
|
ok: { type: "boolean" },
|
|
source: { type: "string", description: "Uploaded filename or server-side path read." },
|
|
format: {
|
|
type: "string",
|
|
nullable: true,
|
|
description: "Bundle format marker, or null for a legacy export.",
|
|
},
|
|
sessions_imported: { type: "integer" },
|
|
sessions_skipped: { type: "integer" },
|
|
agents: { type: "integer" },
|
|
events: { type: "integer" },
|
|
token_usage: { type: "integer" },
|
|
workflows: { type: "integer" },
|
|
dashboard_runs: { type: "integer" },
|
|
alert_rules: { type: "integer" },
|
|
model_pricing: { type: "integer" },
|
|
errors: { type: "integer" },
|
|
},
|
|
},
|
|
CleanupRequest: {
|
|
type: "object",
|
|
properties: {
|
|
abandon_hours: {
|
|
type: "number",
|
|
minimum: 0,
|
|
description: "Mark active sessions abandoned if stale for this many hours",
|
|
},
|
|
purge_days: {
|
|
type: "number",
|
|
minimum: 0,
|
|
description:
|
|
"Delete old completed/error/abandoned sessions older than this many days",
|
|
},
|
|
},
|
|
},
|
|
CleanupResponse: {
|
|
type: "object",
|
|
required: ["ok", "abandoned", "purged_sessions", "purged_events", "purged_agents"],
|
|
properties: {
|
|
ok: { type: "boolean", enum: [true] },
|
|
abandoned: { type: "integer" },
|
|
purged_sessions: { type: "integer" },
|
|
purged_events: { type: "integer" },
|
|
purged_agents: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
paths: {
|
|
"/api/health": {
|
|
get: {
|
|
tags: ["Health"],
|
|
summary: "Health check",
|
|
operationId: "getHealth",
|
|
responses: {
|
|
200: {
|
|
description: "Service is healthy",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/HealthResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/sessions": {
|
|
get: {
|
|
tags: ["Sessions"],
|
|
summary: "List sessions",
|
|
description:
|
|
"Returns a paginated list of sessions with agent counts and per-session cost. Status filter, search, and pagination compose. Cost computation runs over the returned page only — independent of total session count.",
|
|
operationId: "listSessions",
|
|
parameters: [
|
|
{ $ref: "#/components/parameters/SessionStatusQuery" },
|
|
{
|
|
name: "q",
|
|
in: "query",
|
|
schema: { type: "string" },
|
|
description:
|
|
"Case-insensitive search across `id` / `name` / `cwd`. Composes with the status filter when both are present.",
|
|
},
|
|
{ $ref: "#/components/parameters/LimitQuery" },
|
|
{ $ref: "#/components/parameters/OffsetQuery" },
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Session list",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionsListResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ["Sessions"],
|
|
summary: "Create session (idempotent)",
|
|
operationId: "createSession",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionCreateRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
201: {
|
|
description: "Session created",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionCreateResponse" },
|
|
},
|
|
},
|
|
},
|
|
200: {
|
|
description: "Session already exists",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionCreateResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Invalid request body",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/sessions/{id}": {
|
|
get: {
|
|
tags: ["Sessions"],
|
|
summary: "Get session details",
|
|
operationId: "getSession",
|
|
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "Session with associated agents/events",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionDetailResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
patch: {
|
|
tags: ["Sessions"],
|
|
summary: "Update session",
|
|
operationId: "updateSession",
|
|
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionUpdateRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Session updated",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionUpdateResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/sessions/{id}/stats": {
|
|
get: {
|
|
tags: ["Sessions"],
|
|
summary: "Get aggregated session stats",
|
|
description:
|
|
"Returns aggregated counts for the SessionOverview panel: events, events-by-type, top tool usage, error count, agent type/status counts, subagent type breakdown, and token totals. All aggregation runs in SQL — cheap to call even for sessions with tens of thousands of events. Frontend debounces calls to this endpoint on `new_event` / `agent_*` / `session_updated` websocket frames so counters track the running session.",
|
|
operationId: "getSessionStats",
|
|
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "Aggregated session stats",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SessionStatsResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/sessions/{id}/transcripts": {
|
|
get: {
|
|
tags: ["Sessions"],
|
|
summary: "List available transcripts for a session",
|
|
description:
|
|
"Lists every JSONL transcript file associated with a session — the main agent's transcript plus any subagent and compaction transcripts. Used by the Conversation tab on the Session Detail page to populate the transcript switcher.",
|
|
operationId: "listSessionTranscripts",
|
|
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "List of transcripts available for the session",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/TranscriptListResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/sessions/{id}/transcript": {
|
|
get: {
|
|
tags: ["Sessions"],
|
|
summary: "Stream messages from a specific transcript",
|
|
description:
|
|
"Returns parsed messages from a JSONL transcript with cursor-based pagination. Pass `agent_id` to select a specific subagent or compaction transcript. The frontend uses `after` for incremental live updates on `new_event` and `before` to load older messages on scroll-up.",
|
|
operationId: "getSessionTranscript",
|
|
parameters: [
|
|
{ $ref: "#/components/parameters/SessionIdPath" },
|
|
{
|
|
name: "agent_id",
|
|
in: "query",
|
|
schema: { type: "string" },
|
|
description:
|
|
"Transcript identifier — 'main' for the session's main transcript, or a subagent / compaction id from /transcripts.",
|
|
},
|
|
{
|
|
name: "limit",
|
|
in: "query",
|
|
schema: { type: "integer", default: 50, minimum: 1, maximum: 500 },
|
|
description: "Maximum number of messages to return.",
|
|
},
|
|
{
|
|
name: "offset",
|
|
in: "query",
|
|
schema: { type: "integer", minimum: 0 },
|
|
description:
|
|
"Offset from the start of the transcript (mutually exclusive with after/before).",
|
|
},
|
|
{
|
|
name: "after",
|
|
in: "query",
|
|
schema: { type: "integer", minimum: 0 },
|
|
description:
|
|
"Only return messages whose JSONL line number is strictly greater than this value. Used for incremental live updates.",
|
|
},
|
|
{
|
|
name: "before",
|
|
in: "query",
|
|
schema: { type: "integer", minimum: 0 },
|
|
description:
|
|
"Only return messages whose JSONL line number is strictly less than this value. Used to load older messages on scroll-up.",
|
|
},
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Parsed messages with cursor metadata",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/TranscriptResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session or transcript not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/agents": {
|
|
get: {
|
|
tags: ["Agents"],
|
|
summary: "List agents",
|
|
operationId: "listAgents",
|
|
parameters: [
|
|
{ $ref: "#/components/parameters/AgentStatusQuery" },
|
|
{ $ref: "#/components/parameters/SessionFilterQuery" },
|
|
{ $ref: "#/components/parameters/LimitQuery" },
|
|
{ $ref: "#/components/parameters/OffsetQuery" },
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Agent list",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentsListResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ["Agents"],
|
|
summary: "Create agent (idempotent)",
|
|
operationId: "createAgent",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentCreateRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
201: {
|
|
description: "Agent created",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentCreateResponse" },
|
|
},
|
|
},
|
|
},
|
|
200: {
|
|
description: "Agent already exists",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentCreateResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Invalid request body",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/agents/{id}": {
|
|
get: {
|
|
tags: ["Agents"],
|
|
summary: "Get agent",
|
|
operationId: "getAgent",
|
|
parameters: [{ $ref: "#/components/parameters/AgentIdPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "Agent details",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentDetailResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Agent not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
patch: {
|
|
tags: ["Agents"],
|
|
summary: "Update agent",
|
|
operationId: "updateAgent",
|
|
parameters: [{ $ref: "#/components/parameters/AgentIdPath" }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentUpdateRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Agent updated",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AgentUpdateResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Agent not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/events": {
|
|
get: {
|
|
tags: ["Events"],
|
|
summary: "List events with multi-dimensional filtering",
|
|
operationId: "listEvents",
|
|
parameters: [
|
|
{
|
|
in: "query",
|
|
name: "event_type",
|
|
description: "Comma-separated event_type values (e.g. Stop,PreToolUse)",
|
|
schema: { type: "string" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "tool_name",
|
|
description: "Comma-separated tool_name values (e.g. Bash,Edit)",
|
|
schema: { type: "string" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "agent_id",
|
|
description: "Comma-separated agent_id values",
|
|
schema: { type: "string" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "session_id",
|
|
description: "Comma-separated session_id values",
|
|
schema: { type: "string" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "q",
|
|
description: "Text search across summary, tool_name, and data",
|
|
schema: { type: "string" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "from",
|
|
description: "ISO datetime lower bound (inclusive) on created_at",
|
|
schema: { type: "string", format: "date-time" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "to",
|
|
description: "ISO datetime upper bound (inclusive) on created_at",
|
|
schema: { type: "string", format: "date-time" },
|
|
},
|
|
{
|
|
in: "query",
|
|
name: "limit",
|
|
description: "Max rows to return (1-500, default 50)",
|
|
schema: { type: "integer", minimum: 1, maximum: 500, default: 50 },
|
|
},
|
|
{ $ref: "#/components/parameters/OffsetQuery" },
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Event list with total count for pagination",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/EventsListResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/events/facets": {
|
|
get: {
|
|
tags: ["Events"],
|
|
summary: "Distinct event_type and tool_name values available in the DB",
|
|
operationId: "listEventFacets",
|
|
responses: {
|
|
200: {
|
|
description: "Facet values for populating filter dropdowns",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/EventsFacetsResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/stats": {
|
|
get: {
|
|
tags: ["Stats"],
|
|
summary: "Get aggregate dashboard stats",
|
|
operationId: "getStats",
|
|
responses: {
|
|
200: {
|
|
description: "Statistics overview",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/StatsResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/metrics": {
|
|
get: {
|
|
tags: ["Metrics"],
|
|
summary: "Prometheus metrics exposition",
|
|
operationId: "getMetrics",
|
|
description:
|
|
"Exposes the dashboard's live counters (sessions/agents by status, " +
|
|
"event and token totals, connected realtime clients, configured " +
|
|
"remote sources, process uptime/memory, build version) in the " +
|
|
"Prometheus text-exposition format (v0.0.4) for scraping into " +
|
|
"Prometheus / Grafana. Read-only. Mounted under `/api`, so it honors " +
|
|
"the optional `DASHBOARD_TOKEN` guard — point the scrape job's bearer " +
|
|
"token at it when the server is bound to a LAN.",
|
|
responses: {
|
|
200: {
|
|
description: "Prometheus text exposition (v0.0.4).",
|
|
content: {
|
|
"text/plain": {
|
|
schema: { type: "string" },
|
|
example:
|
|
"# HELP ccam_up 1 when the dashboard API is serving this scrape.\n" +
|
|
"# TYPE ccam_up gauge\n" +
|
|
"ccam_up 1\n" +
|
|
"# HELP ccam_sessions Number of sessions by lifecycle status.\n" +
|
|
"# TYPE ccam_sessions gauge\n" +
|
|
'ccam_sessions{status="active"} 3\n',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/analytics": {
|
|
get: {
|
|
tags: ["Analytics"],
|
|
summary: "Get analytics aggregates",
|
|
operationId: "getAnalytics",
|
|
responses: {
|
|
200: {
|
|
description: "Analytics response",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/AnalyticsResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/hooks/event": {
|
|
post: {
|
|
tags: ["Hooks"],
|
|
summary: "Ingest Claude Code hook event",
|
|
operationId: "ingestHookEvent",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/HookEventRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Event processed",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/HookEventResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Invalid hook payload",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/pricing": {
|
|
get: {
|
|
tags: ["Pricing"],
|
|
summary: "List pricing rules",
|
|
operationId: "listPricingRules",
|
|
responses: {
|
|
200: {
|
|
description: "Pricing rules",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/PricingListResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
put: {
|
|
tags: ["Pricing"],
|
|
summary: "Create/update pricing rule",
|
|
operationId: "upsertPricingRule",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/PricingUpsertRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Pricing rule stored",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/PricingUpsertResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Invalid request body",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/pricing/{pattern}": {
|
|
delete: {
|
|
tags: ["Pricing"],
|
|
summary: "Delete pricing rule",
|
|
operationId: "deletePricingRule",
|
|
parameters: [{ $ref: "#/components/parameters/PatternPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "Rule deleted",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/DeleteOkResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Pricing rule not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/pricing/cost": {
|
|
get: {
|
|
tags: ["Pricing"],
|
|
summary: "Get total token cost across all sessions",
|
|
operationId: "getTotalCost",
|
|
responses: {
|
|
200: {
|
|
description: "Cost result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/CostResult" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/pricing/cost/{sessionId}": {
|
|
get: {
|
|
tags: ["Pricing"],
|
|
summary: "Get token cost for one session",
|
|
operationId: "getSessionCost",
|
|
parameters: [
|
|
{
|
|
name: "sessionId",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
},
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Session cost result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/CostResult" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/workflows": {
|
|
get: {
|
|
tags: ["Workflows"],
|
|
summary: "Get workflow intelligence aggregates",
|
|
operationId: "getWorkflowIntelligence",
|
|
parameters: [{ $ref: "#/components/parameters/WorkflowStatusQuery" }],
|
|
responses: {
|
|
200: {
|
|
description: "Workflow aggregate data",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/WorkflowAggregateResponse" },
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Internal server error",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/workflows/session/{id}": {
|
|
get: {
|
|
tags: ["Workflows"],
|
|
summary: "Get workflow drill-in for one session",
|
|
operationId: "getWorkflowSession",
|
|
parameters: [{ $ref: "#/components/parameters/SessionIdPath" }],
|
|
responses: {
|
|
200: {
|
|
description: "Workflow session detail",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/WorkflowSessionResponse" },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Session not found",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Internal server error",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/MessageErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/info": {
|
|
get: {
|
|
tags: ["Settings"],
|
|
summary: "Get system/database/hook diagnostics",
|
|
operationId: "getSettingsInfo",
|
|
responses: {
|
|
200: {
|
|
description: "Settings and diagnostics",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/SettingsInfoResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/clear-data": {
|
|
post: {
|
|
tags: ["Settings"],
|
|
summary: "Delete all dashboard data",
|
|
operationId: "clearData",
|
|
responses: {
|
|
200: {
|
|
description: "Data cleared",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ClearDataResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/reimport": {
|
|
post: {
|
|
tags: ["Settings"],
|
|
summary: "Re-import legacy sessions from ~/.claude",
|
|
operationId: "reimportLegacySessions",
|
|
responses: {
|
|
200: {
|
|
description: "Import completed",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ReimportResponse" },
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Import failed",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/reinstall-hooks": {
|
|
post: {
|
|
tags: ["Settings"],
|
|
summary: "Reinstall Claude Code hooks",
|
|
operationId: "reinstallHooks",
|
|
responses: {
|
|
200: {
|
|
description: "Hooks reinstall result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ReinstallHooksResponse" },
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Hook installation failed",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/reset-pricing": {
|
|
post: {
|
|
tags: ["Settings"],
|
|
summary: "Reset pricing table to defaults",
|
|
operationId: "resetPricing",
|
|
responses: {
|
|
200: {
|
|
description: "Pricing defaults restored",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ResetPricingResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/export": {
|
|
get: {
|
|
tags: ["Settings"],
|
|
summary: "Export all dashboard data as JSON",
|
|
operationId: "exportData",
|
|
responses: {
|
|
200: {
|
|
description: "Export payload (served as attachment)",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ExportResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/settings/cleanup": {
|
|
post: {
|
|
tags: ["Settings"],
|
|
summary: "Abandon stale sessions and optionally purge old history",
|
|
operationId: "cleanupData",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/CleanupRequest" },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Cleanup result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/CleanupResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/import/guide": {
|
|
get: {
|
|
tags: ["Import"],
|
|
summary: "Import guide with OS-aware defaults and step-by-step instructions",
|
|
operationId: "importGuide",
|
|
responses: {
|
|
200: {
|
|
description: "Guide payload",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ImportGuideResponse" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/import/rescan": {
|
|
post: {
|
|
tags: ["Import"],
|
|
summary: "Rescan the default ~/.claude/projects directory",
|
|
operationId: "importRescan",
|
|
responses: {
|
|
200: {
|
|
description: "Import result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Import failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/import/scan-path": {
|
|
post: {
|
|
tags: ["Import"],
|
|
summary: "Import transcripts from an arbitrary absolute directory",
|
|
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.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Import result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Path validation failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
500: {
|
|
description: "Import failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/import/upload": {
|
|
post: {
|
|
tags: ["Import"],
|
|
summary: "Upload JSONL files or archives (.zip, .tar, .tar.gz, .tgz, .gz)",
|
|
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.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Import result",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/ImportResultResponse" },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "No files or no JSONL content",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
413: {
|
|
description: "Extraction limit exceeded (possible zip bomb)",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
500: {
|
|
description: "Upload or import failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/updates/status": {
|
|
get: {
|
|
tags: ["Updates"],
|
|
summary: "Check whether the dashboard git checkout is behind origin",
|
|
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.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Update status query failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/updates/check": {
|
|
post: {
|
|
tags: ["Updates"],
|
|
summary: "Run an update check immediately and broadcast the result",
|
|
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.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Update check failed",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/alerts": {
|
|
get: {
|
|
tags: ["Alerts"],
|
|
summary: "List fired alerts, newest first",
|
|
operationId: "listAlerts",
|
|
parameters: [
|
|
{ $ref: "#/components/parameters/LimitQuery" },
|
|
{ $ref: "#/components/parameters/OffsetQuery" },
|
|
{
|
|
name: "unacked",
|
|
in: "query",
|
|
required: false,
|
|
schema: { type: "boolean" },
|
|
description: "When true, return only unacknowledged alerts",
|
|
},
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Paginated alert feed with total and unacked counts",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
description: "Includes alerts[], total, unacked, limit, offset.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/alerts/rules": {
|
|
get: {
|
|
tags: ["Alerts"],
|
|
summary: "List alert rules",
|
|
operationId: "listAlertRules",
|
|
responses: {
|
|
200: {
|
|
description: "All alert rules with parsed config objects",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ["Alerts"],
|
|
summary: "Create an alert rule",
|
|
operationId: "createAlertRule",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
required: ["name", "rule_type", "config"],
|
|
properties: {
|
|
name: { type: "string" },
|
|
rule_type: {
|
|
type: "string",
|
|
enum: ["event_pattern", "inactivity", "status_duration", "token_threshold"],
|
|
},
|
|
config: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
description:
|
|
"Type-specific config. event_pattern: event_type/tool_name/summary_contains + optional count/window_minutes. inactivity: minutes. status_duration: status + minutes. token_threshold: total_tokens.",
|
|
},
|
|
enabled: { type: "boolean", default: true },
|
|
cooldown_seconds: { type: "integer", default: 300 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
201: {
|
|
description: "Created rule",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Validation error",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/alerts/rules/{id}": {
|
|
patch: {
|
|
tags: ["Alerts"],
|
|
summary: "Update an alert rule (partial; rule_type is immutable)",
|
|
operationId: "updateAlertRule",
|
|
parameters: [
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
description: "Alert rule ID",
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
config: { type: "object", additionalProperties: true },
|
|
enabled: { type: "boolean" },
|
|
cooldown_seconds: { type: "integer" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Updated rule",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
400: {
|
|
description: "Validation error",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
404: {
|
|
description: "Rule not found",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
delete: {
|
|
tags: ["Alerts"],
|
|
summary: "Delete an alert rule and its fired-alert history",
|
|
operationId: "deleteAlertRule",
|
|
parameters: [
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" },
|
|
description: "Alert rule ID",
|
|
},
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Deletion confirmation",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Rule not found",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/alerts/{id}/ack": {
|
|
post: {
|
|
tags: ["Alerts"],
|
|
summary: "Acknowledge one fired alert",
|
|
operationId: "ackAlert",
|
|
parameters: [
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "integer" },
|
|
description: "Alert event ID",
|
|
},
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Acknowledged alert row",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
404: {
|
|
description: "Alert not found",
|
|
content: {
|
|
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/alerts/ack-all": {
|
|
post: {
|
|
tags: ["Alerts"],
|
|
summary: "Acknowledge all unacked alerts",
|
|
operationId: "ackAllAlerts",
|
|
responses: {
|
|
200: {
|
|
description: "Count of acknowledged alerts",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/webhooks/providers": {
|
|
get: {
|
|
tags: ["Webhooks"],
|
|
summary: "List supported providers + their config fields (for the UI)",
|
|
operationId: "listWebhookProviders",
|
|
responses: {
|
|
200: {
|
|
description: "Provider catalog: label, family, url requirements, fields",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/webhooks": {
|
|
get: {
|
|
tags: ["Webhooks"],
|
|
summary: "List webhook targets (URLs masked, secrets redacted)",
|
|
operationId: "listWebhooks",
|
|
responses: {
|
|
200: {
|
|
description: "All configured webhook targets",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ["Webhooks"],
|
|
summary: "Create a webhook target",
|
|
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)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
201: {
|
|
description: "Created target (redacted)",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
400: { description: "Validation error" },
|
|
},
|
|
},
|
|
},
|
|
"/api/webhooks/{id}": {
|
|
patch: {
|
|
tags: ["Webhooks"],
|
|
summary: "Update a webhook target (partial; type is immutable)",
|
|
operationId: "updateWebhook",
|
|
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
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" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Updated target (redacted)",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
400: { description: "Validation error" },
|
|
404: { description: "Target not found" },
|
|
},
|
|
},
|
|
delete: {
|
|
tags: ["Webhooks"],
|
|
summary: "Delete a webhook target and its delivery log",
|
|
operationId: "deleteWebhook",
|
|
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
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",
|
|
operationId: "testWebhook",
|
|
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
responses: {
|
|
200: {
|
|
description: "Delivery result (ok flag carries the downstream outcome)",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
404: { description: "Target not found" },
|
|
},
|
|
},
|
|
},
|
|
"/api/webhooks/{id}/deliveries": {
|
|
get: {
|
|
tags: ["Webhooks"],
|
|
summary: "Recent delivery log for a target",
|
|
operationId: "listWebhookDeliveries",
|
|
parameters: [
|
|
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
{ name: "offset", in: "query", schema: { type: "integer", default: 0 } },
|
|
],
|
|
responses: {
|
|
200: {
|
|
description: "Delivery rows, newest first",
|
|
content: {
|
|
"application/json": { schema: { type: "object", additionalProperties: true } },
|
|
},
|
|
},
|
|
404: { description: "Target not found" },
|
|
},
|
|
},
|
|
},
|
|
"/api/openapi.json": {
|
|
get: {
|
|
tags: ["Documentation"],
|
|
summary: "Get OpenAPI specification JSON",
|
|
description:
|
|
"Returns this OpenAPI 3.0 document as JSON. Both API explorers consume it: Swagger UI (`/api/docs`) for interactive try-it-out requests, and ReDoc (`/api/redoc`) for a read-optimized reference.",
|
|
operationId: "getOpenApiJson",
|
|
responses: {
|
|
200: {
|
|
description: "OpenAPI document",
|
|
content: {
|
|
"application/json": {
|
|
schema: { type: "object", additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/docs": {
|
|
get: {
|
|
tags: ["Documentation"],
|
|
summary: "Swagger UI explorer",
|
|
description:
|
|
"Interactive Swagger UI rendering of this specification, with try-it-out request execution against the live local server.",
|
|
operationId: "getSwaggerUi",
|
|
responses: {
|
|
200: {
|
|
description: "Swagger UI HTML page",
|
|
content: { "text/html": { schema: { type: "string" } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"/api/redoc": {
|
|
get: {
|
|
tags: ["Documentation"],
|
|
summary: "ReDoc API reference",
|
|
description:
|
|
"Read-optimized, three-panel ReDoc rendering of this specification. The ReDoc bundle is served locally from `/api/redoc/redoc.standalone.js` (bundled with the server, never fetched from a CDN), so the reference works fully offline.",
|
|
operationId: "getRedoc",
|
|
responses: {
|
|
200: {
|
|
description: "ReDoc HTML page",
|
|
content: { "text/html": { schema: { type: "string" } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...(issuesUrl
|
|
? {
|
|
"x-issues-url": issuesUrl,
|
|
}
|
|
: {}),
|
|
};
|
|
|
|
return mergeExtraSpec(spec, extraSpec);
|
|
}
|
|
|
|
module.exports = { createOpenApiSpec };
|