readMcpServers only ever parses ~/.claude.json (top-level = user scope,
projects[<root>] = the panel's "projectScoped" bucket, which the CLI itself
calls `local`). The previous user/project scope choice was the exact
inverse: `--scope project` writes .mcp.json, which readMcpServers never
reads (a successful add would vanish), and every "projectScoped" row was
actually `local`, so Remove sent a scope the CLI could never match.
- cc-mcp-cli.js: MCP_SCOPES -> ["user", "local"]; drop the local
mutationQueue/serialize in favor of cc-plugin-cli.js's exported one, so a
plugin install and an MCP add can't spawn `claude` concurrently; drop the
String(v) coercion in env/header flags (a wrong-typed value is now a 400,
not a written "[object Object]"); assertEmbeddedValue allows empty strings
(a legitimate empty stdio arg was previously a 400).
- cc-plugin-cli.js: export `serialize` for reuse.
- CcConfig.tsx: CcMcpScope narrowed to user/local; the projectScoped bucket's
McpCard now sends scope="local"; rows sourced from
~/.claude/settings.json (no CLI scope addresses them) no longer render a
Remove button. Cleaned up i18n keys my own earlier change made dead
(explain.mcp.addCmd/remove/removeButton) and gave the scope <select> its
own short option labels instead of reusing full-sentence section headings.
Mutation goes through `claude mcp add|remove`, never a direct edit of
~/.claude.json — the running CLI rewrites that file concurrently, so a
dashboard write would race it. Reads stay on readMcpServers.
The plan specified a --url flag; the real CLI (v2.1.266) has none —
commandOrUrl is positional, and --header must trail it or the variadic
flag swallows the url. Built against the verified shape.
The Claude writer's containment was lexical only: a symlink at
~/.claude/<type>/<name>, or at any ancestor of it, is a string inside the
root while the write lands wherever it points. The backup destination had
the same exposure, and delete copies through the link before unlinking it.
PUT/DELETE /file also honoured ?cwd=, so a project-scope write could be
aimed at any directory on disk. PATCH /settings already refused that, with
the reasoning in a comment; the two other write routes never adopted it.
The guard moves into config-write.js so both writers run one copy.
The Claude Config page was read-only for these four surfaces. It can now:
- register a marketplace from a pasted GitHub `owner/repo`, http(s) URL or
absolute path, and install / enable / disable / update / uninstall plugins,
all delegated to the `claude plugin …` CLI so its registry stays consistent;
- edit hooks and settings.json keys in the user, project and project-local
layers.
Server:
- `server/lib/cc-plugin-cli.js` wraps the CLI with `execFile` and an explicit
argv array — never a shell string. Every argument is validated before the
spawn (shape allowlist per kind, no control characters, no leading `-`, no
`..`), `CLAUDE_CONFIG_DIR` is pinned to the dashboard's CLAUDE_HOME, stdin is
closed so a prompt errors instead of hanging, and mutating calls are
serialized in-process.
- `cc-mutate.patchSettings` merges individual dotted keys into one settings
layer. It re-reads the file unredacted, so keys the UI never displayed —
including secret-valued ones — survive byte-for-byte. A payload carrying the
`<redacted>` placeholder is refused (EREDACTED), as are `__proto__` /
`prototype` / `constructor` segments; a settings file that does not parse is
reported (EBADJSON) rather than clobbered. Backup first, then atomic write.
- Six additive routes on /api/cc-config; a non-zero CLI exit is a 422 carrying
the CLI's own message, a missing binary a 503.
Client: install/add bars, per-card plugin and marketplace actions behind a
two-click confirm, and edit modes for hooks and settings that send only the
keys that changed. New i18n keys in en + vi; the explainer banners that said
these surfaces were read-only now describe what the writes actually do.
Tests: 61 new server tests (CLI argv construction and validation, the settings
key-merge, and the six routes against a fake `claude` on PATH).
Internal SmartGift build of a Claude Code monitoring dashboard.
Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.
Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.
Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.