30bc6debde
An archive-sourced marketplace entry ("source": "url" — this project's own)
makes Claude Code unpack the plugin rather than clone it, so the cache dir
has no .git. getUpdatesStatus probed for .git first and returned "not a git
clone; check for updates manually", skipping the entire release layer:
update_available stayed false however many releases were published, and
plugin_marketplace/plugin_name were never set, so POST /api/updates/apply's
plugin branch could never fire (it answered 409, claiming nothing newer was
published). The bug reads as "up to date", so it stayed invisible for as
long as the install happened to be current.
The plugin branch now runs BEFORE the .git probe and answers without git at
all: the repository URL comes from the marketplace manifest on disk
(known_marketplaces.json -> .claude-plugin/marketplace.json -> source.url,
null rather than a guessed host for a github owner/repo shorthand), and
package.json is the only local comparison signal. A failed release lookup
is reported as release_check, never as "nothing newer".
UI/CLI: the "not a git clone" note is gated on install_kind !== "plugin", so
the Update now button is offered where the dashboard can actually check and
apply.
2976 lines
105 KiB
JavaScript
2976 lines
105 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.5.0",
|
|
},
|
|
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 "agenthub-export" for exports from this version).',
|
|
example: "agenthub-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 agenthub_up 1 when the dashboard API is serving this scrape.\n" +
|
|
"# TYPE agenthub_up gauge\n" +
|
|
"agenthub_up 1\n" +
|
|
"# HELP agenthub_sessions Number of sessions by lifecycle status.\n" +
|
|
"# TYPE agenthub_sessions gauge\n" +
|
|
'agenthub_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 a newer release of the dashboard has been published",
|
|
operationId: "getUpdatesStatus",
|
|
responses: {
|
|
200: {
|
|
description: "Update check result",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
description:
|
|
'Reads the newest published release from the forge API (derived from the origin remote) and compares its tag against this install\'s package.json version; falls back to a commit count against the canonical default branch when no release can be read, with the reason in release_check. Includes git_repo, update_available, install_kind ("plugin" | "checkout"), plugin_marketplace, local_version, shallow_clone, release_check, latest_release, latest_release_name, latest_release_url, latest_release_published_at, latest_release_notes, release_sha, commits_behind_release, commits_ahead_of_release, dev_build, commits_behind, remote_ref, local_sha, remote_sha, situation, manual_command, and optional error/message fields. dev_build is true when this install is strictly ahead of the newest release; commits_* fields are null on a shallow clone, where distances are meaningless.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
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/updates/apply": {
|
|
post: {
|
|
tags: ["Updates"],
|
|
summary: "Update this install to the newest release and restart the dashboard",
|
|
description:
|
|
"Two modes, chosen by how this copy was installed and never by the request body (which is ignored). checkout: fast-forward onto the release tag, npm run setup, npm run build in production, then self-restart. plugin: the install runs from ~/.claude/plugins/cache/, so a detached helper runs `claude plugin marketplace update`, `claude plugin update -y`, then plugin-bootstrap.js --force from the NEW cache directory, which stops this server and starts the new one. In both modes the response is written before anything stops the process.",
|
|
operationId: "applyUpdate",
|
|
responses: {
|
|
200: {
|
|
description:
|
|
'Update is being applied; the process is about to be replaced. mode: "plugin" means the detached plugin chain is running, otherwise the checkout was fast-forwarded and rebuilt in-place',
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
description:
|
|
'Same shape as GET /api/updates/status, plus applied: true. In plugin mode also mode: "plugin" and runtime_dir (where the chain writes its verdict, read back afterwards as last_plugin_update).',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
409: {
|
|
description:
|
|
"Declined - nothing to apply, or the checkout isn't safely fast-forwardable",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
additionalProperties: true,
|
|
description:
|
|
'applied: false plus reason: "up_to_date" | "not_fast_forwardable" | "plugin_managed". plugin_managed appears here only when a plugin-cache install has nothing to pull: either release_check is "ok" and the published release is already installed, or the release check did not succeed (the message says which — an unknown is never reported as "nothing newer"). When a newer release exists the plugin chain runs and the answer is 200 instead. Run /agenthub-update to force a refresh.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
500: {
|
|
description: "Update apply 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 };
|