daf5b7be92
- Changes panel: a working file opens in a CodeMirror editor with its diff inline (DiffEditor.tsx, @codemirror/merge, lazy-loaded); Changes folds the unchanged lines, Whole file shows them; side by side at 50/50 and full width; a Revert per block; original from HEAD or the branch's merge base (`mergeBase` on GET /git/changes?range=branch) - Every diff: green = line added whole, red = removed whole, blue = edited in place (--diff-mod) - History drawn as a commit graph (#58: `parents` on GET /git/log, --date-order; lib/commitGraph.ts) - Per-workspace never-stage list, drawn orange in the Files tree (--kept); closed Terminal rows past three deleted - Status bar plan quota for Claude (per account) and Codex (GET /api/pricing/quota, server/lib/quota.js); compact hover panel that stays open, Refresh button with a spinner - Settings → Appearance holds language, theme and pet; Ctrl+Shift+C copies; compact sidebar navigation and short session times
13324 lines
508 KiB
YAML
13324 lines
508 KiB
YAML
# DO NOT EDIT BY HAND. Generated from server/openapi.js via `npm run openapi:yaml`.
|
||
# This YAML mirrors the live spec served at GET /api/openapi.json.
|
||
openapi: 3.0.3
|
||
info:
|
||
title: Agent Dashboard for Claude Code API
|
||
version: 2.2.1
|
||
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
|
||
url: https://git.smartgift.io.vn/Smartgift-AI/AgentHub
|
||
externalDocs:
|
||
description: Project documentation
|
||
url: https://git.smartgift.io.vn/Smartgift-AI/AgentHub#readme
|
||
servers:
|
||
- url: http://localhost:4830
|
||
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
|
||
- name: CcConfig
|
||
description: 'Claude Code configuration explorer: discover and (for low-risk artifacts) edit skills, agents, commands, output styles, MCP servers, hooks, settings, memory, plugins, marketplaces, keybindings, statusline'
|
||
- name: CodexConfig
|
||
description: 'Codex CLI configuration explorer: discover and (for low-risk artifacts) edit skills, agents, rules, memory (AGENTS.md), config.toml, MCP servers, plugins, and marketplaces.'
|
||
- name: Run
|
||
description: Spawn and supervise Claude Code runs from the dashboard (headless or interactive conversation), stream output over WebSocket, and manage run history
|
||
- name: Lanes
|
||
description: Durable parallel-work workspaces (called lanes in the API) and dashboard-managed git worktrees. Every /api/lanes route is also mounted at /api/workspaces — the same handlers, the user-facing name.
|
||
- name: Push
|
||
description: Web Push notification subscriptions and broadcast (VAPID); also fires native Electron notifications when hosted in the desktop app
|
||
- name: AI Tools
|
||
description: The catalog of AI coding CLIs a lane can run (server/data/ai-tools/*.json + DASHBOARD_AI_TOOLS_DIR), what is installed on PATH, and the user's enable/default choice
|
||
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
|
||
tool:
|
||
type: string
|
||
description: 'The AI coding CLI that produced the session: `claude` (Claude Code''s hooks and imported transcripts), `codex` (Codex''s rollout journals and its opt-in hooks) or `antigravity` (agy''s conversation files and its opt-in hooks).'
|
||
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
|
||
tool_call_count:
|
||
type: integer
|
||
minimum: 0
|
||
description: 'Exact tool calls: PreToolUse events with a tool_name, or PostToolUse ones when the session has no PreToolUse (an imported session). tools_used is a top-15 list, so its sum is not this.'
|
||
distinct_tool_count:
|
||
type: integer
|
||
minimum: 0
|
||
description: Number of different tool names this session called.
|
||
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 the CLI that sent it (Claude Code: PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd; Codex adds PermissionRequest and Interrupt; agy sends PreInvocation, PostToolUse and Stop)'
|
||
tool:
|
||
type: string
|
||
enum:
|
||
- claude
|
||
- codex
|
||
- antigravity
|
||
description: 'Which CLI sent the hook (`scripts/hook-handler.js <Event> [codex|antigravity]`). An allowlist: anything else is `claude`.'
|
||
data:
|
||
type: object
|
||
description: The raw hook payload. Claude Code and Codex name the session `session_id`; agy names it `conversationId` and is accepted only with a `transcriptPath` under its own CLI directory.
|
||
properties:
|
||
session_id:
|
||
type: string
|
||
tool_name:
|
||
type: string
|
||
transcript_path:
|
||
type: string
|
||
additionalProperties: true
|
||
HookEventResponse:
|
||
type: object
|
||
required:
|
||
- ok
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
event:
|
||
$ref: '#/components/schemas/DashboardEvent'
|
||
ignored:
|
||
type: boolean
|
||
description: The hook maps to nothing AgentHub records (a Codex sub-agent's lifecycle hook, an agy event other than a turn's first PreInvocation, a PostToolUse or a Stop, or a hook from the Antigravity IDE).
|
||
duplicate:
|
||
type: boolean
|
||
description: The same hook delivered twice within 10 seconds; stored once.
|
||
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)
|
||
source:
|
||
type: string
|
||
nullable: true
|
||
enum:
|
||
- sync
|
||
- user
|
||
- null
|
||
description: sync = kept at LiteLLM's published rate by the automatic sync; user = edited through PUT, never touched by the sync; null = from before this field
|
||
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: The uploaded file's name.
|
||
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: integer
|
||
minimum: 1
|
||
description: Delete old completed/error/abandoned sessions older than this many days. A whole number ≥ 1, else 400 INVALID_PURGE_DAYS (a fraction would purge almost everything).
|
||
dry_run:
|
||
type: boolean
|
||
description: true = answer the same counts and change nothing.
|
||
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
|
||
dry_run:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
description: 'Present (true) only on a dry run: the counts were not applied.'
|
||
CcConfigScope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
description: Discovery scope. `user` = ~/.claude (or CLAUDE_HOME); `project` = <cwd>/.claude (and project CLAUDE.md); `all` = both, merged. Defaults to `all` when omitted or unrecognized.
|
||
example: all
|
||
CcConfigFrontmatter:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
description: Parsed YAML frontmatter from the artifact's markdown header (key → string value). Empty object when the file has no parseable frontmatter. Multi-line values are preserved as raw strings; quotes are stripped.
|
||
example:
|
||
name: code-reviewer
|
||
description: Reviews diffs for regressions
|
||
model: sonnet
|
||
CcConfigSkill:
|
||
type: object
|
||
description: 'A discovered skill: a directory containing a SKILL.md file under <root>/skills/<name>/.'
|
||
required:
|
||
- scope
|
||
- name
|
||
- path
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- frontmatter
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
description: Which root this skill was discovered under.
|
||
example: user
|
||
name:
|
||
type: string
|
||
description: Skill directory name.
|
||
example: code-reviewer
|
||
path:
|
||
type: string
|
||
description: Absolute path to the skill directory.
|
||
example: /Users/son/.claude/skills/code-reviewer
|
||
file:
|
||
type: string
|
||
description: Absolute path to the SKILL.md file.
|
||
example: /Users/son/.claude/skills/code-reviewer/SKILL.md
|
||
size:
|
||
type: integer
|
||
description: Size of SKILL.md in bytes.
|
||
example: 2048
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time of SKILL.md in epoch milliseconds.
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
description: True when SKILL.md exceeds 256 KiB and the read was truncated to the first 256 KiB.
|
||
example: false
|
||
frontmatter:
|
||
$ref: '#/components/schemas/CcConfigFrontmatter'
|
||
preview:
|
||
type: string
|
||
description: First 320 characters of the markdown body (after frontmatter).
|
||
example: Use this skill to review pull requests for correctness and style...
|
||
CcConfigMdItem:
|
||
type: object
|
||
description: A single-file markdown artifact (subagent, slash command, or output style) found under <root>/<subdir>/<name>.md.
|
||
required:
|
||
- scope
|
||
- name
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- frontmatter
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
description: Which root this artifact was discovered under.
|
||
example: project
|
||
name:
|
||
type: string
|
||
description: Artifact name (filename with the trailing .md stripped).
|
||
example: backend-reviewer
|
||
file:
|
||
type: string
|
||
description: Absolute path to the .md file.
|
||
example: /repo/.claude/agents/backend-reviewer.md
|
||
size:
|
||
type: integer
|
||
description: File size in bytes.
|
||
example: 1536
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time in epoch milliseconds.
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
description: True when the file exceeded 256 KiB and was truncated.
|
||
example: false
|
||
frontmatter:
|
||
$ref: '#/components/schemas/CcConfigFrontmatter'
|
||
preview:
|
||
type: string
|
||
description: First 320 characters of the markdown body (after frontmatter).
|
||
example: Review backend route and hook logic for regressions...
|
||
CcConfigPluginContributions:
|
||
type: object
|
||
nullable: true
|
||
description: Counts of artifacts a plugin contributes, plus its parsed .claude-plugin/plugin.json. Null when the plugin's installPath does not exist on disk.
|
||
required:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- outputStyles
|
||
- hooks
|
||
- pluginJson
|
||
properties:
|
||
skills:
|
||
type: integer
|
||
description: Number of skill directories (containing SKILL.md) under the plugin.
|
||
example: 3
|
||
agents:
|
||
type: integer
|
||
description: Number of .md agent files under the plugin.
|
||
example: 2
|
||
commands:
|
||
type: integer
|
||
description: Number of .md command files under the plugin.
|
||
example: 5
|
||
outputStyles:
|
||
type: integer
|
||
description: Number of .md output-style files under the plugin.
|
||
example: 0
|
||
hooks:
|
||
type: integer
|
||
description: Number of files under the plugin's hooks/ directory.
|
||
example: 1
|
||
pluginJson:
|
||
type: object
|
||
nullable: true
|
||
additionalProperties: true
|
||
description: Parsed contents of .claude-plugin/plugin.json, or null when absent/unparseable.
|
||
example:
|
||
name: superpowers
|
||
version: 1.2.0
|
||
CcConfigPlugin:
|
||
type: object
|
||
description: An installed plugin instance derived from the plugins manifest.
|
||
required:
|
||
- key
|
||
- name
|
||
- marketplace
|
||
- scope
|
||
- version
|
||
- installPath
|
||
- installedAt
|
||
- lastUpdated
|
||
- gitCommitSha
|
||
- installPathExists
|
||
- enabled
|
||
- contributes
|
||
properties:
|
||
key:
|
||
type: string
|
||
description: Manifest key for this plugin, either the bare name or `<name>@<marketplace>`.
|
||
example: superpowers@obra
|
||
name:
|
||
type: string
|
||
description: Plugin name (the portion of `key` before any `@`).
|
||
example: superpowers
|
||
marketplace:
|
||
type: string
|
||
nullable: true
|
||
description: Marketplace portion of `key` (after `@`), or null when unscoped.
|
||
example: obra
|
||
scope:
|
||
type: string
|
||
description: Install scope reported by the manifest instance (defaults to `user`).
|
||
example: user
|
||
version:
|
||
type: string
|
||
nullable: true
|
||
description: Installed plugin version, or null.
|
||
example: 1.2.0
|
||
installPath:
|
||
type: string
|
||
nullable: true
|
||
description: Absolute install path, or null.
|
||
example: /Users/son/.claude/plugins/superpowers
|
||
installedAt:
|
||
type: string
|
||
nullable: true
|
||
description: Install timestamp from the manifest, or null.
|
||
example: '2026-05-01T12:00:00.000Z'
|
||
lastUpdated:
|
||
type: string
|
||
nullable: true
|
||
description: Last-updated timestamp from the manifest, or null.
|
||
example: '2026-06-01T09:30:00.000Z'
|
||
gitCommitSha:
|
||
type: string
|
||
nullable: true
|
||
description: Pinned git commit SHA from the manifest, or null.
|
||
example: a1b2c3d4e5f6
|
||
installPathExists:
|
||
type: boolean
|
||
description: True when installPath resolves to an existing directory on disk.
|
||
example: true
|
||
enabled:
|
||
type: boolean
|
||
nullable: true
|
||
description: 'Tri-state enabled flag from settings.json `enabledPlugins`: true, false, or null when the plugin is not listed there.'
|
||
example: true
|
||
contributes:
|
||
$ref: '#/components/schemas/CcConfigPluginContributions'
|
||
CcConfigPluginsResponse:
|
||
type: object
|
||
description: Plugins manifest summary plus the resolved plugin list (user scope only).
|
||
required:
|
||
- manifestPath
|
||
- manifestExists
|
||
- plugins
|
||
properties:
|
||
manifestPath:
|
||
type: string
|
||
description: Absolute path to plugins/installed_plugins.json under the Claude home.
|
||
example: /Users/son/.claude/plugins/installed_plugins.json
|
||
manifestExists:
|
||
type: boolean
|
||
description: True when the manifest file was found and parsed.
|
||
example: true
|
||
plugins:
|
||
type: array
|
||
description: Plugins sorted by `key`.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigPlugin'
|
||
CcConfigMcpServer:
|
||
type: object
|
||
description: A summarized MCP server definition. Sensitive details (header values, env values) are NOT returned — only their key NAMES are surfaced.
|
||
required:
|
||
- name
|
||
- source
|
||
- kind
|
||
properties:
|
||
name:
|
||
type: string
|
||
description: Server name (object key in the source config).
|
||
example: github
|
||
source:
|
||
type: string
|
||
description: Where the definition was found, e.g. `~/.claude.json (top-level)`, `~/.claude.json (projects[<root>])`, or `~/.claude/settings.json`.
|
||
example: ~/.claude.json (top-level)
|
||
removable:
|
||
type: boolean
|
||
description: False when no `claude mcp` scope can address this entry (currently only ~/.claude/settings.json-sourced servers), so POST /mcp/remove has no scope that would find it. Absent/true means removable.
|
||
example: true
|
||
kind:
|
||
type: string
|
||
enum:
|
||
- http
|
||
- stdio
|
||
- unknown
|
||
description: 'Transport kind inferred from the definition: `http` (has url), `stdio` (has command), or `unknown`.'
|
||
example: stdio
|
||
url:
|
||
type: string
|
||
description: Endpoint URL — present only for `http` servers.
|
||
example: https://mcp.example.com/sse
|
||
headers:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Header NAMES (values redacted) — present only for `http` servers. Empty array when no headers.
|
||
example:
|
||
- Authorization
|
||
command:
|
||
type: string
|
||
description: Executable — present only for `stdio` servers.
|
||
example: npx
|
||
args:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Command arguments — present only for `stdio` servers.
|
||
example:
|
||
- '-y'
|
||
- '@modelcontextprotocol/server-github'
|
||
envNames:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Environment-variable NAMES (values redacted) — present only for `stdio` servers.
|
||
example:
|
||
- GITHUB_TOKEN
|
||
CcConfigMcpResponse:
|
||
type: object
|
||
description: 'MCP servers split by where they apply: globally to the user, or scoped to the resolved project (cwd) in ~/.claude.json projects[<root>].'
|
||
required:
|
||
- user
|
||
- projectScoped
|
||
properties:
|
||
user:
|
||
type: array
|
||
description: User/global MCP servers (top-level ~/.claude.json plus ~/.claude/settings.json).
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMcpServer'
|
||
projectScoped:
|
||
type: array
|
||
description: MCP servers scoped to the current project root in ~/.claude.json.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMcpServer'
|
||
CcConfigCliResult:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- stdout
|
||
- stderr
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
CcConfigHookEntry:
|
||
type: object
|
||
description: A single flattened hook command bound to a matcher.
|
||
required:
|
||
- matcher
|
||
- type
|
||
- command
|
||
- timeout
|
||
properties:
|
||
matcher:
|
||
type: string
|
||
description: Tool/event matcher pattern (defaults to `*`).
|
||
example: Bash
|
||
type:
|
||
type: string
|
||
description: Hook type (defaults to `command`).
|
||
example: command
|
||
command:
|
||
type: string
|
||
nullable: true
|
||
description: Shell command to run, or null.
|
||
example: node ~/.claude/hooks/handler.js
|
||
timeout:
|
||
type: integer
|
||
nullable: true
|
||
description: Per-hook timeout in seconds, or null when unset.
|
||
example: 30
|
||
CcConfigHookSource:
|
||
type: object
|
||
description: Hooks read from one settings file. `hooks` is keyed by event name (e.g. PreToolUse, PostToolUse, Stop); each known-event value is an array of flattened hook entries, while unknown events are passed through verbatim.
|
||
required:
|
||
- scope
|
||
- file
|
||
- exists
|
||
- hooks
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- project-local
|
||
description: Which settings file this hook block came from.
|
||
example: user
|
||
file:
|
||
type: string
|
||
description: Absolute path to the settings file.
|
||
example: /Users/son/.claude/settings.json
|
||
exists:
|
||
type: boolean
|
||
description: True when the settings file was found and parsed.
|
||
example: true
|
||
hooks:
|
||
type: object
|
||
description: Event name → array of hook entries (for the known event types) or the raw matcher array (for unknown events). Only non-empty events are included.
|
||
additionalProperties:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigHookEntry'
|
||
example:
|
||
PreToolUse:
|
||
- matcher: Bash
|
||
type: command
|
||
command: node guard.js
|
||
timeout: null
|
||
CcConfigSettingsSource:
|
||
type: object
|
||
description: One settings file's redacted contents. Secret-like keys (matching /token|secret|password|api[_-]?key|auth/i with string values) are replaced with the literal string `<redacted>`.
|
||
required:
|
||
- scope
|
||
- file
|
||
- exists
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- project-local
|
||
description: Which settings file this entry represents.
|
||
example: user
|
||
file:
|
||
type: string
|
||
description: Absolute path to the settings file.
|
||
example: /Users/son/.claude/settings.json
|
||
exists:
|
||
type: boolean
|
||
description: True when the file was found and parsed. When false, `data`/`raw_size` are omitted.
|
||
example: true
|
||
data:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Parsed, secret-redacted settings JSON. Present only when `exists` is true.
|
||
example:
|
||
model: sonnet
|
||
apiKey: <redacted>
|
||
statusLine:
|
||
type: command
|
||
raw_size:
|
||
type: integer
|
||
description: Byte length of the raw file contents. Present only when `exists` is true.
|
||
example: 412
|
||
CcConfigMemoryItem:
|
||
type: object
|
||
description: 'A memory artifact: either a primary CLAUDE.md (scope `user`/`project`) or a per-project file-based auto-memory markdown file (scope `auto-memory`). Auto-memory items additionally carry `project`, `name`, `isIndex`, and parsed `frontmatter`.'
|
||
required:
|
||
- scope
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- auto-memory
|
||
description: '`user`/`project` = the two primary CLAUDE.md files; `auto-memory` = a *.md file under ~/.claude/projects/<slug>/memory/.'
|
||
example: auto-memory
|
||
project:
|
||
type: string
|
||
description: 'Auto-memory only: the ~/.claude/projects/<slug> directory name the file belongs to.'
|
||
example: '-Users-son-repo'
|
||
name:
|
||
type: string
|
||
description: 'Auto-memory only: the markdown filename.'
|
||
example: MEMORY.md
|
||
isIndex:
|
||
type: boolean
|
||
description: 'Auto-memory only: true for index/manifest files (MEMORY.md, INDEX-*.md), which sort first.'
|
||
example: true
|
||
file:
|
||
type: string
|
||
description: Absolute path to the memory file.
|
||
example: /Users/son/.claude/projects/-Users-son-repo/memory/MEMORY.md
|
||
size:
|
||
type: integer
|
||
description: File size in bytes.
|
||
example: 980
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time in epoch milliseconds.
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
description: True when the file exceeded 256 KiB and was truncated.
|
||
example: false
|
||
frontmatter:
|
||
$ref: '#/components/schemas/CcConfigFrontmatter'
|
||
preview:
|
||
type: string
|
||
description: First 480 characters of the body (frontmatter stripped for auto-memory files; raw head for CLAUDE.md).
|
||
example: '## Persistent facts\n- Never run destructive ops without confirmation...'
|
||
CcConfigMarketplace:
|
||
type: object
|
||
description: A known plugin marketplace and (best-effort) its parsed manifest summary.
|
||
required:
|
||
- name
|
||
- source
|
||
- installLocation
|
||
- lastUpdated
|
||
- pluginCount
|
||
- marketplaceName
|
||
- marketplaceDescription
|
||
- marketplaceOwner
|
||
properties:
|
||
name:
|
||
type: string
|
||
description: Marketplace key/name.
|
||
example: obra
|
||
source:
|
||
type: object
|
||
nullable: true
|
||
additionalProperties: true
|
||
description: Source descriptor object (e.g. git/github source), or null.
|
||
example:
|
||
source: github
|
||
repo: obra/superpowers-marketplace
|
||
installLocation:
|
||
type: string
|
||
nullable: true
|
||
description: Absolute path where the marketplace is checked out, or null.
|
||
example: /Users/son/.claude/plugins/marketplaces/obra
|
||
lastUpdated:
|
||
type: string
|
||
nullable: true
|
||
description: Last-updated timestamp, or null.
|
||
example: '2026-06-10T00:00:00.000Z'
|
||
pluginCount:
|
||
type: integer
|
||
nullable: true
|
||
description: Number of plugins in the marketplace manifest, or null when no manifest was readable.
|
||
example: 12
|
||
marketplaceName:
|
||
type: string
|
||
nullable: true
|
||
description: '`name` from the marketplace manifest, or null.'
|
||
example: Superpowers
|
||
marketplaceDescription:
|
||
type: string
|
||
nullable: true
|
||
description: '`description` from the marketplace manifest, or null.'
|
||
example: Curated agent skills
|
||
marketplaceOwner:
|
||
description: '`owner` from the marketplace manifest (object or string), or null.'
|
||
nullable: true
|
||
oneOf:
|
||
- type: object
|
||
additionalProperties: true
|
||
- type: string
|
||
example:
|
||
name: obra
|
||
CcConfigMarketplacesResponse:
|
||
type: object
|
||
description: Known-marketplaces manifest summary plus the resolved marketplace list.
|
||
required:
|
||
- knownPath
|
||
- knownExists
|
||
- items
|
||
properties:
|
||
knownPath:
|
||
type: string
|
||
description: Absolute path to plugins/known_marketplaces.json under the Claude home.
|
||
example: /Users/son/.claude/plugins/known_marketplaces.json
|
||
knownExists:
|
||
type: boolean
|
||
description: True when the known-marketplaces file was found and parsed.
|
||
example: true
|
||
items:
|
||
type: array
|
||
description: Marketplaces sorted by name.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMarketplace'
|
||
CcConfigKeybinding:
|
||
type: object
|
||
description: A single key → action binding.
|
||
required:
|
||
- key
|
||
- action
|
||
properties:
|
||
key:
|
||
type: string
|
||
description: Key chord.
|
||
example: ctrl+s
|
||
action:
|
||
type: string
|
||
description: Action name (stringified).
|
||
example: submit
|
||
CcConfigKeybindingGroup:
|
||
type: object
|
||
description: A context-scoped group of keybindings.
|
||
required:
|
||
- context
|
||
- bindings
|
||
properties:
|
||
context:
|
||
type: string
|
||
description: The context the bindings apply in (empty string when global).
|
||
example: editor
|
||
bindings:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigKeybinding'
|
||
CcConfigKeybindingsResponse:
|
||
type: object
|
||
description: 'Parsed ~/.claude/keybindings.json. When the file is absent, only `{ file, exists: false }` is returned.'
|
||
required:
|
||
- file
|
||
- exists
|
||
properties:
|
||
file:
|
||
type: string
|
||
description: Absolute path to keybindings.json.
|
||
example: /Users/son/.claude/keybindings.json
|
||
exists:
|
||
type: boolean
|
||
description: True when the file was found and parsed.
|
||
example: true
|
||
schema:
|
||
type: string
|
||
nullable: true
|
||
description: '`$schema` value from the file, or null. Present only when `exists` is true.'
|
||
example: https://json.schemastore.org/claude-keybindings
|
||
docs:
|
||
type: string
|
||
nullable: true
|
||
description: '`$docs` value from the file, or null. Present only when `exists` is true.'
|
||
example: https://docs.claude.com/keybindings
|
||
groups:
|
||
type: array
|
||
description: Binding groups. Present only when `exists` is true.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigKeybindingGroup'
|
||
CcConfigStatuslineScript:
|
||
type: object
|
||
description: A statusline script (statusline.py or statusline-command.sh) discovered under the Claude home.
|
||
required:
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- preview
|
||
properties:
|
||
file:
|
||
type: string
|
||
description: Absolute path to the script.
|
||
example: /Users/son/.claude/statusline.py
|
||
size:
|
||
type: integer
|
||
description: File size in bytes.
|
||
example: 1280
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time in epoch milliseconds.
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
description: True when the file exceeded 256 KiB and was truncated.
|
||
example: false
|
||
preview:
|
||
type: string
|
||
description: First 4000 characters of the script body.
|
||
example: '#!/usr/bin/env python3\nimport json, sys\n...'
|
||
CcConfigStatuslineResponse:
|
||
type: object
|
||
description: The statusLine config block from user settings.json plus any statusline scripts.
|
||
required:
|
||
- config
|
||
- scripts
|
||
properties:
|
||
config:
|
||
type: object
|
||
nullable: true
|
||
additionalProperties: true
|
||
description: The `statusLine` object from ~/.claude/settings.json, or null when unset.
|
||
example:
|
||
type: command
|
||
command: python3 ~/.claude/statusline.py
|
||
scripts:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigStatuslineScript'
|
||
CcConfigHookScript:
|
||
type: object
|
||
description: A file in the ~/.claude/hooks/ directory.
|
||
required:
|
||
- name
|
||
- file
|
||
- size
|
||
- mtime
|
||
properties:
|
||
name:
|
||
type: string
|
||
description: File name.
|
||
example: post-tool-use.js
|
||
file:
|
||
type: string
|
||
description: Absolute path to the script.
|
||
example: /Users/son/.claude/hooks/post-tool-use.js
|
||
size:
|
||
type: integer
|
||
description: File size in bytes.
|
||
example: 640
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time in epoch milliseconds.
|
||
example: 1718900000000
|
||
CcConfigHookScriptsResponse:
|
||
type: object
|
||
description: Listing of the ~/.claude/hooks/ directory (files only, sorted by name).
|
||
required:
|
||
- dir
|
||
- items
|
||
properties:
|
||
dir:
|
||
type: string
|
||
description: Absolute path to the hooks directory.
|
||
example: /Users/son/.claude/hooks
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigHookScript'
|
||
CcConfigScopeCount:
|
||
type: object
|
||
description: Per-scope counts (user vs project).
|
||
required:
|
||
- user
|
||
- project
|
||
properties:
|
||
user:
|
||
type: integer
|
||
description: Count under the user root.
|
||
example: 12
|
||
project:
|
||
type: integer
|
||
description: Count under the project root.
|
||
example: 3
|
||
CcConfigOverviewResponse:
|
||
type: object
|
||
description: 'Top-level summary for the Claude Config Explorer landing page: resolved roots plus aggregate counts across every surface.'
|
||
required:
|
||
- roots
|
||
- counts
|
||
properties:
|
||
roots:
|
||
type: object
|
||
description: The four filesystem roots the explorer reads from.
|
||
required:
|
||
- claudeHome
|
||
- projectClaudeDir
|
||
- projectRoot
|
||
- claudeJson
|
||
properties:
|
||
claudeHome:
|
||
type: string
|
||
description: Claude home (CLAUDE_HOME or ~/.claude).
|
||
example: /Users/son/.claude
|
||
projectClaudeDir:
|
||
type: string
|
||
description: Project .claude directory under the resolved cwd.
|
||
example: /repo/.claude
|
||
projectRoot:
|
||
type: string
|
||
description: Resolved project root (the cwd).
|
||
example: /repo
|
||
claudeJson:
|
||
type: string
|
||
description: Path to ~/.claude.json (resolved from $HOME, not CLAUDE_HOME).
|
||
example: /Users/son/.claude.json
|
||
counts:
|
||
type: object
|
||
description: Aggregate counts by surface.
|
||
required:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- outputStyles
|
||
- plugins
|
||
- pluginsEnabled
|
||
- pluginsDisabled
|
||
- marketplaces
|
||
- keybindings
|
||
- mcpServers
|
||
- hooks
|
||
- memory
|
||
- settingsFiles
|
||
properties:
|
||
skills:
|
||
$ref: '#/components/schemas/CcConfigScopeCount'
|
||
agents:
|
||
$ref: '#/components/schemas/CcConfigScopeCount'
|
||
commands:
|
||
$ref: '#/components/schemas/CcConfigScopeCount'
|
||
outputStyles:
|
||
$ref: '#/components/schemas/CcConfigScopeCount'
|
||
plugins:
|
||
type: integer
|
||
description: Total plugins.
|
||
example: 8
|
||
pluginsEnabled:
|
||
type: integer
|
||
description: Plugins explicitly enabled in settings.
|
||
example: 6
|
||
pluginsDisabled:
|
||
type: integer
|
||
description: Plugins explicitly disabled in settings.
|
||
example: 1
|
||
marketplaces:
|
||
type: integer
|
||
description: Known marketplaces.
|
||
example: 2
|
||
keybindings:
|
||
type: integer
|
||
description: Total individual key bindings across all groups.
|
||
example: 24
|
||
mcpServers:
|
||
type: object
|
||
description: MCP server counts by scope.
|
||
required:
|
||
- user
|
||
- project
|
||
properties:
|
||
user:
|
||
type: integer
|
||
example: 3
|
||
project:
|
||
type: integer
|
||
example: 1
|
||
hooks:
|
||
type: object
|
||
description: Total hook entries per settings scope.
|
||
required:
|
||
- user
|
||
- project
|
||
- project-local
|
||
properties:
|
||
user:
|
||
type: integer
|
||
example: 2
|
||
project:
|
||
type: integer
|
||
example: 0
|
||
project-local:
|
||
type: integer
|
||
example: 1
|
||
memory:
|
||
type: integer
|
||
description: Total memory items.
|
||
example: 5
|
||
settingsFiles:
|
||
type: integer
|
||
description: Number of settings files that exist (of the three scopes).
|
||
example: 2
|
||
CcConfigSkillsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigSkill'
|
||
CcConfigAgentsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMdItem'
|
||
CcConfigCommandsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMdItem'
|
||
CcConfigOutputStylesResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMdItem'
|
||
CcConfigHooksResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigHookSource'
|
||
CcConfigSettingsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigSettingsSource'
|
||
CcConfigMemoryResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigMemoryItem'
|
||
CcConfigFileReadResponse:
|
||
type: object
|
||
description: Body of a single file resolved under an allowed root. Returned only on success; failures return an ErrorResponse with HTTP 400.
|
||
required:
|
||
- ok
|
||
- file
|
||
- truncated
|
||
- size
|
||
- text
|
||
- mtime
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
description: Always true on success.
|
||
example: true
|
||
file:
|
||
type: string
|
||
description: Absolute, resolved path that was read.
|
||
example: /Users/son/.claude/skills/code-reviewer/SKILL.md
|
||
truncated:
|
||
type: boolean
|
||
description: True when the file exceeded 256 KiB and only the first 256 KiB are in `text`.
|
||
example: false
|
||
size:
|
||
type: integer
|
||
description: Full file size in bytes.
|
||
example: 2048
|
||
text:
|
||
type: string
|
||
description: File contents (truncated to 256 KiB).
|
||
example: '---\nname: code-reviewer\n---\n# Code Reviewer\n...'
|
||
mtime:
|
||
type: number
|
||
description: Last-modified time in epoch milliseconds.
|
||
example: 1718900000000
|
||
CcConfigFileWriteRequest:
|
||
type: object
|
||
description: Create-or-overwrite request for a low-risk text artifact. `name` is required for every type except `memory` (which targets CLAUDE.md). `project` is required for `auto-memory`. `scope` is ignored for `auto-memory` (always under the Claude home).
|
||
required:
|
||
- scope
|
||
- type
|
||
- content
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
description: 'Target root: `user` (Claude home) or `project` (<cwd>/.claude, or project root for memory). Ignored for `auto-memory`.'
|
||
example: user
|
||
type:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- output-styles
|
||
- memory
|
||
- auto-memory
|
||
description: Artifact type. `skills` writes <root>/skills/<name>/SKILL.md; `agents`/`commands`/`output-styles` write <root>/<type>/<name>.md; `memory` writes the scope's CLAUDE.md; `auto-memory` writes ~/.claude/projects/<project>/memory/<name>.
|
||
example: agents
|
||
name:
|
||
type: string
|
||
description: Artifact name. Required for all types except `memory`. For skills/agents/commands/output-styles must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/. For auto-memory must be a flat *.md filename (/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/i, no `..`).
|
||
example: my-reviewer
|
||
content:
|
||
type: string
|
||
description: Full file contents to write. Must be a string and ≤ 256 KiB (UTF-8 bytes).
|
||
example: '---\nname: my-reviewer\n---\n# My Reviewer\nReview the diff...'
|
||
project:
|
||
type: string
|
||
description: 'Required for `auto-memory`: the ~/.claude/projects/<slug> directory name. Validated against /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,255}$/ and rejected if it contains `..`.'
|
||
example: '-Users-son-repo'
|
||
CcConfigFileWriteResponse:
|
||
type: object
|
||
description: Result of a successful create-or-overwrite.
|
||
required:
|
||
- ok
|
||
- file
|
||
- target
|
||
- backupPath
|
||
- created
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
example: true
|
||
file:
|
||
type: string
|
||
description: Absolute path to the .md file that was written.
|
||
example: /Users/son/.claude/agents/my-reviewer.md
|
||
target:
|
||
type: string
|
||
description: Absolute path to the artifact target (the skill directory for skills; same as `file` otherwise).
|
||
example: /Users/son/.claude/agents/my-reviewer.md
|
||
backupPath:
|
||
type: string
|
||
nullable: true
|
||
description: Absolute path to the timestamped backup created before overwriting, or null when this was a fresh create (nothing to back up).
|
||
example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
created:
|
||
type: boolean
|
||
description: True when the artifact did not exist before this write.
|
||
example: true
|
||
CcConfigFileDeleteRequest:
|
||
type: object
|
||
description: Delete request for a low-risk text artifact. Same (scope, type, name, project) semantics as the write request, without `content`.
|
||
required:
|
||
- scope
|
||
- type
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
description: Target root. Ignored for `auto-memory`.
|
||
example: user
|
||
type:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- output-styles
|
||
- memory
|
||
- auto-memory
|
||
description: Artifact type to delete.
|
||
example: agents
|
||
name:
|
||
type: string
|
||
description: Artifact name. Required for all types except `memory`.
|
||
example: my-reviewer
|
||
project:
|
||
type: string
|
||
description: 'Required for `auto-memory`: the projects/<slug> directory name.'
|
||
example: '-Users-son-repo'
|
||
CcConfigFileDeleteResponse:
|
||
type: object
|
||
description: Result of a successful delete (a mandatory backup is created first).
|
||
required:
|
||
- ok
|
||
- file
|
||
- target
|
||
- backupPath
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
example: true
|
||
file:
|
||
type: string
|
||
description: Absolute path to the .md file inside the deleted target.
|
||
example: /Users/son/.claude/agents/my-reviewer.md
|
||
target:
|
||
type: string
|
||
description: Absolute path to the deleted artifact (skill directory for skills).
|
||
example: /Users/son/.claude/agents/my-reviewer.md
|
||
backupPath:
|
||
type: string
|
||
nullable: true
|
||
description: Absolute path to the backup taken before deletion.
|
||
example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
CcConfigBackup:
|
||
type: object
|
||
description: A single backup entry under a cc-config-backups directory.
|
||
required:
|
||
- scope
|
||
- type
|
||
- name
|
||
- backupPath
|
||
- isDir
|
||
- mtime
|
||
- size
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- auto-memory
|
||
description: Scope the backup belongs to. `auto-memory` backups are per-project.
|
||
example: user
|
||
project:
|
||
type: string
|
||
description: 'Auto-memory only: the projects/<slug> directory name.'
|
||
example: '-Users-son-repo'
|
||
type:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- output-styles
|
||
- memory
|
||
- auto-memory
|
||
description: Artifact type the backup was taken from.
|
||
example: agents
|
||
name:
|
||
type: string
|
||
description: Backup file/directory name (includes the timestamp and .bak suffix).
|
||
example: my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
backupPath:
|
||
type: string
|
||
description: Absolute path to the backup.
|
||
example: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
isDir:
|
||
type: boolean
|
||
description: True when the backup is a directory (skill backups).
|
||
example: false
|
||
mtime:
|
||
type: number
|
||
description: Backup modification time in epoch milliseconds (results sort newest first).
|
||
example: 1718900000000
|
||
size:
|
||
type: integer
|
||
nullable: true
|
||
description: Backup size in bytes, or null for directory backups.
|
||
example: 2048
|
||
CcConfigBackupsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
description: Backups across the requested scope/type buckets, sorted newest first.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigBackup'
|
||
CcConfigAccountIdentity:
|
||
type: object
|
||
description: Identity of one Anthropic account. Deliberately token-free - the auth-profile endpoints never return credential material, only enough to tell accounts apart.
|
||
properties:
|
||
accountUuid:
|
||
type: string
|
||
nullable: true
|
||
example: c9b05d52-391c-449b-a6b1-e161edea7d8a
|
||
email:
|
||
type: string
|
||
nullable: true
|
||
example: you@example.com
|
||
displayName:
|
||
type: string
|
||
nullable: true
|
||
example: Vĩ
|
||
organization:
|
||
type: string
|
||
nullable: true
|
||
example: Smartgift
|
||
seatTier:
|
||
type: string
|
||
nullable: true
|
||
example: team_standard
|
||
subscriptionType:
|
||
type: string
|
||
nullable: true
|
||
example: max
|
||
expiresAt:
|
||
type: integer
|
||
nullable: true
|
||
description: Access-token expiry, epoch ms. A switch still works past it - the refresh token is stored too.
|
||
example: 1788776815474
|
||
CcConfigProfile:
|
||
allOf:
|
||
- $ref: '#/components/schemas/CcConfigAccountIdentity'
|
||
- type: object
|
||
required:
|
||
- name
|
||
- label
|
||
- active
|
||
properties:
|
||
name:
|
||
type: string
|
||
description: Internal identifier; also its filename under `<CLAUDE_HOME>/agenthub-profiles/`. Every mutation route takes this, and a rename never changes it.
|
||
pattern: ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,39}$
|
||
example: work
|
||
label:
|
||
type: string
|
||
description: Display name the user edits. Free-form (spaces and diacritics allowed) because it is never used as a path; falls back to `name` for profiles saved before labels existed.
|
||
example: Tài khoản chính
|
||
savedAt:
|
||
type: integer
|
||
nullable: true
|
||
description: When the profile was last snapshotted, epoch ms.
|
||
example: 1788743353379
|
||
active:
|
||
type: boolean
|
||
description: True when this profile's account is the one logged in right now.
|
||
CcConfigProfilesResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
- active
|
||
properties:
|
||
items:
|
||
type: array
|
||
description: Saved profiles, sorted by name.
|
||
items:
|
||
$ref: '#/components/schemas/CcConfigProfile'
|
||
active:
|
||
nullable: true
|
||
description: The account logged in right now, or null when nobody is.
|
||
allOf:
|
||
- $ref: '#/components/schemas/CcConfigAccountIdentity'
|
||
CcConfigProfileActivateResult:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- name
|
||
- savedOutgoing
|
||
- written
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
name:
|
||
type: string
|
||
example: work
|
||
savedOutgoing:
|
||
type: string
|
||
nullable: true
|
||
description: Profile the outgoing account was re-snapshotted into (so tokens Claude Code refreshed since its last save are not lost), or null when the target was already active.
|
||
example: personal
|
||
written:
|
||
type: array
|
||
description: Absolute paths rewritten by the switch.
|
||
items:
|
||
type: string
|
||
example:
|
||
- /Users/son/.claude/.credentials.json
|
||
- /Users/son/.claude.json
|
||
CcConfigUsageWindow:
|
||
type: object
|
||
nullable: true
|
||
description: One quota window as the account reports it.
|
||
properties:
|
||
utilization:
|
||
type: number
|
||
nullable: true
|
||
description: Percent of the window's allowance already spent, 0-100.
|
||
example: 48
|
||
resetsAt:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: When the window rolls over, when the account reports one.
|
||
CodexConfigScope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
description: Discovery scope. `user` = $CODEX_HOME (or ~/.codex); `project` = <cwd>/.codex; `all` = both, merged. Defaults to `all` when omitted or unrecognized.
|
||
example: all
|
||
CodexConfigSkill:
|
||
type: object
|
||
description: 'A discovered skill: a directory containing a SKILL.md under <root>/skills/<name>/.'
|
||
required:
|
||
- scope
|
||
- name
|
||
- path
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- frontmatter
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
example: user
|
||
name:
|
||
type: string
|
||
example: code-reviewer
|
||
path:
|
||
type: string
|
||
example: /home/user/.codex/skills/code-reviewer
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/skills/code-reviewer/SKILL.md
|
||
size:
|
||
type: integer
|
||
example: 2048
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
example: false
|
||
frontmatter:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
example:
|
||
name: code-reviewer
|
||
description: Reviews diffs
|
||
preview:
|
||
type: string
|
||
example: Use this skill to review pull requests...
|
||
CodexConfigAgent:
|
||
type: object
|
||
description: A discovered TOML agent definition (<root>/agents/<name>.toml).
|
||
required:
|
||
- scope
|
||
- name
|
||
- file
|
||
- size
|
||
- mtime
|
||
- meta
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
example: user
|
||
name:
|
||
type: string
|
||
example: reviewer
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/agents/reviewer.toml
|
||
size:
|
||
type: integer
|
||
example: 512
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
meta:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Best-effort top-level scalar fields (name, description, model, sandbox_mode).
|
||
example:
|
||
name: reviewer
|
||
model: gpt-5
|
||
preview:
|
||
type: string
|
||
example: |-
|
||
name = "reviewer"
|
||
model = "gpt-5"
|
||
CodexConfigRule:
|
||
type: object
|
||
description: A discovered rules file (<root>/rules/<name>.rules).
|
||
required:
|
||
- scope
|
||
- name
|
||
- file
|
||
- size
|
||
- mtime
|
||
- preview
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
example: user
|
||
name:
|
||
type: string
|
||
example: default
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/rules/default.rules
|
||
size:
|
||
type: integer
|
||
example: 128
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
preview:
|
||
type: string
|
||
example: allow
|
||
CodexConfigMemoryEntry:
|
||
type: object
|
||
description: AGENTS.md at either the Codex home or the project root.
|
||
required:
|
||
- scope
|
||
- file
|
||
- exists
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
example: project
|
||
file:
|
||
type: string
|
||
example: /repo/AGENTS.md
|
||
exists:
|
||
type: boolean
|
||
example: true
|
||
size:
|
||
type: integer
|
||
example: 256
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
preview:
|
||
type: string
|
||
example: |-
|
||
# Project memory
|
||
Hello.
|
||
CodexConfigConfigEntry:
|
||
type: object
|
||
description: config.toml at either the Codex home or the project .codex dir.
|
||
required:
|
||
- scope
|
||
- file
|
||
- exists
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
example: user
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/config.toml
|
||
exists:
|
||
type: boolean
|
||
example: true
|
||
size:
|
||
type: integer
|
||
example: 640
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
content:
|
||
type: string
|
||
example: |
|
||
model = "gpt-5"
|
||
truncated:
|
||
type: boolean
|
||
example: false
|
||
CodexConfigOverviewResponse:
|
||
type: object
|
||
required:
|
||
- roots
|
||
- counts
|
||
properties:
|
||
roots:
|
||
type: object
|
||
properties:
|
||
codexHome:
|
||
type: string
|
||
example: /home/user/.codex
|
||
projectCodexDir:
|
||
type: string
|
||
example: /repo/.codex
|
||
projectRoot:
|
||
type: string
|
||
example: /repo
|
||
counts:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
user:
|
||
type: integer
|
||
project:
|
||
type: integer
|
||
example:
|
||
skills:
|
||
user: 3
|
||
project: 1
|
||
agents:
|
||
user: 2
|
||
project: 0
|
||
rules:
|
||
user: 1
|
||
project: 0
|
||
memory:
|
||
user: 1
|
||
project: 1
|
||
config:
|
||
user: 1
|
||
project: 0
|
||
CodexConfigFileReadResponse:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- file
|
||
- size
|
||
- mtime
|
||
- truncated
|
||
- text
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/rules/default.rules
|
||
size:
|
||
type: integer
|
||
example: 128
|
||
mtime:
|
||
type: number
|
||
example: 1718900000000
|
||
truncated:
|
||
type: boolean
|
||
example: false
|
||
text:
|
||
type: string
|
||
example: allow
|
||
CodexConfigFileWriteResponse:
|
||
type: object
|
||
required:
|
||
- file
|
||
- created
|
||
- backup
|
||
properties:
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/rules/default.rules
|
||
created:
|
||
type: boolean
|
||
example: true
|
||
backup:
|
||
type: object
|
||
nullable: true
|
||
additionalProperties: true
|
||
description: Backup metadata when an existing file was overwritten; null on first create.
|
||
CodexConfigFileDeleteResponse:
|
||
type: object
|
||
required:
|
||
- file
|
||
- backup
|
||
properties:
|
||
file:
|
||
type: string
|
||
example: /home/user/.codex/rules/default.rules
|
||
backup:
|
||
type: object
|
||
additionalProperties: true
|
||
CodexConfigBackupsResponse:
|
||
type: object
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
type:
|
||
type: string
|
||
example: rules
|
||
name:
|
||
type: string
|
||
example: default.rules.2026-06-25T12-00-00.000Z.bak
|
||
file:
|
||
type: string
|
||
mtime:
|
||
type: number
|
||
size:
|
||
type: integer
|
||
nullable: true
|
||
CodexConfigCliActionResponse:
|
||
type: object
|
||
description: Result of a successful mutating `codex` CLI call.
|
||
required:
|
||
- ok
|
||
- stdout
|
||
- stderr
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
RunHandle:
|
||
type: object
|
||
description: Live, in-memory view of a spawned `claude` subprocess. Returned by GET /api/run (in `items`), POST /api/run (201), and GET /api/run/{id}. Backed by the spawner's handle map, which reaps each handle 5 minutes after the process exits — after that the run is only visible via GET /api/run/history.
|
||
required:
|
||
- id
|
||
- pid
|
||
- mode
|
||
- cwd
|
||
- model
|
||
- permissionMode
|
||
- effort
|
||
- prompt
|
||
- argv
|
||
- resumeSessionId
|
||
- status
|
||
- startedAt
|
||
- endedAt
|
||
- exitCode
|
||
- signal
|
||
- error
|
||
- sessionId
|
||
- envelopeCount
|
||
- stdoutTail
|
||
- stderrTail
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
description: Server-generated run id (UUID v4). Distinct from the Claude session id.
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid:
|
||
type: integer
|
||
nullable: true
|
||
description: OS process id of the spawned `claude` child, or null if the spawn failed.
|
||
example: 48213
|
||
mode:
|
||
type: string
|
||
enum:
|
||
- headless
|
||
- conversation
|
||
description: '`headless`: single-shot — prompt is passed via argv `-p`, stdin is closed, the process exits after one turn. `conversation`: multi-turn — stdin stays open (`--input-format stream-json`) and follow-up turns are delivered via POST /api/run/{id}/message.'
|
||
example: conversation
|
||
cwd:
|
||
type: string
|
||
description: Absolute working directory the child was spawned in (sanitised at request time — must be an existing absolute directory).
|
||
example: /Users/dev/projects/my-app
|
||
model:
|
||
type: string
|
||
nullable: true
|
||
description: Model alias/id passed via `--model`, or null to inherit the user's default.
|
||
example: claude-opus-4-8
|
||
permissionMode:
|
||
type: string
|
||
enum:
|
||
- auto
|
||
- acceptEdits
|
||
- default
|
||
- plan
|
||
- bypassPermissions
|
||
description: Permission mode passed via `--permission-mode`. Defaults to `auto` when omitted or invalid.
|
||
example: auto
|
||
effort:
|
||
type: string
|
||
enum:
|
||
- low
|
||
- medium
|
||
- high
|
||
- xhigh
|
||
- max
|
||
nullable: true
|
||
description: Thinking-effort level passed via `--effort` (higher = more reasoning tokens before the assistant turn). Null inherits the model default.
|
||
example: high
|
||
prompt:
|
||
type: string
|
||
description: The initial user prompt. May be empty only when resuming a conversation (the child idles on the resumed transcript until a follow-up arrives).
|
||
example: Refactor the auth module and add tests.
|
||
argv:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Exact argv vector passed to the `claude` binary (always includes `--output-format stream-json --verbose --include-partial-messages` plus the resolved flags).
|
||
example:
|
||
- '--output-format'
|
||
- stream-json
|
||
- '--verbose'
|
||
- '--include-partial-messages'
|
||
- '--permission-mode'
|
||
- acceptEdits
|
||
- '--input-format'
|
||
- stream-json
|
||
- '--model'
|
||
- claude-opus-4-8
|
||
resumeSessionId:
|
||
type: string
|
||
nullable: true
|
||
description: Claude session id this run resumed (`--resume`), or null for a fresh run. Conversation mode only.
|
||
example: null
|
||
status:
|
||
type: string
|
||
enum:
|
||
- spawning
|
||
- running
|
||
- completed
|
||
- error
|
||
- killed
|
||
description: Lifecycle status. `spawning` -> `running` on the first parsed output envelope; terminal states are `completed` (exit 0), `error` (non-zero exit or spawn error), or `killed` (terminated via DELETE). Each transition is broadcast as a `run_status` WebSocket message.
|
||
example: running
|
||
startedAt:
|
||
type: integer
|
||
format: int64
|
||
description: Spawn time in epoch milliseconds.
|
||
example: 1718000000000
|
||
endedAt:
|
||
type: integer
|
||
format: int64
|
||
nullable: true
|
||
description: Exit time in epoch milliseconds, or null while still live.
|
||
example: null
|
||
exitCode:
|
||
type: integer
|
||
nullable: true
|
||
description: Process exit code once the child exits; null while running or if killed by signal.
|
||
example: null
|
||
signal:
|
||
type: string
|
||
nullable: true
|
||
description: Terminating signal name (e.g. `SIGTERM`/`SIGKILL`) if the child was killed by a signal; null otherwise.
|
||
example: null
|
||
error:
|
||
type: string
|
||
nullable: true
|
||
description: Spawn/runtime error message if the child failed to start or errored; null otherwise.
|
||
example: null
|
||
sessionId:
|
||
type: string
|
||
nullable: true
|
||
description: Claude session id, captured from the `system/init` stream envelope (optimistically pre-seeded with resumeSessionId). Used to deep-link to /api/sessions/{id}. Null until the init envelope arrives.
|
||
example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
envelopeCount:
|
||
type: integer
|
||
minimum: 0
|
||
description: Total stream-json envelopes parsed for this run so far (monotonic; not capped by the in-memory replay buffer).
|
||
example: 12
|
||
stdoutTail:
|
||
type: string
|
||
description: Trailing slice of the child's raw stdout (capped at ~4 KiB) for quick diagnostics.
|
||
example: |
|
||
{"type":"result","subtype":"success",...}
|
||
stderrTail:
|
||
type: string
|
||
description: Trailing slice of the child's raw stderr (capped at ~4 KiB); also captures parse errors.
|
||
example: ''
|
||
RunHandleWithEnvelopes:
|
||
allOf:
|
||
- $ref: '#/components/schemas/RunHandle'
|
||
- type: object
|
||
description: RunHandle plus the in-memory envelope replay buffer, returned by GET /api/run/{id}?envelopes=1.
|
||
required:
|
||
- envelopes
|
||
properties:
|
||
envelopes:
|
||
type: array
|
||
description: The most recent stream-json envelopes (capped at 500 per handle) passed through verbatim from the `claude` child — system/init, assistant text + tool_use, user tool_result, result/success, partial stream_event deltas, etc. Lets a late-attaching client replay what it missed. Full transcript is always available via /api/sessions/{id}.
|
||
items:
|
||
type: object
|
||
additionalProperties: true
|
||
RunListResponse:
|
||
type: object
|
||
description: All currently-tracked run handles (newest first) plus concurrency telemetry.
|
||
required:
|
||
- items
|
||
- maxConcurrent
|
||
- activeCount
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/RunHandle'
|
||
description: Live + recently-exited handles, sorted by startedAt descending. Handles are reaped 5 minutes after exit.
|
||
maxConcurrent:
|
||
type: integer
|
||
description: Concurrency cap (env `RUN_MAX_CONCURRENT`, default effectively uncapped at 10000). Spawns over this throw ECONCURRENCY (HTTP 429).
|
||
example: 10000
|
||
activeCount:
|
||
type: integer
|
||
minimum: 0
|
||
description: Number of handles currently in `spawning` or `running` status.
|
||
example: 2
|
||
RunHistoryItem:
|
||
type: object
|
||
description: A persisted run record from the `dashboard_runs` sqlite table. Survives the 5-minute in-memory reap so past runs remain visible and resumable. Fields are snake_case (DB column names) and timestamps are ISO-8601 strings, distinct from the camelCase epoch-ms fields on RunHandle.
|
||
required:
|
||
- id
|
||
- session_id
|
||
- mode
|
||
- cwd
|
||
- model
|
||
- permission_mode
|
||
- effort
|
||
- resume_session_id
|
||
- prompt_preview
|
||
- status
|
||
- exit_code
|
||
- started_at
|
||
- ended_at
|
||
- isLive
|
||
- lane_id
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
description: Run id (matches RunHandle.id).
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
session_id:
|
||
type: string
|
||
nullable: true
|
||
description: Claude session id once known (from system/init), else null.
|
||
example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
mode:
|
||
type: string
|
||
enum:
|
||
- headless
|
||
- conversation
|
||
description: Run mode at spawn time.
|
||
example: conversation
|
||
cwd:
|
||
type: string
|
||
description: Absolute working directory the run was spawned in.
|
||
example: /Users/dev/projects/my-app
|
||
model:
|
||
type: string
|
||
nullable: true
|
||
description: Model used, or null for the default.
|
||
example: claude-opus-4-8
|
||
permission_mode:
|
||
type: string
|
||
nullable: true
|
||
enum:
|
||
- auto
|
||
- acceptEdits
|
||
- default
|
||
- plan
|
||
- bypassPermissions
|
||
description: Permission mode recorded at spawn time.
|
||
example: auto
|
||
effort:
|
||
type: string
|
||
nullable: true
|
||
enum:
|
||
- low
|
||
- medium
|
||
- high
|
||
- xhigh
|
||
- max
|
||
description: Effort level recorded at spawn time, or null.
|
||
example: high
|
||
resume_session_id:
|
||
type: string
|
||
nullable: true
|
||
description: Session id this run resumed, or null.
|
||
example: null
|
||
prompt_preview:
|
||
type: string
|
||
nullable: true
|
||
description: First 500 characters of the initial prompt (truncated preview for the history list), or null.
|
||
example: Refactor the auth module and add tests.
|
||
status:
|
||
type: string
|
||
enum:
|
||
- spawning
|
||
- running
|
||
- completed
|
||
- error
|
||
- killed
|
||
- abandoned
|
||
description: Persisted status. `abandoned` is set on server boot for rows left `running`/`spawning` by a previous process (their in-memory handles were wiped by the restart) so the UI does not show them as live.
|
||
example: completed
|
||
exit_code:
|
||
type: integer
|
||
nullable: true
|
||
description: Recorded process exit code, or null.
|
||
example: 0
|
||
started_at:
|
||
type: string
|
||
format: date-time
|
||
description: Spawn time (ISO-8601).
|
||
example: '2026-06-25T17:00:00.000Z'
|
||
ended_at:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: Exit time (ISO-8601), or null if still running / never recorded.
|
||
example: '2026-06-25T17:01:42.500Z'
|
||
isLive:
|
||
type: boolean
|
||
description: 'Computed at request time: true when a live in-memory handle for this id is currently `running` or `spawning`. Lets the UI mark which history rows are re-attachable.'
|
||
example: false
|
||
lane_id:
|
||
type: integer
|
||
nullable: true
|
||
description: Lane this run was started through (POST /api/lanes/{id}/start), or null for a run spawned straight from POST /api/run. Filterable via ?laneId=.
|
||
example: null
|
||
RunHistoryResponse:
|
||
type: object
|
||
description: Persisted run history, most recent first. Empty `items` when the persistence DB is unavailable.
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/RunHistoryItem'
|
||
RunCwdSuggestion:
|
||
type: object
|
||
description: A suggested working directory for spawning a run. Only directories that still exist on disk are returned.
|
||
required:
|
||
- kind
|
||
- path
|
||
- label
|
||
properties:
|
||
kind:
|
||
type: string
|
||
enum:
|
||
- dashboard
|
||
- home
|
||
- recent
|
||
description: '`dashboard`: the dashboard server''s own cwd (always first). `home`: $HOME. `recent`: distinct cwds Claude Code has been used in, sourced from the sessions table.'
|
||
example: recent
|
||
path:
|
||
type: string
|
||
description: Absolute, resolved directory path.
|
||
example: /Users/dev/projects/my-app
|
||
label:
|
||
type: string
|
||
description: Human-friendly label (defaults to the directory basename).
|
||
example: my-app
|
||
RunCwdsResponse:
|
||
type: object
|
||
description: Working-directory suggestions for the Run launcher.
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/RunCwdSuggestion'
|
||
RunFilesResponse:
|
||
type: object
|
||
description: File-path autocomplete results for the prompt editor's `@` references — up to 40 paths relative to the given cwd, shortest first.
|
||
required:
|
||
- items
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Paths relative to the resolved cwd. Dotfiles (except .env/.gitignore) and heavy build dirs (node_modules, .git, dist, build, etc.) are skipped.
|
||
example:
|
||
- package.json
|
||
- src/index.ts
|
||
- server/routes/run.js
|
||
RunBinaryResponse:
|
||
type: object
|
||
description: Whether the `claude` binary is resolvable on PATH (probed via which/where; the binary is not invoked). Lets the UI warn before the user clicks Run.
|
||
required:
|
||
- found
|
||
- path
|
||
properties:
|
||
found:
|
||
type: boolean
|
||
description: True when `claude` resolves on PATH.
|
||
example: true
|
||
path:
|
||
type: string
|
||
nullable: true
|
||
description: Absolute path to the resolved `claude` binary, or null when not found.
|
||
example: /usr/local/bin/claude
|
||
RunSpawnRequest:
|
||
type: object
|
||
description: 'Request body for spawning a run. SIDE EFFECT: a successful call spawns a real `claude` process in `cwd`, begins streaming `run_stream`/`run_status` WebSocket messages, and persists a row to `dashboard_runs`.'
|
||
required:
|
||
- prompt
|
||
properties:
|
||
prompt:
|
||
type: string
|
||
description: Initial user prompt. Required and non-empty UNLESS mode is `conversation` AND resumeSessionId is set (a resumed conversation may start empty and idle on stdin).
|
||
example: Refactor the auth module and add tests.
|
||
mode:
|
||
type: string
|
||
enum:
|
||
- headless
|
||
- conversation
|
||
default: conversation
|
||
description: '`headless` for a single-shot run; `conversation` for a multi-turn run that accepts follow-ups via POST /api/run/{id}/message. Any value other than `headless` is treated as `conversation`.'
|
||
example: conversation
|
||
cwd:
|
||
type: string
|
||
description: Absolute working directory for the child. Must exist as a directory at request time. Omitted/empty defaults to the dashboard server's cwd. Non-absolute or missing paths are rejected with EBADCWD.
|
||
example: /Users/dev/projects/my-app
|
||
model:
|
||
type: string
|
||
description: Optional model alias/id passed via `--model`. Omit to inherit the user's default.
|
||
example: claude-opus-4-8
|
||
resumeSessionId:
|
||
type: string
|
||
description: Optional Claude session id to resume via `--resume`. Must match /^[A-Za-z0-9-]{8,}$/ and requires conversation mode, else EBADSESSION / EBADMODE.
|
||
example: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
effort:
|
||
type: string
|
||
enum:
|
||
- low
|
||
- medium
|
||
- high
|
||
- xhigh
|
||
- max
|
||
description: Optional thinking-effort level passed via `--effort`. An invalid value is rejected with EBADEFFORT.
|
||
example: high
|
||
permissionMode:
|
||
type: string
|
||
enum:
|
||
- auto
|
||
- acceptEdits
|
||
- default
|
||
- plan
|
||
- bypassPermissions
|
||
default: auto
|
||
description: Permission mode passed via `--permission-mode`. Unknown values silently fall back to `auto`.
|
||
example: auto
|
||
RunSpawnConcurrencyResponse:
|
||
type: object
|
||
description: HTTP 429 body when the concurrency cap is reached (ECONCURRENCY). In addition to the standard `error` envelope, this response carries a top-level `running` array listing the runs currently occupying the cap.
|
||
required:
|
||
- error
|
||
- running
|
||
properties:
|
||
error:
|
||
$ref: '#/components/schemas/ErrorObject'
|
||
running:
|
||
type: array
|
||
description: The live runs (status running/spawning) that are holding the concurrency slots.
|
||
items:
|
||
type: object
|
||
required:
|
||
- id
|
||
- pid
|
||
- startedAt
|
||
- mode
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid:
|
||
type: integer
|
||
nullable: true
|
||
example: 48213
|
||
startedAt:
|
||
type: integer
|
||
format: int64
|
||
example: 1718000000000
|
||
mode:
|
||
type: string
|
||
enum:
|
||
- headless
|
||
- conversation
|
||
example: conversation
|
||
RunMessageRequest:
|
||
type: object
|
||
description: 'Request body for sending a follow-up turn into a running conversation. SIDE EFFECT: writes a stream-json user envelope to the child''s stdin and broadcasts a `run_input_ack` WebSocket message.'
|
||
required:
|
||
- text
|
||
properties:
|
||
text:
|
||
type: string
|
||
description: The follow-up user message. Required and non-empty (else EBADINPUT).
|
||
example: Also update the README to document the new flags.
|
||
RunMessageResponse:
|
||
type: object
|
||
description: Acknowledgement that the follow-up turn was written to the child's stdin.
|
||
required:
|
||
- messageId
|
||
properties:
|
||
messageId:
|
||
type: string
|
||
format: uuid
|
||
description: Server-generated id for this input turn; echoed in the `run_input_ack` WebSocket message.
|
||
example: 9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f
|
||
RunKillResponse:
|
||
type: object
|
||
description: Acknowledgement that a kill was issued (or that the run had already terminated).
|
||
required:
|
||
- ok
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
example: true
|
||
LaneWorktreeCreateRequest:
|
||
type: object
|
||
required:
|
||
- sourceRepo
|
||
properties:
|
||
sourceRepo:
|
||
type: string
|
||
description: Existing absolute path to the source git repository.
|
||
example: /Users/me/src/project
|
||
title:
|
||
type: string
|
||
description: Human-readable lane title.
|
||
example: Criteria form
|
||
base:
|
||
type: string
|
||
description: Preferred base branch. Defaults to the LANE_BASE_BRANCH env var, or `main` when that is also unset.
|
||
example: main
|
||
slug:
|
||
type: string
|
||
description: Optional branch/directory slug override.
|
||
example: criteria-form
|
||
LaneEnsureRequest:
|
||
type: object
|
||
required:
|
||
- cwd
|
||
properties:
|
||
cwd:
|
||
type: string
|
||
description: Absolute working directory to find or adopt a lane for. A lane whose own cwd is this path, or the closest path-boundary parent of it, is returned as-is.
|
||
example: /Users/me/src/project/packages/app
|
||
title:
|
||
type: string
|
||
description: Title for the lane if one has to be created; ignored when one already exists.
|
||
example: App package
|
||
exact:
|
||
type: boolean
|
||
description: Only a lane on this very directory counts; a parent directory's lane is not returned, so a nested directory gets its own lane.
|
||
LaneDestructiveActionRequest:
|
||
type: object
|
||
required:
|
||
- confirm
|
||
- expect
|
||
properties:
|
||
confirm:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
force:
|
||
type: boolean
|
||
description: Required by reset/remove when unpushed commits exist.
|
||
expect:
|
||
type: object
|
||
description: 'Required complete facts returned by the preceding preflight: head, dirty, untracked, unpushed for reset/remove; sessions, events, tokenRows for purge. Differences return 409 ESTALE without destructive work.'
|
||
additionalProperties: true
|
||
LaneStageDetectionFields:
|
||
type: object
|
||
description: Fields the server's stage-detection heuristic (server/lib/stage-detect.js) adds to every lane returned by GET /api/lanes and GET /api/lanes/:id. An inferred stage is never evidence and never renders as done — see docs/LANES.md#stage-detection.
|
||
properties:
|
||
detected_stage:
|
||
type: string
|
||
nullable: true
|
||
description: Stage id inferred from ingested tool events, or null if no signal has been seen. Independent of the agent's own declared `stage`.
|
||
detected_signal:
|
||
type: string
|
||
nullable: true
|
||
description: The tool-event signal that produced detected_stage, capped at 120 characters; null when detected_stage is null.
|
||
detected:
|
||
type: boolean
|
||
description: Present on each entry of pipeline_nodes. True for the inferred node and any node before it that carries no declared record; decorates that node's state without ever upgrading it to done.
|
||
PushVapidKeyResponse:
|
||
type: object
|
||
required:
|
||
- publicKey
|
||
description: The server's VAPID public key. The browser passes this base64url-encoded key to `PushManager.subscribe({ applicationServerKey })` so the push service will accept deliveries signed by this server's private key.
|
||
properties:
|
||
publicKey:
|
||
type: string
|
||
description: Base64url-encoded VAPID (P-256 ECDSA) public application server key. Generated once and persisted alongside the SQLite DB so the web app and native apps share one key pair.
|
||
example: BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8
|
||
PushSubscriptionKeys:
|
||
type: object
|
||
required:
|
||
- p256dh
|
||
- auth
|
||
description: Client encryption keys produced by the browser's PushManager subscription. Both are required to encrypt Web Push payloads for the endpoint.
|
||
properties:
|
||
p256dh:
|
||
type: string
|
||
description: Base64url-encoded P-256 ECDH public key from the browser subscription (`subscription.getKey('p256dh')`). Stored verbatim in the `push_subscriptions` table.
|
||
example: BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM
|
||
auth:
|
||
type: string
|
||
description: Base64url-encoded auth secret from the browser subscription (`subscription.getKey('auth')`). Stored verbatim in the `push_subscriptions` table.
|
||
example: tBHItJI5svbpez7KI4CCXg
|
||
PushSubscribeRequest:
|
||
type: object
|
||
required:
|
||
- endpoint
|
||
- keys
|
||
description: A browser PushSubscription serialized for storage. Persisted via `INSERT OR REPLACE` keyed on `endpoint`, so re-subscribing the same endpoint is idempotent (it overwrites the stored keys rather than duplicating the row).
|
||
properties:
|
||
endpoint:
|
||
type: string
|
||
format: uri
|
||
description: The push service delivery URL from `subscription.endpoint`. Acts as the primary key in `push_subscriptions`; sending later POSTs encrypted payloads here. Subscriptions that return HTTP 410 (Gone) during `/send` are pruned automatically.
|
||
example: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ
|
||
keys:
|
||
$ref: '#/components/schemas/PushSubscriptionKeys'
|
||
PushSubscribeResponse:
|
||
type: object
|
||
required:
|
||
- ok
|
||
description: Confirmation that the subscription was stored (or overwritten).
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
description: Always `true` on success.
|
||
example: true
|
||
PushUnsubscribeRequest:
|
||
type: object
|
||
required:
|
||
- endpoint
|
||
description: 'Identifies the subscription to delete by its push-service endpoint. NOTE: the endpoint is supplied in the request BODY (DELETE with a JSON body), not as a query parameter.'
|
||
properties:
|
||
endpoint:
|
||
type: string
|
||
format: uri
|
||
description: 'The `endpoint` of the subscription to remove from `push_subscriptions`. Deletion is idempotent — removing an endpoint that is not stored still returns `{ ok: true }`.'
|
||
example: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ
|
||
PushOkResponse:
|
||
type: object
|
||
required:
|
||
- ok
|
||
description: Generic success acknowledgement returned by subscribe/unsubscribe.
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
description: Always `true` on success.
|
||
example: true
|
||
PushSendRequest:
|
||
type: object
|
||
required:
|
||
- title
|
||
- body
|
||
description: Notification content to broadcast. `title`/`body` are mandatory; a missing one yields a 400. The same content is delivered to every reachable surface (native Electron notification + all stored Web Push subscriptions).
|
||
properties:
|
||
title:
|
||
type: string
|
||
description: Notification title line.
|
||
example: Session completed
|
||
body:
|
||
type: string
|
||
description: Notification body text.
|
||
example: Your Claude Code session finished with 3 subagents.
|
||
url:
|
||
type: string
|
||
description: Optional same-origin path the Web Push delivery navigates to on click, via the service worker's `notificationclick` handler (`client/public/sw.js`). Ignored by the native Electron leg, which has no click handling. Omit for notifications with no specific target.
|
||
example: /workspace?lane=3
|
||
PushSendResponse:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- native
|
||
- pushed
|
||
- failed
|
||
description: Reports which delivery surfaces actually fired. This lets the client distinguish a real delivery from a silent no-op (no subscribers AND no Electron host), which would otherwise look like success.
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
description: Always `true` when dispatch ran without throwing.
|
||
example: true
|
||
native:
|
||
type: boolean
|
||
description: '`true` when a native OS notification was shown via Electron''s main-process Notification API (i.e. the server is hosted inside the desktop app and notifications are supported). `false` under a plain `npm start` host.'
|
||
example: false
|
||
pushed:
|
||
type: integer
|
||
minimum: 0
|
||
description: Count of stored Web Push subscriptions that accepted the encrypted payload (fulfilled `web-push` sends).
|
||
example: 2
|
||
failed:
|
||
type: integer
|
||
minimum: 0
|
||
description: Count of Web Push sends that were rejected. Subscriptions rejected with HTTP 410 (Gone) are deleted from `push_subscriptions` as part of this request.
|
||
example: 1
|
||
errors:
|
||
type: array
|
||
description: Why the rejected sends were rejected, straight from the push service (capped at 3). Diagnostic only - it never includes the subscription endpoint, which is a capability URL. Drives the Settings test-notification banner, so a delivery that reached nobody names its reason instead of looking like success.
|
||
items:
|
||
type: object
|
||
properties:
|
||
statusCode:
|
||
type: integer
|
||
nullable: true
|
||
description: HTTP status the push service answered with, when there was one.
|
||
example: 403
|
||
message:
|
||
type: string
|
||
description: Response body or error message, truncated to 200 characters.
|
||
example: UnauthorizedRegistration
|
||
AiToolsTool:
|
||
type: object
|
||
required:
|
||
- id
|
||
- label
|
||
- installed
|
||
- enabled
|
||
- source
|
||
properties:
|
||
id:
|
||
type: string
|
||
example: antigravity
|
||
label:
|
||
type: string
|
||
example: Antigravity
|
||
command:
|
||
type: string
|
||
nullable: true
|
||
example: agy
|
||
homepageUrl:
|
||
type: string
|
||
nullable: true
|
||
installed:
|
||
type: boolean
|
||
path:
|
||
type: string
|
||
nullable: true
|
||
example: /home/u/.local/bin/agy
|
||
enabled:
|
||
type: boolean
|
||
source:
|
||
type: string
|
||
enum:
|
||
- builtin
|
||
- remote
|
||
- custom
|
||
launcher:
|
||
type: string
|
||
nullable: true
|
||
enum:
|
||
- claude
|
||
- codex
|
||
- null
|
||
prompt:
|
||
type: object
|
||
nullable: true
|
||
properties:
|
||
mode:
|
||
type: string
|
||
enum:
|
||
- argv
|
||
- flag
|
||
- after-start
|
||
resume:
|
||
type: boolean
|
||
description: Offers Resume last session
|
||
bypass:
|
||
type: boolean
|
||
description: Offers the per-run Bypass permissions switch
|
||
routerCapable:
|
||
type: boolean
|
||
description: Can use an AI Router profile
|
||
install:
|
||
type: object
|
||
nullable: true
|
||
description: 'How Install runs: npm install -g <package>@latest, or a vendor script'
|
||
properties:
|
||
kind:
|
||
type: string
|
||
enum:
|
||
- npm
|
||
- script
|
||
package:
|
||
type: string
|
||
example: '@openai/codex'
|
||
url:
|
||
type: string
|
||
example: https://cursor.com/install
|
||
update:
|
||
type: object
|
||
nullable: true
|
||
properties:
|
||
kind:
|
||
type: string
|
||
enum:
|
||
- npm
|
||
- script
|
||
- self
|
||
args:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: self only
|
||
AiToolsJob:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: integer
|
||
tool:
|
||
type: string
|
||
nullable: true
|
||
description: null for update-all
|
||
action:
|
||
type: string
|
||
enum:
|
||
- install
|
||
- update
|
||
- update-all
|
||
status:
|
||
type: string
|
||
enum:
|
||
- running
|
||
- ok
|
||
- failed
|
||
startedAt:
|
||
type: string
|
||
endedAt:
|
||
type: string
|
||
nullable: true
|
||
exitCode:
|
||
type: integer
|
||
nullable: true
|
||
log:
|
||
type: string
|
||
description: stdout+stderr, last 256 KB
|
||
AiToolsView:
|
||
type: object
|
||
required:
|
||
- tools
|
||
- default
|
||
properties:
|
||
tools:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/AiToolsTool'
|
||
default:
|
||
type: string
|
||
nullable: true
|
||
description: Effective default tool id
|
||
catalog:
|
||
type: object
|
||
properties:
|
||
source:
|
||
type: string
|
||
enum:
|
||
- builtin
|
||
- remote
|
||
tag:
|
||
type: string
|
||
nullable: true
|
||
fetchedAt:
|
||
type: string
|
||
nullable: true
|
||
AiToolsError:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
SessionsFacetsResponse:
|
||
type: object
|
||
description: 'Facet values for the Sessions page filter UI: the distinct working directories (cwd) and the distinct data-source ids seen across all sessions.'
|
||
required:
|
||
- cwds
|
||
- sources
|
||
properties:
|
||
cwds:
|
||
type: array
|
||
description: Distinct, non-empty session working directories (the `cwd` column), sorted ascending. Powers the cwd filter dropdown.
|
||
items:
|
||
type: string
|
||
example:
|
||
- /Users/dev/AgentHub
|
||
- /Users/son/code/another-project
|
||
sources:
|
||
type: array
|
||
description: Distinct data-source ids seen across all sessions (the `sessions.source` column). Always includes the built-in `local` history; each configured remote SSH machine contributes its `remote_sources.id`. Powers the source filter dropdown and the `sources` query param on the list/analytics endpoints.
|
||
items:
|
||
type: string
|
||
example:
|
||
- local
|
||
- 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
RemoteSource:
|
||
type: object
|
||
description: A configured remote SSH machine the dashboard pulls Claude Code history from. NO secrets are stored on this record — SSH authentication defers entirely to the host's SSH stack (ssh-agent, `~/.ssh/config`, and key files). `host` is an SSH destination (`user@host`) or a `~/.ssh/config` alias.
|
||
required:
|
||
- id
|
||
- label
|
||
- host
|
||
- ssh_port
|
||
- identity_file
|
||
- remote_home
|
||
- enabled
|
||
- status
|
||
- last_error
|
||
- last_sync_at
|
||
- last_sync_counts
|
||
- created_at
|
||
- updated_at
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: Primary key — the remote-source id (also used as `sessions.source`).
|
||
example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
label:
|
||
type: string
|
||
description: Human-readable name shown in the UI.
|
||
example: Work laptop
|
||
host:
|
||
type: string
|
||
description: SSH destination (`user@host`) or a `~/.ssh/config` alias resolved by the host SSH stack.
|
||
example: son@studio.local
|
||
ssh_port:
|
||
type: integer
|
||
nullable: true
|
||
description: Optional SSH port; null defers to the SSH default / `~/.ssh/config`.
|
||
example: 22
|
||
identity_file:
|
||
type: string
|
||
nullable: true
|
||
description: Optional path to a private-key file passed to ssh (`-i`); null to omit.
|
||
example: ~/.ssh/id_ed25519
|
||
remote_home:
|
||
type: string
|
||
nullable: true
|
||
description: Optional remote Claude home to read transcripts from; null defaults to the remote `~/.claude`.
|
||
example: ~/.claude
|
||
enabled:
|
||
type: boolean
|
||
description: Whether this source is eligible for scheduled/manual syncs.
|
||
example: true
|
||
status:
|
||
type: string
|
||
enum:
|
||
- idle
|
||
- syncing
|
||
- ok
|
||
- error
|
||
description: Last known sync status of the source.
|
||
example: ok
|
||
last_error:
|
||
type: string
|
||
nullable: true
|
||
description: Error message from the last failed sync/test, or null.
|
||
example: null
|
||
last_sync_at:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: ISO-8601 timestamp of the last successful sync, or null.
|
||
example: '2026-07-22T18:41:55.117Z'
|
||
last_sync_counts:
|
||
type: object
|
||
nullable: true
|
||
additionalProperties: true
|
||
description: Counters from the last sync (imported / skipped / backfilled / errors / sessions_seen / sessions_tagged), or null if never synced.
|
||
example:
|
||
imported: 9
|
||
skipped: 41
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 50
|
||
sessions_tagged: 50
|
||
created_at:
|
||
type: string
|
||
format: date-time
|
||
description: ISO-8601 creation timestamp.
|
||
example: '2026-07-20T09:15:00.000Z'
|
||
updated_at:
|
||
type: string
|
||
format: date-time
|
||
description: ISO-8601 timestamp of the last edit.
|
||
example: '2026-07-22T18:41:55.117Z'
|
||
RemoteSourceCreateRequest:
|
||
type: object
|
||
description: Request body to register a remote SSH source. `label` and `host` are required; the rest are optional. No credentials are ever accepted or stored — auth defers to the host SSH stack.
|
||
required:
|
||
- label
|
||
- host
|
||
properties:
|
||
label:
|
||
type: string
|
||
description: Human-readable name.
|
||
example: Work laptop
|
||
host:
|
||
type: string
|
||
description: SSH destination (`user@host`) or a `~/.ssh/config` alias.
|
||
example: son@studio.local
|
||
ssh_port:
|
||
type: integer
|
||
description: Optional SSH port.
|
||
example: 22
|
||
identity_file:
|
||
type: string
|
||
description: Optional private-key path passed to ssh (`-i`).
|
||
example: ~/.ssh/id_ed25519
|
||
remote_home:
|
||
type: string
|
||
description: Optional remote Claude home; defaults to the remote `~/.claude`.
|
||
example: ~/.claude
|
||
enabled:
|
||
type: boolean
|
||
description: Whether the source is enabled for syncing (default true).
|
||
example: true
|
||
RemoteSourceUpdateRequest:
|
||
type: object
|
||
description: Partial update for a remote source. Only the keys present in the body are changed; omitted keys are left as-is. Same field set as create; both `label` and `host` are optional here.
|
||
properties:
|
||
label:
|
||
type: string
|
||
example: Studio Mac
|
||
host:
|
||
type: string
|
||
example: son@studio.local
|
||
ssh_port:
|
||
type: integer
|
||
nullable: true
|
||
example: 2222
|
||
identity_file:
|
||
type: string
|
||
nullable: true
|
||
example: ~/.ssh/id_ed25519
|
||
remote_home:
|
||
type: string
|
||
nullable: true
|
||
example: ~/.claude
|
||
enabled:
|
||
type: boolean
|
||
example: false
|
||
RemoteSourceResponse:
|
||
type: object
|
||
required:
|
||
- source
|
||
properties:
|
||
source:
|
||
$ref: '#/components/schemas/RemoteSource'
|
||
RemoteSourceListResponse:
|
||
type: object
|
||
required:
|
||
- sources
|
||
properties:
|
||
sources:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/RemoteSource'
|
||
RemoteSourceTestResponse:
|
||
type: object
|
||
description: Result of an SSH connectivity probe.
|
||
required:
|
||
- ok
|
||
- message
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
message:
|
||
type: string
|
||
description: Human-readable probe result.
|
||
example: Connected; found 24 project directories under ~/.claude/projects.
|
||
remoteProjects:
|
||
type: array
|
||
description: Optional list of remote project directories discovered during the probe (present on success).
|
||
items:
|
||
type: string
|
||
example:
|
||
- '-Users-son-code-foo'
|
||
- '-Users-son-code-bar'
|
||
RemoteSourceSyncResponse:
|
||
type: object
|
||
description: Counters from a pull-now sync against the remote source.
|
||
required:
|
||
- ok
|
||
- imported
|
||
- skipped
|
||
- backfilled
|
||
- errors
|
||
- sessions_seen
|
||
- sessions_tagged
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
imported:
|
||
type: integer
|
||
example: 9
|
||
skipped:
|
||
type: integer
|
||
example: 41
|
||
backfilled:
|
||
type: integer
|
||
example: 0
|
||
errors:
|
||
type: integer
|
||
example: 0
|
||
sessions_seen:
|
||
type: integer
|
||
example: 50
|
||
sessions_tagged:
|
||
type: integer
|
||
description: Number of imported sessions stamped with this source's id.
|
||
example: 50
|
||
SettingsClaudeHomeResponse:
|
||
type: object
|
||
description: The Claude Code home directory the dashboard reads transcripts and settings from. Defaults to `~/.claude` unless overridden via the CLAUDE_HOME environment variable.
|
||
required:
|
||
- claude_home
|
||
properties:
|
||
claude_home:
|
||
type: string
|
||
description: Absolute path to the active Claude Code home directory (CLAUDE_HOME, or `<homedir>/.claude` when unset).
|
||
example: /Users/son/.claude
|
||
env_owned:
|
||
type: boolean
|
||
description: 'True when the real environment, or the checkout''s own `.env`, set this variable at startup: either wins over the settings file, so a saved value is overridden again at the next start.'
|
||
SettingsClaudeHomeUpdateRequest:
|
||
type: object
|
||
description: Request body for changing the Claude Code home directory. A leading `~` is expanded to the user's home directory; the resolved path must be absolute and point to an existing directory.
|
||
required:
|
||
- path
|
||
properties:
|
||
path:
|
||
type: string
|
||
description: New Claude Code home directory. A leading `~/` is expanded to the OS home directory before validation. Must resolve to an absolute path that exists and is a directory.
|
||
example: ~/.codefuse/engine/cc
|
||
SettingsClaudeHomeUpdateResponse:
|
||
type: object
|
||
description: Confirmation that CLAUDE_HOME was updated. The new value is applied to process.env immediately and persisted to the settings file (`$XDG_CONFIG_HOME/agenthub/settings.env`, default `~/.config/agenthub/`, or AGENTHUB_SETTINGS_FILE).
|
||
required:
|
||
- ok
|
||
- claude_home
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
claude_home:
|
||
type: string
|
||
description: The resolved absolute path now in effect (after `~` expansion).
|
||
example: /Users/son/.codefuse/engine/cc
|
||
env_owned:
|
||
type: boolean
|
||
description: 'True when the real environment, or the checkout''s own `.env`, set this variable at startup: either wins over the settings file, so a saved value is overridden again at the next start.'
|
||
routers:
|
||
type: object
|
||
properties:
|
||
missing:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: '`<lane id> (<title>): <profile>` for every lane naming an AI Router profile the new home''s store (`<CLAUDE_HOME>/agenthub-routers/`) does not have — such a lane starts on its base configuration.'
|
||
WorkflowToolRun:
|
||
type: object
|
||
description: 'A Claude Code Workflow-tool run (issue #167): a fleet of sub-agents spawned by the ''Workflow'' tool (or self-paced /loop). These emit no hooks; the source of truth is the on-disk run journal, ingested into the `workflows` table (see server/lib/workflow-ingest.js). Keyed by `run_id` and parented to the launching session. The JSON-blob columns `phases` and `progress` are parsed into arrays before serialization.'
|
||
required:
|
||
- run_id
|
||
- session_id
|
||
- status
|
||
- agent_count
|
||
- total_tokens
|
||
- total_tool_calls
|
||
- phases
|
||
- progress
|
||
- source
|
||
- created_at
|
||
- updated_at
|
||
properties:
|
||
run_id:
|
||
type: string
|
||
description: Primary key — the workflow run id.
|
||
example: wf_a1b2c3d4
|
||
session_id:
|
||
type: string
|
||
description: The session that launched this run (FK into sessions.id).
|
||
example: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
task_id:
|
||
type: string
|
||
nullable: true
|
||
description: Optional task/issue identifier associated with the run.
|
||
example: ISSUE-167
|
||
name:
|
||
type: string
|
||
nullable: true
|
||
description: Human-readable run name from the journal, if present.
|
||
example: Refactor pricing engine
|
||
status:
|
||
type: string
|
||
description: Open status string (e.g. running | completed | error | failed). Intentionally not constrained to an enum so new harness states never trip a stale constraint.
|
||
example: completed
|
||
default_model:
|
||
type: string
|
||
nullable: true
|
||
description: Default model the run delegated work to, when recorded.
|
||
example: claude-opus-4-8
|
||
started_at:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: When the run started, if known.
|
||
example: '2026-06-25T18:04:11.122Z'
|
||
ended_at:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: When the run finished, if known.
|
||
example: '2026-06-25T18:09:47.530Z'
|
||
duration_ms:
|
||
type: integer
|
||
nullable: true
|
||
description: Total run duration in milliseconds, if known.
|
||
example: 336408
|
||
agent_count:
|
||
type: integer
|
||
minimum: 0
|
||
description: Number of inner agents in this run.
|
||
example: 6
|
||
total_tokens:
|
||
type: integer
|
||
minimum: 0
|
||
description: Aggregate token usage across the run's inner agents.
|
||
example: 1284750
|
||
total_tool_calls:
|
||
type: integer
|
||
minimum: 0
|
||
description: Aggregate tool-call count across the run's inner agents.
|
||
example: 412
|
||
phases:
|
||
type: array
|
||
description: Parsed `phases[]` array from the run journal (verbatim journal payload, opaque to this API). Empty array when absent or unparseable.
|
||
items:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
- name: plan
|
||
status: completed
|
||
- name: implement
|
||
status: completed
|
||
progress:
|
||
type: array
|
||
description: Parsed `workflowProgress[]` array from the run journal (verbatim journal payload, opaque to this API). Empty array when absent or unparseable.
|
||
items:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
- step: 1
|
||
label: scaffold
|
||
done: true
|
||
script_path:
|
||
type: string
|
||
nullable: true
|
||
description: Path to the run's driving script, if recorded.
|
||
example: /Users/son/.claude/projects/-Users-son-code/wf_a1b2c3d4.sh
|
||
journal_path:
|
||
type: string
|
||
nullable: true
|
||
description: Path to the on-disk run journal this row was ingested from.
|
||
example: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json
|
||
source:
|
||
type: string
|
||
description: Ingestion source for the row (defaults to 'journal').
|
||
example: journal
|
||
created_at:
|
||
type: string
|
||
format: date-time
|
||
description: Row creation timestamp.
|
||
example: '2026-06-25T18:09:48.001Z'
|
||
updated_at:
|
||
type: string
|
||
format: date-time
|
||
description: Row last-update timestamp.
|
||
example: '2026-06-25T18:09:48.001Z'
|
||
WorkflowRunsListResponse:
|
||
type: object
|
||
description: Paginated list of Workflow-tool runs with status counts. `total` reflects the active filter (status when supplied, otherwise the full table); `counts` is always the whole-table breakdown by status.
|
||
required:
|
||
- runs
|
||
- total
|
||
- counts
|
||
- limit
|
||
- offset
|
||
properties:
|
||
runs:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/WorkflowToolRun'
|
||
total:
|
||
type: integer
|
||
minimum: 0
|
||
description: 'Total runs matching the current filter (independent of limit/offset). Equals the status-filtered count when `status` is supplied, otherwise the full-table count. Note: not narrowed by `session_id`.'
|
||
example: 42
|
||
counts:
|
||
type: object
|
||
description: Whole-table run counts grouped by status (not affected by filters).
|
||
additionalProperties:
|
||
type: integer
|
||
minimum: 0
|
||
example:
|
||
completed: 30
|
||
error: 5
|
||
running: 7
|
||
limit:
|
||
type: integer
|
||
description: Effective page size used.
|
||
example: 50
|
||
offset:
|
||
type: integer
|
||
description: Effective pagination offset used.
|
||
example: 0
|
||
WorkflowRunDetailResponse:
|
||
type: object
|
||
description: A single Workflow-tool run with its linked inner agents and the events attributed to those agents (chronological, capped at 5000).
|
||
required:
|
||
- workflow
|
||
- agents
|
||
- events
|
||
properties:
|
||
workflow:
|
||
$ref: '#/components/schemas/WorkflowToolRun'
|
||
agents:
|
||
type: array
|
||
description: Inner agents linked to this run via agents.workflow_run_id.
|
||
items:
|
||
$ref: '#/components/schemas/Agent'
|
||
events:
|
||
type: array
|
||
description: Events attributed to this run's inner agents, ordered by created_at then id. Capped at 5000 rows.
|
||
items:
|
||
$ref: '#/components/schemas/DashboardEvent'
|
||
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, newest activity first, each enriched with a SQL `agent_count` (LEFT JOIN onto agents), a `last_activity` alias of `updated_at`, and a `cost` computed from the session's token usage against the current pricing rules. The `status` and `q` filters compose (AND) with each other and with pagination; `q` is a case-insensitive LIKE across `id`, `name`, and `cwd`. `total` reflects all rows matching the filters independent of `limit`/`offset` so paginators stay accurate, while `cost` is only calculated for the rows on the returned page (when `sort_by=price` it is computed across all matching rows so the price sort is correct). The endpoint is read-only with no side effects; `metadata` on each session is returned as a raw JSON-encoded string, not a parsed object.
|
||
operationId: listSessions
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionStatusQuery'
|
||
example: active
|
||
- name: q
|
||
in: query
|
||
schema:
|
||
type: string
|
||
description: Case-insensitive search across `id` / `name` / `cwd`. Composes with the status filter when both are present.
|
||
example: pricing
|
||
- name: tool
|
||
in: query
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- claude
|
||
- codex
|
||
- antigravity
|
||
description: Only the sessions of one CLI. Applied before `limit`, so a busy Codex lane cannot push Claude sessions off a page.
|
||
example: claude
|
||
- name: awaiting
|
||
in: query
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- '1'
|
||
description: '`1` = only sessions blocked on the user (`awaiting_input_since IS NOT NULL AND status = ''active''`), the UI''s Waiting filter. Applied before `limit`/`offset`, so `total` stays exact.'
|
||
example: '1'
|
||
- name: from
|
||
in: query
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
description: Inclusive lower bound on `started_at` (ISO 8601). An unparseable value is ignored. Applied before `limit`/`offset`.
|
||
example: '2026-10-01T00:00:00.000Z'
|
||
- name: to
|
||
in: query
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
description: Inclusive upper bound on `started_at` (ISO 8601). An unparseable value is ignored. Applied before `limit`/`offset`.
|
||
example: '2026-10-05T23:59:59.999Z'
|
||
- $ref: '#/components/parameters/SourcesQuery'
|
||
example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
- $ref: '#/components/parameters/LimitQuery'
|
||
example: 50
|
||
- $ref: '#/components/parameters/OffsetQuery'
|
||
example: 0
|
||
responses:
|
||
'200':
|
||
description: Session list
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionsListResponse'
|
||
example:
|
||
sessions:
|
||
- id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
status: active
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}'
|
||
updated_at: '2026-06-25T14:31:50.119Z'
|
||
agent_count: 4
|
||
last_activity: '2026-06-25T14:31:50.119Z'
|
||
cost: 0.8421
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
- id: 1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9
|
||
name: Fix flaky transcript pagination test
|
||
status: completed
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-sonnet-4-20250514
|
||
started_at: '2026-06-24T09:12:00.000Z'
|
||
ended_at: '2026-06-24T09:48:32.501Z'
|
||
metadata: null
|
||
updated_at: '2026-06-24T09:48:32.501Z'
|
||
agent_count: 1
|
||
last_activity: '2026-06-24T09:48:32.501Z'
|
||
cost: 0.1532
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
limit: 50
|
||
offset: 0
|
||
total: 137
|
||
post:
|
||
tags:
|
||
- Sessions
|
||
summary: Create session (idempotent)
|
||
description: 'Creates a session keyed by `id`. The operation is idempotent: if a session with that `id` already exists it is returned untouched with `created: false` and HTTP 200; only a brand-new row yields `created: true` and HTTP 201. New sessions are inserted with `status: "active"` and any omitted optional fields stored as null. The `metadata` field is accepted as a JSON object in the request but persisted (and returned on the session) as a JSON-encoded string. A successful create broadcasts a `session_created` websocket frame. A missing `id` returns 400 with code `INVALID_INPUT`.'
|
||
operationId: createSession
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionCreateRequest'
|
||
example:
|
||
id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
metadata:
|
||
source: hook
|
||
git_branch: feat/spend-budgets
|
||
responses:
|
||
'200':
|
||
description: Session already exists
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionCreateResponse'
|
||
example:
|
||
session:
|
||
id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
status: active
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}'
|
||
updated_at: '2026-06-25T14:31:50.119Z'
|
||
agent_count: 4
|
||
last_activity: '2026-06-25T14:31:50.119Z'
|
||
cost: 0.8421
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
created: false
|
||
'201':
|
||
description: Session created
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionCreateResponse'
|
||
example:
|
||
session:
|
||
id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
status: active
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}'
|
||
updated_at: '2026-06-25T14:02:11.004Z'
|
||
agent_count: 0
|
||
last_activity: '2026-06-25T14:02:11.004Z'
|
||
cost: 0
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
created: true
|
||
'400':
|
||
description: Invalid request body
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: id is required
|
||
/api/sessions/{id}:
|
||
get:
|
||
tags:
|
||
- Sessions
|
||
summary: Get session details
|
||
description: Returns a single session together with all of its agents (chronological) and persisted events. Read-only, no side effects. The session's `metadata` and each event's `data` are returned as raw JSON-encoded strings, not parsed objects. Returns 404 with code `NOT_FOUND` when no session matches the path `id`.
|
||
operationId: getSession
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
responses:
|
||
'200':
|
||
description: Session with associated agents/events
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionDetailResponse'
|
||
example:
|
||
session:
|
||
id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
status: active
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
metadata: '{"source":"hook","git_branch":"feat/spend-budgets"}'
|
||
updated_at: '2026-06-25T14:31:50.119Z'
|
||
agent_count: 4
|
||
last_activity: '2026-06-25T14:31:50.119Z'
|
||
cost: 0.8421
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
agents:
|
||
- id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Main Agent
|
||
type: main
|
||
subagent_type: null
|
||
status: working
|
||
task: null
|
||
current_tool: Edit
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
parent_agent_id: null
|
||
metadata: '{"model":"claude-opus-4-20250514"}'
|
||
updated_at: '2026-06-25T14:31:50.119Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
- id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: null
|
||
updated_at: '2026-06-25T14:14:09.882Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
events:
|
||
- id: 48213
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
event_type: PostToolUse
|
||
tool_name: Edit
|
||
summary: Edited server/routes/pricing.js
|
||
data: '{"tool_input":{"file_path":"server/routes/pricing.js"},"tool_response":{"success":true}}'
|
||
created_at: '2026-06-25T14:31:50.119Z'
|
||
'404':
|
||
description: Session not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Session not found
|
||
patch:
|
||
tags:
|
||
- Sessions
|
||
summary: Update session
|
||
description: Partially updates a session by `id`. Only `name`, `status`, `ended_at`, and `metadata` are accepted; any field omitted from the body is passed as null and the underlying UPDATE uses COALESCE, so a null leaves the existing column value unchanged (partial-update semantics) — you cannot clear a field to null through this endpoint. `metadata` is supplied as a JSON object but stored and returned as a JSON-encoded string. A successful update re-reads the row and broadcasts a `session_updated` websocket frame. Returns 404 with code `NOT_FOUND` when the session does not exist.
|
||
operationId: updateSession
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionUpdateRequest'
|
||
example:
|
||
status: completed
|
||
ended_at: '2026-06-25T15:07:44.220Z'
|
||
metadata:
|
||
source: hook
|
||
git_branch: feat/spend-budgets
|
||
outcome: merged
|
||
responses:
|
||
'200':
|
||
description: Session updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionUpdateResponse'
|
||
example:
|
||
session:
|
||
id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Refactor pricing route + add cost endpoint
|
||
status: completed
|
||
cwd: /Users/dev/AgentHub
|
||
model: claude-opus-4-20250514
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: '2026-06-25T15:07:44.220Z'
|
||
metadata: '{"source":"hook","git_branch":"feat/spend-budgets","outcome":"merged"}'
|
||
updated_at: '2026-06-25T15:07:44.220Z'
|
||
agent_count: 4
|
||
last_activity: '2026-06-25T15:07:44.220Z'
|
||
cost: 0.8421
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
'404':
|
||
description: Session not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Session not found
|
||
/api/sessions/{id}/stats:
|
||
get:
|
||
tags:
|
||
- Sessions
|
||
summary: Get aggregated session stats
|
||
description: 'Returns aggregated counts for the SessionOverview panel: total events, events-by-type, the top 15 tools by usage, the exact `tool_call_count` (PreToolUse events with a `tool_name`, or PostToolUse when the session has none, e.g. an imported one) and `distinct_tool_count` (different tool names) — `tools_used` is a top-15 list, so its sum is not the count —, an error count (events whose `event_type`/`summary` match /error/i or /failed/i), the event time range, agent type/status counts, the subagent-type breakdown (excluding the special `compaction` type, which is surfaced under `agents.compaction`), and token totals. All aggregation runs in SQL, so it stays cheap even for sessions with tens of thousands of events; the endpoint is read-only with no side effects. The frontend debounces calls on `new_event` / `agent_*` / `session_updated` websocket frames so the counters track a running session. Returns 404 with code `NOT_FOUND` when the session does not exist.'
|
||
operationId: getSessionStats
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
responses:
|
||
'200':
|
||
description: Aggregated session stats
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionStatsResponse'
|
||
example:
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
total_events: 1284
|
||
events_by_type:
|
||
- event_type: PostToolUse
|
||
count: 612
|
||
- event_type: PreToolUse
|
||
count: 612
|
||
- event_type: Notification
|
||
count: 41
|
||
- event_type: Stop
|
||
count: 19
|
||
tools_used:
|
||
- tool_name: Bash
|
||
count: 188
|
||
- tool_name: Edit
|
||
count: 143
|
||
- tool_name: Read
|
||
count: 121
|
||
- tool_name: Grep
|
||
count: 77
|
||
tool_call_count: 612
|
||
distinct_tool_count: 9
|
||
error_count: 6
|
||
first_event_at: '2026-06-25T14:02:11.052Z'
|
||
last_event_at: '2026-06-25T14:31:50.119Z'
|
||
agents:
|
||
total: 4
|
||
main: 1
|
||
subagent: 3
|
||
compaction: 1
|
||
by_status:
|
||
working: 1
|
||
completed: 2
|
||
error: 1
|
||
subagent_types:
|
||
- subagent_type: Explore
|
||
count: 2
|
||
- subagent_type: general-purpose
|
||
count: 1
|
||
tokens:
|
||
input_tokens: 18422
|
||
output_tokens: 9134
|
||
cache_read_tokens: 1204880
|
||
cache_write_tokens: 88210
|
||
'404':
|
||
description: Session not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Session not found
|
||
/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 — by scanning the on-disk Claude project directory (live files, falling back to import-time snapshots). Read-only, no side effects. Each entry carries a best-effort `db_agent_id` resolved by matching transcripts to tracked agents (exact id first, then positional-by-time within each type group); it may be null when a transcript has no matching agent row. Used by the Conversation tab to populate the transcript switcher. Returns 404 with code `NOT_FOUND` when the session does not exist.
|
||
operationId: listSessionTranscripts
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
responses:
|
||
'200':
|
||
description: List of transcripts available for the session
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TranscriptListResponse'
|
||
example:
|
||
transcripts:
|
||
- id: main
|
||
name: Main Agent
|
||
type: main
|
||
has_transcript: true
|
||
db_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
- id: ad18a79192af10ed1
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
has_transcript: true
|
||
db_agent_id: ad18a79192af10ed1
|
||
- id: acompact-7c1e2f90
|
||
name: Context Compaction
|
||
type: compaction
|
||
subagent_type: null
|
||
has_transcript: true
|
||
db_agent_id: null
|
||
'404':
|
||
description: Session not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Session not found
|
||
/api/sessions/{id}/transcript:
|
||
get:
|
||
tags:
|
||
- Sessions
|
||
summary: Stream messages from a specific transcript
|
||
description: 'Returns parsed, renderable messages from a JSONL transcript with cursor-based pagination, reading the live file under ~/.claude/projects and falling back to the durable import-time snapshot. Pass `agent_id` to select a specific subagent or compaction transcript (default is the session''s main transcript). Pagination cursors are mutually exclusive: `after` returns messages strictly newer than a JSONL line number (incremental live updates on `new_event`), `before` returns messages strictly older than a line (load-on-scroll-up), and `offset` is legacy start-offset paging. `last_line`/`first_line` are the JSONL line numbers of the newest/oldest returned message — feed them back as `after`/`before`. When the session, transcript file, or path cannot be found the endpoint degrades gracefully to an empty result (`messages: []`, `total: 0`, `has_more: false`) rather than erroring. Read-only, no side effects.'
|
||
operationId: getSessionTranscript
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
- 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.
|
||
example: main
|
||
- name: limit
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
default: 50
|
||
minimum: 1
|
||
maximum: 500
|
||
description: Maximum number of messages to return.
|
||
example: 50
|
||
- name: offset
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
description: Offset from the start of the transcript (mutually exclusive with after/before).
|
||
example: 0
|
||
- 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.
|
||
example: 842
|
||
- 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.
|
||
example: 200
|
||
responses:
|
||
'200':
|
||
description: Parsed messages with cursor metadata
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TranscriptResponse'
|
||
example:
|
||
messages:
|
||
- type: user
|
||
timestamp: '2026-06-25T14:02:11.004Z'
|
||
content:
|
||
- type: text
|
||
text: Refactor the pricing route and add a cost endpoint.
|
||
- type: assistant
|
||
timestamp: '2026-06-25T14:02:18.771Z'
|
||
model: claude-opus-4-20250514
|
||
content:
|
||
- type: thinking
|
||
text: I'll start by reading server/routes/pricing.js.
|
||
- type: tool_use
|
||
name: Read
|
||
id: toolu_01A7c2Df9
|
||
input:
|
||
file_path: server/routes/pricing.js
|
||
usage:
|
||
input_tokens: 412
|
||
output_tokens: 96
|
||
- type: user
|
||
timestamp: '2026-06-25T14:02:19.330Z'
|
||
content:
|
||
- type: tool_result
|
||
id: toolu_01A7c2Df9
|
||
output: |-
|
||
const { Router } = require("express");
|
||
...
|
||
is_error: false
|
||
total: 1284
|
||
has_more: true
|
||
last_line: 5310
|
||
first_line: 5301
|
||
'404':
|
||
description: Session or transcript not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Session not found
|
||
/api/agents:
|
||
get:
|
||
tags:
|
||
- Agents
|
||
summary: List agents
|
||
description: 'Returns agents, most recent first. Filters are applied with precedence rather than composition: `session_ids` (comma-separated, duplicates and blanks dropped, at most 200 ids, `limit` applied but not `offset`, newest `started_at` first; an empty list answers no agents) wins over everything else and returns every agent of those sessions in one call; next, when `session_id` is supplied it wins and returns every agent for that session (ignoring `status` and pagination); otherwise a `status` filter returns paginated agents in that lifecycle state; otherwise all agents are returned paginated. `limit` defaults to 10000 when not a positive integer. Read-only, no side effects. Each agent''s `metadata` is returned as a raw JSON-encoded string, not a parsed object.'
|
||
operationId: listAgents
|
||
parameters:
|
||
- $ref: '#/components/parameters/AgentStatusQuery'
|
||
example: working
|
||
- $ref: '#/components/parameters/SessionFilterQuery'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
- name: session_ids
|
||
in: query
|
||
schema:
|
||
type: string
|
||
description: 'Comma-separated session ids: every agent of those sessions in one call (the Dashboard''s active trees). Duplicates and blanks are dropped, at most 200 ids are read, `limit` still applies and `offset` does not; ordered by `started_at` DESC. Takes precedence over `session_id`, `status` and `sources`. An empty list answers `{agents: []}`.'
|
||
example: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d,0c4e9a1b-2d3f-4a5b-8c6d-7e8f9a0b1c2d
|
||
- $ref: '#/components/parameters/SourcesQuery'
|
||
example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
- $ref: '#/components/parameters/LimitQuery'
|
||
example: 50
|
||
- $ref: '#/components/parameters/OffsetQuery'
|
||
example: 0
|
||
responses:
|
||
'200':
|
||
description: Agent list
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentsListResponse'
|
||
example:
|
||
agents:
|
||
- id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Main Agent
|
||
type: main
|
||
subagent_type: null
|
||
status: working
|
||
task: null
|
||
current_tool: Edit
|
||
started_at: '2026-06-25T14:02:11.004Z'
|
||
ended_at: null
|
||
parent_agent_id: null
|
||
metadata: '{"model":"claude-opus-4-20250514"}'
|
||
updated_at: '2026-06-25T14:31:50.119Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
- id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: null
|
||
updated_at: '2026-06-25T14:14:09.882Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
limit: 50
|
||
offset: 0
|
||
post:
|
||
tags:
|
||
- Agents
|
||
summary: Create agent (idempotent)
|
||
description: 'Creates an agent keyed by `id`. The operation is idempotent: if an agent with that `id` already exists it is returned untouched with `created: false` and HTTP 200; only a brand-new row yields `created: true` and HTTP 201. Omitted optional fields default server-side — `type` to `"main"`, `status` to `"waiting"` — and other unspecified columns are stored as null. `metadata` is accepted as a JSON object but persisted (and returned) as a JSON-encoded string. A successful create broadcasts an `agent_created` websocket frame. Missing `id`, `session_id`, or `name` returns 400 with code `INVALID_INPUT`.'
|
||
operationId: createAgent
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentCreateRequest'
|
||
example:
|
||
id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: working
|
||
task: Map every caller of calculateCost() across server/routes
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata:
|
||
spawned_by: Task
|
||
responses:
|
||
'200':
|
||
description: Agent already exists
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentCreateResponse'
|
||
example:
|
||
agent:
|
||
id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: null
|
||
updated_at: '2026-06-25T14:14:09.882Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
created: false
|
||
'201':
|
||
description: Agent created
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentCreateResponse'
|
||
example:
|
||
agent:
|
||
id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: working
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: null
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: '{"spawned_by":"Task"}'
|
||
updated_at: '2026-06-25T14:10:22.310Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
created: true
|
||
'400':
|
||
description: Invalid request body
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: id, session_id, and name are required
|
||
/api/agents/{id}:
|
||
get:
|
||
tags:
|
||
- Agents
|
||
summary: Get agent
|
||
description: Returns a single agent by `id`. Read-only, no side effects. The agent's `metadata` is returned as a raw JSON-encoded string, not a parsed object. Returns 404 with code `NOT_FOUND` when no agent matches the path `id`.
|
||
operationId: getAgent
|
||
parameters:
|
||
- $ref: '#/components/parameters/AgentIdPath'
|
||
example: ad18a79192af10ed1
|
||
responses:
|
||
'200':
|
||
description: Agent details
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentDetailResponse'
|
||
example:
|
||
agent:
|
||
id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: null
|
||
updated_at: '2026-06-25T14:14:09.882Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
'404':
|
||
description: Agent not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Agent not found
|
||
patch:
|
||
tags:
|
||
- Agents
|
||
summary: Update agent
|
||
description: 'Partially updates an agent by `id`. Accepts `name`, `status`, `task`, `current_tool`, `ended_at`, and `metadata`. The UPDATE uses COALESCE, so any field omitted (passed as null) leaves the existing column value unchanged — with one deliberate exception: `current_tool` is written through verbatim when present in the body, so it can be explicitly cleared to null (e.g. when a tool call finishes). `metadata` is supplied as a JSON object but stored and returned as a JSON-encoded string. A successful update re-reads the row and broadcasts an `agent_updated` websocket frame. Returns 404 with code `NOT_FOUND` when the agent does not exist.'
|
||
operationId: updateAgent
|
||
parameters:
|
||
- $ref: '#/components/parameters/AgentIdPath'
|
||
example: ad18a79192af10ed1
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentUpdateRequest'
|
||
example:
|
||
status: completed
|
||
current_tool: null
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
responses:
|
||
'200':
|
||
description: Agent updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AgentUpdateResponse'
|
||
example:
|
||
agent:
|
||
id: ad18a79192af10ed1
|
||
session_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d
|
||
name: Explore pricing module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map every caller of calculateCost() across server/routes
|
||
current_tool: null
|
||
started_at: '2026-06-25T14:10:22.310Z'
|
||
ended_at: '2026-06-25T14:14:09.882Z'
|
||
parent_agent_id: b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d-main
|
||
metadata: null
|
||
updated_at: '2026-06-25T14:14:09.882Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
'404':
|
||
description: Agent not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Agent not found
|
||
/api/events:
|
||
get:
|
||
tags:
|
||
- Events
|
||
summary: List events with multi-dimensional filtering
|
||
operationId: listEvents
|
||
description: |-
|
||
Returns a paginated, reverse-chronological slice of the `events` table (ordered by `created_at DESC, id DESC`) together with the total row count matching the active filters, so the UI can drive a paginator without a second request.
|
||
|
||
All four entity filters — `event_type`, `tool_name`, `agent_id`, and `session_id` — accept a **comma-separated list (CSV)** of values and match with `IN (...)` semantics: passing `event_type=Stop,PreToolUse` returns rows whose `event_type` is either `Stop` OR `PreToolUse`. Values are trimmed and blank entries are dropped. Filters are combined with one another using AND.
|
||
|
||
`q` performs a case-insensitive substring (`LIKE %q%`) search across the `summary`, `tool_name`, and the JSON-encoded `data` columns. `from`/`to` are inclusive ISO-8601 datetime bounds on `created_at`; unparseable values are ignored rather than rejected. `limit` is clamped to 1–500 (default 50) and `offset` is clamped to >= 0 (default 0).
|
||
|
||
Note: each returned event's `data` field is a **JSON-encoded string**, not a nested object — callers must `JSON.parse` it to inspect the payload.
|
||
|
||
Each row also carries its labels, so a list page need not fetch every session and agent to name them: `session_name`, `session_cwd`, `session_lane_id`, `agent_name` and `agent_subagent_type` (nullable; `null` when the session or agent row is missing). They are REST-only — the WebSocket `new_event` frame is unchanged.
|
||
parameters:
|
||
- in: query
|
||
name: event_type
|
||
description: 'Comma-separated (CSV) list of `event_type` values; matched with IN semantics (OR within the list). Common values: PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd.'
|
||
schema:
|
||
type: string
|
||
example: Stop,PreToolUse
|
||
- in: query
|
||
name: tool_name
|
||
description: 'Comma-separated (CSV) list of `tool_name` values; matched with IN semantics (OR within the list). Common values: Bash, Edit, Read, Write, Grep, Glob, Task.'
|
||
schema:
|
||
type: string
|
||
example: Bash,Edit
|
||
- in: query
|
||
name: agent_id
|
||
description: Comma-separated (CSV) list of `agent_id` values; matched with IN semantics. The main agent of a session uses the id `<session_id>-main`.
|
||
schema:
|
||
type: string
|
||
example: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main
|
||
- in: query
|
||
name: session_id
|
||
description: Comma-separated (CSV) list of `session_id` values; matched with IN semantics (OR within the list).
|
||
schema:
|
||
type: string
|
||
example: 8f3c2a10-1b2c-4d5e-9f80-112233445566,2a7d9e44-3c1f-4a6b-bc20-aabbccddeeff
|
||
- in: query
|
||
name: q
|
||
description: Case-insensitive substring search (`LIKE %q%`) applied across the `summary`, `tool_name`, and JSON-encoded `data` columns.
|
||
schema:
|
||
type: string
|
||
example: curl
|
||
- in: query
|
||
name: from
|
||
description: ISO-8601 datetime lower bound (inclusive) on `created_at`. Unparseable values are ignored.
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
example: '2026-06-25T00:00:00.000Z'
|
||
- in: query
|
||
name: to
|
||
description: ISO-8601 datetime upper bound (inclusive) on `created_at`. Unparseable values are ignored.
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
example: '2026-06-26T00:00:00.000Z'
|
||
- $ref: '#/components/parameters/SourcesQuery'
|
||
example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
- in: query
|
||
name: limit
|
||
description: Max rows to return; clamped to 1–500 (default 50).
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 500
|
||
default: 50
|
||
example: 50
|
||
- $ref: '#/components/parameters/OffsetQuery'
|
||
responses:
|
||
'200':
|
||
description: Event list with total count for pagination
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/EventsListResponse'
|
||
example:
|
||
events:
|
||
- id: 48213
|
||
session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566
|
||
agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main
|
||
event_type: PreToolUse
|
||
tool_name: Bash
|
||
summary: 'Bash: curl -s https://api.example.com/health'
|
||
data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"curl -s https://api.example.com/health","description":"Check upstream health"},"cwd":"/Users/dev/project"}'
|
||
created_at: '2026-06-25T18:42:07.512Z'
|
||
- id: 48212
|
||
session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566
|
||
agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main
|
||
event_type: Stop
|
||
tool_name: null
|
||
summary: Session finished responding
|
||
data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"Stop"}'
|
||
created_at: '2026-06-25T18:41:55.004Z'
|
||
limit: 50
|
||
offset: 0
|
||
total: 1342
|
||
/api/events/facets:
|
||
get:
|
||
tags:
|
||
- Events
|
||
summary: Distinct event_type and tool_name values available in the DB
|
||
operationId: listEventFacets
|
||
description: Returns the distinct, non-null `event_type` and `tool_name` values currently present in the `events` table, each sorted alphabetically. The UI uses this to populate the filter dropdowns on the Events screen without hardcoding the set of tools or hook types — so the lists automatically reflect whatever has actually been ingested. Both arrays are independent and may be empty when the table holds no matching rows.
|
||
responses:
|
||
'200':
|
||
description: Facet values for populating filter dropdowns
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/EventsFacetsResponse'
|
||
example:
|
||
event_types:
|
||
- Notification
|
||
- PostToolUse
|
||
- PreToolUse
|
||
- SessionEnd
|
||
- SessionStart
|
||
- Stop
|
||
- SubagentStop
|
||
tool_names:
|
||
- Bash
|
||
- Edit
|
||
- Glob
|
||
- Grep
|
||
- Read
|
||
- Task
|
||
- Write
|
||
/api/stats:
|
||
get:
|
||
tags:
|
||
- Stats
|
||
summary: Get aggregate dashboard stats
|
||
operationId: getStats
|
||
description: |-
|
||
Returns the headline counters shown across the top of the dashboard: total and active session/agent counts, total event count, today's event count, and the current number of live WebSocket connections.
|
||
|
||
The overview counters are spread at the top level of the response object. Two additional maps, `agents_by_status` and `sessions_by_status`, break the counts down by lifecycle status (e.g. agents: working/waiting/completed/error; sessions: active/completed/error/abandoned). **Statuses with a zero count are omitted from these maps**, so callers must not assume every status key is present.
|
||
|
||
`events_today` is computed in the caller's local day. Pass `tz_offset` as the minutes value from JavaScript's `Date.prototype.getTimezoneOffset()` (for example `420` for US Pacific Daylight Time, `300` for US Eastern Daylight Time, `0` for UTC). When omitted or non-numeric, the server falls back to UTC (offset 0).
|
||
parameters:
|
||
- in: query
|
||
name: tz_offset
|
||
description: Caller timezone offset in MINUTES, as returned by JS `Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for UTC). Used to bucket `events_today` into the caller's local day. Defaults to 0 (UTC) when omitted or non-numeric.
|
||
schema:
|
||
type: integer
|
||
example: 420
|
||
- $ref: '#/components/parameters/SourcesQuery'
|
||
example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
responses:
|
||
'200':
|
||
description: Statistics overview
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/StatsResponse'
|
||
example:
|
||
total_sessions: 184
|
||
active_sessions: 3
|
||
active_agents: 5
|
||
total_agents: 372
|
||
total_events: 28451
|
||
events_today: 612
|
||
ws_connections: 2
|
||
agents_by_status:
|
||
working: 4
|
||
waiting: 1
|
||
completed: 360
|
||
error: 7
|
||
sessions_by_status:
|
||
active: 3
|
||
completed: 175
|
||
error: 6
|
||
/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.
|
||
# TYPE agenthub_up gauge
|
||
agenthub_up 1
|
||
# HELP agenthub_sessions Number of sessions by lifecycle status.
|
||
# TYPE agenthub_sessions gauge
|
||
agenthub_sessions{status="active"} 3
|
||
/api/analytics:
|
||
get:
|
||
tags:
|
||
- Analytics
|
||
summary: Get analytics aggregates
|
||
operationId: getAnalytics
|
||
description: |-
|
||
Returns the full analytics rollup powering the Analytics screen: aggregate token usage (`tokens`), total estimated spend across all sessions (`total_cost`, in USD, computed from the configured pricing rules), per-tool invocation counts (`tool_usage`), per-day event and session time series (`daily_events`, `daily_sessions`), the distribution of subagent types (`agent_types`), per-type event counts (`event_types`), the mean number of events per session (`avg_events_per_session`), the total subagent count (`total_subagents`), and a nested `overview` object mirroring the headline session/agent/event counters.
|
||
|
||
As with `/api/stats`, the top-level `agents_by_status` and `sessions_by_status` maps **omit statuses whose count is zero**. The `agent_types[].subagent_type` field may be `null` for the main agent / untyped subagents.
|
||
|
||
The daily time series are bucketed by the caller's local day. Pass `tz_offset` as the minutes value from JS `Date.prototype.getTimezoneOffset()` (e.g. `420` for PDT). When omitted or non-numeric, the server buckets in UTC.
|
||
parameters:
|
||
- in: query
|
||
name: tz_offset
|
||
description: Caller timezone offset in MINUTES, as returned by JS `Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for UTC). Used to bucket the `daily_events` / `daily_sessions` time series into the caller's local day. Defaults to UTC when omitted or non-numeric.
|
||
schema:
|
||
type: integer
|
||
example: 420
|
||
- $ref: '#/components/parameters/SourcesQuery'
|
||
example: local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
responses:
|
||
'200':
|
||
description: Analytics response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AnalyticsResponse'
|
||
example:
|
||
tokens:
|
||
total_input: 4821002
|
||
total_output: 1933517
|
||
total_cache_read: 19288440
|
||
total_cache_write: 2044120
|
||
total_cost: 42.7183
|
||
tool_usage:
|
||
- tool_name: Bash
|
||
count: 5821
|
||
- tool_name: Read
|
||
count: 4310
|
||
- tool_name: Edit
|
||
count: 2980
|
||
- tool_name: Grep
|
||
count: 1744
|
||
daily_events:
|
||
- date: '2026-06-23'
|
||
count: 488
|
||
- date: '2026-06-24'
|
||
count: 921
|
||
- date: '2026-06-25'
|
||
count: 612
|
||
daily_sessions:
|
||
- date: '2026-06-23'
|
||
count: 4
|
||
- date: '2026-06-24'
|
||
count: 9
|
||
- date: '2026-06-25'
|
||
count: 6
|
||
agent_types:
|
||
- subagent_type: null
|
||
count: 184
|
||
- subagent_type: general-purpose
|
||
count: 96
|
||
- subagent_type: Explore
|
||
count: 71
|
||
- subagent_type: code-reviewer
|
||
count: 21
|
||
event_types:
|
||
- event_type: PreToolUse
|
||
count: 14210
|
||
- event_type: PostToolUse
|
||
count: 13988
|
||
- event_type: Stop
|
||
count: 168
|
||
- event_type: SubagentStop
|
||
count: 85
|
||
avg_events_per_session: 154.6
|
||
total_subagents: 188
|
||
overview:
|
||
total_sessions: 184
|
||
active_sessions: 3
|
||
active_agents: 5
|
||
total_agents: 372
|
||
total_events: 28451
|
||
agents_by_status:
|
||
working: 4
|
||
waiting: 1
|
||
completed: 360
|
||
error: 7
|
||
sessions_by_status:
|
||
active: 3
|
||
completed: 175
|
||
error: 6
|
||
/api/hooks/event:
|
||
post:
|
||
tags:
|
||
- Hooks
|
||
summary: Ingest Claude Code hook event
|
||
operationId: ingestHookEvent
|
||
description: |-
|
||
Primary ingestion endpoint for Claude Code lifecycle hooks. The hook handler posts an envelope of the form `{ hook_type, data }`, where `hook_type` is the Claude Code hook name (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd) and `data` carries the raw hook payload — at minimum a `session_id`. The server upserts the session and its main agent on first sight, applies the appropriate lifecycle state transition, extracts token usage and compaction signals from the transcript when present, persists an `events` row (storing `data` as a JSON-encoded string), and broadcasts a `new_event` message over the WebSocket.
|
||
|
||
On success the response is `{ ok: true, event: { ... } }`, where `event` echoes the normalized row that was just inserted (`session_id`, `agent_id`, `event_type`, `tool_name`, `summary`, `created_at`). Ingestion is designed to be fail-safe and non-blocking for the hook caller.
|
||
|
||
The optional `tool` (`codex`, `antigravity`; anything else is `claude`) names the CLI that sent the hook. A Codex or agy hook is mapped onto the same hook types first (a Codex PermissionRequest becomes a Notification, an Interrupt a Stop; an agy turn's first PreInvocation a UserPromptSubmit), and one that maps to nothing is answered `{ ok: true, ignored: true }`. A hook delivered twice is answered `{ ok: true, duplicate: true }` and stored once.
|
||
|
||
Validation failures return HTTP 400 with an `ErrorResponse` body (`{ error: { code, message } }`): `INVALID_INPUT` when `hook_type` or `data` is missing, and `MISSING_SESSION` when `data.session_id` is absent.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HookEventRequest'
|
||
example:
|
||
hook_type: PreToolUse
|
||
data:
|
||
session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566
|
||
hook_event_name: PreToolUse
|
||
tool_name: Bash
|
||
tool_input:
|
||
command: curl -s https://api.example.com/health
|
||
description: Check upstream health
|
||
cwd: /Users/dev/project
|
||
transcript_path: /Users/dev/.claude/projects/-Users-dev-project/8f3c2a10-1b2c-4d5e-9f80-112233445566.jsonl
|
||
responses:
|
||
'200':
|
||
description: Event processed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HookEventResponse'
|
||
example:
|
||
ok: true
|
||
event:
|
||
session_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566
|
||
agent_id: 8f3c2a10-1b2c-4d5e-9f80-112233445566-main
|
||
event_type: PreToolUse
|
||
tool_name: Bash
|
||
summary: 'Bash: curl -s https://api.example.com/health'
|
||
created_at: '2026-06-25T18:42:07.512Z'
|
||
'400':
|
||
description: Invalid hook payload
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: MISSING_SESSION
|
||
message: session_id is required in data
|
||
/api/pricing:
|
||
get:
|
||
tags:
|
||
- Pricing
|
||
summary: List pricing rules
|
||
operationId: listPricingRules
|
||
description: 'Returns every stored pricing rule, wrapped as `{ pricing: [ ... ] }`. Each rule carries per-MTok (per-million-token) rates for input, output, cache reads, and the two cache-write tiers (5-minute and 1-hour ephemeral), plus optional fast-mode input/output rates (0 = not configured). Rules are matched against model ids by treating the SQL `%` wildcard in `model_pattern` as `.*`; when several rules match, the longest (most specific) pattern wins. Rates here feed the cost calculations under `/api/pricing/cost`.'
|
||
responses:
|
||
'200':
|
||
description: Pricing rules
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PricingListResponse'
|
||
example:
|
||
pricing:
|
||
- model_pattern: claude-opus-4%
|
||
display_name: Claude Opus 4 (family)
|
||
input_per_mtok: 15
|
||
output_per_mtok: 75
|
||
cache_read_per_mtok: 1.5
|
||
cache_write_per_mtok: 18.75
|
||
cache_write_1h_per_mtok: 30
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-25T18:42:11.000Z'
|
||
- model_pattern: claude-haiku%
|
||
display_name: Claude Haiku (family)
|
||
input_per_mtok: 0.8
|
||
output_per_mtok: 4
|
||
cache_read_per_mtok: 0.08
|
||
cache_write_per_mtok: 1
|
||
cache_write_1h_per_mtok: 1.6
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-20T09:15:00.000Z'
|
||
put:
|
||
tags:
|
||
- Pricing
|
||
summary: Create/update pricing rule
|
||
operationId: upsertPricingRule
|
||
description: 'Creates a pricing rule or updates the existing one with the same `model_pattern` (upsert keyed on `model_pattern`). `model_pattern` and `display_name` are required; every `*_per_mtok` rate is optional and defaults to 0 when omitted. Use the SQL `%` wildcard in `model_pattern` to match a model family (e.g. `claude-opus-4%`). Set `fast_input_per_mtok` / `fast_output_per_mtok` only if the model bills fast-mode usage at a premium; leave them 0 otherwise. Note the asymmetry with the list endpoint: the response wraps a SINGLE stored rule as `{ pricing: <rule> }` (not an array). A missing `model_pattern` or `display_name` returns 400 `INVALID_INPUT`, and so does any `*_per_mtok` rate that is not a non-negative finite number (numeric strings are coerced; NaN and negative rates are rejected before anything is written).'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PricingUpsertRequest'
|
||
example:
|
||
model_pattern: claude-opus-4%
|
||
display_name: Claude Opus 4 (family)
|
||
input_per_mtok: 15
|
||
output_per_mtok: 75
|
||
cache_read_per_mtok: 1.5
|
||
cache_write_per_mtok: 18.75
|
||
cache_write_1h_per_mtok: 30
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
responses:
|
||
'200':
|
||
description: Pricing rule stored
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PricingUpsertResponse'
|
||
example:
|
||
pricing:
|
||
model_pattern: claude-opus-4%
|
||
display_name: Claude Opus 4 (family)
|
||
input_per_mtok: 15
|
||
output_per_mtok: 75
|
||
cache_read_per_mtok: 1.5
|
||
cache_write_per_mtok: 18.75
|
||
cache_write_1h_per_mtok: 30
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-25T18:42:11.000Z'
|
||
'400':
|
||
description: Invalid request body
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: model_pattern and display_name are required
|
||
/api/pricing/{pattern}:
|
||
delete:
|
||
tags:
|
||
- Pricing
|
||
summary: Delete pricing rule
|
||
operationId: deletePricingRule
|
||
description: 'Deletes the pricing rule whose `model_pattern` exactly matches the `pattern` path segment. The pattern is URL-ENCODED: the SQL `%` wildcard must be sent as `%25` (so `claude-opus-4%` becomes `claude-opus-4%25`). The server decodes it before lookup. Returns `{ ok: true }` on success, or 404 `NOT_FOUND` if no rule matches.'
|
||
parameters:
|
||
- name: pattern
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Model pattern (URL-encoded). The SQL `%` wildcard must be escaped as `%25` (e.g. `claude-opus-4%25` for the rule `claude-opus-4%`).
|
||
example: claude-opus-4%25
|
||
responses:
|
||
'200':
|
||
description: Rule deleted
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/DeleteOkResponse'
|
||
example:
|
||
ok: true
|
||
'404':
|
||
description: Pricing rule not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Pricing rule not found
|
||
/api/pricing/cost:
|
||
get:
|
||
tags:
|
||
- Pricing
|
||
summary: Get total token cost across all sessions
|
||
operationId: getTotalCost
|
||
description: Computes the aggregate token cost across EVERY session by matching each (model, speed, inference_geo, service_tier) usage bucket against the most specific pricing rule. Returns `total_cost`, a per-bucket `breakdown`, `feature_costs` (web-search surcharge, code-execution container time with the org free-hours allowance applied), `unpriced_models` (usage with no matching rule, contributing $0 so the total stays honest), and `daily_costs` bucketed by local calendar day. Pass `tz_offset` (minutes; the JS `Date.getTimezoneOffset()` value, e.g. 300 for US Eastern, -120 for CEST) so day boundaries align with the viewer's timezone; omitted/invalid offsets fall back to UTC. Honors the `sources` data-scope filter, like the sessions / stats / analytics endpoints, so the reported cost matches the active scope.
|
||
parameters:
|
||
- name: tz_offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
description: Viewer timezone offset in minutes, as returned by `Date.getTimezoneOffset()` (positive for zones behind UTC, e.g. 300 = US Eastern, -120 = CEST). Shifts the `daily_costs` day boundaries; invalid or omitted values default to UTC.
|
||
example: 300
|
||
- name: sources
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Comma-separated data-source ids to include (local history is `local`; remote SSH machines use their `remote_sources.id`). Omit for all sources. Narrows the aggregate cost to the given origins.
|
||
example: local
|
||
- name: lane
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
description: Only the sessions this workspace owns — the same rule as GET /api/sessions?lane=. 400 INVALID_INPUT when not an id.
|
||
example: 5
|
||
- name: date
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
format: date
|
||
description: 'Only that local day (YYYY-MM-DD, in `tz_offset`''s timezone): the total and breakdown count the tokens spent that day. 400 INVALID_INPUT for any other shape.'
|
||
example: '2026-10-06'
|
||
responses:
|
||
'200':
|
||
description: Cost result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CostResult'
|
||
example:
|
||
total_cost: 12.8431
|
||
breakdown:
|
||
- model: claude-opus-4-8
|
||
speed: standard
|
||
inference_geo: global
|
||
service_tier: standard
|
||
input_tokens: 184320
|
||
output_tokens: 51200
|
||
cache_read_tokens: 920000
|
||
cache_write_tokens: 64000
|
||
cache_write_1h_tokens: 12000
|
||
web_search_requests: 8
|
||
web_fetch_requests: 3
|
||
code_execution_requests: 2
|
||
cost: 8.4127
|
||
matched_rule: claude-opus-4%
|
||
- model: claude-haiku-4-5
|
||
speed: fast
|
||
inference_geo: us
|
||
service_tier: standard
|
||
input_tokens: 512000
|
||
output_tokens: 128000
|
||
cache_read_tokens: 64000
|
||
cache_write_tokens: 8000
|
||
cache_write_1h_tokens: 0
|
||
web_search_requests: 0
|
||
web_fetch_requests: 0
|
||
code_execution_requests: 0
|
||
cost: 1.5904
|
||
matched_rule: claude-haiku%
|
||
feature_costs:
|
||
web_search_cost: 0.08
|
||
web_fetch_cost: 0
|
||
code_execution_cost: 0
|
||
code_execution_hours_estimated: 0.1667
|
||
code_execution_free_hours: 50
|
||
unpriced_models:
|
||
- model: claude-experimental-preview
|
||
input_tokens: 4096
|
||
output_tokens: 2048
|
||
cache_read_tokens: 0
|
||
cache_write_tokens: 0
|
||
daily_costs:
|
||
- date: '2026-06-23'
|
||
cost: 3.1102
|
||
- date: '2026-06-24'
|
||
cost: 5.7421
|
||
- date: '2026-06-25'
|
||
cost: 3.9908
|
||
/api/pricing/cost/{sessionId}:
|
||
get:
|
||
tags:
|
||
- Pricing
|
||
summary: Get token cost for one session
|
||
operationId: getSessionCost
|
||
description: Same cost computation as `/api/pricing/cost`, but scoped to a single session's token usage. Returns the identical `CostResult` shape (`total_cost`, `breakdown`, `feature_costs`, `unpriced_models`, `daily_costs`); `daily_costs` holds at most one entry — the session's start date in the viewer's local day, or an empty array if the session id is unknown. Pass `tz_offset` (minutes, `Date.getTimezoneOffset()`) to place that start date in the viewer's timezone; defaults to UTC.
|
||
parameters:
|
||
- name: sessionId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Session ID to price.
|
||
example: sess_8f2a3b1c
|
||
- name: tz_offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
description: Viewer timezone offset in minutes (`Date.getTimezoneOffset()`; 300 = US Eastern, -120 = CEST). Places the session start date in the viewer's local day; defaults to UTC when omitted or invalid.
|
||
example: 300
|
||
responses:
|
||
'200':
|
||
description: Session cost result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CostResult'
|
||
example:
|
||
total_cost: 8.4127
|
||
breakdown:
|
||
- model: claude-opus-4-8
|
||
speed: standard
|
||
inference_geo: global
|
||
service_tier: standard
|
||
input_tokens: 184320
|
||
output_tokens: 51200
|
||
cache_read_tokens: 920000
|
||
cache_write_tokens: 64000
|
||
cache_write_1h_tokens: 12000
|
||
web_search_requests: 8
|
||
web_fetch_requests: 3
|
||
code_execution_requests: 2
|
||
cost: 8.4127
|
||
matched_rule: claude-opus-4%
|
||
- model: claude-haiku-4-5
|
||
speed: fast
|
||
inference_geo: us
|
||
service_tier: standard
|
||
input_tokens: 512000
|
||
output_tokens: 128000
|
||
cache_read_tokens: 64000
|
||
cache_write_tokens: 8000
|
||
cache_write_1h_tokens: 0
|
||
web_search_requests: 0
|
||
web_fetch_requests: 0
|
||
code_execution_requests: 0
|
||
cost: 1.5904
|
||
matched_rule: claude-haiku%
|
||
feature_costs:
|
||
web_search_cost: 0.08
|
||
web_fetch_cost: 0
|
||
code_execution_cost: 0
|
||
code_execution_hours_estimated: 0.1667
|
||
code_execution_free_hours: 50
|
||
unpriced_models:
|
||
- model: claude-experimental-preview
|
||
input_tokens: 4096
|
||
output_tokens: 2048
|
||
cache_read_tokens: 0
|
||
cache_write_tokens: 0
|
||
daily_costs:
|
||
- date: '2026-06-25'
|
||
cost: 8.4127
|
||
/api/workflows:
|
||
get:
|
||
tags:
|
||
- Workflows
|
||
summary: Get workflow intelligence aggregates
|
||
description: 'Returns the full workflow-intelligence aggregate powering the Workflows analytics page — 11 sections in one payload: `stats` (headline counters: sessions, agents, subagents, success rate, avg depth/duration, compactions, top tool flow), `orchestration` (subagent-type breakdown + delegation edges + outcomes), `toolFlow` (tool-to-tool transitions + tool counts), `effectiveness` (per-subagent-type success rate, avg duration, weekly trend), `patterns` (frequent subagent sequences + solo-session share), `modelDelegation` (model usage for main/sub agents + tokens by model), `errorPropagation` (errors by depth/type + error rate), `concurrency` (averaged agent swim-lane start/end), `complexity` (per-session agent/token/duration rows), `compaction` (compaction counts + tokens recovered), and `cooccurrence` (directed subagent-after-subagent pairs). The optional `status` query filter scopes every section to sessions of one status, and `sections` computes and returns only the named ones (`stats.topFlow` is the head of `toolFlow.transitions`, one pass for both). The bundle is computed in a worker thread on its own read-only connection, so it never stalls other requests; identical requests in flight share one run, and one over `AGENTHUB_READ_TIMEOUT_MS` (default 60 s) answers 503 `EREADTIMEOUT`. Errors use the SHORT `{ error: { message } }` shape.'
|
||
operationId: getWorkflowIntelligence
|
||
parameters:
|
||
- $ref: '#/components/parameters/WorkflowStatusQuery'
|
||
- name: sections
|
||
in: query
|
||
required: false
|
||
description: 'Comma-separated subset of the bundle to compute: stats, orchestration, toolFlow, effectiveness, patterns, modelDelegation, errorPropagation, concurrency, complexity, compaction, cooccurrence. Unknown names are ignored; none known is a 400.'
|
||
schema:
|
||
type: string
|
||
example: stats,toolFlow
|
||
responses:
|
||
'200':
|
||
description: Workflow aggregate data
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/WorkflowAggregateResponse'
|
||
example:
|
||
stats:
|
||
totalSessions: 412
|
||
totalAgents: 1893
|
||
totalSubagents: 1481
|
||
avgSubagents: 3.6
|
||
successRate: 94.2
|
||
avgDepth: 1.4
|
||
avgDurationSec: 1187
|
||
totalCompactions: 96
|
||
avgCompactions: 0.2
|
||
topFlow:
|
||
source: Read
|
||
target: Edit
|
||
count: 3120
|
||
orchestration:
|
||
sessionCount: 412
|
||
mainCount: 412
|
||
subagentTypes:
|
||
- subagent_type: general-purpose
|
||
count: 612
|
||
completed: 590
|
||
errors: 8
|
||
- subagent_type: Explore
|
||
count: 388
|
||
completed: 380
|
||
errors: 2
|
||
edges:
|
||
- source: main
|
||
target: general-purpose
|
||
weight: 612
|
||
outcomes:
|
||
- status: completed
|
||
count: 1402
|
||
- status: error
|
||
count: 79
|
||
compactions:
|
||
total: 96
|
||
sessions: 71
|
||
toolFlow:
|
||
transitions:
|
||
- source: Read
|
||
target: Edit
|
||
value: 3120
|
||
toolCounts:
|
||
- tool_name: Read
|
||
count: 9821
|
||
- tool_name: Edit
|
||
count: 5402
|
||
- tool_name: Bash
|
||
count: 4810
|
||
effectiveness:
|
||
- subagent_type: general-purpose
|
||
total: 612
|
||
completed: 590
|
||
errors: 8
|
||
sessions: 281
|
||
successRate: 98.7
|
||
avgDuration: 142
|
||
trend:
|
||
- 88
|
||
- 91
|
||
- 102
|
||
- 97
|
||
- 110
|
||
- 24
|
||
- 12
|
||
patterns:
|
||
patterns:
|
||
- steps:
|
||
- Explore
|
||
- general-purpose
|
||
count: 142
|
||
percentage: 34.5
|
||
soloSessionCount: 120
|
||
soloPercentage: 29.1
|
||
modelDelegation:
|
||
mainModels:
|
||
- model: claude-opus-4-8
|
||
agent_count: 290
|
||
session_count: 290
|
||
subagentModels:
|
||
- model: claude-opus-4-8
|
||
agent_count: 980
|
||
tokensByModel:
|
||
- model: claude-opus-4-8
|
||
input_tokens: 4820112
|
||
output_tokens: 1920334
|
||
cache_read_tokens: 88201442
|
||
cache_write_tokens: 12044210
|
||
errorPropagation:
|
||
byDepth:
|
||
- depth: 0
|
||
count: 41
|
||
- depth: 1
|
||
count: 38
|
||
byType:
|
||
- subagent_type: general-purpose
|
||
count: 8
|
||
eventErrors:
|
||
- summary: 'Error in Bash: command not found'
|
||
count: 5
|
||
sessionsWithErrors: 33
|
||
totalSessions: 412
|
||
errorRate: 8
|
||
concurrency:
|
||
aggregateLanes:
|
||
- name: Main Agent
|
||
avgStart: 0
|
||
avgEnd: 1
|
||
count: 412
|
||
- name: Explore
|
||
avgStart: 0.12
|
||
avgEnd: 0.31
|
||
count: 388
|
||
complexity:
|
||
- id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
name: Refactor auth flow
|
||
status: completed
|
||
duration: 2538
|
||
agentCount: 6
|
||
subagentCount: 5
|
||
totalTokens: 940212
|
||
model: claude-opus-4-8
|
||
compaction:
|
||
totalCompactions: 96
|
||
tokensRecovered: 18402991
|
||
perSession:
|
||
- session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
compactions: 3
|
||
sessionsWithCompactions: 71
|
||
totalSessions: 412
|
||
cooccurrence:
|
||
- source: Explore
|
||
target: general-purpose
|
||
weight: 142
|
||
'400':
|
||
description: '`sections` names no known section'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_SECTIONS
|
||
message: 'sections must name some of: stats, …'
|
||
'500':
|
||
description: Internal server error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: 'no such table: agents'
|
||
'503':
|
||
description: The aggregate ran past `AGENTHUB_READ_TIMEOUT_MS`
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EREADTIMEOUT
|
||
message: read query took over 60000 ms
|
||
/api/workflows/session/{id}:
|
||
get:
|
||
tags:
|
||
- Workflows
|
||
summary: Get workflow drill-in for one session
|
||
description: 'Returns the workflow drill-in for a single session, used by the session-level Workflow view: `session` (the session row), `tree` (the recursive parent→child agent tree rooted at the main agent), `toolTimeline` (chronological tool events with tool_name/event_type/agent_id/summary), `swimLanes` (a flat per-agent start/end lane list for the Gantt-style view), and `events` (the chronological event stream, capped at the first 500 rows). Returns 404 with the SHORT `{ error: { message } }` shape when the session id is unknown.'
|
||
operationId: getWorkflowSession
|
||
parameters:
|
||
- $ref: '#/components/parameters/SessionIdPath'
|
||
responses:
|
||
'200':
|
||
description: Workflow session detail
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/WorkflowSessionResponse'
|
||
example:
|
||
session:
|
||
id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
name: Refactor auth flow
|
||
status: completed
|
||
cwd: /Users/son/code/foo
|
||
model: claude-opus-4-8
|
||
started_at: '2026-06-25T13:00:00.000Z'
|
||
ended_at: '2026-06-25T13:42:18.220Z'
|
||
metadata: null
|
||
updated_at: '2026-06-25T13:42:18.220Z'
|
||
tree:
|
||
- id: agent_main_5f3c0e2a
|
||
name: Main agent
|
||
type: main
|
||
subagent_type: null
|
||
status: completed
|
||
task: null
|
||
started_at: '2026-06-25T13:00:00.000Z'
|
||
ended_at: '2026-06-25T13:42:18.220Z'
|
||
children:
|
||
- id: agent_sub_a1
|
||
name: Explore the auth module
|
||
type: subagent
|
||
subagent_type: Explore
|
||
status: completed
|
||
task: Map auth call sites
|
||
started_at: '2026-06-25T13:02:10.000Z'
|
||
ended_at: '2026-06-25T13:06:55.000Z'
|
||
children: []
|
||
toolTimeline:
|
||
- id: 91021
|
||
tool_name: Read
|
||
event_type: PreToolUse
|
||
agent_id: agent_main_5f3c0e2a
|
||
created_at: '2026-06-25T13:01:02.500Z'
|
||
summary: Read server/auth.js
|
||
swimLanes:
|
||
- id: agent_main_5f3c0e2a
|
||
name: Main agent
|
||
type: main
|
||
subagent_type: null
|
||
status: completed
|
||
started_at: '2026-06-25T13:00:00.000Z'
|
||
ended_at: '2026-06-25T13:42:18.220Z'
|
||
parent_agent_id: null
|
||
events:
|
||
- id: 91021
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
agent_id: agent_main_5f3c0e2a
|
||
event_type: PreToolUse
|
||
tool_name: Read
|
||
summary: Read server/auth.js
|
||
data: null
|
||
created_at: '2026-06-25T13:01:02.500Z'
|
||
'404':
|
||
description: Session not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: Session not found
|
||
'500':
|
||
description: Internal server error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: 'no such table: agents'
|
||
/api/settings/info:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Get system/database/hook diagnostics
|
||
description: 'Returns a diagnostics snapshot used by the Settings page: `db` (database file path, on-disk size in bytes, per-table row counts, SQLite pragmas, and recent event load over the last 5/15/60 minutes), `hooks` (whether the Claude Code hook-handler is installed in `settings.json`, the settings path, and a per-hook-type installed map), `server` (process uptime, Node version, platform, live WebSocket connection count, memory/CPU/host stats), and `transcript_cache` (number of cached transcript entries and the cached paths). Read-only and cheap to poll.'
|
||
operationId: getSettingsInfo
|
||
responses:
|
||
'200':
|
||
description: Settings and diagnostics
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SettingsInfoResponse'
|
||
example:
|
||
db:
|
||
path: /Users/dev/AgentHub/server/data/agent-monitor.db
|
||
size: 48922624
|
||
counts:
|
||
sessions: 412
|
||
agents: 1893
|
||
events: 58117
|
||
model_pricing: 11
|
||
token_usage: 401
|
||
pragmas:
|
||
journal_mode: wal
|
||
synchronous: 1
|
||
auto_vacuum: 0
|
||
encoding: UTF-8
|
||
foreign_keys: 1
|
||
busy_timeout: 5000
|
||
load_stats:
|
||
m5: 23
|
||
m15: 88
|
||
h1: 511
|
||
hooks:
|
||
installed: true
|
||
path: /Users/son/.claude/settings.json
|
||
hooks:
|
||
PreToolUse: true
|
||
PostToolUse: true
|
||
Stop: true
|
||
SubagentStop: true
|
||
Notification: true
|
||
SessionStart: true
|
||
SessionEnd: true
|
||
server:
|
||
uptime: 14523.91
|
||
node_version: v22.14.0
|
||
platform: darwin
|
||
ws_connections: 2
|
||
memory:
|
||
rss: 142802944
|
||
heapTotal: 71303168
|
||
heapUsed: 58392104
|
||
external: 3211884
|
||
heapLimit: 4345298944
|
||
arrayBuffers: 1048576
|
||
cpu_load:
|
||
- 2.13
|
||
- 2.45
|
||
- 2.31
|
||
arch: arm64
|
||
total_mem: 17179869184
|
||
free_mem: 2147483648
|
||
cpus: 10
|
||
transcript_cache:
|
||
entries: 37
|
||
paths:
|
||
- /Users/son/.claude/projects/-Users-son-code-foo/abc123.jsonl
|
||
- /Users/son/.claude/projects/-Users-son-code-bar/def456.jsonl
|
||
/api/settings/clear-data:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Delete all dashboard data
|
||
description: '⚠ DESTRUCTIVE — IRREVERSIBLE. Deletes ALL sessions, agents, events, token_usage rows, the fired-alert feed (alert_events), and the webhook delivery log. There is no confirmation step and no undo — export first via GET /api/settings/export if you need a backup. User CONFIGURATION survives: alert *rules*, webhook *targets*, and model_pricing are preserved (they''re settings, not captured data). The response echoes the row counts that existed BEFORE the wipe so the UI can report what was removed.'
|
||
operationId: clearData
|
||
responses:
|
||
'200':
|
||
description: Data cleared
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ClearDataResponse'
|
||
example:
|
||
ok: true
|
||
cleared:
|
||
sessions: 412
|
||
agents: 1893
|
||
events: 58117
|
||
model_pricing: 11
|
||
token_usage: 401
|
||
/api/settings/reimport:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Re-import legacy sessions from ~/.claude
|
||
description: Re-runs the legacy history importer against the default `~/.claude` projects directory, funneling every transcript through the same parser + importSession pipeline the live server uses. This is IDEMPOTENT and ADDITIVE — already-imported sessions are deduplicated (counted under `skipped`), token counts and compaction baselines are preserved so cost never double-counts, and nothing existing is deleted. The response reports how many sessions were `imported` vs `skipped`, plus an `errors` count for transcripts that failed to parse.
|
||
operationId: reimportLegacySessions
|
||
responses:
|
||
'200':
|
||
description: Import completed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ReimportResponse'
|
||
example:
|
||
ok: true
|
||
imported: 38
|
||
skipped: 374
|
||
errors: 0
|
||
'500':
|
||
description: Import failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: IMPORT_FAILED
|
||
message: 'ENOENT: no such file or directory, scandir ''/Users/son/.claude/projects'''
|
||
/api/settings/reinstall-hooks:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Reinstall Claude Code hooks
|
||
description: 'Re-runs the hook installer to (re)wire the dashboard''s hook-handler into Claude Code''s `settings.json` for all seven hook types (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, SessionStart, SessionEnd). Safe to re-run — it overwrites/repairs the dashboard''s own hook entries without touching unrelated user hooks. The response returns the post-install hook status so the UI can confirm every hook type is now `installed: true`.'
|
||
operationId: reinstallHooks
|
||
responses:
|
||
'200':
|
||
description: Hooks reinstall result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ReinstallHooksResponse'
|
||
example:
|
||
ok: true
|
||
hooks:
|
||
installed: true
|
||
path: /Users/son/.claude/settings.json
|
||
hooks:
|
||
PreToolUse: true
|
||
PostToolUse: true
|
||
Stop: true
|
||
SubagentStop: true
|
||
Notification: true
|
||
SessionStart: true
|
||
SessionEnd: true
|
||
'500':
|
||
description: Hook installation failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: HOOK_INSTALL_FAILED
|
||
message: 'EACCES: permission denied, open ''/Users/son/.claude/settings.json'''
|
||
/api/settings/reset-pricing:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Reset pricing table to defaults
|
||
description: '⚠ DESTRUCTIVE to pricing customizations. Deletes EVERY row in the model_pricing table and re-seeds it from the dashboard''s built-in DEFAULT_PRICING list. Any custom rates or custom model patterns you added are permanently lost — there is no undo. Captured session/token data is untouched (only the pricing rules used to *compute* cost change). The response returns the full freshly-seeded pricing table. With `{"scope": "claude"}` (the Claude Code tab''s button) only `claude-…` rows are deleted and re-seeded; every other vendor''s rows and custom patterns are kept.'
|
||
operationId: resetPricing
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- claude
|
||
description: 'Reset only `claude-…` rows. Omitted: every row.'
|
||
responses:
|
||
'200':
|
||
description: Pricing defaults restored
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ResetPricingResponse'
|
||
example:
|
||
ok: true
|
||
pricing:
|
||
- model_pattern: claude-opus-4*
|
||
display_name: Claude Opus 4
|
||
input_per_mtok: 15
|
||
output_per_mtok: 75
|
||
cache_read_per_mtok: 1.5
|
||
cache_write_per_mtok: 18.75
|
||
cache_write_1h_per_mtok: 30
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-26T00:00:00.000Z'
|
||
- model_pattern: claude-sonnet-4*
|
||
display_name: Claude Sonnet 4
|
||
input_per_mtok: 3
|
||
output_per_mtok: 15
|
||
cache_read_per_mtok: 0.3
|
||
cache_write_per_mtok: 3.75
|
||
cache_write_1h_per_mtok: 6
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-26T00:00:00.000Z'
|
||
/api/settings/export:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Export all dashboard data as JSON
|
||
description: 'Exports the entire dataset as a single versioned JSON document — all sessions, agents, events, token_usage rows, workflows, dashboard_runs, alert_rules, and model_pricing — stamped with `format: "agenthub-export"`, `version`, and `exported_at`. Served with a `Content-Disposition: attachment` header (filename `agent-monitor-export-YYYY-MM-DD.json`) so browsers download it. Use it to back up before a destructive operation (clear-data / cleanup with purge_days) or to migrate/consolidate data across machines — the bundle is re-importable via POST /api/settings/import. Read-only; nothing is modified.'
|
||
operationId: exportData
|
||
responses:
|
||
'200':
|
||
description: Export payload (served as attachment)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ExportResponse'
|
||
example:
|
||
format: agenthub-export
|
||
version: 1
|
||
exported_at: '2026-06-26T01:12:44.913Z'
|
||
sessions:
|
||
- id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
name: Refactor auth flow
|
||
status: completed
|
||
cwd: /Users/son/code/foo
|
||
model: claude-opus-4-8
|
||
started_at: '2026-06-25T13:00:00.000Z'
|
||
ended_at: '2026-06-25T13:42:18.220Z'
|
||
metadata: null
|
||
updated_at: '2026-06-25T13:42:18.220Z'
|
||
agents:
|
||
- id: agent_main_5f3c0e2a
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
name: Main agent
|
||
type: main
|
||
subagent_type: null
|
||
status: completed
|
||
task: null
|
||
current_tool: null
|
||
started_at: '2026-06-25T13:00:00.000Z'
|
||
ended_at: '2026-06-25T13:42:18.220Z'
|
||
parent_agent_id: null
|
||
metadata: null
|
||
updated_at: '2026-06-25T13:42:18.220Z'
|
||
events:
|
||
- id: 91021
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
agent_id: agent_main_5f3c0e2a
|
||
event_type: PreToolUse
|
||
tool_name: Edit
|
||
summary: Edit server/auth.js
|
||
data: null
|
||
created_at: '2026-06-25T13:05:42.001Z'
|
||
token_usage:
|
||
- session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
model: claude-opus-4-8
|
||
input_tokens: 18422
|
||
output_tokens: 9120
|
||
cache_read_tokens: 220184
|
||
cache_write_tokens: 41002
|
||
model_pricing:
|
||
- model_pattern: claude-opus-4*
|
||
display_name: Claude Opus 4
|
||
input_per_mtok: 15
|
||
output_per_mtok: 75
|
||
cache_read_per_mtok: 1.5
|
||
cache_write_per_mtok: 18.75
|
||
cache_write_1h_per_mtok: 30
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-26T00:00:00.000Z'
|
||
- model_pattern: claude-sonnet-4*
|
||
display_name: Claude Sonnet 4
|
||
input_per_mtok: 3
|
||
output_per_mtok: 15
|
||
cache_read_per_mtok: 0.3
|
||
cache_write_per_mtok: 3.75
|
||
cache_write_1h_per_mtok: 6
|
||
fast_input_per_mtok: 0
|
||
fast_output_per_mtok: 0
|
||
updated_at: '2026-06-26T00:00:00.000Z'
|
||
/api/settings/cleanup:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Abandon stale sessions and optionally purge old history
|
||
description: 'Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. `purge_days` must be a whole number ≥ 1, else 400 `INVALID_PURGE_DAYS` (a fraction like 0.1 would purge almost everything). `dry_run: true` answers the same counts (plus `dry_run: true`) and changes nothing — preview a purge before running it. The response reports counts for each phase.'
|
||
operationId: cleanupData
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CleanupRequest'
|
||
examples:
|
||
tidyOnly:
|
||
summary: 'Safe: abandon stale sessions only (no deletion)'
|
||
value:
|
||
abandon_hours: 12
|
||
abandonAndPurge:
|
||
summary: Abandon stale sessions, then purge history older than 90 days (destructive)
|
||
value:
|
||
abandon_hours: 12
|
||
purge_days: 90
|
||
preview:
|
||
summary: 'Dry run: count what a 90-day purge would delete, change nothing'
|
||
value:
|
||
purge_days: 90
|
||
dry_run: true
|
||
responses:
|
||
'200':
|
||
description: Cleanup result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CleanupResponse'
|
||
example:
|
||
ok: true
|
||
abandoned: 3
|
||
purged_sessions: 57
|
||
purged_events: 14820
|
||
purged_agents: 241
|
||
'400':
|
||
description: purge_days is not a whole number of days, 1 or more
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_PURGE_DAYS
|
||
message: purge_days must be a whole number of days, 1 or more
|
||
/api/import/guide:
|
||
get:
|
||
tags:
|
||
- Import
|
||
summary: Import guide with OS-aware defaults and step-by-step instructions
|
||
description: 'Returns the OS-aware import guide the Import page renders verbatim: the detected `platform`, the default `~/.claude/projects` directory (raw + display form), whether it exists and how many projects/JSONL files it holds, an OS-specific `archive_command` for bundling history off another machine, the supported file extensions, the upload size/count limits, and four ordered `steps` (locate → archive → choose mode → verify). Read-only; performs no import.'
|
||
operationId: importGuide
|
||
responses:
|
||
'200':
|
||
description: Guide payload
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ImportGuideResponse'
|
||
example:
|
||
platform: darwin
|
||
default_projects_dir: /Users/son/.claude/projects
|
||
default_projects_dir_display: ~/.claude/projects
|
||
default_projects_dir_exists: true
|
||
default_projects_dir_stats:
|
||
projects: 24
|
||
jsonl_files: 312
|
||
archive_command: tar -czf claude-history.tar.gz -C ~/.claude projects
|
||
supported_extensions:
|
||
- .jsonl
|
||
- .meta.json
|
||
- .zip
|
||
- .tar
|
||
- .tar.gz
|
||
- .tgz
|
||
- .gz
|
||
max_upload_bytes: 1073741824
|
||
max_upload_files: 2000
|
||
steps:
|
||
- id: locate
|
||
title: Locate your Claude Code history
|
||
body: Claude Code stores every session as a JSONL transcript under ~/.claude/projects. Each subdirectory is named after the working directory where the session started (with slashes replaced by dashes).
|
||
- id: archive
|
||
title: Bundle it for transfer (optional)
|
||
body: |-
|
||
If you're importing from another machine, archive the whole projects folder first:
|
||
|
||
tar -czf claude-history.tar.gz -C ~/.claude projects
|
||
|
||
Move claude-history.tar.gz to this machine however you like (AirDrop, scp, USB, cloud storage).
|
||
- id: choose
|
||
title: Pick an import mode
|
||
body: 'Rescan default: re-read ~/.claude/projects on this machine and import anything new. From folder: point the dashboard at any directory you''ve extracted history into. Upload: drag-drop JSONL files or an archive directly into the browser.'
|
||
- id: verify
|
||
title: Verify tokens and cost
|
||
body: 'Imports are idempotent: re-running is always safe. Token counts are deduplicated per session ID, with compaction baselines preserved so cost never double-counts. After import, open Analytics → Cost to confirm the breakdown.'
|
||
/api/import/rescan:
|
||
post:
|
||
tags:
|
||
- Import
|
||
summary: Rescan the default ~/.claude/projects directory
|
||
description: 'Re-scans the default `~/.claude/projects` directory and imports anything new through the live ingestion pipeline. IDEMPOTENT and ADDITIVE — re-running is always safe, already-imported sessions are deduplicated (`skipped`), and token/compaction baselines are preserved so cost never double-counts. Progress is broadcast over the WebSocket as `import.progress` frames while it runs. The response reports `imported` / `skipped` / `backfilled` / `errors` plus `sessions_seen` and `files_scanned`, with `source: "default"`.'
|
||
operationId: importRescan
|
||
responses:
|
||
'200':
|
||
description: Import result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ImportResultResponse'
|
||
example:
|
||
ok: true
|
||
source: default
|
||
imported: 14
|
||
skipped: 298
|
||
backfilled: 2
|
||
errors: 0
|
||
sessions_seen: 312
|
||
files_scanned: 312
|
||
'500':
|
||
description: Import failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: IMPORT_FAILED
|
||
message: Failed to read projects directory
|
||
/api/import/scan-path:
|
||
post:
|
||
tags:
|
||
- Import
|
||
summary: Import transcripts from an arbitrary absolute directory
|
||
description: 'Imports transcripts from an arbitrary directory you point the dashboard at (e.g. history extracted from another machine). The `path` must resolve to an existing directory: a leading `~` is expanded to the home directory, the result must be absolute, and subdirectories are walked recursively for `.jsonl` files. Same idempotent, baseline-preserving pipeline as the default rescan; progress is broadcast as `import.progress`. The response echoes the resolved `path` and the per-run counters with `source: "path"`.'
|
||
operationId: importScanPath
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- path
|
||
properties:
|
||
path:
|
||
type: string
|
||
description: Absolute directory path. Tilde (~) is expanded. Walks subdirectories recursively.
|
||
examples:
|
||
absolute:
|
||
summary: Absolute directory
|
||
value:
|
||
path: /Users/son/Downloads/claude-history/projects
|
||
tilde:
|
||
summary: Tilde expanded to home directory
|
||
value:
|
||
path: ~/Downloads/claude-history/projects
|
||
responses:
|
||
'200':
|
||
description: Import result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ImportResultResponse'
|
||
example:
|
||
ok: true
|
||
source: path
|
||
path: /Users/son/Downloads/claude-history/projects
|
||
imported: 9
|
||
skipped: 41
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 50
|
||
files_scanned: 50
|
||
'400':
|
||
description: Path validation failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
missing:
|
||
summary: path omitted
|
||
value:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: '`path` is required'
|
||
notAbsolute:
|
||
summary: path is not absolute
|
||
value:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: '`path` must be an absolute path'
|
||
notFound:
|
||
summary: path does not exist
|
||
value:
|
||
error:
|
||
code: PATH_NOT_FOUND
|
||
message: 'Path does not exist: /tmp/nope'
|
||
notDir:
|
||
summary: path is a file, not a directory
|
||
value:
|
||
error:
|
||
code: NOT_A_DIRECTORY
|
||
message: 'Path is not a directory: /tmp/foo.jsonl'
|
||
'500':
|
||
description: Import failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: IMPORT_FAILED
|
||
message: Unexpected end of JSON input
|
||
/api/import/upload:
|
||
post:
|
||
tags:
|
||
- Import
|
||
summary: Upload JSONL files or archives (.zip, .tar, .tar.gz, .tgz, .gz)
|
||
description: 'Imports history uploaded directly from the browser as `multipart/form-data` under the `files` field. Accepts raw `.jsonl` / `.meta.json` transcripts and/or archives (`.zip`, `.tar`, `.tar.gz`, `.tgz`, `.gz`), which are extracted into a temp dir and walked for JSONL content. Unsupported extensions are silently rejected and reported in `rejected_files`. Extraction is bounded to defend against zip bombs — exceeding the limit returns 413. Same idempotent import pipeline; progress is broadcast as `import.progress`. On success the response carries `source: "upload"` plus `files_received`, `rejected_files`, `entries_extracted`, `entries_skipped`, and the standard import counters. (Requires the optional `multer` dependency; a missing install yields a 500.)'
|
||
operationId: importUpload
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
multipart/form-data:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
files:
|
||
type: array
|
||
items:
|
||
type: string
|
||
format: binary
|
||
description: Files to import. Supports .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.
|
||
example:
|
||
files:
|
||
- claude-history.tar.gz
|
||
- session-extra.jsonl
|
||
responses:
|
||
'200':
|
||
description: Import result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ImportResultResponse'
|
||
example:
|
||
ok: true
|
||
source: upload
|
||
files_received: 3
|
||
rejected_files:
|
||
- notes.txt
|
||
entries_extracted: 128
|
||
entries_skipped: 2
|
||
imported: 12
|
||
skipped: 116
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 128
|
||
files_scanned: 128
|
||
'400':
|
||
description: No files or no JSONL content
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
noFiles:
|
||
summary: No supported files in the upload
|
||
value:
|
||
error:
|
||
code: NO_FILES
|
||
message: No supported files in upload. 1 file(s) rejected (unsupported extension).
|
||
noJsonl:
|
||
summary: Archive extracted but contained no .jsonl
|
||
value:
|
||
error:
|
||
code: NO_JSONL
|
||
message: 'No .jsonl files were found in the uploaded content. Supported inputs: .jsonl, .meta.json, .zip, .tar, .tar.gz, .tgz, .gz.'
|
||
'413':
|
||
description: Extraction limit exceeded (possible zip bomb)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EXTRACTION_LIMIT_EXCEEDED
|
||
message: Archive exceeded the extraction size limit (possible zip bomb).
|
||
'500':
|
||
description: Upload or import failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
noMulter:
|
||
summary: Optional multer dependency not installed
|
||
value:
|
||
error:
|
||
code: UPLOADER_UNAVAILABLE
|
||
message: File upload requires `multer`. Run `npm install` to pick up new deps.
|
||
importFailed:
|
||
summary: Import pipeline error
|
||
value:
|
||
error:
|
||
code: IMPORT_FAILED
|
||
message: Unexpected end of JSON input
|
||
/api/updates/status:
|
||
get:
|
||
tags:
|
||
- Updates
|
||
summary: Check whether the dashboard git checkout is behind origin
|
||
description: 'Reports whether the dashboard''s own git checkout is behind its upstream remote, so a user can pull and restart manually (the dashboard never self-restarts). The response is a variant object: when it IS a git checkout it includes `git_repo: true`, `repo_root`, `update_available`, `commits_behind`, `remote_ref`, `local_sha`, `remote_sha`, and a copy-pasteable `manual_command`; when it is NOT a git checkout (e.g. an npm/tarball install) it returns `git_repo: false` with a `message` and no diff fields. Read-only — this only inspects git, it does not fetch destructively or modify the working tree.'
|
||
operationId: getUpdatesStatus
|
||
responses:
|
||
'200':
|
||
description: Update check result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Includes git_repo, update_available, commits_behind, remote_ref, local_sha, remote_sha, manual_command, and optional error/message fields.
|
||
examples:
|
||
updateAvailable:
|
||
summary: Behind origin — update available
|
||
value:
|
||
git_repo: true
|
||
repo_root: /Users/dev/AgentHub
|
||
update_available: true
|
||
commits_behind: 4
|
||
remote_ref: origin/master
|
||
local_sha: de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7
|
||
remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
manual_command: npm run self-update
|
||
checked_at: '2026-06-26T01:20:09.553Z'
|
||
upToDate:
|
||
summary: Up to date with origin
|
||
value:
|
||
git_repo: true
|
||
repo_root: /Users/dev/AgentHub
|
||
update_available: false
|
||
commits_behind: 0
|
||
remote_ref: origin/master
|
||
local_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
manual_command: npm run self-update
|
||
checked_at: '2026-06-26T01:25:41.002Z'
|
||
notARepo:
|
||
summary: Not a git checkout — detection unavailable
|
||
value:
|
||
git_repo: false
|
||
update_available: false
|
||
message: Not a git checkout — update detection is unavailable for this install.
|
||
'500':
|
||
description: Update status query failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: UPDATE_STATUS_FAILED
|
||
message: 'git: command not found'
|
||
/api/updates/check:
|
||
post:
|
||
tags:
|
||
- Updates
|
||
summary: Run an update check immediately and broadcast the result
|
||
description: Runs the same upstream check as GET /api/updates/status immediately and, in addition to returning the result, broadcasts it to every connected client as an `update_status` WebSocket message — so all open dashboard tabs refresh their update banner at once. The response shape is identical to GET /api/updates/status (the same git_repo / update_available / commits_behind / manual_command variant object). Read-only with respect to the working tree.
|
||
operationId: triggerUpdatesCheck
|
||
responses:
|
||
'200':
|
||
description: Fresh update status payload (also broadcast over WebSocket)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Same shape as GET /api/updates/status. Also sent as an update_status WebSocket message to all connected clients.
|
||
examples:
|
||
updateAvailable:
|
||
summary: Behind origin — update available
|
||
value:
|
||
git_repo: true
|
||
repo_root: /Users/dev/AgentHub
|
||
update_available: true
|
||
commits_behind: 4
|
||
remote_ref: origin/master
|
||
local_sha: de5d0891a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7
|
||
remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
manual_command: npm run self-update
|
||
checked_at: '2026-06-26T01:20:09.553Z'
|
||
upToDate:
|
||
summary: Up to date with origin
|
||
value:
|
||
git_repo: true
|
||
repo_root: /Users/dev/AgentHub
|
||
update_available: false
|
||
commits_behind: 0
|
||
remote_ref: origin/master
|
||
local_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
remote_sha: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
|
||
manual_command: npm run self-update
|
||
checked_at: '2026-06-26T01:25:41.002Z'
|
||
'500':
|
||
description: Update check failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: UPDATE_CHECK_FAILED
|
||
message: 'git fetch failed: network unreachable'
|
||
/api/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
|
||
description: Returns the fired-alert feed, newest first, as `{ alerts, total, unacked, limit, offset }`. Each alert event carries the originating rule's id/name/type, a human-readable `message`, an `acknowledged` flag (0/1), `created_at`, and `details` — which is a JSON STRING (not an object) that callers must `JSON.parse`. `limit` is clamped to 1–200 (default 50) and negative `offset` is clamped to 0. Set `unacked=true` to return only unacknowledged alerts; `total` then counts only unacked rows, while `unacked` always reports the global unacknowledged count.
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 200
|
||
default: 50
|
||
description: Page size, clamped to the 1–200 range (default 50). Values outside the range are clamped, not rejected.
|
||
example: 50
|
||
- name: offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
description: Pagination offset; negative values are clamped to 0.
|
||
example: 0
|
||
- name: unacked
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: boolean
|
||
description: When the literal string "true", return only unacknowledged alerts (and scope `total` to that subset).
|
||
example: 'true'
|
||
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.
|
||
example:
|
||
alerts:
|
||
- id: 42
|
||
rule_id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
rule_name: Idle session watchdog
|
||
rule_type: inactivity
|
||
message: Session sess_8f2a has been inactive for 35 minutes
|
||
details: '{"session_id":"sess_8f2a","minutes":35,"threshold":30}'
|
||
acknowledged: 0
|
||
created_at: '2026-06-25T17:05:44.000Z'
|
||
- id: 41
|
||
rule_id: 1a2b3c4d-5e6f-7081-9201-aabbccddeeff
|
||
rule_name: Heavy token burn
|
||
rule_type: token_threshold
|
||
message: Session sess_3c1d crossed 5,000,000 total tokens
|
||
details: '{"session_id":"sess_3c1d","total_tokens":5120000,"threshold":5000000}'
|
||
acknowledged: 1
|
||
created_at: '2026-06-25T16:40:02.000Z'
|
||
total: 2
|
||
unacked: 1
|
||
limit: 50
|
||
offset: 0
|
||
/api/alerts/rules:
|
||
get:
|
||
tags:
|
||
- Alerts
|
||
summary: List alert rules
|
||
operationId: listAlertRules
|
||
description: 'Returns all alert rules as `{ rules: [ ... ] }`. Each rule''s `config` is returned as a PARSED object (the column is stored as JSON text), and `enabled` is coerced to a boolean. The `config` shape depends on `rule_type`: `event_pattern` uses event_type / tool_name / summary_contains plus optional count + window_minutes; `inactivity` uses `minutes`; `status_duration` uses `status` + `minutes`; `token_threshold` uses `total_tokens`.'
|
||
responses:
|
||
'200':
|
||
description: All alert rules with parsed config objects
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
rules:
|
||
- id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
name: Idle session watchdog
|
||
rule_type: inactivity
|
||
config:
|
||
minutes: 30
|
||
enabled: true
|
||
cooldown_seconds: 300
|
||
created_at: '2026-06-10T12:00:00.000Z'
|
||
updated_at: '2026-06-24T08:30:00.000Z'
|
||
- id: 1a2b3c4d-5e6f-7081-9201-aabbccddeeff
|
||
name: Heavy token burn
|
||
rule_type: token_threshold
|
||
config:
|
||
total_tokens: 5000000
|
||
enabled: true
|
||
cooldown_seconds: 600
|
||
created_at: '2026-06-12T14:20:00.000Z'
|
||
updated_at: '2026-06-12T14:20:00.000Z'
|
||
post:
|
||
tags:
|
||
- Alerts
|
||
summary: Create an alert rule
|
||
operationId: createAlertRule
|
||
description: |-
|
||
Creates an alert rule and returns it serialized as `{ rule: { ... } }` with HTTP 201. `name`, `rule_type`, and `config` are required; the `config` shape is validated per `rule_type`:
|
||
- `event_pattern`: `{ event_type?, tool_name?, summary_contains?, count?, window_minutes? }` — fires when matching events accumulate.
|
||
- `inactivity`: `{ minutes }` — fires when a session goes idle.
|
||
- `status_duration`: `{ status, minutes }` — fires when a session holds a status too long.
|
||
- `token_threshold`: `{ total_tokens }` — fires when usage crosses a ceiling.
|
||
`enabled` defaults to true and `cooldown_seconds` defaults to 300 (must be a non-negative integer). A bad name, unknown `rule_type`, invalid `config`, or negative cooldown returns 400 `INVALID_INPUT`.
|
||
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
|
||
examples:
|
||
inactivity:
|
||
summary: Inactivity rule
|
||
value:
|
||
name: Idle session watchdog
|
||
rule_type: inactivity
|
||
config:
|
||
minutes: 30
|
||
enabled: true
|
||
cooldown_seconds: 300
|
||
event_pattern:
|
||
summary: Event-pattern rule (repeated tool errors)
|
||
value:
|
||
name: Repeated Bash failures
|
||
rule_type: event_pattern
|
||
config:
|
||
event_type: PostToolUse
|
||
tool_name: Bash
|
||
summary_contains: error
|
||
count: 3
|
||
window_minutes: 10
|
||
enabled: true
|
||
cooldown_seconds: 600
|
||
status_duration:
|
||
summary: Status-duration rule
|
||
value:
|
||
name: Stuck waiting too long
|
||
rule_type: status_duration
|
||
config:
|
||
status: waiting
|
||
minutes: 15
|
||
enabled: true
|
||
cooldown_seconds: 300
|
||
token_threshold:
|
||
summary: Token-threshold rule
|
||
value:
|
||
name: Heavy token burn
|
||
rule_type: token_threshold
|
||
config:
|
||
total_tokens: 5000000
|
||
enabled: true
|
||
cooldown_seconds: 600
|
||
responses:
|
||
'201':
|
||
description: Created rule
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
rule:
|
||
id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
name: Idle session watchdog
|
||
rule_type: inactivity
|
||
config:
|
||
minutes: 30
|
||
enabled: true
|
||
cooldown_seconds: 300
|
||
created_at: '2026-06-10T12:00:00.000Z'
|
||
updated_at: '2026-06-24T08:30:00.000Z'
|
||
'400':
|
||
description: Validation error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: name is required
|
||
/api/alerts/rules/{id}:
|
||
patch:
|
||
tags:
|
||
- Alerts
|
||
summary: Update an alert rule (partial; rule_type is immutable)
|
||
operationId: updateAlertRule
|
||
description: 'Partially updates an alert rule and returns it serialized as `{ rule: { ... } }`. Only the fields present in the body change; `rule_type` CANNOT be changed and any supplied `config` is validated against the rule''s STORED type. `name` (if present) must be a non-empty string and `cooldown_seconds` (if present) must be a non-negative integer. Returns 404 `NOT_FOUND` for an unknown id, or 400 `INVALID_INPUT` for a bad name, invalid config, or negative cooldown.'
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Alert rule ID (UUID).
|
||
example: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
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
|
||
examples:
|
||
disableRule:
|
||
summary: Disable a rule without touching its config
|
||
value:
|
||
enabled: false
|
||
retuneInactivity:
|
||
summary: Re-tune an inactivity rule's threshold + cooldown
|
||
value:
|
||
config:
|
||
minutes: 45
|
||
cooldown_seconds: 900
|
||
rename:
|
||
summary: Rename a rule
|
||
value:
|
||
name: Idle session watchdog (prod)
|
||
responses:
|
||
'200':
|
||
description: Updated rule
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
rule:
|
||
id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
name: Idle session watchdog
|
||
rule_type: inactivity
|
||
config:
|
||
minutes: 30
|
||
enabled: false
|
||
cooldown_seconds: 900
|
||
created_at: '2026-06-10T12:00:00.000Z'
|
||
updated_at: '2026-06-24T08:30:00.000Z'
|
||
'400':
|
||
description: Validation error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_INPUT
|
||
message: cooldown_seconds must be a non-negative integer
|
||
'404':
|
||
description: Rule not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Alert rule not found
|
||
delete:
|
||
tags:
|
||
- Alerts
|
||
summary: Delete an alert rule and its fired-alert history
|
||
operationId: deleteAlertRule
|
||
description: 'Deletes the alert rule with the given id. Its fired-alert history cascades away with it (the foreign key is ON DELETE CASCADE), so any alerts previously raised by this rule are also removed from the feed. Returns `{ ok: true }` on success or 404 `NOT_FOUND` for an unknown id.'
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Alert rule ID (UUID).
|
||
example: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
responses:
|
||
'200':
|
||
description: Deletion confirmation
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
ok: true
|
||
'404':
|
||
description: Rule not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Alert rule not found
|
||
/api/alerts/{id}/ack:
|
||
post:
|
||
tags:
|
||
- Alerts
|
||
summary: Acknowledge one fired alert
|
||
operationId: ackAlert
|
||
description: 'Marks a single fired alert (by its integer event id) as acknowledged and returns the updated row as `{ alert: { ... } }` (with `acknowledged: 1`). Acknowledging also broadcasts an `alert_updated` WebSocket message so connected dashboards refresh their unacked badge. The id must be numeric; an unknown id returns 404 `NOT_FOUND`.'
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
description: Alert event ID (numeric).
|
||
example: 42
|
||
responses:
|
||
'200':
|
||
description: Acknowledged alert row
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
alert:
|
||
id: 42
|
||
rule_id: 7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91
|
||
rule_name: Idle session watchdog
|
||
rule_type: inactivity
|
||
message: Session sess_8f2a has been inactive for 35 minutes
|
||
details: '{"session_id":"sess_8f2a","minutes":35,"threshold":30}'
|
||
acknowledged: 1
|
||
created_at: '2026-06-25T17:05:44.000Z'
|
||
'404':
|
||
description: Alert not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Alert not found
|
||
/api/alerts/ack-all:
|
||
post:
|
||
tags:
|
||
- Alerts
|
||
summary: Acknowledge all unacked alerts
|
||
operationId: ackAllAlerts
|
||
description: 'Acknowledges every currently unacknowledged alert in one call and returns `{ ok: true, acknowledged: <count> }` where `acknowledged` is the number of rows actually updated. When at least one alert is acknowledged, an `alert_updated` WebSocket message (`{ acked_all: true }`) is broadcast so dashboards clear their unacked badge. Calling this when nothing is unacked returns `acknowledged: 0`.'
|
||
responses:
|
||
'200':
|
||
description: Count of acknowledged alerts
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
ok: true
|
||
acknowledged: 3
|
||
/api/webhooks/providers:
|
||
get:
|
||
tags:
|
||
- Webhooks
|
||
summary: List supported providers + their config fields (for the UI)
|
||
description: 'Returns the redacted provider catalog the webhook-target editor renders. For each of the 14 supported providers (Slack, Discord, Teams, Google Chat, Mattermost, Rocket.Chat, Telegram, PagerDuty, Opsgenie, Splunk On-Call, Zapier, Make, n8n, Pipedream) plus the `generic` family, it lists: a human label, the provider family, whether the URL must be https / is user-supplied, and the per-provider config field definitions (key, label, type, required, options, and whether the field is secret). No secret values are ever included — this is purely the *shape* of the form, not stored credentials.'
|
||
operationId: listWebhookProviders
|
||
responses:
|
||
'200':
|
||
description: 'Provider catalog: label, family, url requirements, fields'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
providers:
|
||
slack:
|
||
label: Slack
|
||
family: chat
|
||
https: true
|
||
urlRequired: true
|
||
fields: []
|
||
telegram:
|
||
label: Telegram
|
||
family: chat
|
||
https: true
|
||
urlRequired: false
|
||
fields:
|
||
- key: bot_token
|
||
label: Bot token
|
||
type: string
|
||
required: true
|
||
secret: true
|
||
- key: chat_id
|
||
label: Chat ID
|
||
type: string
|
||
required: true
|
||
secret: false
|
||
pagerduty:
|
||
label: PagerDuty
|
||
family: incident
|
||
https: true
|
||
urlRequired: false
|
||
fields:
|
||
- key: routing_key
|
||
label: Routing key
|
||
type: string
|
||
required: true
|
||
secret: true
|
||
- key: severity
|
||
label: Severity
|
||
type: enum
|
||
required: false
|
||
options:
|
||
- critical
|
||
- error
|
||
- warning
|
||
- info
|
||
secret: false
|
||
generic:
|
||
label: Generic webhook
|
||
family: generic
|
||
https: false
|
||
urlRequired: true
|
||
fields: []
|
||
/api/webhooks:
|
||
get:
|
||
tags:
|
||
- Webhooks
|
||
summary: List webhook targets (URLs masked, secrets redacted)
|
||
description: 'Lists every configured webhook target. Responses are always redacted: the full URL is never returned — only `url_preview` (protocol + host + a `…` + the last 4 chars), `has_secret` is a boolean (never the secret itself), and any secret-flagged provider config fields (routing keys, API keys, bot tokens) and custom header values are masked to `••••`. Each target also carries its most recent delivery outcome in `last_delivery` (or null if it has never fired).'
|
||
operationId: listWebhooks
|
||
responses:
|
||
'200':
|
||
description: All configured webhook targets
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
targets:
|
||
- id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
name: Eng on-call (Slack)
|
||
type: slack
|
||
enabled: true
|
||
url_preview: https://hooks.slack.com/…BXqZ
|
||
has_secret: false
|
||
headers: null
|
||
config: null
|
||
rule_ids:
|
||
- rule_inactivity_30m
|
||
created_at: '2026-06-25T14:03:11.482Z'
|
||
updated_at: '2026-06-25T14:03:11.482Z'
|
||
last_delivery:
|
||
status: success
|
||
status_code: 200
|
||
attempts: 1
|
||
error: null
|
||
created_at: '2026-06-25T18:41:55.117Z'
|
||
- id: 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
name: Internal alert sink
|
||
type: generic
|
||
enabled: true
|
||
url_preview: https://alerts.internal.example.com/…hook
|
||
has_secret: true
|
||
headers:
|
||
X-Api-Key: ••••
|
||
Authorization: ••••
|
||
config: null
|
||
rule_ids: null
|
||
created_at: '2026-06-20T09:15:00.000Z'
|
||
updated_at: '2026-06-24T22:48:30.901Z'
|
||
last_delivery:
|
||
status: failed
|
||
status_code: 503
|
||
attempts: 3
|
||
error: Service Unavailable
|
||
created_at: '2026-06-24T22:48:30.901Z'
|
||
post:
|
||
tags:
|
||
- Webhooks
|
||
summary: Create a webhook target
|
||
description: Creates a webhook target that fires when alerts match. `name` and `type` are required. `url` is required for most providers but is derived or defaulted for a few (Telegram and Opsgenie derive it from config; PagerDuty defaults it) — consult GET /api/webhooks/providers for which fields each provider needs. `config` carries provider-specific params (e.g. `{ chat_id }` for Telegram, `{ routing_key, severity }` for PagerDuty, `{ api_key, region }` for Opsgenie). `secret` (HMAC-SHA256 signing) and custom `headers` apply only to the generic family and are silently ignored for other providers. `rule_ids` optionally scopes the target to specific alert rules; omit it to fire for all rules. The response is the created target, REDACTED the same way as the list endpoint (URL masked, secrets shown only as `has_secret`/`••••`).
|
||
operationId: createWebhook
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
- type
|
||
properties:
|
||
name:
|
||
type: string
|
||
type:
|
||
type: string
|
||
enum:
|
||
- slack
|
||
- discord
|
||
- teams
|
||
- google_chat
|
||
- mattermost
|
||
- rocketchat
|
||
- telegram
|
||
- pagerduty
|
||
- opsgenie
|
||
- splunk_oncall
|
||
- zapier
|
||
- make
|
||
- n8n
|
||
- pipedream
|
||
- generic
|
||
url:
|
||
type: string
|
||
format: uri
|
||
description: Required for most providers; omit for those that derive their URL (Telegram, Opsgenie) or default it (PagerDuty). See GET /api/webhooks/providers.
|
||
enabled:
|
||
type: boolean
|
||
default: true
|
||
config:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Provider-specific params, e.g. { chat_id } (Telegram), { routing_key, severity } (PagerDuty), { api_key, region } (Opsgenie).
|
||
secret:
|
||
type: string
|
||
description: 'Generic family only: HMAC-SHA256 signing secret'
|
||
headers:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
description: 'Generic family only: extra request headers'
|
||
rule_ids:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: 'Optional: scope to specific alert rules (omit for all)'
|
||
examples:
|
||
slack:
|
||
summary: Slack incoming webhook scoped to one rule
|
||
value:
|
||
name: Eng on-call (Slack)
|
||
type: slack
|
||
url: https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXXXXXXXXXXXXBXqZ
|
||
enabled: true
|
||
rule_ids:
|
||
- rule_inactivity_30m
|
||
pagerduty:
|
||
summary: PagerDuty (URL defaulted, routing key in config)
|
||
value:
|
||
name: Sev1 pager
|
||
type: pagerduty
|
||
config:
|
||
routing_key: R0ABCDEF0123456789ABCDEF01
|
||
severity: critical
|
||
generic:
|
||
summary: Generic webhook with HMAC secret and custom headers
|
||
value:
|
||
name: Internal alert sink
|
||
type: generic
|
||
url: https://alerts.internal.example.com/cc-monitor/hook
|
||
secret: whsec_4f8c2a9e1b6d7f0a3c5e8b1d2f4a6c8e
|
||
headers:
|
||
X-Api-Key: k_live_abc123
|
||
Authorization: Bearer t0ken
|
||
responses:
|
||
'201':
|
||
description: Created target (redacted)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
target:
|
||
id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
name: Eng on-call (Slack)
|
||
type: slack
|
||
enabled: true
|
||
url_preview: https://hooks.slack.com/…BXqZ
|
||
has_secret: false
|
||
headers: null
|
||
config: null
|
||
rule_ids:
|
||
- rule_inactivity_30m
|
||
created_at: '2026-06-25T14:03:11.482Z'
|
||
updated_at: '2026-06-25T14:03:11.482Z'
|
||
last_delivery:
|
||
status: success
|
||
status_code: 200
|
||
attempts: 1
|
||
error: null
|
||
created_at: '2026-06-25T18:41:55.117Z'
|
||
'400':
|
||
description: Validation error
|
||
/api/webhooks/{id}:
|
||
patch:
|
||
tags:
|
||
- Webhooks
|
||
summary: Update a webhook target (partial; type is immutable)
|
||
description: Partially updates a webhook target. Only the keys present in the body are changed; omitted keys are left as-is. The provider `type` is immutable. `config` is MERGED over the existing config and re-validated, so a single field (e.g. `severity`, `region`) can change without re-sending the secret fields. For the generic family, `secret` may be omitted (keep current) or sent as `null` (clear it); `headers` and `rule_ids` replace their stored value when present. The response is the updated target, REDACTED (URL masked, secrets shown only as `has_secret`/`••••`).
|
||
operationId: updateWebhook
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
url:
|
||
type: string
|
||
format: uri
|
||
description: Omit to keep current
|
||
enabled:
|
||
type: boolean
|
||
config:
|
||
type: object
|
||
additionalProperties: true
|
||
description: Provider params; merged over existing (secrets kept if omitted)
|
||
secret:
|
||
type:
|
||
- string
|
||
- 'null'
|
||
description: 'Generic family only: omit to keep, null to clear'
|
||
headers:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
rule_ids:
|
||
type: array
|
||
items:
|
||
type: string
|
||
examples:
|
||
disable:
|
||
summary: Disable a target without touching anything else
|
||
value:
|
||
enabled: false
|
||
renameAndRescope:
|
||
summary: Rename and re-scope to different rules
|
||
value:
|
||
name: Eng on-call (Slack) — muted weekends
|
||
rule_ids:
|
||
- rule_token_threshold_5m
|
||
mergeConfig:
|
||
summary: Change one PagerDuty config field (routing key kept)
|
||
value:
|
||
config:
|
||
severity: warning
|
||
clearSecret:
|
||
summary: 'Generic family: clear the signing secret'
|
||
value:
|
||
secret: null
|
||
responses:
|
||
'200':
|
||
description: Updated target (redacted)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
target:
|
||
id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
name: Eng on-call (Slack) — muted weekends
|
||
type: slack
|
||
enabled: true
|
||
url_preview: https://hooks.slack.com/…BXqZ
|
||
has_secret: false
|
||
headers: null
|
||
config: null
|
||
rule_ids:
|
||
- rule_inactivity_30m
|
||
created_at: '2026-06-25T14:03:11.482Z'
|
||
updated_at: '2026-06-25T14:03:11.482Z'
|
||
last_delivery:
|
||
status: success
|
||
status_code: 200
|
||
attempts: 1
|
||
error: null
|
||
created_at: '2026-06-25T18:41:55.117Z'
|
||
'400':
|
||
description: Validation error
|
||
'404':
|
||
description: Target not found
|
||
delete:
|
||
tags:
|
||
- Webhooks
|
||
summary: Delete a webhook target and its delivery log
|
||
description: 'Permanently deletes a webhook target. Its delivery-log history cascades away with it. Returns `{ ok: true }` on success. This only removes the delivery channel — alert *rules* and the fired-alert feed are untouched.'
|
||
operationId: deleteWebhook
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
responses:
|
||
'200':
|
||
description: Deleted
|
||
'404':
|
||
description: Target not found
|
||
/api/webhooks/{id}/test:
|
||
post:
|
||
tags:
|
||
- Webhooks
|
||
summary: Send a synthetic test alert to a target
|
||
description: Sends a synthetic test alert to the target and reports the delivery outcome synchronously. The HTTP status is always 200 when the target exists — the *request itself* succeeded — and the `ok` flag carries the downstream delivery result. `status` is the HTTP status code returned by the provider (or null if the request never completed), `attempts` is how many tries were made (the delivery layer retries transient failures), and `error` is a human-readable failure reason or null on success.
|
||
operationId: testWebhook
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
responses:
|
||
'200':
|
||
description: Delivery result (ok flag carries the downstream outcome)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
examples:
|
||
success:
|
||
summary: Provider accepted the test payload
|
||
value:
|
||
ok: true
|
||
status: 200
|
||
attempts: 1
|
||
error: null
|
||
failure:
|
||
summary: Provider rejected or was unreachable after retries
|
||
value:
|
||
ok: false
|
||
status: 503
|
||
attempts: 3
|
||
error: Service Unavailable
|
||
'404':
|
||
description: Target not found
|
||
/api/webhooks/{id}/deliveries:
|
||
get:
|
||
tags:
|
||
- Webhooks
|
||
summary: Recent delivery log for a target
|
||
description: Returns the recent delivery log for a target, newest first. Each row records the alert that fired, the resulting HTTP status, the number of attempts, any error text, and a timestamp. `limit` is clamped to 1–200 (default 20) and `offset` to ≥0 (default 0). This is an audit trail of past sends — it does not trigger a new delivery (use POST /{id}/test for that).
|
||
operationId: listWebhookDeliveries
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
example: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
- name: limit
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
default: 20
|
||
description: Max rows to return (clamped to 1–200).
|
||
example: 20
|
||
- name: offset
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
default: 0
|
||
description: Pagination offset (clamped to ≥0).
|
||
example: 0
|
||
responses:
|
||
'200':
|
||
description: Delivery rows, newest first
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
deliveries:
|
||
- id: 5012
|
||
target_id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
alert_event_id: 8841
|
||
status: success
|
||
status_code: 200
|
||
attempts: 1
|
||
error: null
|
||
created_at: '2026-06-25T18:41:55.117Z'
|
||
- id: 5008
|
||
target_id: 9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e
|
||
alert_event_id: 8839
|
||
status: failed
|
||
status_code: 429
|
||
attempts: 3
|
||
error: Too Many Requests
|
||
created_at: '2026-06-25T18:30:02.004Z'
|
||
limit: 20
|
||
offset: 0
|
||
'404':
|
||
description: Target not found
|
||
/api/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
|
||
/api/cc-config/overview:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: Config explorer overview
|
||
description: Read-only. Returns the four resolved filesystem roots plus aggregate counts across every surface (skills, agents, commands, output styles, plugins, marketplaces, keybindings, MCP servers, hooks, memory, settings files). All sub-reads degrade to empty on any I/O error, so this endpoint never errors.
|
||
operationId: ccConfigGetOverview
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Overview of roots and counts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigOverviewResponse'
|
||
example:
|
||
roots:
|
||
claudeHome: /Users/son/.claude
|
||
projectClaudeDir: /repo/.claude
|
||
projectRoot: /repo
|
||
claudeJson: /Users/son/.claude.json
|
||
counts:
|
||
skills:
|
||
user: 12
|
||
project: 3
|
||
agents:
|
||
user: 5
|
||
project: 2
|
||
commands:
|
||
user: 8
|
||
project: 0
|
||
outputStyles:
|
||
user: 1
|
||
project: 0
|
||
plugins: 8
|
||
pluginsEnabled: 6
|
||
pluginsDisabled: 1
|
||
marketplaces: 2
|
||
keybindings: 24
|
||
mcpServers:
|
||
user: 3
|
||
project: 1
|
||
hooks:
|
||
user: 2
|
||
project: 0
|
||
project-local: 1
|
||
memory: 5
|
||
settingsFiles: 2
|
||
/api/cc-config/skills:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List discovered skills
|
||
description: Read-only. Lists skill directories (each containing a SKILL.md) under the user and/or project roots, with parsed frontmatter and a 320-char body preview. Files over 256 KiB are read truncated. Degrades to an empty list on I/O errors.
|
||
operationId: ccConfigGetSkills
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Skills list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigSkillsResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
name: code-reviewer
|
||
path: /Users/son/.claude/skills/code-reviewer
|
||
file: /Users/son/.claude/skills/code-reviewer/SKILL.md
|
||
size: 2048
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
frontmatter:
|
||
name: code-reviewer
|
||
description: Reviews diffs
|
||
preview: Use this skill to review pull requests...
|
||
/api/cc-config/agents:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List subagents
|
||
description: Read-only. Lists single-file subagent definitions (<root>/agents/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors.
|
||
operationId: ccConfigGetAgents
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Agents list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigAgentsResponse'
|
||
example:
|
||
items:
|
||
- scope: project
|
||
name: backend-reviewer
|
||
file: /repo/.claude/agents/backend-reviewer.md
|
||
size: 1536
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
frontmatter:
|
||
name: backend-reviewer
|
||
model: sonnet
|
||
preview: Review backend route and hook logic for regressions...
|
||
/api/cc-config/commands:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List slash commands
|
||
description: Read-only. Lists slash-command definitions (<root>/commands/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors.
|
||
operationId: ccConfigGetCommands
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Commands list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigCommandsResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
name: commit
|
||
file: /Users/son/.claude/commands/commit.md
|
||
size: 800
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
frontmatter:
|
||
description: Create a commit
|
||
preview: Commit the staged changes with a descriptive message...
|
||
/api/cc-config/output-styles:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List output styles
|
||
description: Read-only. Lists output-style definitions (<root>/output-styles/*.md) under the user and/or project roots, with parsed frontmatter and a 320-char preview. Truncated above 256 KiB; degrades to empty on errors.
|
||
operationId: ccConfigGetOutputStyles
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default). Unrecognized values fall back to `all`.'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Output styles list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigOutputStylesResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
name: concise
|
||
file: /Users/son/.claude/output-styles/concise.md
|
||
size: 420
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
frontmatter:
|
||
name: concise
|
||
preview: Respond tersely, no preamble...
|
||
/api/cc-config/plugins:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List installed plugins
|
||
description: 'Read-only. Reads the user-scope plugins manifest (~/.claude/plugins/installed_plugins.json), resolves each instance''s on-disk contributions, and reports its tri-state enabled flag from settings.json. Read-only by design: plugins are written concurrently by the running Claude Code CLI. No scope/cwd parameters. Degrades to an empty plugin list when the manifest is missing.'
|
||
operationId: ccConfigGetPlugins
|
||
responses:
|
||
'200':
|
||
description: Plugins manifest summary and list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigPluginsResponse'
|
||
example:
|
||
manifestPath: /Users/son/.claude/plugins/installed_plugins.json
|
||
manifestExists: true
|
||
plugins:
|
||
- key: superpowers@obra
|
||
name: superpowers
|
||
marketplace: obra
|
||
scope: user
|
||
version: 1.2.0
|
||
installPath: /Users/son/.claude/plugins/superpowers
|
||
installedAt: '2026-05-01T12:00:00.000Z'
|
||
lastUpdated: '2026-06-01T09:30:00.000Z'
|
||
gitCommitSha: a1b2c3d4e5f6
|
||
installPathExists: true
|
||
enabled: true
|
||
contributes:
|
||
skills: 3
|
||
agents: 2
|
||
commands: 5
|
||
outputStyles: 0
|
||
hooks: 1
|
||
pluginJson:
|
||
name: superpowers
|
||
version: 1.2.0
|
||
/api/cc-config/plugins/update-all:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: Progress of the current or last update-all batch
|
||
description: Read-only. Returns the detached batch worker's on-disk journal from ~/.claude/agenthub/runtime/cc-update-all.json, or `{batch:null}` when no batch has ever run. `status` is `running`|`done`|`partial`|`failed`|`abandoned`; `abandoned` is DERIVED, not stored — a `running` journal older than one hour is reported that way because the worker is detached and a host reboot leaves nobody to write the terminal entry. `partial` means the batch finished with at least one failed step. Malformed or half-written journals return null rather than erroring, since the UI polls this endpoint.
|
||
operationId: ccConfigGetUpdateAll
|
||
responses:
|
||
'200':
|
||
description: The batch journal, or null.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
batch:
|
||
status: partial
|
||
started_at: '2026-09-10T08:30:00.000Z'
|
||
finished_at: '2026-09-10T08:34:12.000Z'
|
||
steps:
|
||
- key: marketplaces
|
||
label: Update all marketplaces
|
||
status: ok
|
||
- key: plugin:ponytail@ponytail
|
||
label: ponytail@ponytail
|
||
status: ok
|
||
- key: plugin:vercel@claude-plugins-official
|
||
label: vercel@claude-plugins-official
|
||
status: failed
|
||
error: marketplace unreachable
|
||
- key: reload
|
||
label: Reload open Claude Code sessions
|
||
status: ok
|
||
lanes_total: 2
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Update every marketplace and plugin, then reload the open sessions
|
||
description: 'Mutating. Spawns a DETACHED worker and returns immediately — the response is an acknowledgement, not a result; poll GET on this path to follow it. Order: `claude plugin marketplace update` (no name = all), then `claude plugin update <id> -y` for every installed plugin, then `/reload-plugins` + `/reload-skills` typed into the live lane panes. The plugin this dashboard itself runs from is updated LAST and followed by `plugin-bootstrap.js --force`, which stops this server and starts the replacement — which is why the work cannot happen in the request handler and why the journal lives outside the plugin cache. Plugin ids come from `claude plugin list --json`, never from the body, and the "self" plugin is derived from this server''s own path, so no caller can nominate a different plugin for the restart. Answers 409 EBUSY when a batch is already in flight; the body is ignored apart from an optional `reload:false`.'
|
||
operationId: ccConfigUpdateAll
|
||
responses:
|
||
'200':
|
||
description: Batch worker spawned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
ok: true
|
||
spawned: true
|
||
runtime_dir: /Users/son/.claude/agenthub/runtime
|
||
planned: 7
|
||
self_plugin:
|
||
marketplace: sg
|
||
plugin: agenthub
|
||
'409':
|
||
description: A batch is already running.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/cc-config/reload:
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Tell the open Claude Code sessions to re-read plugins and skills
|
||
description: 'Types `/reload-plugins` and `/reload-skills` into the dashboard''s live lane panes over `tmux send-keys`. Both commands are scoped BY CLAUDE CODE to the session that runs them, so there is no way to reload a session from outside — typing into a session that is already running is what a human would do, and it is what this does. It spawns no `claude` process: a one-shot `claude -p "/reload-plugins"` would reload only its own short-lived session and leave a junk session behind. Changes nothing on disk, so it does not queue on the CLI mutation chain and emits no `cc_config_changed`. Panes sitting at a shell prompt are skipped (the text would run as a shell command); only `claude` lanes are addressed, since Codex has no such command. The two commands are typed one second apart (`SETTLE_MS`): Claude Code opens its slash-command menu as soon as `/` is typed and swallows the Enter that follows, so back-to-back sends merged into the single line `/reload-plugins/reload-skills`, which runs as `/reload-plugins` with an argument and never reloads skills. Optional body `{kinds:["plugins"|"skills"]}` narrows which commands are sent; unknown kinds are ignored, as the command strings are fixed server-side.'
|
||
operationId: ccConfigReload
|
||
responses:
|
||
'200':
|
||
description: Commands delivered. `lanes_total` counts only panes running a live claude; zero is a normal outcome, not a failure.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
example:
|
||
ok: true
|
||
commands:
|
||
- /reload-plugins
|
||
- /reload-skills
|
||
lanes_total: 1
|
||
lanes:
|
||
- id: agenthub-lane-3
|
||
laneId: 3
|
||
sent:
|
||
- /reload-plugins
|
||
- /reload-skills
|
||
'400':
|
||
description: '`kinds` was present but not an array.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/cc-config/mcp:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List MCP servers
|
||
description: Read-only. Discovers MCP servers from ~/.claude.json (top-level and projects[<root>]) and ~/.claude/settings.json, split into `user` and `projectScoped`. Header and env VALUES are never returned — only their key NAMES. Read-only by design (the CLI writes these files concurrently). Degrades to empty arrays on errors.
|
||
operationId: ccConfigGetMcp
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: MCP servers grouped by scope.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigMcpResponse'
|
||
example:
|
||
user:
|
||
- name: github
|
||
source: ~/.claude.json (top-level)
|
||
kind: stdio
|
||
command: npx
|
||
args:
|
||
- '-y'
|
||
- '@modelcontextprotocol/server-github'
|
||
envNames:
|
||
- GITHUB_TOKEN
|
||
projectScoped:
|
||
- name: internal
|
||
source: ~/.claude.json (projects[/repo])
|
||
kind: http
|
||
url: https://mcp.example.com/sse
|
||
headers:
|
||
- Authorization
|
||
/api/cc-config/mcp/add:
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Add an MCP server
|
||
description: 'Mutating. Runs `claude mcp add` — never a direct edit of ~/.claude.json, which the running CLI rewrites concurrently. Body carries either `url` (+ optional `transport: http|sse` and `headers`) for a remote server, or `command` + `args` (+ optional `env`) for a stdio server; exactly one of the two forms is required. `scope` is required and is `user`|`local` only: `user` writes ~/.claude.json''s top level, `local` writes its `projects[<root>]` block (GET /mcp''s `projectScoped` bucket). The CLI''s own `project` scope (a shared `.mcp.json`) is deliberately not offered — `readMcpServers` never reads that file, so a server added there would report success and never appear in the list. A non-zero CLI exit is returned as 422, not 500. Emits a `cc_config_changed` websocket event on success.'
|
||
operationId: ccConfigPostMcpAdd
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
- scope
|
||
properties:
|
||
name:
|
||
type: string
|
||
example: vercel
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- local
|
||
example: user
|
||
transport:
|
||
type: string
|
||
enum:
|
||
- http
|
||
- sse
|
||
url:
|
||
type: string
|
||
example: https://mcp.vercel.com
|
||
headers:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
example:
|
||
Authorization: Bearer xyz
|
||
command:
|
||
type: string
|
||
example: npx
|
||
args:
|
||
type: array
|
||
items:
|
||
type: string
|
||
example:
|
||
- '-y'
|
||
- server-github
|
||
env:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
example:
|
||
GITHUB_TOKEN: xxx
|
||
responses:
|
||
'200':
|
||
description: The CLI's stdout/stderr on success.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigCliResult'
|
||
'400':
|
||
description: Bad name/scope/args, or an incompatible combination.
|
||
'422':
|
||
description: The CLI exited non-zero (e.g. the name is already taken).
|
||
/api/cc-config/mcp/remove:
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Remove an MCP server
|
||
description: Mutating. Runs `claude mcp remove`. `scope` (`user`|`local`) narrows which config layer to remove from; omit it to let the CLI find the server wherever it exists. The CLI's own `project` scope is refused with 400 — the panel has no row sourced from `.mcp.json` to remove. A non-zero CLI exit (e.g. the name does not exist) is returned as 422. Emits a `cc_config_changed` websocket event on success.
|
||
operationId: ccConfigPostMcpRemove
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
example: vercel
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- local
|
||
responses:
|
||
'200':
|
||
description: The CLI's stdout/stderr on success.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigCliResult'
|
||
'400':
|
||
description: Bad name/scope.
|
||
'422':
|
||
description: The CLI exited non-zero (e.g. no such server).
|
||
/api/cc-config/hooks:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List configured hooks
|
||
description: Read-only. Reads hooks from the user, project, and project-local settings files. Known event types (SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStop, Notification, PreCompact) are flattened into matcher/type/command/timeout entries; unknown events are passed through verbatim. Hooks in settings are read-only here. Degrades to per-file `exists:false` on errors.
|
||
operationId: ccConfigGetHooks
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Hook sources by scope.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigHooksResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
file: /Users/son/.claude/settings.json
|
||
exists: true
|
||
hooks:
|
||
PostToolUse:
|
||
- matcher: '*'
|
||
type: command
|
||
command: node ~/.claude/hooks/post-tool-use.js
|
||
timeout: null
|
||
- scope: project
|
||
file: /repo/.claude/settings.json
|
||
exists: false
|
||
hooks: {}
|
||
/api/cc-config/settings:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List settings files
|
||
description: Read-only. Returns the user, project, and project-local settings.json contents with secret-like keys redacted to the literal `<redacted>` (keys matching /token|secret|password|api[_-]?key|auth/i with string values). Missing files report `exists:false`. Settings files are read-only here (concurrent CLI writes).
|
||
operationId: ccConfigGetSettings
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Settings sources by scope (secrets redacted).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigSettingsResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
file: /Users/son/.claude/settings.json
|
||
exists: true
|
||
data:
|
||
model: sonnet
|
||
apiKey: <redacted>
|
||
raw_size: 412
|
||
- scope: project
|
||
file: /repo/.claude/settings.json
|
||
exists: false
|
||
- scope: project-local
|
||
file: /repo/.claude/settings.local.json
|
||
exists: false
|
||
/api/cc-config/memory:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List memory files
|
||
description: Read-only. Returns the two primary CLAUDE.md files (scopes `user`/`project`) plus every per-project file-based auto-memory markdown file under ~/.claude/projects/<slug>/memory/ (scope `auto-memory`, sorted index files first). Previews are 480 chars; files over 256 KiB are truncated. Degrades to fewer files on errors.
|
||
operationId: ccConfigGetMemory
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Memory items.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigMemoryResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
file: /Users/son/.claude/CLAUDE.md
|
||
size: 1200
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
preview: '# Workspace & Productivity System...'
|
||
- scope: auto-memory
|
||
project: '-Users-son-repo'
|
||
name: MEMORY.md
|
||
isIndex: true
|
||
file: /Users/son/.claude/projects/-Users-son-repo/memory/MEMORY.md
|
||
size: 980
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
frontmatter: {}
|
||
preview: '## Persistent facts\n- Never run destructive ops without confirmation...'
|
||
/api/cc-config/marketplaces:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List plugin marketplaces
|
||
description: Read-only. Reads ~/.claude/plugins/known_marketplaces.json and, best-effort, each marketplace's .claude-plugin/marketplace.json for plugin count and metadata. No scope/cwd parameters. Degrades to an empty list when the file is missing.
|
||
operationId: ccConfigGetMarketplaces
|
||
responses:
|
||
'200':
|
||
description: Known marketplaces summary and list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigMarketplacesResponse'
|
||
example:
|
||
knownPath: /Users/son/.claude/plugins/known_marketplaces.json
|
||
knownExists: true
|
||
items:
|
||
- name: obra
|
||
source:
|
||
source: github
|
||
repo: obra/superpowers-marketplace
|
||
installLocation: /Users/son/.claude/plugins/marketplaces/obra
|
||
lastUpdated: '2026-06-10T00:00:00.000Z'
|
||
pluginCount: 12
|
||
marketplaceName: Superpowers
|
||
marketplaceDescription: Curated agent skills
|
||
marketplaceOwner:
|
||
name: obra
|
||
/api/cc-config/keybindings:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: Get keybindings
|
||
description: Read-only. Parses ~/.claude/keybindings.json into context-scoped groups of key → action bindings. When the file is absent, returns only `{ file, exists:false }`. No scope/cwd parameters.
|
||
operationId: ccConfigGetKeybindings
|
||
responses:
|
||
'200':
|
||
description: Parsed keybindings (or exists:false).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigKeybindingsResponse'
|
||
example:
|
||
file: /Users/son/.claude/keybindings.json
|
||
exists: true
|
||
schema: null
|
||
docs: null
|
||
groups:
|
||
- context: editor
|
||
bindings:
|
||
- key: ctrl+s
|
||
action: submit
|
||
put:
|
||
tags:
|
||
- CcConfig
|
||
summary: Overwrite keybindings
|
||
description: 'Mutating. Overwrites ~/.claude/keybindings.json from a structured list of context groups (`{ context, bindings: [{ key, action }] }`); on disk the bindings become an object keyed by `key`. The existing file is backed up first (under cc-config-backups/keybindings/) and any top-level metadata (`$schema`, `$docs`, unknown keys) is preserved — only the `bindings` array is replaced. Rejects duplicate contexts and duplicate keys within a context. Writes are atomic. Unlike settings.json, keybindings.json is not rewritten mid-session by the live CLI, so this edit is safe. Emits a `cc_config_changed` websocket event on success.'
|
||
operationId: ccConfigPutKeybindings
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- groups
|
||
properties:
|
||
groups:
|
||
type: array
|
||
description: Full set of context-scoped keybinding groups to persist.
|
||
items:
|
||
type: object
|
||
required:
|
||
- context
|
||
- bindings
|
||
properties:
|
||
context:
|
||
type: string
|
||
example: Global
|
||
bindings:
|
||
type: array
|
||
items:
|
||
type: object
|
||
required:
|
||
- key
|
||
- action
|
||
properties:
|
||
key:
|
||
type: string
|
||
example: ctrl+t
|
||
action:
|
||
type: string
|
||
example: toggleTodos
|
||
example:
|
||
groups:
|
||
- context: Global
|
||
bindings:
|
||
- key: ctrl+t
|
||
action: toggleTodos
|
||
- context: Chat
|
||
bindings:
|
||
- key: escape
|
||
action: cancel
|
||
responses:
|
||
'200':
|
||
description: keybindings.json written (backup taken when it already existed).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileWriteResponse'
|
||
example:
|
||
ok: true
|
||
file: /Users/son/.claude/keybindings.json
|
||
target: /Users/son/.claude/keybindings.json
|
||
backupPath: /Users/son/.claude/cc-config-backups/keybindings/keybindings.json.2026-06-25T12-00-00.000Z.bak
|
||
created: false
|
||
'400':
|
||
description: Bad request. `EBADREQ` — `groups` is missing or not an array. `EBADCONTENT` — a group/binding is malformed, a context is empty, or there is a duplicate context or duplicate key within a context.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADCONTENT
|
||
message: duplicate key "ctrl+t" in context Global
|
||
'413':
|
||
description: '`ETOOLARGE` — the serialized keybindings exceed the 256 KiB limit.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ETOOLARGE
|
||
message: content exceeds 262144 bytes
|
||
/api/cc-config/statusline:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: Get statusline config and scripts
|
||
description: Read-only. Returns the `statusLine` block from ~/.claude/settings.json plus any statusline scripts (statusline.py, statusline-command.sh) with a 4000-char preview. Scripts over 256 KiB are truncated. No scope/cwd parameters.
|
||
operationId: ccConfigGetStatusline
|
||
responses:
|
||
'200':
|
||
description: Statusline config and scripts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigStatuslineResponse'
|
||
example:
|
||
config:
|
||
type: command
|
||
command: python3 ~/.claude/statusline.py
|
||
scripts:
|
||
- file: /Users/son/.claude/statusline.py
|
||
size: 1280
|
||
mtime: 1718900000000
|
||
truncated: false
|
||
preview: '#!/usr/bin/env python3\nimport json, sys\n...'
|
||
/api/cc-config/hook-scripts:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List hook scripts
|
||
description: Read-only. Lists files in the ~/.claude/hooks/ directory (name, path, size, mtime), sorted by name. Returns metadata only — no file contents. Use GET /file to read an individual script. No scope/cwd parameters.
|
||
operationId: ccConfigGetHookScripts
|
||
responses:
|
||
'200':
|
||
description: Hook scripts directory listing.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigHookScriptsResponse'
|
||
example:
|
||
dir: /Users/son/.claude/hooks
|
||
items:
|
||
- name: post-tool-use.js
|
||
file: /Users/son/.claude/hooks/post-tool-use.js
|
||
size: 640
|
||
mtime: 1718900000000
|
||
/api/cc-config/file:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: Read a single file body
|
||
description: 'Read-only. Returns the body of one file. Strict path containment: the resolved absolute path MUST live under the Claude home, the project .claude dir, or (only for a file literally named CLAUDE.md) the project root. Bodies over 256 KiB are truncated. NOTE: there is no 404 path — a missing, unreadable, or out-of-root file all return 400.'
|
||
operationId: ccConfigGetFile
|
||
parameters:
|
||
- name: path
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Absolute path to read. Must resolve under an allowed root; under the project root only CLAUDE.md is permitted.
|
||
example: /Users/son/.claude/skills/code-reviewer/SKILL.md
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: File body with metadata.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileReadResponse'
|
||
example:
|
||
ok: true
|
||
file: /Users/son/.claude/skills/code-reviewer/SKILL.md
|
||
truncated: false
|
||
size: 2048
|
||
text: '---\nname: code-reviewer\n---\n# Code Reviewer\n...'
|
||
mtime: 1718900000000
|
||
'400':
|
||
description: Bad or denied path. `BAD_PATH` — `path` query is missing or empty. `READ_DENIED` — path is outside the allowed roots, only CLAUDE.md is readable from the project root, or the file is missing/unreadable (there is no 404).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: READ_DENIED
|
||
message: path is outside allowed roots
|
||
put:
|
||
tags:
|
||
- CcConfig
|
||
summary: Create or overwrite a text artifact
|
||
description: Mutating. Creates or overwrites a low-risk text artifact (skills, agents, commands, output-styles, memory, auto-memory). Before overwriting an existing artifact a timestamped backup is created under a cc-config-backups directory (well outside the dirs Claude Code scans); fresh creates have no backup (backupPath:null). Writes are atomic (temp file + rename). Names are validated against a strict allowlist and the resolved path is re-checked to be inside its containment root. Content must be a string ≤ 256 KiB (UTF-8). Plugins, MCP servers, hooks-in-settings, and settings.json are intentionally NOT writable. Emits a `cc_config_changed` websocket event on success.
|
||
operationId: ccConfigPutFile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileWriteRequest'
|
||
example:
|
||
scope: user
|
||
type: agents
|
||
name: my-reviewer
|
||
content: '---\nname: my-reviewer\n---\n# My Reviewer\nReview the diff...'
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Artifact created or overwritten.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileWriteResponse'
|
||
example:
|
||
ok: true
|
||
file: /Users/son/.claude/agents/my-reviewer.md
|
||
target: /Users/son/.claude/agents/my-reviewer.md
|
||
backupPath: null
|
||
created: true
|
||
'400':
|
||
description: Bad request. `EBADREQ` — scope/type missing or not strings. `EBADTYPE` — unknown type. `EBADSCOPE` — scope not user/project. `EBADNAME` — name fails the allowlist (or auto-memory name is not a flat *.md). `EBADPROJECT` — invalid auto-memory project slug. `EBADCONTENT` — content is not a string. `EOUTOFROOT` — resolved path escapes its containment root.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADNAME
|
||
message: name must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/
|
||
'413':
|
||
description: '`ETOOLARGE` — content exceeds the 256 KiB (262144-byte) limit.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ETOOLARGE
|
||
message: content exceeds 262144 bytes
|
||
'500':
|
||
description: '`EINTERNAL` (or any unmapped error code) — unexpected filesystem error.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EINTERNAL
|
||
message: 'EACCES: permission denied'
|
||
delete:
|
||
tags:
|
||
- CcConfig
|
||
summary: Delete a text artifact
|
||
description: Mutating. Deletes a low-risk text artifact (same type/scope/name/project semantics as PUT, no content). A mandatory backup is created BEFORE deletion — if the backup fails the original is left intact. Skill deletes remove the whole skill directory. Emits a `cc_config_changed` websocket event on success. Plugins, MCP servers, hooks-in-settings, and settings.json are NOT deletable.
|
||
operationId: ccConfigDeleteFile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileDeleteRequest'
|
||
example:
|
||
scope: user
|
||
type: agents
|
||
name: my-reviewer
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Artifact deleted (backup taken first).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigFileDeleteResponse'
|
||
example:
|
||
ok: true
|
||
file: /Users/son/.claude/agents/my-reviewer.md
|
||
target: /Users/son/.claude/agents/my-reviewer.md
|
||
backupPath: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
'400':
|
||
description: Bad request. `EBADREQ` — scope/type missing or not strings. `EBADTYPE` — unknown type. `EBADSCOPE` — scope not user/project. `EBADNAME` — invalid name. `EBADPROJECT` — invalid auto-memory project slug. `EOUTOFROOT` — resolved path escapes its containment root.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADTYPE
|
||
message: 'unknown type: plugins'
|
||
'404':
|
||
description: '`ENOTFOUND` — the target artifact does not exist.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ENOTFOUND
|
||
message: agents/my-reviewer does not exist
|
||
'500':
|
||
description: '`EINTERNAL` (or any unmapped error code) — unexpected filesystem error.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EINTERNAL
|
||
message: 'EACCES: permission denied'
|
||
/api/cc-config/backups:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List artifact backups
|
||
description: 'Read-only. Lists backups taken by the mutation endpoints, across the user and project roots plus per-project auto-memory backup dirs. Optional `scope`/`type` narrow the search. Results are sorted newest first. Best-effort: unreadable backup dirs are skipped, never erroring.'
|
||
operationId: ccConfigGetBackups
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
description: Narrow to a single root scope. Only `user` or `project` are honored; any other value is ignored (both scopes scanned).
|
||
example: user
|
||
- name: type
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- commands
|
||
- output-styles
|
||
- memory
|
||
- auto-memory
|
||
description: Narrow to a single artifact type. When omitted, all types are scanned.
|
||
example: agents
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .claude dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Backups sorted newest first.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigBackupsResponse'
|
||
example:
|
||
items:
|
||
- scope: user
|
||
type: agents
|
||
name: my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
backupPath: /Users/son/.claude/cc-config-backups/agents/my-reviewer.md.2026-06-25T12-00-00.000Z.bak
|
||
isDir: false
|
||
mtime: 1718900000000
|
||
size: 2048
|
||
/api/cc-config/profiles:
|
||
get:
|
||
tags:
|
||
- CcConfig
|
||
summary: List Claude Code auth profiles
|
||
description: Read-only. A *profile* is one Anthropic account - one quota - saved under a name, so the local `claude` CLI can be switched between accounts while every other config surface stays put. Profiles live one JSON file per profile under `<CLAUDE_HOME>/agenthub-profiles/` (dir 0700, files 0600) and hold live OAuth tokens, so this endpoint returns identity metadata ONLY - never token material.
|
||
operationId: ccConfigGetProfiles
|
||
responses:
|
||
'200':
|
||
description: Saved profiles plus the account logged in right now.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigProfilesResponse'
|
||
'409':
|
||
description: '`EBADJSON` - a credentials/config file on disk is not valid JSON.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Save the logged-in account as a profile
|
||
description: |-
|
||
Snapshots the account currently logged in (its `claudeAiOauth` blob plus `oauthAccount`/`userID`) into a profile. The dashboard CANNOT log an account in - only `claude auth login` mints tokens - so adding another account is always: log in from a terminal, then call this to capture what the CLI wrote.
|
||
|
||
One account is one profile. Saving an account that another profile already holds is refused with `EDUPLICATE` rather than making a second copy: duplicates both report themselves `active`, leaving nothing to switch between. Re-posting the same account into its OWN profile is the token-refresh path and is allowed - and it preserves that profile's `label`.
|
||
|
||
`name` is optional and normally omitted: the server derives a filename-safe identifier plus a readable initial `label` from the account, and the user renames the label afterwards via PATCH. That is what keeps the only string a user types free of filename rules.
|
||
operationId: ccConfigSaveProfile
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
pattern: ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,39}$
|
||
description: Optional explicit identifier/filename. Omit it to have one derived from the account.
|
||
example: work
|
||
responses:
|
||
'200':
|
||
description: The saved profile's identity.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
allOf:
|
||
- $ref: '#/components/schemas/CcConfigAccountIdentity'
|
||
- type: object
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
name:
|
||
type: string
|
||
label:
|
||
type: string
|
||
'400':
|
||
description: '`EBADNAME` - an explicit name was given and is not filename-safe.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: '`EDUPLICATE` - another profile already holds this account (its name is in the message); `ENOAUTH` - no Claude account is logged in to snapshot; `EBADJSON` - a config file is unparseable.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
patch:
|
||
tags:
|
||
- CcConfig
|
||
summary: Rename a saved auth profile
|
||
description: |-
|
||
Changes a profile's display `label` only. The `name` identifier deliberately does not move: a rename is a cosmetic edit, not a re-keying, so it cannot fail halfway and leave a profile unreachable - and every stored credential is rewritten untouched.
|
||
|
||
A label is free-form: 1-60 characters, spaces and diacritics allowed, because it is never used as a path. Control characters are rejected (`EBADLABEL`), it is stored trimmed, and it must be unique across profiles case-insensitively (`ELABELTAKEN`).
|
||
operationId: ccConfigRenameProfile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
- label
|
||
properties:
|
||
name:
|
||
type: string
|
||
description: Profile to rename (its identifier, not its label).
|
||
example: work
|
||
label:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 60
|
||
description: New display label.
|
||
example: Tài khoản chính
|
||
responses:
|
||
'200':
|
||
description: Profile renamed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
name:
|
||
type: string
|
||
label:
|
||
type: string
|
||
example:
|
||
ok: true
|
||
name: work
|
||
label: Tài khoản chính
|
||
'400':
|
||
description: '`EBADLABEL` - the label is empty, longer than 60 characters, or contains control characters; `EBADNAME` - the name is not filename-safe.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'404':
|
||
description: '`ENOTFOUND` - no profile by that name.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: '`ELABELTAKEN` - another profile already uses that label.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
delete:
|
||
tags:
|
||
- CcConfig
|
||
summary: Forget a saved auth profile
|
||
description: Deletes the stored profile file. The identifier travels in the body, not the URL (same reason as `DELETE /file`). Refused (`EACTIVE`) only when it is the LAST saved copy of the account logged in right now - that copy is those credentials' only home outside the files a switch is about to overwrite. A redundant copy of the live account IS deletable, so an accidental duplicate can be cleaned up; a blanket active-guard would leave two profiles of one account undeletable forever.
|
||
operationId: ccConfigDeleteProfile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
example: work
|
||
responses:
|
||
'200':
|
||
description: Profile deleted.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
name:
|
||
type: string
|
||
example:
|
||
ok: true
|
||
name: work
|
||
'400':
|
||
description: '`EBADNAME` - the name is not filename-safe.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'404':
|
||
description: '`ENOTFOUND` - no profile by that name.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: '`EACTIVE` - refusing to delete the only saved copy of the account that is logged in right now.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/cc-config/profiles/activate:
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: Switch the local CLI to another Anthropic account
|
||
description: |-
|
||
Makes a saved profile the live account. Swaps EXACTLY two identity artifacts and nothing else: `claudeAiOauth` inside `<CLAUDE_HOME>/.credentials.json` (on macOS: the login Keychain item `Claude Code-credentials`, which is where the CLI reads it), and `oauthAccount` + `userID` inside the `.claude.json` config file(s) (key-merged in place, and only where the file already exists, so `projects`/`numStartups`/everything else survives). Skills, subagents, commands, memory, plugins, marketplaces, MCP servers and settings are never touched - that is what makes a switch keep the whole workspace and change only the quota. `mcpOAuth` stays put too (machine MCP tokens, not account identity).
|
||
|
||
The outgoing account is re-snapshotted into its own profile FIRST, so tokens Claude Code refreshed since its last save are not lost. A switch takes effect for NEW Claude Code sessions only, and a session still running will rewrite the config on exit - close them first.
|
||
operationId: ccConfigActivateProfile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
example: work
|
||
responses:
|
||
'200':
|
||
description: The switch completed; `written` lists the rewritten paths.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CcConfigProfileActivateResult'
|
||
'400':
|
||
description: '`EBADNAME` - the name is not filename-safe.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'404':
|
||
description: '`ENOTFOUND` - no profile by that name.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: '`EUNSAVED` - the account logged in right now is not saved as a profile, and switching away would discard the only copy of its credentials; `EBADJSON` - a config file is unparseable, nothing was written.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/cc-config/profiles/usage:
|
||
post:
|
||
tags:
|
||
- CcConfig
|
||
summary: How much of a saved profile's account quota is already spent
|
||
description: |-
|
||
Answers `{ name, refreshed, fiveHour, sevenDay, checkedAt }`, each window being `{ utilization, resetsAt }` - a percentage and a rollover time. There is no local source for it: nothing under `CLAUDE_HOME` records a utilization and the credentials file carries only `subscriptionType`/`rateLimitTier`, so the server asks the endpoint the CLI's own `/usage` reads (`GET https://api.anthropic.com/api/oauth/usage`) with that profile's OAuth token.
|
||
|
||
A POST, and behind the same-origin guard, because answering it is a WRITE. A saved profile's access token lasts hours, so every profile that is not the active one is normally expired; reading its usage refreshes it first, and the refresh ROTATES the stored credentials - Anthropic retires the refresh token it was handed. The new pair is written straight back into the profile file, because a refresh whose result is dropped leaves an account that can never be activated again without a fresh `claude auth login`. A still-valid token is used as-is and nothing is rewritten; a token that looks valid but is refused is retried once through the refresh. Nothing calls this on a timer - it is one profile per user request. The OAuth token is sent to Anthropic and nowhere else, and never appears in the response.
|
||
operationId: ccConfigProfileUsage
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
example: work
|
||
responses:
|
||
'200':
|
||
description: Percentages and reset times for the two quota windows.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
refreshed:
|
||
type: boolean
|
||
description: True when the stored token was expired and had to be rotated.
|
||
fiveHour:
|
||
$ref: '#/components/schemas/CcConfigUsageWindow'
|
||
sevenDay:
|
||
$ref: '#/components/schemas/CcConfigUsageWindow'
|
||
checkedAt:
|
||
type: string
|
||
format: date-time
|
||
'401':
|
||
description: '`EUNAUTHORIZED` - the account refused the token even after a refresh.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'404':
|
||
description: '`ENOTFOUND` - no profile by that name.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: '`ENOREFRESH`/`ENOAUTH` - the profile holds no usable token; re-save it from a login.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'502':
|
||
description: '`EUPSTREAM` - Anthropic''s token or usage endpoint did not cooperate.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/codex-config/overview:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Codex config explorer overview
|
||
description: Read-only. Returns the three resolved filesystem roots plus per-scope counts across skills, agents, rules, memory, and config.
|
||
operationId: codexConfigGetOverview
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Overview of roots and counts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigOverviewResponse'
|
||
/api/codex-config/skills:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List discovered skills
|
||
operationId: codexConfigGetSkills
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default).'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: List discovered skills.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
skills:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CodexConfigSkill'
|
||
example:
|
||
skills:
|
||
- scope: user
|
||
name: code-reviewer
|
||
frontmatter: {}
|
||
preview: ...
|
||
/api/codex-config/agents:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List discovered agent definitions
|
||
operationId: codexConfigGetAgents
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default).'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: List discovered agent definitions.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
agents:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CodexConfigAgent'
|
||
example:
|
||
agents:
|
||
- scope: user
|
||
name: reviewer
|
||
meta:
|
||
model: gpt-5
|
||
/api/codex-config/rules:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List discovered rules files
|
||
operationId: codexConfigGetRules
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- all
|
||
default: all
|
||
description: 'Discovery scope: `user`, `project`, or `all` (default).'
|
||
example: all
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: List discovered rules files.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
rules:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CodexConfigRule'
|
||
example:
|
||
rules:
|
||
- scope: user
|
||
name: default
|
||
preview: allow
|
||
/api/codex-config/memory:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Read AGENTS.md at both scopes
|
||
operationId: codexConfigGetMemory
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Read AGENTS.md at both scopes.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
memory:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CodexConfigMemoryEntry'
|
||
example:
|
||
memory:
|
||
- scope: project
|
||
file: /repo/AGENTS.md
|
||
exists: true
|
||
/api/codex-config/config:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Read config.toml at both scopes
|
||
operationId: codexConfigGetConfig
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Read config.toml at both scopes.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
config:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/CodexConfigConfigEntry'
|
||
example:
|
||
config:
|
||
- scope: user
|
||
file: /home/user/.codex/config.toml
|
||
exists: true
|
||
/api/codex-config/file:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Read a single file body
|
||
description: 'Read-only, no filesystem side effect. Returns the body of one file. Strict path containment: the resolved absolute path must live under CODEX_HOME, the project .codex dir, or (only for a file literally named AGENTS.md) the project root, and must not escape that root through a symlink. Error messages never reveal what does or does not exist outside the root.'
|
||
operationId: codexConfigGetFile
|
||
parameters:
|
||
- name: path
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
example: /home/user/.codex/rules/default.rules
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: File body with metadata.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigFileReadResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: READ_DENIED
|
||
message: path escapes the allowed root
|
||
'404':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: file not found
|
||
put:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Create or overwrite a text artifact
|
||
description: Writes skills/agents/rules/memory/config. `name` is required for skills/agents/rules and forbidden for memory/config (fixed filenames). `project` scope always resolves against the dashboard server's own cwd — there is no `?cwd=` override on writes, unlike the read routes, so a caller cannot point a write at an arbitrary directory. Backs up any existing file first; broadcasts `codex_config_changed` on success.
|
||
operationId: codexConfigPutFile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- scope
|
||
- type
|
||
- content
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
type:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- rules
|
||
- memory
|
||
- config
|
||
name:
|
||
type: string
|
||
nullable: true
|
||
example: default
|
||
content:
|
||
type: string
|
||
example:
|
||
scope: user
|
||
type: rules
|
||
name: default
|
||
content: allow
|
||
responses:
|
||
'200':
|
||
description: File written.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigFileWriteResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADNAME
|
||
message: name must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
|
||
'413':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ETOOLARGE
|
||
message: content exceeds 262144 bytes
|
||
delete:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Delete a text artifact
|
||
description: Deletes skills/agents/rules/memory/config. `project` scope always resolves against the dashboard server's own cwd — no `?cwd=` override, same as PUT. Backup is mandatory and runs first. Broadcasts `codex_config_changed` on success.
|
||
operationId: codexConfigDeleteFile
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- scope
|
||
- type
|
||
properties:
|
||
scope:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
type:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- rules
|
||
- memory
|
||
- config
|
||
name:
|
||
type: string
|
||
nullable: true
|
||
example:
|
||
scope: user
|
||
type: agents
|
||
name: reviewer
|
||
responses:
|
||
'200':
|
||
description: File deleted.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigFileDeleteResponse'
|
||
'404':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ENOTFOUND
|
||
message: agents/reviewer does not exist
|
||
/api/codex-config/backups:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List backups
|
||
description: Read-only. Lists timestamped backups, optionally filtered by scope and/or type.
|
||
operationId: codexConfigGetBackups
|
||
parameters:
|
||
- name: scope
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- user
|
||
- project
|
||
- name: type
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- skills
|
||
- agents
|
||
- rules
|
||
- memory
|
||
- config
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Override the working directory used to resolve the project root and project .codex dir. Defaults to the dashboard server's own cwd.
|
||
example: /repo
|
||
responses:
|
||
'200':
|
||
description: Backups list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigBackupsResponse'
|
||
/api/codex-config/mcp:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List configured MCP servers
|
||
description: Reads live from `codex mcp list --json` — there is no on-disk reader for this surface.
|
||
operationId: codexConfigGetMcp
|
||
responses:
|
||
'200':
|
||
description: MCP servers as reported by the CLI.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
mcp: {}
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
'502':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLIBADOUTPUT
|
||
message: could not parse `codex` JSON output
|
||
'503':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLINOTFOUND
|
||
message: the `codex` CLI was not found on PATH
|
||
/api/codex-config/plugins:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List installed plugins
|
||
description: Reads live from `codex plugin list --json` and unwraps its `{installed, available}` object down to the `installed` array — the route's `plugins` field is always a list.
|
||
operationId: codexConfigGetPlugins
|
||
responses:
|
||
'200':
|
||
description: Installed plugins as reported by the CLI.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
plugins:
|
||
type: array
|
||
items: {}
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
'502':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLIBADOUTPUT
|
||
message: could not parse `codex` JSON output
|
||
'503':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLINOTFOUND
|
||
message: the `codex` CLI was not found on PATH
|
||
/api/codex-config/marketplaces:
|
||
get:
|
||
tags:
|
||
- CodexConfig
|
||
summary: List registered marketplaces
|
||
description: Reads live from `codex plugin marketplace list --json` and unwraps its `{marketplaces}` object down to a bare array.
|
||
operationId: codexConfigGetMarketplaces
|
||
responses:
|
||
'200':
|
||
description: Marketplaces as reported by the CLI.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
marketplaces:
|
||
type: array
|
||
items: {}
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
'502':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLIBADOUTPUT
|
||
message: could not parse `codex` JSON output
|
||
'503':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLINOTFOUND
|
||
message: the `codex` CLI was not found on PATH
|
||
/api/codex-config/mcp/add:
|
||
post:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Add an MCP server
|
||
description: Runs `codex mcp add`. Accepts either a streamable-HTTP server (`url`, optional `bearerTokenEnvVar`) or a stdio server (`command`, `args`, optional `env`) — never both.
|
||
operationId: codexConfigPostMcpAdd
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
url:
|
||
type: string
|
||
bearerTokenEnvVar:
|
||
type: string
|
||
command:
|
||
type: string
|
||
args:
|
||
type: array
|
||
items:
|
||
type: string
|
||
env:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
example:
|
||
name: github
|
||
url: https://mcp.example.com/github
|
||
responses:
|
||
'200':
|
||
description: MCP server added.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigCliActionResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADARG
|
||
message: url must be a valid http(s) URL
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
/api/codex-config/mcp/remove:
|
||
post:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Remove an MCP server
|
||
operationId: codexConfigPostMcpRemove
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
properties:
|
||
name:
|
||
type: string
|
||
example:
|
||
name: github
|
||
responses:
|
||
'200':
|
||
description: MCP server removed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigCliActionResponse'
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
/api/codex-config/plugins/action:
|
||
post:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Add or remove a plugin
|
||
description: Runs `codex plugin add <spec>` or `codex plugin remove <spec>`.
|
||
operationId: codexConfigPostPluginsAction
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- spec
|
||
- action
|
||
properties:
|
||
spec:
|
||
type: string
|
||
action:
|
||
type: string
|
||
enum:
|
||
- add
|
||
- remove
|
||
example:
|
||
spec: demo@market
|
||
action: add
|
||
responses:
|
||
'200':
|
||
description: Plugin action applied.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigCliActionResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADACTION
|
||
message: action must be one of add, remove
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
/api/codex-config/marketplaces/add:
|
||
post:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Register a marketplace
|
||
description: Runs `codex plugin marketplace add <source>`. `source` is an owner/repo shorthand, an http(s) git URL, or an absolute local path — the same set `claude plugin marketplace add` accepts; ssh remotes are not.
|
||
operationId: codexConfigPostMarketplacesAdd
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- url
|
||
properties:
|
||
url:
|
||
type: string
|
||
example:
|
||
url: openai/codex
|
||
responses:
|
||
'200':
|
||
description: Marketplace added.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigCliActionResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADARG
|
||
message: source must be owner/repo, an http(s) URL, or an absolute path
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
/api/codex-config/marketplaces/action:
|
||
post:
|
||
tags:
|
||
- CodexConfig
|
||
summary: Remove a marketplace
|
||
description: Runs `codex plugin marketplace remove <name>`.
|
||
operationId: codexConfigPostMarketplacesAction
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- name
|
||
- action
|
||
properties:
|
||
name:
|
||
type: string
|
||
action:
|
||
type: string
|
||
enum:
|
||
- remove
|
||
example:
|
||
name: demo-mp
|
||
action: remove
|
||
responses:
|
||
'200':
|
||
description: Marketplace action applied.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CodexConfigCliActionResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADACTION
|
||
message: action must be one of remove
|
||
'422':
|
||
description: '`ECLIEXIT` — the `codex` CLI exited non-zero. stdout/stderr are preserved.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
error:
|
||
type: object
|
||
properties:
|
||
code:
|
||
type: string
|
||
message:
|
||
type: string
|
||
code:
|
||
type: integer
|
||
nullable: true
|
||
stdout:
|
||
type: string
|
||
stderr:
|
||
type: string
|
||
example:
|
||
error:
|
||
code: ECLIEXIT
|
||
message: marketplace "nope" is not registered
|
||
code: 1
|
||
stdout: ''
|
||
stderr: |
|
||
✘ marketplace "nope" is not registered
|
||
'503':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ECLINOTFOUND
|
||
message: the `codex` CLI was not found on PATH
|
||
/api/run:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: List live + recently-exited runs
|
||
description: Returns every run handle currently held in memory (newest first) plus concurrency telemetry (`maxConcurrent`, `activeCount`). Handles are reaped 5 minutes after the process exits, so terminal runs disappear from here but remain in GET /api/run/history. Read-only — no process is spawned. The loopback same-origin guard applies.
|
||
operationId: runList
|
||
responses:
|
||
'200':
|
||
description: Tracked runs and concurrency telemetry
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunListResponse'
|
||
example:
|
||
items:
|
||
- id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid: 48213
|
||
mode: conversation
|
||
cwd: /Users/dev/projects/my-app
|
||
model: claude-opus-4-8
|
||
permissionMode: acceptEdits
|
||
effort: high
|
||
prompt: Refactor the auth module and add tests.
|
||
argv:
|
||
- '--output-format'
|
||
- stream-json
|
||
- '--verbose'
|
||
- '--include-partial-messages'
|
||
- '--permission-mode'
|
||
- acceptEdits
|
||
- '--input-format'
|
||
- stream-json
|
||
resumeSessionId: null
|
||
status: running
|
||
startedAt: 1718000000000
|
||
endedAt: null
|
||
exitCode: null
|
||
signal: null
|
||
error: null
|
||
sessionId: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
envelopeCount: 12
|
||
stdoutTail: |
|
||
{"type":"assistant",...}
|
||
stderrTail: ''
|
||
maxConcurrent: 10000
|
||
activeCount: 1
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
post:
|
||
tags:
|
||
- Run
|
||
summary: Spawn a new run
|
||
description: Spawns a real `claude` Code subprocess in the sanitised `cwd`. In `headless` mode the prompt is passed via argv and stdin is closed (one turn, then exit); in `conversation` mode stdin stays open for follow-ups via POST /api/run/{id}/message, and an existing session may be resumed with `resumeSessionId`. The child is always run with `--output-format stream-json --verbose --include-partial-messages`; parsed envelopes are broadcast as `run_stream` WebSocket messages and lifecycle transitions as `run_status`. A row is persisted to `dashboard_runs`, and the in-memory handle is reaped 5 minutes after exit. Concurrency is capped (ECONCURRENCY -> 429). The loopback same-origin guard applies. Returns the freshly-created RunHandle.
|
||
operationId: runSpawn
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunSpawnRequest'
|
||
example:
|
||
prompt: Refactor the auth module and add tests.
|
||
mode: conversation
|
||
cwd: /Users/dev/projects/my-app
|
||
model: claude-opus-4-8
|
||
effort: high
|
||
permissionMode: acceptEdits
|
||
responses:
|
||
'201':
|
||
description: Run spawned; returns the new run handle (status `spawning`).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunHandle'
|
||
example:
|
||
id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid: 48213
|
||
mode: conversation
|
||
cwd: /Users/dev/projects/my-app
|
||
model: claude-opus-4-8
|
||
permissionMode: acceptEdits
|
||
effort: high
|
||
prompt: Refactor the auth module and add tests.
|
||
argv:
|
||
- '--output-format'
|
||
- stream-json
|
||
- '--verbose'
|
||
- '--include-partial-messages'
|
||
- '--permission-mode'
|
||
- acceptEdits
|
||
- '--input-format'
|
||
- stream-json
|
||
- '--model'
|
||
- claude-opus-4-8
|
||
- '--effort'
|
||
- high
|
||
resumeSessionId: null
|
||
status: spawning
|
||
startedAt: 1718000000000
|
||
endedAt: null
|
||
exitCode: null
|
||
signal: null
|
||
error: null
|
||
sessionId: null
|
||
envelopeCount: 0
|
||
stdoutTail: ''
|
||
stderrTail: ''
|
||
'400':
|
||
description: Invalid spawn request — EBADPROMPT (prompt required), EBADCWD (cwd not an existing absolute directory), EBADMODE (bad mode, or resumeSessionId outside conversation mode), EBADEFFORT (effort not low/medium/high/xhigh/max), or EBADSESSION (resumeSessionId not a valid session id).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
badPrompt:
|
||
value:
|
||
error:
|
||
code: EBADPROMPT
|
||
message: prompt is required
|
||
badCwd:
|
||
value:
|
||
error:
|
||
code: EBADCWD
|
||
message: cwd must be an absolute path
|
||
badMode:
|
||
value:
|
||
error:
|
||
code: EBADMODE
|
||
message: mode must be "headless" or "conversation"
|
||
badEffort:
|
||
value:
|
||
error:
|
||
code: EBADEFFORT
|
||
message: 'effort must be one of: low, medium, high, xhigh, max'
|
||
badSession:
|
||
value:
|
||
error:
|
||
code: EBADSESSION
|
||
message: resumeSessionId is not a valid session id
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
'429':
|
||
description: Concurrency cap reached (ECONCURRENCY). The body additionally carries a top-level `running` array listing the runs currently holding the cap.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunSpawnConcurrencyResponse'
|
||
example:
|
||
error:
|
||
code: ECONCURRENCY
|
||
message: concurrency limit 10000 reached
|
||
running:
|
||
- id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid: 48213
|
||
startedAt: 1718000000000
|
||
mode: conversation
|
||
'500':
|
||
description: Unexpected spawn failure (EINTERNAL).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EINTERNAL
|
||
message: spawn claude ENOENT
|
||
/api/run/history:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: List persisted run history
|
||
description: 'Returns the persistent history of runs spawned via the dashboard, sourced from the `dashboard_runs` sqlite table (newest first). Unlike GET /api/run, this survives the 5-minute in-memory reap so past runs stay visible and resumable for days. Each item is cross-referenced against live handles to set `isLive`. Returns `{ items: [] }` if the persistence DB is unavailable. Read-only — no process is spawned. The loopback same-origin guard applies.'
|
||
operationId: runHistory
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 500
|
||
default: 50
|
||
description: Maximum number of history rows to return (default 50, clamped to 1–500).
|
||
example: 50
|
||
- name: laneId
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
description: Return only the runs started through this lane (POST /api/lanes/{id}/start). Omit for every run; runs spawned from POST /api/run carry no lane and are excluded whenever this is set.
|
||
example: 5
|
||
- name: tool
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Return only the runs of this AI Tools id — `shell` for a plain terminal tab (the sidebar's recently closed Terminal tabs).
|
||
example: shell
|
||
responses:
|
||
'200':
|
||
description: Run history, most recent first
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunHistoryResponse'
|
||
example:
|
||
items:
|
||
- id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
session_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
mode: conversation
|
||
cwd: /Users/dev/projects/my-app
|
||
model: claude-opus-4-8
|
||
permission_mode: acceptEdits
|
||
effort: high
|
||
resume_session_id: null
|
||
prompt_preview: Refactor the auth module and add tests.
|
||
status: completed
|
||
exit_code: 0
|
||
started_at: '2026-06-25T17:00:00.000Z'
|
||
ended_at: '2026-06-25T17:01:42.500Z'
|
||
isLive: false
|
||
lane_id: null
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
/api/run/history/{id}:
|
||
delete:
|
||
tags:
|
||
- Run
|
||
summary: Forget a closed Terminal tab
|
||
description: Deletes one `dashboard_runs` row whose tool is `shell` — a closed Terminal tab, which has no session — so it leaves the sidebar's recently-closed list. Rows of any other tool are never deleted (404). The loopback same-origin guard applies.
|
||
operationId: runHistoryForget
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: The history row's id (RunHistoryItem.id).
|
||
example: agenthub-lane-5-2@2026-10-06T08:00:00.000Z
|
||
responses:
|
||
'200':
|
||
description: Removed
|
||
content:
|
||
application/json:
|
||
example:
|
||
removed: agenthub-lane-5-2
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
'404':
|
||
description: No closed Terminal tab with that id (ENOTFOUND)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: That terminal is still open (ERUNLIVE)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/run/cwds:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: Suggest working directories
|
||
description: 'Suggests plausible working directories for the Run launcher: the dashboard server''s cwd (always first), $HOME, and distinct recent cwds Claude Code has been used in (from the sessions table). Only directories that still exist on disk are returned; the DB lookup is best-effort. Read-only — no process is spawned. The loopback same-origin guard applies.'
|
||
operationId: runCwds
|
||
parameters:
|
||
- name: q
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Optional case-insensitive substring filter (also applied client-side).
|
||
example: my-app
|
||
responses:
|
||
'200':
|
||
description: Working-directory suggestions
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunCwdsResponse'
|
||
example:
|
||
items:
|
||
- kind: dashboard
|
||
path: /Users/dev/AgentHub
|
||
label: Dashboard server
|
||
- kind: home
|
||
path: /Users/dev
|
||
label: Home
|
||
- kind: recent
|
||
path: /Users/dev/projects/my-app
|
||
label: my-app
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
/api/run/files:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: Autocomplete files within a cwd
|
||
description: Walks the given `cwd` and returns up to 40 file paths (relative to that cwd, shortest first) for the prompt editor's `@` references. The cwd is validated via the same sanitiser as spawning (must be an existing absolute directory). Dotfiles (except .env/.gitignore) and heavy build dirs (node_modules, .git, dist, build, out, .next, coverage, target, .venv, __pycache__, etc.) are skipped; the walk is bounded (≤5000 entries visited). Read-only — no process is spawned. The loopback same-origin guard applies.
|
||
operationId: runFiles
|
||
parameters:
|
||
- name: cwd
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Absolute directory to walk. Must exist; defaults to the dashboard server's cwd when omitted. Invalid values yield 400 EBADCWD.
|
||
example: /Users/dev/projects/my-app
|
||
- name: q
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Optional case-insensitive substring filter applied to the relative path.
|
||
example: route
|
||
responses:
|
||
'200':
|
||
description: Matching file paths relative to cwd
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunFilesResponse'
|
||
example:
|
||
items:
|
||
- package.json
|
||
- src/index.ts
|
||
- server/routes/run.js
|
||
'400':
|
||
description: 'Invalid cwd (EBADCWD): not a string, not absolute, or does not exist.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADCWD
|
||
message: 'cwd does not exist: /no/such/dir'
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
/api/run/binary:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: Check whether the `claude` binary is on PATH
|
||
description: Probes PATH (via which/where) for the `claude` binary so the UI can warn before the user clicks Run. The binary is NOT invoked — only resolved. Read-only — no process is spawned beyond the lookup. The loopback same-origin guard applies.
|
||
operationId: runBinary
|
||
responses:
|
||
'200':
|
||
description: Binary resolution result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunBinaryResponse'
|
||
example:
|
||
found: true
|
||
path: /usr/local/bin/claude
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
/api/run/{id}/message:
|
||
post:
|
||
tags:
|
||
- Run
|
||
summary: Send a follow-up turn to a conversation run
|
||
description: 'Writes a stream-json user envelope to a running conversation''s stdin and broadcasts a `run_input_ack` WebSocket message. Only valid for `conversation`-mode runs that are still `running`/`spawning` with writable stdin. SIDE EFFECT: the child resumes processing the new turn and emits further `run_stream`/`run_status` messages. The loopback same-origin guard applies.'
|
||
operationId: runSendMessage
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: Run id (RunHandle.id).
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunMessageRequest'
|
||
example:
|
||
text: Also update the README to document the new flags.
|
||
responses:
|
||
'200':
|
||
description: Turn written to stdin
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunMessageResponse'
|
||
example:
|
||
messageId: 9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f
|
||
'400':
|
||
description: Cannot deliver input — EBADINPUT (text required), EWRONGMODE (run is not conversation mode), ENOTRUNNING (run is not running/spawning), or ESTDINCLOSED (child stdin not writable).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
badInput:
|
||
value:
|
||
error:
|
||
code: EBADINPUT
|
||
message: text is required
|
||
wrongMode:
|
||
value:
|
||
error:
|
||
code: EWRONGMODE
|
||
message: only conversation mode accepts follow-up input
|
||
notRunning:
|
||
value:
|
||
error:
|
||
code: ENOTRUNNING
|
||
message: run is completed
|
||
stdinClosed:
|
||
value:
|
||
error:
|
||
code: ESTDINCLOSED
|
||
message: stdin is not writable
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
'404':
|
||
description: No run with this id (ENOTFOUND) — it never existed or was reaped.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ENOTFOUND
|
||
message: run not found
|
||
/api/run/{id}:
|
||
get:
|
||
tags:
|
||
- Run
|
||
summary: Get a single run
|
||
description: Returns the in-memory handle for one run. Pass `?envelopes=1` to additionally include the bounded (≤500) stream-json envelope replay buffer so a client can re-attach to an in-flight run and see what it missed. Available only while the handle is live or within the 5-minute post-exit reap window — afterwards use GET /api/run/history. Read-only — no process is spawned. The loopback same-origin guard applies.
|
||
operationId: runGet
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: Run id (RunHandle.id).
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
- name: envelopes
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- '1'
|
||
description: Set to `1` to include the `envelopes` replay buffer in the response.
|
||
example: '1'
|
||
responses:
|
||
'200':
|
||
description: The run handle (with `envelopes` appended when `?envelopes=1`).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
oneOf:
|
||
- $ref: '#/components/schemas/RunHandle'
|
||
- $ref: '#/components/schemas/RunHandleWithEnvelopes'
|
||
example:
|
||
id: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
pid: 48213
|
||
mode: conversation
|
||
cwd: /Users/dev/projects/my-app
|
||
model: claude-opus-4-8
|
||
permissionMode: acceptEdits
|
||
effort: high
|
||
prompt: Refactor the auth module and add tests.
|
||
argv:
|
||
- '--output-format'
|
||
- stream-json
|
||
- '--verbose'
|
||
- '--include-partial-messages'
|
||
resumeSessionId: null
|
||
status: running
|
||
startedAt: 1718000000000
|
||
endedAt: null
|
||
exitCode: null
|
||
signal: null
|
||
error: null
|
||
sessionId: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
envelopeCount: 12
|
||
stdoutTail: |
|
||
{"type":"assistant",...}
|
||
stderrTail: ''
|
||
envelopes:
|
||
- type: system
|
||
subtype: init
|
||
session_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
|
||
- type: assistant
|
||
message:
|
||
role: assistant
|
||
content:
|
||
- type: text
|
||
text: Working on it...
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
'404':
|
||
description: No run with this id (ENOTFOUND) — it never existed or was reaped.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ENOTFOUND
|
||
message: run not found
|
||
delete:
|
||
tags:
|
||
- Run
|
||
summary: Kill a run
|
||
description: 'Terminates a live run: sends SIGTERM (escalating to SIGKILL after ~5s if needed), sets the handle status to `killed`, broadcasts a `run_status` WebSocket message, persists the status to `dashboard_runs`, and schedules the handle for the 5-minute reap. Idempotent — returns `{ ok: true }` even if the run had already terminated. The loopback same-origin guard applies.'
|
||
operationId: runKill
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: Run id (RunHandle.id).
|
||
example: 3f2c9a1e-7b4d-4e21-9b6a-1d2e3f4a5b6c
|
||
responses:
|
||
'200':
|
||
description: Kill issued (or run already terminated).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RunKillResponse'
|
||
example:
|
||
ok: true
|
||
'403':
|
||
description: 'Cross-origin rejected (EBADORIGIN). Enforced by the router-level loopback same-origin guard on every /api/run route: a browser request carrying an Origin/Referer whose host is not localhost / 127.0.0.1 / ::1 / 0.0.0.0 is refused. Requests without an Origin header (e.g. curl) are allowed.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADORIGIN
|
||
message: cross-origin requests are not allowed
|
||
'404':
|
||
description: No run with this id (ENOTFOUND) — it never existed or was reaped.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: ENOTFOUND
|
||
message: run not found
|
||
/api/lanes/pick-path:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Open the OS's own folder/file picker on the server's desktop
|
||
description: 'Shows zenity/kdialog (Linux), osascript (macOS) or a WinForms dialog (Windows) on the SERVER''s desktop and waits for the user (up to 10 minutes; closed when the request goes away). Only for a browser on the same machine: a non-loopback client, or a request carrying X-Forwarded-For/Forwarded, gets EREMOTE and should fall back to GET /api/lanes/browse. One dialog at a time. Nothing from the request reaches argv but the starting folder, resolved to an existing absolute directory first.'
|
||
operationId: pickLanePath
|
||
requestBody:
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
kind:
|
||
type: string
|
||
enum:
|
||
- dir
|
||
- file
|
||
default: dir
|
||
start:
|
||
type: string
|
||
description: Folder (or file) to open the dialog at.
|
||
responses:
|
||
'200':
|
||
description: '`{path}` — the chosen absolute path — or `{cancelled: true}`.'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
path:
|
||
type: string
|
||
cancelled:
|
||
type: boolean
|
||
'403':
|
||
description: EBADORIGIN (not same-origin) or EREMOTE (not a local browser).
|
||
'409':
|
||
description: EBUSY — a picker is already open.
|
||
'500':
|
||
description: EDIALOG — the dialog program failed.
|
||
'501':
|
||
description: ENODIALOG — no desktop session or no dialog program.
|
||
/api/lanes/ensure:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Find or adopt the lane owning a working directory
|
||
description: 'Idempotent: canonicalizes `cwd` to its real path when it exists (the same path `createLane` stores), then returns the lane whose cwd is an exact match or the longest path-boundary parent of `cwd` with `created: false` (200) — with `exact: true` only an exact match counts, so a directory nested inside another lane gets a lane of its own (the Add-lane modal sends it) — otherwise creates an `adopted` lane and returns it with `created: true` (201). The Workspace page opens on a directory rather than a lane id, so this is how it gets exactly one lane for that directory. Concurrent calls for the same path resolve to ONE lane: the `lanes.cwd` UNIQUE constraint decides, and the loser re-reads and returns the winner''s lane.'
|
||
operationId: ensureLane
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LaneEnsureRequest'
|
||
responses:
|
||
'200':
|
||
description: An existing lane already owns that cwd.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- lane
|
||
- created
|
||
properties:
|
||
lane:
|
||
type: object
|
||
additionalProperties: true
|
||
created:
|
||
type: boolean
|
||
enum:
|
||
- false
|
||
'201':
|
||
description: No lane owned that cwd, so an adopted one was created.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- lane
|
||
- created
|
||
properties:
|
||
lane:
|
||
type: object
|
||
additionalProperties: true
|
||
created:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
'400':
|
||
description: cwd is missing or not an absolute path (EBADCWD).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/lanes/worktree:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Provision a managed git worktree lane
|
||
description: Validates the absolute source repository, creates a managed lane in `provisioning` state, and returns immediately. Worktree creation continues under the lane lock; the existing `lane_update` broadcast reports either `idle` or `failed` with git stderr in `notes`.
|
||
operationId: createLaneWorktree
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LaneWorktreeCreateRequest'
|
||
responses:
|
||
'202':
|
||
description: Managed lane accepted for background provisioning.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- lane
|
||
properties:
|
||
lane:
|
||
type: object
|
||
additionalProperties: true
|
||
'400':
|
||
description: 'EBADSOURCEREPO: sourceRepo is relative, missing, or not a git repository. EEMPTYREPO: it has no commits, so there is nothing to branch a worktree from (no lane row is created).'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'409':
|
||
description: The computed worktree directory already belongs to a lane, or no unique directory was available after 50 attempts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/lanes/{id}:
|
||
patch:
|
||
tags:
|
||
- Lanes
|
||
summary: Partially update a lane
|
||
description: Updates lane fields, including run_id; browser requests must pass the loopback same-origin guard. The provisioning-time facts kind, source_repo, slug and base_branch are NOT patchable — kind is check 1 of the destroy guard — and are silently ignored here.
|
||
operationId: updateLane
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: true
|
||
responses:
|
||
'200':
|
||
description: Updated lane.
|
||
'400':
|
||
description: kind was not one of adopted|managed (EBADKIND).
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/preflight:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: Count facts before a destructive lane action
|
||
operationId: preflightLaneAction
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: action
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- reset
|
||
- remove
|
||
- purge
|
||
responses:
|
||
'200':
|
||
description: 'Current counted facts for the selected action. reset/remove additionally return blocked[] (hard blockers: adopted, missing, unreadable, unpushed-commits — the only one force overrides) and warnings[] (informational only, e.g. no-remote).'
|
||
'400':
|
||
description: Unknown action.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: A lane's working-copy facts
|
||
description: Branch, short HEAD, that commit's subject, and the uncommitted counts for the lane's cwd. Read-only, so no same-origin guard. Kept out of GET /api/lanes because it shells out to git five times and that payload is polled and re-broadcast on every lane_update. A cwd that is missing, is not a git repository, or makes git fail returns available:false with HTTP 200 — a lane pointing at a plain directory is a normal state, not a fault.
|
||
operationId: getLaneGitFacts
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
responses:
|
||
'200':
|
||
description: available:true with {branch, head, unborn, subject, dirty, untracked, files, hasRemote, upstream, ahead, behind}, or available:false alone. A repo with no commit yet is available with unborn:true, head:null and subject:"". files names the files behind those counts as {path, status, added, deleted} records, capped at 20; the counts themselves are never capped. status is the two-character `git status --porcelain` code and path is the new path for a rename; added/deleted come from one `git diff --numstat HEAD` and are null where git has no delta to report (untracked, binary, renamed, or no commit to diff against).
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/runtime:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: A lane's own application stack
|
||
description: Slot, ports, per-service liveness, log paths and the last boot error. Recomputed on every call from pid files and port probes rather than cached, because a process can die to OOM or a stray kill without telling anyone. Read-only, so no same-origin guard. Kept out of GET /api/lanes because it opens a socket per declared port and stats every pid file. A lane whose repository declares no .agenthub/profile returns available:false with HTTP 200 — most lanes never run a stack, which is a normal state, not a fault.
|
||
operationId: getLaneRuntime
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
responses:
|
||
'200':
|
||
description: available:false (no profile); available:true with provisioned:false (no slot yet); or the full facts with slot, ports, services, up, healthy, steppedAside, logs and lastError.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/up:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Boot a lane's stack
|
||
description: 'Runs the profile''s boot then health hooks. Returns 202 and finishes in the background because a build can take minutes; progress streams as lane_hook_output and the attempt ends with a lane_runtime message. Does NOT run bootstrap — installing dependencies on every boot would make a routine restart minutes long. A failing health check leaves the processes running, because their logs are what identify the service that never came up. Writes only slot and ports on the lane row: never stage, status or notes.'
|
||
operationId: upLane
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
build:
|
||
type: boolean
|
||
default: true
|
||
description: false passes --no-build to the boot hook, reusing an existing build.
|
||
responses:
|
||
'202':
|
||
description: Accepted; the boot runs in the background.
|
||
'400':
|
||
description: ENOPROFILE — no .agenthub/profile, with the paths searched.
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
'404':
|
||
description: Lane not found.
|
||
'409':
|
||
description: ESLOTS (every slot taken) or EPORTBUSY (with the occupying pids).
|
||
/api/lanes/{id}/down:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Stop a lane's stack
|
||
description: Kills each recorded pid tree bottom-up (killing a parent first reparents its children to init, where nothing knows to look for them), then sweeps listeners on the lane's ports ONLY when a pid file existed — a lane whose stack is already down still owns its port numbers, and an unconditional sweep would kill a server the user started there. Idempotent, and a no-op for a lane that was never up.
|
||
operationId: downLane
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
responses:
|
||
'200':
|
||
description: '{ok, killed: number[], runtime}.'
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/hook/{name}:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Run one of the lane profile's hooks
|
||
description: The surface a driving session uses for ci-gate, e2e, migrate and friends. Returns 202; output streams as lane_hook_output and completion arrives as lane_hook_result with the exit code. The name is checked against a fixed allowlist BEFORE anything is spawned, and args travels as an array of strings straight into argv — neither is ever joined into a command string.
|
||
operationId: runLaneHook
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: name
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- bootstrap
|
||
- boot
|
||
- health
|
||
- migrate
|
||
- seed
|
||
- ci-gate
|
||
- e2e
|
||
- regen
|
||
- db-create
|
||
- db-drop
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
args:
|
||
type: array
|
||
items:
|
||
type: string
|
||
responses:
|
||
'202':
|
||
description: Accepted; the hook runs in the background.
|
||
'400':
|
||
description: ENOHOOK (name outside the allowlist) or ENOPROFILE.
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
'404':
|
||
description: Lane not found.
|
||
'409':
|
||
description: ENOSLOT — the lane has no runtime yet; bring it up first.
|
||
/api/lanes/{id}/logs/{svc}:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: Tail a lane's hook or service log
|
||
description: The resolved path is confined to the lane's log directory after realpath, so a name from the request can never escape it. Read-only, so no same-origin guard.
|
||
operationId: getLaneLog
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: svc
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
- name: tail
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
default: 65536
|
||
maximum: 1048576
|
||
description: Trailing bytes to return; capped at 1 MiB.
|
||
responses:
|
||
'200':
|
||
description: '{available, svc, size, truncated, text}, or {available:false} for a lane with no slot.'
|
||
'404':
|
||
description: Lane not found, or ENOLOG — no such log for this lane.
|
||
/api/lanes/branches:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: A candidate source repo's local branches
|
||
description: 'Feeds the Add-lane picker: given a repo path, returns its local branches (not origin/* refs — those aren''t checkout-able into a new worktree without a fetch first) plus which one is currently checked out. Same repo validation as POST /api/lanes/worktree; a repo neither endpoint can resolve can''t be provisioned from either. Read-only, no same-origin guard.'
|
||
operationId: listLaneBranches
|
||
parameters:
|
||
- name: repo
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Absolute path to an existing git repository.
|
||
responses:
|
||
'200':
|
||
description: '{branches: string[], current: string | null, worktrees: {path, branch?, locked?}[]}. The worktrees ride along so the picker can grey out branches git would refuse to check out twice.'
|
||
'400':
|
||
description: repo is missing, relative, does not exist, or is not a git repo.
|
||
/api/lanes/{id}/git/changes:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: A lane's code changes, at one of three depths
|
||
description: Backs the Workspace Changes panel. No extra params = a summary of every changed file; full=1 adds every file's hunks (review mode); path=<file> returns that one file's hunks. range=work (default) is HEAD vs the working tree with untracked files included; range=branch is <base_branch>...HEAD; range=commit needs sha= and shows that one commit. context= widens each hunk (default 3, clamped to 5000) — the viewer's expand button. Lines arrive with both gutters resolved. Capped at 300 files, 200 KB of patch per file and 1 MB per full read; a cap that bit sets `truncated`. Read-only, no same-origin guard.
|
||
operationId: getLaneChanges
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: range
|
||
in: query
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- work
|
||
- branch
|
||
- commit
|
||
default: work
|
||
- name: sha
|
||
in: query
|
||
schema:
|
||
type: string
|
||
description: 'range=commit only: the commit to show.'
|
||
- name: context
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
default: 3
|
||
maximum: 5000
|
||
description: Unchanged lines around each hunk.
|
||
- name: path
|
||
in: query
|
||
schema:
|
||
type: string
|
||
description: Return only this file, with its hunks.
|
||
- name: full
|
||
in: query
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- '1'
|
||
- 'true'
|
||
description: Include every file's hunks (review mode).
|
||
responses:
|
||
'200':
|
||
description: '{available, range, base, baseRef, sha, context, files, truncated}, or {available:false} for a cwd git cannot read.'
|
||
'400':
|
||
description: ENOBASE (range=branch on a lane with no base branch), ENOFILECHANGE (path has no changes in this range), EBADRANGE or EBADREV.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/log:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: The lane's recent commits
|
||
description: Newest first, up to limit (default 30, max 200). file= narrows it to the commits touching one path (relative, no `..`, else 400 EBADPATH). Reading a commit's diff is GET /api/lanes/{id}/git/changes?range=commit&sha=. Read-only, no same-origin guard.
|
||
operationId: listLaneCommits
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: limit
|
||
in: query
|
||
schema:
|
||
type: integer
|
||
default: 30
|
||
maximum: 200
|
||
- name: file
|
||
in: query
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'200':
|
||
description: '{available, commits:[{sha, short, subject, author, date, refs}]}.'
|
||
'400':
|
||
description: EBADPATH (file= is absolute or has a `..` segment).
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/file:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: One file's whole content at a revision
|
||
description: rev=work (the file on disk, untracked included), HEAD, or a commit-ish. The `work` revision is the only one that reads the filesystem and it is realpath-confined to the lane directory. Binary content is reported, never returned. Capped at 1 MB. Read-only, no same-origin guard.
|
||
operationId: readLaneFile
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: path
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
- name: rev
|
||
in: query
|
||
schema:
|
||
type: string
|
||
default: work
|
||
responses:
|
||
'200':
|
||
description: '{available, path, rev, binary, truncated, lines}.'
|
||
'400':
|
||
description: EBADPATH (incl. a symlink resolving outside the lane), EBADREV or ENOFILE.
|
||
'403':
|
||
description: ESECRET — `.claude/settings.local.json` (the AI Router key) is refused at every rev, at any depth, and under any path that resolves to it (a symlinked `.claude`).
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/raw:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: One image file's bytes at a revision
|
||
description: 'For the viewer''s <img>. Same path, rev and secret guards as /git/file, plus an extension allowlist (png, jpg, jpeg, gif, webp, avif, bmp, ico, svg) that sets the Content-Type, and a 20 MB cap. Served with `Content-Security-Policy: …; sandbox` and `X-Content-Type-Options: nosniff`, so an SVG opened from this URL runs no script. Read-only, no same-origin guard.'
|
||
operationId: readLaneImage
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: path
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: string
|
||
- name: rev
|
||
in: query
|
||
schema:
|
||
type: string
|
||
default: work
|
||
responses:
|
||
'200':
|
||
description: The image bytes, typed by extension.
|
||
'400':
|
||
description: EBADPATH, EBADREV, ENOFILE, ENOTIMAGE or ETOOBIG.
|
||
'403':
|
||
description: ESECRET.
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/ls:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: Tracked paths matching a substring (quick-open)
|
||
description: Reads `git ls-files`, so ignored files never appear and no skip-list is maintained. Basename hits rank first. Capped at 500 paths. Read-only, no same-origin guard.
|
||
operationId: listLanePaths
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: q
|
||
in: query
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'200':
|
||
description: '{available, paths, truncated, total}.'
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/tree:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: One directory level of tracked files
|
||
description: 'The file explorer''s expand-a-folder read: tracked entries under path=, folded into directories at that level. Read-only, no same-origin guard.'
|
||
operationId: listLaneTree
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: path
|
||
in: query
|
||
schema:
|
||
type: string
|
||
default: ''
|
||
responses:
|
||
'200':
|
||
description: '{available, path, entries:[{name, path, dir}], truncated}.'
|
||
'400':
|
||
description: EBADPATH (the path escapes the lane).
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/tree-all:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: Every level of the tracked tree in one read
|
||
description: 'The file explorer''s expand-all-folders read: ONE git ls-files, grouped into levels keyed by directory ("" is the root), each level shaped and capped like GET /api/lanes/{id}/git/tree''s entries. Read-only, no same-origin guard.'
|
||
operationId: listLaneTreeAll
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
responses:
|
||
'200':
|
||
description: '{available, levels:{<dir>:[{name, path, dir}]}, truncated}.'
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/branches:
|
||
get:
|
||
tags:
|
||
- Lanes
|
||
summary: The lane repo's own local branches
|
||
description: Feeds the lane's branch switcher. The repository comes from the lane row, never from the query — that is what GET /api/lanes/branches is for. Read-only, no same-origin guard.
|
||
operationId: listLaneOwnBranches
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
responses:
|
||
'200':
|
||
description: '{available, branches, current, worktrees}.'
|
||
'404':
|
||
description: Lane not found.
|
||
/api/lanes/{id}/git/commit-message:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Suggest a commit message for the staged changes
|
||
description: Runs the user's `claude` CLI (`-p --safe-mode --no-session-persistence --tools "" --model haiku`) on the STAGED diff of the lane's cwd (or `?session=` worktree), sent on stdin with the last ten commit subjects; a `settings.local.json` is never included. Reads only — nothing is staged or committed, no tool runs and no session is saved. Same-origin, since each call spends the user's Claude quota. 400 ENOSTAGED when nothing is staged, 400 ENOTOOL when `claude` is not on PATH, 502 EAI when the CLI fails, answers empty or runs past 120 s.
|
||
operationId: suggestLaneCommitMessage
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: session
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'200':
|
||
description: The suggested message
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
message:
|
||
type: string
|
||
required:
|
||
- message
|
||
'400':
|
||
description: ENOSTAGED / ENOTOOL / ENOWORKTREE
|
||
'403':
|
||
description: Cross-origin request refused
|
||
'404':
|
||
description: No such workspace
|
||
'502':
|
||
description: EAI — the claude CLI failed
|
||
/api/lanes/{id}/git/{action}:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: One git write on the lane's working copy
|
||
description: 'fetch (--all --prune), pull (ALWAYS --ff-only — a diverged branch is reported, never merged), push (-u <remote> HEAD, so only the branch the lane is on; remote = the branch''s upstream remote, else the only remote, else origin, else 400 ENOREMOTE), stage/unstage (per file; staging a conflicted file that still has conflict markers is 400 EMARKERS), discard (per file; irreversible, so it REQUIRES confirm:true), stage-hunk / discard-hunk (ONE hunk of a tracked file''s `git diff HEAD`, named by its `@@` line and re-read server-side at the given context — the applied patch is always git''s own text, never one from the request; discard-hunk REQUIRES confirm:true; 400 EUNTRACKED for an untracked file, 409 EHUNKGONE when the hunk is no longer in the diff), commit (git add -A then commit -m <message>, or only:"staged" to take the index as it stands), switch (git switch, -c to create; refuses a dirty tree so an agent''s half-finished work is never carried onto another branch), branch-create / branch-delete (create never switches; delete is `git branch -d`, refuses main/master/development/develop, the current branch and one held by another worktree), publish (the ONE write whose destination comes from the request: `git remote add origin -- <url>` then `git push -u origin HEAD`, only in a repo with a commit and NO remote — it never re-points one; the url must be https:// without userinfo, ssh:// or scp-like user@host:path, and origin is removed again if the push fails), and merge / resolve / merge-commit / merge-abort (issue #45: merge a local or remote-tracking branch that exists — clean tree and a repo-root lane only, 400 ESUBDIR otherwise — answering conflicts:[paths] when it stops on conflicts; resolve {path, side: ours|theirs|done} a conflicted path, done refusing leftover markers with 400 EMARKERS; merge-commit is 409 ECONFLICTS while anything is unmerged; merge-abort REQUIRES confirm:true; pull/switch/merge/hunk actions are 409 EMERGING mid-merge). fetch/pull/push/publish needing a login answer 409 EGITAUTH with auth:{protocol,host,username,rejected}; resend with credentials:{protocol,host,username,password,remember} (a repo remote''s host only, else 400 EBADHOST). A path in the body must be one `git status` is reporting as changed, so no path outside the lane is reachable. Guarded and serialized per git index (the lane lock keyed on the worktree''s git dir, so two lanes sharing one index cannot collide on index.lock). Deliberately no rebase, reset or stash: reset stays behind the destroy guard on POST /api/lanes/{id}/reset. Network actions are killed after 60s.'
|
||
operationId: runLaneGitAction
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: action
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- fetch
|
||
- pull
|
||
- push
|
||
- stage
|
||
- unstage
|
||
- discard
|
||
- stage-hunk
|
||
- discard-hunk
|
||
- commit
|
||
- switch
|
||
- branch-create
|
||
- branch-delete
|
||
- publish
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
url:
|
||
type: string
|
||
description: publish only; https:// (no userinfo), ssh:// or user@host:path, max 1024 chars.
|
||
message:
|
||
type: string
|
||
description: commit only; required, max 4000 chars.
|
||
only:
|
||
type: string
|
||
enum:
|
||
- staged
|
||
description: commit only; commit the index as it stands, with no add -A.
|
||
branch:
|
||
type: string
|
||
description: switch, branch-create and branch-delete.
|
||
base:
|
||
type: string
|
||
description: branch-create only; defaults to HEAD.
|
||
create:
|
||
type: boolean
|
||
description: switch only; create the branch.
|
||
paths:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: stage, unstage, discard; each must be a path git reports changed.
|
||
path:
|
||
type: string
|
||
description: stage-hunk, discard-hunk; a path git reports changed (not untracked).
|
||
hunk:
|
||
type: string
|
||
description: stage-hunk, discard-hunk; the hunk's `@@` line as the diff returned it. The server re-reads the diff and applies git's own text of that hunk; a hunk no longer present is 409 EHUNKGONE.
|
||
block:
|
||
type: integer
|
||
description: stage-hunk, discard-hunk (optional); act on only the block-th run of consecutive +/- lines inside that hunk — the other runs are folded into context. Out of range is 409 EHUNKGONE.
|
||
context:
|
||
type: integer
|
||
description: stage-hunk, discard-hunk; the context the hunk was read at.
|
||
confirm:
|
||
type: boolean
|
||
description: discard and discard-hunk; the call is refused without it.
|
||
responses:
|
||
'200':
|
||
description: '{action, ok:true, output} — git''s own stdout+stderr; publish adds remote:"origin".'
|
||
'400':
|
||
description: EBADACTION, EBADMESSAGE, EBADBRANCH, EBADPATH, ECONFIRM, EPROTECTED, ENOBRANCH, EBRANCHEXISTS, EBRANCHBUSY, EDIRTY, ENOCHANGES, ESTAGEDOUTSIDE, ENOREMOTE, EUNTRACKED, EBADURL, EREMOTEEXISTS, ENOCOMMITS, EBADHOST, EBADCRED, ESUBDIR, EBADSIDE or EMARKERS.
|
||
'404':
|
||
description: Lane not found.
|
||
'409':
|
||
description: EGIT — git ran and refused (a rejected push, a diverged --ff-only pull); its stderr is appended to error.message. EGITAUTH — the remote wants a login (carries auth). EHUNKGONE, EMERGING, ENOMERGE, ECONFLICTS.
|
||
'500':
|
||
description: Unexpected server-side failure.
|
||
/api/lanes/{id}/{action}:
|
||
post:
|
||
tags:
|
||
- Lanes
|
||
summary: Confirm a reset, managed-worktree removal, or session purge
|
||
description: Actions run under the lane lock after waiting for the lane child's actual exit. A failed spawn is already exited because no child started. Reset requires a live managed worktree; remove tears down a managed worktree, prunes git's stale record when the directory was deleted by hand, or only forgets an adopted-lane row without touching its directory. Reset/remove require force when managed work has unpushed commits.
|
||
operationId: runLaneDestructiveAction
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
- name: action
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- reset
|
||
- remove
|
||
- purge
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LaneDestructiveActionRequest'
|
||
responses:
|
||
'200':
|
||
description: Action completed; purge includes its deleted-row counts.
|
||
'400':
|
||
description: Confirmation or complete expect facts missing, or the managed-worktree guard refused the target.
|
||
'403':
|
||
description: The browser request was not same-origin/loopback.
|
||
'409':
|
||
description: Preflight facts changed (ESTALE) or force is required (EUNPUSHED).
|
||
'500':
|
||
description: Git, run-exit timeout, or internal failure; git failures include error.stderr.
|
||
/api/push/vapid-public-key:
|
||
get:
|
||
tags:
|
||
- Push
|
||
summary: Get the VAPID public key
|
||
description: Returns the server's VAPID public application server key so a browser can register a Web Push subscription via `PushManager.subscribe({ applicationServerKey })`. The key pair is generated once and persisted in the shared data directory alongside the SQLite DB, so the web app and native apps reuse a single key pair across restarts. No authentication — this is a local-first dashboard. Safe to call repeatedly; always returns the same key.
|
||
operationId: pushVapidPublicKey
|
||
responses:
|
||
'200':
|
||
description: The VAPID public key
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushVapidKeyResponse'
|
||
example:
|
||
publicKey: BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8
|
||
/api/push/subscribe:
|
||
post:
|
||
tags:
|
||
- Push
|
||
summary: Register a Web Push subscription
|
||
description: 'Stores a browser PushSubscription so future `/api/push/send` broadcasts reach this endpoint. Persisted with `INSERT OR REPLACE INTO push_subscriptions (endpoint, p256dh, auth)`, keyed on `endpoint` — so the operation is idempotent: re-subscribing the same endpoint overwrites its keys instead of creating a duplicate. No authentication (local-first). Requires `endpoint`, `keys.p256dh`, and `keys.auth`; any missing field returns 400.'
|
||
operationId: pushSubscribe
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushSubscribeRequest'
|
||
example:
|
||
endpoint: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ
|
||
keys:
|
||
p256dh: BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM
|
||
auth: tBHItJI5svbpez7KI4CCXg
|
||
responses:
|
||
'200':
|
||
description: Subscription stored (created or overwritten)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushSubscribeResponse'
|
||
example:
|
||
ok: true
|
||
'400':
|
||
description: Missing required fields (one of `endpoint`, `keys.p256dh`, `keys.auth` was absent)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: Missing required fields
|
||
delete:
|
||
tags:
|
||
- Push
|
||
summary: Remove a Web Push subscription
|
||
description: 'Deletes a stored subscription so it stops receiving broadcasts. The endpoint identifier is supplied in the request BODY (a DELETE with a JSON body), NOT as a query parameter. Idempotent — deleting an endpoint that is not stored still returns `{ ok: true }`. No authentication (local-first). A missing `endpoint` returns 400.'
|
||
operationId: pushUnsubscribe
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushUnsubscribeRequest'
|
||
example:
|
||
endpoint: https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLWZha2UtZW5kcG9pbnQ
|
||
responses:
|
||
'200':
|
||
description: Subscription removed (or no-op if it was not stored)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushOkResponse'
|
||
example:
|
||
ok: true
|
||
'400':
|
||
description: Missing endpoint in the request body
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: Missing endpoint
|
||
/api/push/send:
|
||
post:
|
||
tags:
|
||
- Push
|
||
summary: Broadcast a notification to all surfaces
|
||
description: 'Dispatches a notification to every reachable surface at once: it fires a native OS notification via Electron''s main-process Notification API when the server is hosted inside the desktop app, AND sends an encrypted Web Push delivery to every stored subscription. Both legs run unconditionally so whichever surface the user is on receives the alert — under `npm start` the native leg is a no-op, and under the desktop app the Web Push leg is typically a no-op (Electron has no FCM credentials, so `push_subscriptions` is empty). Subscriptions rejected with HTTP 410 (Gone) are pruned from `push_subscriptions` during the request. The response reports `{ native, pushed, failed, errors }` so the caller can tell a real delivery from a silent no-op, and can show WHY a rejected delivery failed. No authentication (local-first). A missing `title` or `body` returns 400; an unexpected dispatch error returns 500.'
|
||
operationId: pushSend
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushSendRequest'
|
||
example:
|
||
title: Session completed
|
||
body: Your Claude Code session finished with 3 subagents.
|
||
responses:
|
||
'200':
|
||
description: Dispatch ran; the body reports which surfaces fired and how many Web Push deliveries succeeded/failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PushSendResponse'
|
||
example:
|
||
ok: true
|
||
native: false
|
||
pushed: 2
|
||
failed: 1
|
||
'400':
|
||
description: Missing title or body
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: Missing title or body
|
||
'500':
|
||
description: Dispatch error while broadcasting the notification
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MessageErrorResponse'
|
||
example:
|
||
error:
|
||
message: Push service unavailable
|
||
/api/ai-tools:
|
||
get:
|
||
tags:
|
||
- AI Tools
|
||
summary: List the AI Tools catalog
|
||
description: Every catalog tool with whether it was found on PATH, whether it is enabled, and the effective default (stored default if installed+enabled, else claude, else the first installed enabled tool, else null). Arg arrays are never returned.
|
||
operationId: aiToolsList
|
||
responses:
|
||
'200':
|
||
description: The catalog with detection and the effective default
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsView'
|
||
/api/ai-tools/refresh:
|
||
post:
|
||
tags:
|
||
- AI Tools
|
||
summary: Re-detect installed tools
|
||
description: Merges the login shell's PATH ($SHELL -ilc, 5 s timeout, off with AGENTHUB_SHELL_PATH=0) into the server's, re-reads the catalog and re-detects. Same-origin only.
|
||
operationId: aiToolsRefresh
|
||
responses:
|
||
'200':
|
||
description: The catalog with detection and the effective default
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsView'
|
||
/api/ai-tools/{id}:
|
||
put:
|
||
tags:
|
||
- AI Tools
|
||
summary: Enable/disable a tool or make it the default
|
||
description: A disabled tool is hidden from Add lane and a NEW lane for it answers 400 EDISABLED; existing lanes keep working. Disabling the default clears it. Same-origin only.
|
||
operationId: aiToolsUpdate
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
enabled:
|
||
type: boolean
|
||
default:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
responses:
|
||
'200':
|
||
description: The catalog with detection and the effective default
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsView'
|
||
'400':
|
||
description: EBADDEFAULT (not installed or disabled) or EBADFIELD
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'404':
|
||
description: ENOTOOL — unknown id
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
/api/ai-tools/{id}/install:
|
||
post:
|
||
tags:
|
||
- AI Tools
|
||
summary: Install a tool
|
||
description: Runs the catalog's install (npm install -g <package>@latest with the login shell's npm, or the vendor script downloaded by the server and run with bash). One job at a time. Same-origin only.
|
||
operationId: aiToolsInstall
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'202':
|
||
description: The job started; poll GET /api/ai-tools/job
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
job:
|
||
$ref: '#/components/schemas/AiToolsJob'
|
||
'400':
|
||
description: ENOINSTALL
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'404':
|
||
description: ENOTOOL
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'409':
|
||
description: EINSTALLED, ENONPM or EBUSY
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
/api/ai-tools/{id}/update:
|
||
post:
|
||
tags:
|
||
- AI Tools
|
||
summary: Update a tool
|
||
description: npm update only for a tool whose binary lives under npm's global prefix (else ENOTNPM); a self updater runs the tool's own command. Same-origin only.
|
||
operationId: aiToolsUpdateTool
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'202':
|
||
description: The job started; poll GET /api/ai-tools/job
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
job:
|
||
$ref: '#/components/schemas/AiToolsJob'
|
||
'400':
|
||
description: ENOUPDATE
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'404':
|
||
description: ENOTOOL
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'409':
|
||
description: ENOTINSTALLED, ENOTNPM, ENONPM or EBUSY
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
/api/ai-tools/update-all:
|
||
post:
|
||
tags:
|
||
- AI Tools
|
||
summary: Update every tool with an update
|
||
description: 'One job: every npm-managed tool with a newer version, then every installed self updater. Same-origin only.'
|
||
operationId: aiToolsUpdateAll
|
||
responses:
|
||
'202':
|
||
description: The job started; poll GET /api/ai-tools/job
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
job:
|
||
$ref: '#/components/schemas/AiToolsJob'
|
||
'400':
|
||
description: ENOTHING
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
'409':
|
||
description: EBUSY
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsError'
|
||
/api/ai-tools/job:
|
||
get:
|
||
tags:
|
||
- AI Tools
|
||
summary: The current or last install/update job
|
||
operationId: aiToolsJob
|
||
responses:
|
||
'200':
|
||
description: The job, or null
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/AiToolsJob'
|
||
/api/ai-tools/versions:
|
||
get:
|
||
tags:
|
||
- AI Tools
|
||
summary: npm version badges
|
||
description: 'For installed tools with an npm update: whether npm manages them, the installed and latest version, and updateAvailable. Cached 24 h.'
|
||
operationId: aiToolsVersions
|
||
responses:
|
||
'200':
|
||
description: Map of tool id to its versions
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
npmManaged:
|
||
type: boolean
|
||
installed:
|
||
type: string
|
||
nullable: true
|
||
latest:
|
||
type: string
|
||
nullable: true
|
||
updateAvailable:
|
||
type: boolean
|
||
/api/ai-tools/{id}/icon:
|
||
get:
|
||
tags:
|
||
- AI Tools
|
||
summary: A tool's icon
|
||
description: The favicon of the tool's faviconDomain (or homepage host), fetched once by the server and cached. Raster only, never SVG; served with nosniff and CSP sandbox.
|
||
operationId: aiToolsIcon
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
responses:
|
||
'200':
|
||
description: The image
|
||
content:
|
||
image/*: {}
|
||
'204':
|
||
description: No icon (the client shows a letter avatar)
|
||
'404':
|
||
description: Unknown tool id
|
||
/api/sessions/facets:
|
||
get:
|
||
tags:
|
||
- Sessions
|
||
summary: List session facet values
|
||
description: 'Returns the distinct facet values for the Sessions page filters: the non-empty working directories (the `cwd` column, sorted ascending) in `cwds`, and the distinct data-source ids (the `sessions.source` column) in `sources`. `sources` always includes the built-in `local` history plus any configured remote SSH machines. Always returns a 200 with (possibly empty) arrays.'
|
||
operationId: listSessionFacets
|
||
responses:
|
||
'200':
|
||
description: Distinct session working directories and data-source ids
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionsFacetsResponse'
|
||
example:
|
||
cwds:
|
||
- /Users/dev/AgentHub
|
||
- /Users/son/code/another-project
|
||
sources:
|
||
- local
|
||
- 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
/api/remote-sources:
|
||
get:
|
||
tags:
|
||
- Remote Sources
|
||
summary: List remote data sources
|
||
description: Returns every configured remote SSH source the dashboard pulls Claude Code history from. NO secrets are ever returned — these records store none (SSH auth defers to the host SSH stack). Each entry carries its last sync `status`, `last_error`, `last_sync_at`, and `last_sync_counts`. Read-only; always 200.
|
||
operationId: listRemoteSources
|
||
responses:
|
||
'200':
|
||
description: All configured remote sources
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceListResponse'
|
||
example:
|
||
sources:
|
||
- id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
label: Work laptop
|
||
host: son@studio.local
|
||
ssh_port: 22
|
||
identity_file: ~/.ssh/id_ed25519
|
||
remote_home: ~/.claude
|
||
enabled: true
|
||
status: ok
|
||
last_error: null
|
||
last_sync_at: '2026-07-22T18:41:55.117Z'
|
||
last_sync_counts:
|
||
imported: 9
|
||
skipped: 41
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 50
|
||
sessions_tagged: 50
|
||
created_at: '2026-07-20T09:15:00.000Z'
|
||
updated_at: '2026-07-22T18:41:55.117Z'
|
||
post:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Register a remote data source
|
||
description: 'Registers a remote SSH source. `label` and `host` are required; `host` is an SSH destination (`user@host`) or a `~/.ssh/config` alias. Optional `ssh_port`, `identity_file`, `remote_home`, and `enabled` fine-tune the connection. No credentials are accepted or stored — auth defers to the host SSH stack. Returns the created source (201). Validation failures return 400 `{ error: { code, message } }` with one of the codes INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME.'
|
||
operationId: createRemoteSource
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceCreateRequest'
|
||
examples:
|
||
minimal:
|
||
summary: Minimal — label + SSH config alias
|
||
value:
|
||
label: Work laptop
|
||
host: studio
|
||
full:
|
||
summary: Full — explicit port, key, and remote home
|
||
value:
|
||
label: Work laptop
|
||
host: son@studio.local
|
||
ssh_port: 22
|
||
identity_file: ~/.ssh/id_ed25519
|
||
remote_home: ~/.claude
|
||
enabled: true
|
||
responses:
|
||
'201':
|
||
description: Remote source created
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceResponse'
|
||
example:
|
||
source:
|
||
id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
label: Work laptop
|
||
host: son@studio.local
|
||
ssh_port: 22
|
||
identity_file: ~/.ssh/id_ed25519
|
||
remote_home: ~/.claude
|
||
enabled: true
|
||
status: idle
|
||
last_error: null
|
||
last_sync_at: null
|
||
last_sync_counts: null
|
||
created_at: '2026-07-22T18:41:55.117Z'
|
||
updated_at: '2026-07-22T18:41:55.117Z'
|
||
'400':
|
||
description: 'Validation error (codes: INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME).'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
examples:
|
||
label:
|
||
summary: Missing/blank label
|
||
value:
|
||
error:
|
||
code: INVALID_LABEL
|
||
message: '`label` is required'
|
||
host:
|
||
summary: Missing/invalid host
|
||
value:
|
||
error:
|
||
code: INVALID_HOST
|
||
message: '`host` is required'
|
||
port:
|
||
summary: Port out of range
|
||
value:
|
||
error:
|
||
code: INVALID_PORT
|
||
message: '`ssh_port` must be 1–65535'
|
||
/api/remote-sources/{id}:
|
||
patch:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Update a remote data source (partial)
|
||
description: Partially updates a remote source. Only the keys present in the body are changed; omitted keys are left as-is. The same validation as create applies to any field that is present. Returns the updated source, or 404 when the id is unknown.
|
||
operationId: updateRemoteSource
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Remote source id.
|
||
example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceUpdateRequest'
|
||
examples:
|
||
disable:
|
||
summary: Disable a source without touching anything else
|
||
value:
|
||
enabled: false
|
||
rename:
|
||
summary: Rename and change port
|
||
value:
|
||
label: Studio Mac
|
||
ssh_port: 2222
|
||
responses:
|
||
'200':
|
||
description: Updated remote source
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceResponse'
|
||
example:
|
||
source:
|
||
id: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
label: Studio Mac
|
||
host: son@studio.local
|
||
ssh_port: 2222
|
||
identity_file: ~/.ssh/id_ed25519
|
||
remote_home: ~/.claude
|
||
enabled: false
|
||
status: ok
|
||
last_error: null
|
||
last_sync_at: '2026-07-22T18:41:55.117Z'
|
||
last_sync_counts:
|
||
imported: 9
|
||
skipped: 41
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 50
|
||
sessions_tagged: 50
|
||
created_at: '2026-07-20T09:15:00.000Z'
|
||
updated_at: '2026-07-22T19:02:10.400Z'
|
||
'400':
|
||
description: 'Validation error (codes: INVALID_LABEL, INVALID_HOST, INVALID_PORT, INVALID_IDENTITY_FILE, INVALID_REMOTE_HOME).'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_PORT
|
||
message: '`ssh_port` must be 1–65535'
|
||
'404':
|
||
description: Remote source not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Remote source not found
|
||
delete:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Delete a remote data source
|
||
description: Deletes a remote source. By default its imported sessions are DETACHED — reassigned to the built-in `local` source — so history is preserved. Pass `?purge=true` to instead permanently DELETE that source's imported sessions along with the source. The response reports whether a purge occurred.
|
||
operationId: deleteRemoteSource
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Remote source id.
|
||
example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
- name: purge
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: boolean
|
||
default: false
|
||
description: When true, also delete this source's imported sessions. When false/omitted, those sessions are reattached to `local`.
|
||
example: true
|
||
responses:
|
||
'200':
|
||
description: Deleted (with the purge outcome)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- purged
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
example: true
|
||
purged:
|
||
type: boolean
|
||
description: True when the source's imported sessions were deleted (purge=true); false when they were detached to `local`.
|
||
example: false
|
||
examples:
|
||
detached:
|
||
summary: Default — sessions detached to local
|
||
value:
|
||
ok: true
|
||
purged: false
|
||
purged:
|
||
summary: purge=true — sessions deleted
|
||
value:
|
||
ok: true
|
||
purged: true
|
||
'404':
|
||
description: Remote source not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Remote source not found
|
||
/api/remote-sources/{id}/test:
|
||
post:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Probe SSH connectivity to a remote source
|
||
description: Runs an SSH connectivity probe against the source and reports the outcome synchronously. The `ok` flag carries the probe result and `message` is a human-readable summary; on success `remoteProjects` may list the remote project directories discovered under the remote Claude home. This does not import anything — use POST /{id}/sync to pull.
|
||
operationId: testRemoteSource
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Remote source id.
|
||
example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
responses:
|
||
'200':
|
||
description: Probe result (ok flag carries the connectivity outcome)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceTestResponse'
|
||
examples:
|
||
success:
|
||
summary: Reachable
|
||
value:
|
||
ok: true
|
||
message: Connected; found 24 project directories under ~/.claude/projects.
|
||
remoteProjects:
|
||
- '-Users-son-code-foo'
|
||
- '-Users-son-code-bar'
|
||
failure:
|
||
summary: Unreachable / auth failed
|
||
value:
|
||
ok: false
|
||
message: 'ssh: connect to host studio.local port 22: Connection refused'
|
||
'404':
|
||
description: Remote source not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Remote source not found
|
||
/api/remote-sources/{id}/sync:
|
||
post:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Pull Claude Code history from a remote source now
|
||
description: Triggers an immediate pull of Claude Code history from the remote source over SSH, importing new transcripts through the same idempotent, baseline-preserving pipeline used for local imports and tagging imported sessions with this source's id. The response reports the per-run counters. Sync progress/completion is also broadcast over the WebSocket as `remote_source.status` frames.
|
||
operationId: syncRemoteSource
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Remote source id.
|
||
example: 4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11
|
||
responses:
|
||
'200':
|
||
description: Sync result
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RemoteSourceSyncResponse'
|
||
example:
|
||
ok: true
|
||
imported: 9
|
||
skipped: 41
|
||
backfilled: 0
|
||
errors: 0
|
||
sessions_seen: 50
|
||
sessions_tagged: 50
|
||
'404':
|
||
description: Remote source not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: NOT_FOUND
|
||
message: Remote source not found
|
||
'500':
|
||
description: Sync failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: SYNC_FAILED
|
||
message: ssh exited with code 255
|
||
/api/remote-sources/sync-all:
|
||
post:
|
||
tags:
|
||
- Remote Sources
|
||
summary: Sync all enabled remote sources now
|
||
description: Pulls Claude Code history from every enabled remote source over SSH, sequentially (one connection at a time). Per-source failures are isolated — one unreachable source never aborts the others — and each outcome is returned in `results`. Progress/completion is also broadcast over the WebSocket as `remote_source.status` frames. Always returns 200.
|
||
operationId: syncAllRemoteSources
|
||
responses:
|
||
'200':
|
||
description: Per-source sync outcomes
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
synced:
|
||
type: integer
|
||
description: Number of enabled sources that were attempted.
|
||
results:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
ok:
|
||
type: boolean
|
||
error:
|
||
type: string
|
||
example:
|
||
ok: true
|
||
synced: 2
|
||
results:
|
||
- id: src_a
|
||
ok: true
|
||
/api/settings/codex-hooks:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Status of AgentHub's hooks in Codex
|
||
description: 'Per event (SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, Stop, Interrupt, SessionEnd): whether AgentHub''s group is in `$CODEX_HOME/hooks.json`, and whether Codex''s `config.toml` holds a trust entry for it. Codex runs a hook only once the user trusts it; AgentHub only reads that trust.'
|
||
operationId: getCodexHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Codex hooks stand
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- trusted
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`$CODEX_HOME/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`$CODEX_HOME/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
description: Codex's config.toml holds a trust entry for every one of AgentHub's hooks
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Install (or reinstall) AgentHub's hooks in Codex
|
||
description: Refuses a cross-origin request (403 EBADORIGIN). Writes the forwarder script and adds one hook of AgentHub's per event to `$CODEX_HOME/hooks.json`, keeping every other entry. The command is the script's stable path, so Codex's trust survives AgentHub updates; the server rewrites the script's content at every start. Codex asks the user to trust the new hooks the next time it starts.
|
||
operationId: installCodexHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Codex hooks stand
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- trusted
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`$CODEX_HOME/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`$CODEX_HOME/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
description: Codex's config.toml holds a trust entry for every one of AgentHub's hooks
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
'400':
|
||
description: EBADJSON (hooks.json is not a JSON object), EFOREIGN (the forwarder path holds a script that is not AgentHub's) or EUNSUPPORTED (Windows)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
delete:
|
||
tags:
|
||
- Settings
|
||
summary: Remove AgentHub's hooks from Codex
|
||
description: Refuses a cross-origin request (403 EBADORIGIN). Takes AgentHub's own hooks (matched by their exact command) out of `$CODEX_HOME/hooks.json` (deleting the file when nothing else is left, or emptying it through the link when it is a symlink) and deletes the forwarder script when it is AgentHub's. 400 EBADJSON when hooks.json is not a JSON object.
|
||
operationId: removeCodexHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Codex hooks stand
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- trusted
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`$CODEX_HOME/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`$CODEX_HOME/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
description: Codex's config.toml holds a trust entry for every one of AgentHub's hooks
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
trusted:
|
||
type: boolean
|
||
'400':
|
||
description: EBADJSON (hooks.json is not a JSON object), EFOREIGN (the forwarder path holds a script that is not AgentHub's) or EUNSUPPORTED (Windows)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/settings/antigravity-hooks:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Status of AgentHub's hook in Antigravity
|
||
description: 'Per event (PreInvocation, PostToolUse, Stop): whether AgentHub''s named hook `agenthub` in `~/.gemini/config/hooks.json` runs its forwarder. PreToolUse is never installed: agy treats its answer as a permission decision.'
|
||
operationId: getAntigravityHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Antigravity hook stands
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- enabled
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`~/.gemini/config/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`~/.gemini/config/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
enabled:
|
||
type: boolean
|
||
description: 'Installed and not switched off (`"enabled": false`) in the file'
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Install (or reinstall) AgentHub's hook in Antigravity
|
||
description: Refuses a cross-origin request (403 EBADORIGIN). Writes the forwarder script and sets the named hook `agenthub` in `~/.gemini/config/hooks.json`, keeping every other named hook. agy loads it at its next start, with no trust step.
|
||
operationId: installAntigravityHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Antigravity hook stands
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- enabled
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`~/.gemini/config/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`~/.gemini/config/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
enabled:
|
||
type: boolean
|
||
description: 'Installed and not switched off (`"enabled": false`) in the file'
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
'400':
|
||
description: EBADJSON (hooks.json is not a JSON object), EFOREIGN (the forwarder path holds a script that is not AgentHub's) or EUNSUPPORTED (Windows)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
delete:
|
||
tags:
|
||
- Settings
|
||
summary: Remove AgentHub's hook from Antigravity
|
||
description: Refuses a cross-origin request (403 EBADORIGIN). Deletes the named hook `agenthub` (and the file when nothing else is left, unless it is a symlink, which is emptied through the link) and the forwarder script when it is AgentHub's. 400 EBADJSON when hooks.json is not a JSON object.
|
||
operationId: removeAntigravityHooks
|
||
responses:
|
||
'200':
|
||
description: Where AgentHub's Antigravity hook stands
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- supported
|
||
- readable
|
||
- path
|
||
- script
|
||
- installed
|
||
- enabled
|
||
- events
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: False on Windows (the command is a POSIX script)
|
||
readable:
|
||
type: boolean
|
||
description: False when hooks.json is not a JSON object
|
||
error:
|
||
type: string
|
||
path:
|
||
type: string
|
||
description: '`~/.gemini/config/hooks.json`'
|
||
script:
|
||
type: string
|
||
description: '`~/.gemini/config/agenthub-hook.sh`, the forwarder'
|
||
installed:
|
||
type: boolean
|
||
enabled:
|
||
type: boolean
|
||
description: 'Installed and not switched off (`"enabled": false`) in the file'
|
||
events:
|
||
type: object
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
installed:
|
||
type: boolean
|
||
'400':
|
||
description: EBADJSON (hooks.json is not a JSON object), EFOREIGN (the forwarder path holds a script that is not AgentHub's) or EUNSUPPORTED (Windows)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
'500':
|
||
description: 'ESAVE: the file system refused the write'
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/settings/codex-home:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Get the active Codex home directory
|
||
description: Returns the Codex home directory (CODEX_HOME, else `<homedir>/.codex`) the dashboard reads Codex config and rollout journals from, writes AI Router layers into, and hands every codex lane pane through tmux `-e`.
|
||
operationId: getCodexHome
|
||
responses:
|
||
'200':
|
||
description: Current Codex home directory
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- codex_home
|
||
- env_owned
|
||
properties:
|
||
codex_home:
|
||
type: string
|
||
env_owned:
|
||
type: boolean
|
||
example:
|
||
codex_home: /Users/son/.codex
|
||
env_owned: false
|
||
put:
|
||
tags:
|
||
- Settings
|
||
summary: Update the Codex home directory
|
||
description: Same validation and persistence as PUT /api/settings/claude-home (403 EBADORIGIN from another site, settings file, EBADHOME for a path that is or contains the OS home, INVALID_PATH otherwise). On success it re-writes the AI Router layer (`<name>.config.toml`) of every codex lane on a router into the new home, reported as `routers`, because `codex -p <name>` loads it from there. A codex pane already running keeps its old home until restarted.
|
||
operationId: updateCodexHome
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- path
|
||
properties:
|
||
path:
|
||
type: string
|
||
example:
|
||
path: ~/.codex-work
|
||
responses:
|
||
'200':
|
||
description: Codex home updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
- ok
|
||
- codex_home
|
||
- env_owned
|
||
- routers
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
enum:
|
||
- true
|
||
codex_home:
|
||
type: string
|
||
env_owned:
|
||
type: boolean
|
||
routers:
|
||
type: object
|
||
required:
|
||
- updated
|
||
- failed
|
||
properties:
|
||
updated:
|
||
type: integer
|
||
failed:
|
||
type: array
|
||
items:
|
||
type: string
|
||
example:
|
||
ok: true
|
||
codex_home: /Users/son/.codex-work
|
||
env_owned: false
|
||
routers:
|
||
updated: 1
|
||
failed: []
|
||
'400':
|
||
description: Invalid path (INVALID_PATH) or too broad (EBADHOME).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: EBADHOME
|
||
message: 'Too broad for CODEX_HOME: / contains your home directory'
|
||
'500':
|
||
description: The settings file could not be written (ESAVE).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/settings/claude-home:
|
||
get:
|
||
tags:
|
||
- Settings
|
||
summary: Get the active Claude Code home directory
|
||
description: Returns the Claude Code home directory the dashboard uses to locate transcripts and settings. Resolves to the CLAUDE_HOME environment variable when set, otherwise `<homedir>/.claude`. Always returns 200.
|
||
operationId: getClaudeHome
|
||
responses:
|
||
'200':
|
||
description: Current Claude Code home directory
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SettingsClaudeHomeResponse'
|
||
example:
|
||
claude_home: /Users/son/.claude
|
||
put:
|
||
tags:
|
||
- Settings
|
||
summary: Update the Claude Code home directory
|
||
description: Changes the Claude Code home directory used for transcript/settings discovery. A leading `~/` in `path` is expanded to the OS home directory; the resolved value must be an absolute path that exists and is a directory. On success the new value is applied to process.env immediately (so subsequent reads use it) and persisted to the settings file (`$XDG_CONFIG_HOME/agenthub/settings.env`, default `~/.config/agenthub/`, or AGENTHUB_SETTINGS_FILE — never the repo `.env`, which a plugin update replaces). The data dir (database, runtime) is derived from CLAUDE_HOME, so the first change also pins the one in use as DASHBOARD_DATA_DIR unless the environment already sets it. Refuses a cross-origin request (403 EBADORIGIN). Returns 400 EBADHOME when the path is, or contains (symlinks resolved), the OS user's home directory — `/`, `/home`, `~` — because everything under CLAUDE_HOME becomes readable through the config panel; 400 INVALID_PATH when `path` is missing/not a string, or the resolved path is not absolute, does not exist, or is not a directory.
|
||
operationId: updateClaudeHome
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SettingsClaudeHomeUpdateRequest'
|
||
example:
|
||
path: ~/.codefuse/engine/cc
|
||
responses:
|
||
'200':
|
||
description: Claude Code home updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SettingsClaudeHomeUpdateResponse'
|
||
example:
|
||
ok: true
|
||
claude_home: /Users/son/.codefuse/engine/cc
|
||
'400':
|
||
description: Invalid path — `path` missing or not a string, or the resolved path is not absolute / does not exist / is not a directory (code INVALID_PATH), or it is or contains the OS home directory (code EBADHOME).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: INVALID_PATH
|
||
message: 'Directory does not exist: /Users/son/.codefuse/engine/cc'
|
||
'500':
|
||
description: The settings file could not be written (ESAVE).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
/api/workflows/runs:
|
||
get:
|
||
tags:
|
||
- Workflows
|
||
summary: List Workflow-tool runs
|
||
description: Returns a paginated list of Workflow-tool runs (fleets of sub-agents spawned by the Claude Code 'Workflow' tool / self-paced /loop), newest first. Filter by `status` (the literal `all` is treated as no filter) or by `session_id`; `session_id` takes precedence over `status` when both are supplied. `counts` is always the whole-table breakdown by status. JSON-blob columns (`phases`, `progress`) are parsed into arrays in each run.
|
||
operationId: listWorkflowRuns
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 1000
|
||
default: 50
|
||
description: Page size, clamped to 1–1000 (default 50).
|
||
- name: offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
default: 0
|
||
description: Pagination offset (clamped to >= 0).
|
||
- name: status
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Filter by run status (open string, e.g. running | completed | error | failed). The literal value `all` is treated as no filter.
|
||
- name: session_id
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: Filter to runs launched by this session. Takes precedence over `status` when both are provided.
|
||
responses:
|
||
'200':
|
||
description: Paginated list of workflow runs with status counts
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/WorkflowRunsListResponse'
|
||
example:
|
||
runs:
|
||
- run_id: wf_a1b2c3d4
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
task_id: ISSUE-167
|
||
name: Refactor pricing engine
|
||
status: completed
|
||
default_model: claude-opus-4-8
|
||
started_at: '2026-06-25T18:04:11.122Z'
|
||
ended_at: '2026-06-25T18:09:47.530Z'
|
||
duration_ms: 336408
|
||
agent_count: 6
|
||
total_tokens: 1284750
|
||
total_tool_calls: 412
|
||
phases:
|
||
- name: plan
|
||
status: completed
|
||
progress:
|
||
- step: 1
|
||
label: scaffold
|
||
done: true
|
||
script_path: null
|
||
journal_path: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json
|
||
source: journal
|
||
created_at: '2026-06-25T18:09:48.001Z'
|
||
updated_at: '2026-06-25T18:09:48.001Z'
|
||
total: 42
|
||
counts:
|
||
completed: 30
|
||
error: 5
|
||
running: 7
|
||
limit: 50
|
||
offset: 0
|
||
'500':
|
||
description: Failed to list workflow runs (code WORKFLOW_LIST_FAILED).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: WORKFLOW_LIST_FAILED
|
||
message: 'no such table: workflows'
|
||
/api/workflows/runs/{runId}:
|
||
get:
|
||
tags:
|
||
- Workflows
|
||
summary: Get a Workflow-tool run with its agents and events
|
||
description: Returns one Workflow-tool run (by `run_id`) together with its linked inner agents and the events attributed to those agents (chronological, capped at 5000 rows). The run's JSON-blob columns (`phases`, `progress`) are parsed into arrays. Returns 404 WORKFLOW_NOT_FOUND when no run matches the id.
|
||
operationId: getWorkflowRun
|
||
parameters:
|
||
- name: runId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: The workflow run id (workflows.run_id).
|
||
responses:
|
||
'200':
|
||
description: Workflow run with inner agents and their events
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/WorkflowRunDetailResponse'
|
||
example:
|
||
workflow:
|
||
run_id: wf_a1b2c3d4
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
task_id: ISSUE-167
|
||
name: Refactor pricing engine
|
||
status: completed
|
||
default_model: claude-opus-4-8
|
||
started_at: '2026-06-25T18:04:11.122Z'
|
||
ended_at: '2026-06-25T18:09:47.530Z'
|
||
duration_ms: 336408
|
||
agent_count: 6
|
||
total_tokens: 1284750
|
||
total_tool_calls: 412
|
||
phases:
|
||
- name: plan
|
||
status: completed
|
||
progress:
|
||
- step: 1
|
||
label: scaffold
|
||
done: true
|
||
script_path: null
|
||
journal_path: /Users/son/.claude/projects/-Users-son-code/5f3c0e2a/workflows/wf_a1b2c3d4.json
|
||
source: journal
|
||
created_at: '2026-06-25T18:09:48.001Z'
|
||
updated_at: '2026-06-25T18:09:48.001Z'
|
||
agents:
|
||
- id: agent-7f1c
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
name: implementer
|
||
type: subagent
|
||
subagent_type: general-purpose
|
||
status: completed
|
||
task: Implement pricing changes
|
||
current_tool: null
|
||
started_at: '2026-06-25T18:04:30.000Z'
|
||
ended_at: '2026-06-25T18:08:12.000Z'
|
||
parent_agent_id: null
|
||
metadata: null
|
||
updated_at: '2026-06-25T18:08:12.000Z'
|
||
awaiting_input_since: null
|
||
awaiting_reason: null
|
||
events:
|
||
- id: 90211
|
||
session_id: 5f3c0e2a-1b9d-4c77-8a21-9e0f7b6d4c11
|
||
agent_id: agent-7f1c
|
||
event_type: PostToolUse
|
||
tool_name: Edit
|
||
summary: Edited server/routes/pricing.js
|
||
data: null
|
||
created_at: '2026-06-25T18:05:02.144Z'
|
||
'404':
|
||
description: No workflow run matches the id (code WORKFLOW_NOT_FOUND).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: WORKFLOW_NOT_FOUND
|
||
message: Workflow run not found
|
||
'500':
|
||
description: Failed to load workflow run detail (code WORKFLOW_DETAIL_FAILED).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorResponse'
|
||
example:
|
||
error:
|
||
code: WORKFLOW_DETAIL_FAILED
|
||
message: database is locked
|
||
/api/settings/import:
|
||
post:
|
||
tags:
|
||
- Settings
|
||
summary: Restore (import) a previously exported data bundle
|
||
description: 'Restores a bundle produced by GET /api/settings/export, uploaded as `multipart/form-data` with a single `file` field (the dashboard''s Restore button and the `agenthub import-data` CLI both upload it; the server never reads a path named by a request). Same-origin only. At most 512 MB (`413 FILE_TOO_LARGE`); a file that is not JSON or not an export answers `400 INVALID_FORMAT` without echoing its text, no file `400 NO_FILE`. The restore is idempotent and NON-DESTRUCTIVE: it is session-atomic, so a session already present (matched by its UUID) is skipped whole together with its agents/events/token_usage/workflows, and independent config rows (dashboard_runs, alert_rules, model_pricing) are inserted only when absent. Restored event payloads are trimmed like every event insert (`trim_event_data`). Nothing existing is overwritten — ideal for consolidating several machines into one dashboard. The response reports per-table counts.'
|
||
operationId: importData
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
multipart/form-data:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
file:
|
||
type: string
|
||
format: binary
|
||
description: The export .json file.
|
||
responses:
|
||
'200':
|
||
description: Per-table restore counts
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ImportResponse'
|
||
example:
|
||
ok: true
|
||
source: agent-monitor-export-2026-06-26.json
|
||
format: agenthub-export
|
||
sessions_imported: 42
|
||
sessions_skipped: 3
|
||
agents: 96
|
||
events: 5120
|
||
token_usage: 42
|
||
workflows: 4
|
||
dashboard_runs: 11
|
||
alert_rules: 2
|
||
model_pricing: 0
|
||
errors: 0
|
||
'400':
|
||
description: Missing file (NO_FILE), invalid JSON or unrecognized export format (INVALID_FORMAT)
|
||
'403':
|
||
description: Cross-origin request (EBADORIGIN)
|
||
'413':
|
||
description: The file is larger than 512 MB (FILE_TOO_LARGE)
|
||
x-issues-url: https://git.smartgift.io.vn/Smartgift-AI/AgentHub/issues
|