11dcc04ac4
Four defects, all from one design mistake: a profile's display name and its filename were the same string. - Saving the logged-in account made a SECOND profile of an account another profile already held. Both then reported themselves active, so there was nothing to switch between. `POST /profiles` now refuses `EDUPLICATE` and names the profile that already holds the account. Re-saving into the account's own profile stays allowed - that is the token-refresh path. - Worse, an accidental duplicate could not be cleaned up: two profiles of one account are both active, and the delete guard refused any active profile, so neither was ever deletable. The guard now blocks only the LAST saved copy of the live account, which is the actual credential-loss condition it exists for. The trash button had to follow, or the server would allow what the UI hid. - A name could not contain spaces or diacritics, because it had to be filename-safe. A profile now carries `name` (internal identifier, still the filename, still what every route takes) and `label` (what the user reads and edits: 1-60 free-form characters, control characters rejected, stored trimmed, unique case-insensitively). A label never touches a path, which is what makes "Tài khoản chính" a legal name. - Naming happened up front in a "save current account as" field. That field is gone. The server derives the identifier, the save button lives on the live account's card and appears only while it is unsaved, and the label is edited by clicking it (Enter saves, Escape cancels). Renaming rewrites the label in place and deliberately does NOT move `name`, so it cannot half-fail and leave a profile unreachable. Profiles written before labels existed report `label` equal to their `name`, so nothing needs migrating. Adds `PATCH /api/cc-config/profiles`; `POST` now takes an optional `name`. Verified: server 1431/1431 (11 new), client 459/459, tsc clean. Also exercised end to end against a COPY of the real profile store on this machine: the duplicate deleted, the last copy still refused, a Vietnamese label accepted, and the stored access/refresh tokens and 0600 mode intact after a rename.
2433 lines
91 KiB
JavaScript
2433 lines
91 KiB
JavaScript
/**
|
|
* @file OpenAPI 3.0 fragment for the Claude Config Explorer router
|
|
* (`server/routes/cc-config.js`, mounted at `/api/cc-config`). Documents
|
|
* every read surface (skills, agents, commands, output styles, plugins, MCP
|
|
* servers, hooks, settings, memory, marketplaces, keybindings, statusline,
|
|
* hook scripts, single-file body) plus the low-risk mutation surface
|
|
* (PUT/DELETE /file), the backups listing, and the auth-profile surface
|
|
* (`/profiles*` — swap which Anthropic account the local CLI runs as).
|
|
*
|
|
* Exports `{ tags, schemas, paths }` for deep-merge by
|
|
* `server/openapi-extra.js`. All schema names are prefixed with `CcConfig`
|
|
* to avoid collisions with the base spec. The shared `{ error: { code,
|
|
* message } }` envelope reuses the base `ErrorResponse` schema (defined in
|
|
* server/openapi.js) — it is NOT redefined here.
|
|
*
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
// ── Tags ───────────────────────────────────────────────────────────────
|
|
|
|
const tags = [
|
|
{
|
|
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",
|
|
},
|
|
];
|
|
|
|
// ── Shared sub-schemas ─────────────────────────────────────────────────
|
|
|
|
const schemas = {
|
|
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",
|
|
},
|
|
},
|
|
|
|
// ── Skill ──────────────────────────────────────────────────────────
|
|
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...",
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Markdown surface item (agents / commands / output-styles) ───────
|
|
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...",
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Plugin contributions ────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Plugin ───────────────────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── MCP server ─────────────────────────────────────────────────────
|
|
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)",
|
|
},
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Hooks ────────────────────────────────────────────────────────────
|
|
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 },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Settings ───────────────────────────────────────────────────────
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Memory ───────────────────────────────────────────────────────────
|
|
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...",
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Marketplaces ───────────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Keybindings ────────────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Statusline ─────────────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Hook scripts ───────────────────────────────────────────────────
|
|
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" },
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Overview ─────────────────────────────────────────────────────────
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── List wrappers ────────────────────────────────────────────────────
|
|
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" } },
|
|
},
|
|
},
|
|
|
|
// ── /file read ───────────────────────────────────────────────────────
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── PUT /file ──────────────────────────────────────────────────────
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── DELETE /file ───────────────────────────────────────────────────
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
|
|
// ── Backups ──────────────────────────────────────────────────────────
|
|
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"],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
// ── Reusable parameters (inlined per-operation to match base style) ────
|
|
|
|
const scopeParam = {
|
|
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",
|
|
};
|
|
|
|
const cwdParam = {
|
|
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",
|
|
};
|
|
|
|
// ── Paths ───────────────────────────────────────────────────────────────
|
|
|
|
const paths = {
|
|
"/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: [cwdParam],
|
|
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: [scopeParam, cwdParam],
|
|
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: [scopeParam, cwdParam],
|
|
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: [scopeParam, cwdParam],
|
|
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: [scopeParam, cwdParam],
|
|
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/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: [cwdParam],
|
|
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/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: [cwdParam],
|
|
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: [cwdParam],
|
|
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: [cwdParam],
|
|
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",
|
|
},
|
|
cwdParam,
|
|
],
|
|
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: [cwdParam],
|
|
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: [cwdParam],
|
|
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",
|
|
},
|
|
cwdParam,
|
|
],
|
|
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.\n\nOne 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`.\n\n`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.\n\nA 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`, 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).\n\nThe 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" } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
module.exports = { tags, schemas, paths };
|