chore: maintenance — dependency majors, rename step 4, member Claude config, OpenAPI coverage
Dependencies
- Security: body-parser and multer DoS advisories fixed (npm audit: 0).
- Server: Express 5 (query parser kept "extended", req.body defaults to {},
SPA catch-all /{*splat}, send-file helper restores Express 4's dot-folder
rule for sendFile/download, alerts ack route without inline regex),
better-sqlite3 13, js-yaml 5 (server/lib/yaml.js keeps js-yaml 4's default
schema), uuid dropped for crypto.randomUUID, node-pty beta.18, multer 2.5.
- Client: React 19, xterm 6 (WebGL renderer replaces the removed canvas one,
DOM fallback on context loss; Alt+arrow word moves re-mapped), lucide-react 1,
TypeScript 7, i18next/react-i18next/codemirror minors.
Workspace rename step 4
- `agenthub lanes` and `--lane` exit 1 with "renamed: use …"; client api.ts,
the CLI and the MCP server call /api/workspaces; /api/lanes stays mounted.
Server mode (S3): members manage their own Claude Code config
- server/lib/cc-target.js resolves whose config a /cc-config request touches
and gates the router by allowlist; /profiles* and /plugins/update-all stay
admin-only. Member writes go through write-as, CLI calls through sudo as the
member (never -y), reads through openSafe; cc_config_changed reaches only
its owner. Not verified live on an S3 install yet.
Other
- Chat: an HTML file is an artifact only when written whole and not a project
page (index.html, or under src/, public/, templates/, …).
- OpenAPI covers every /api route; openapi-coverage.test.js guards it.
- read-pool test keeps its event loop alive (its worker is unref'd).
- Docs: plan statuses, checklists, CLI.md, LANES.md, SERVER-MODE.md, API.md.
This commit is contained in:
@@ -44,6 +44,12 @@
|
||||
* route can map it to HTTP 422. Only a bad argument or a missing binary
|
||||
* throws — those are programming/environment errors, not CLI outcomes.
|
||||
*
|
||||
* S3, a member's own config (`acct`): the CLI runs AS the member through
|
||||
* sudo (cc-plugin-cli's `runClaude`), `user` scope only (their CLI runs in
|
||||
* their home, no project), and with no `-e`/`--header` at all — those carry
|
||||
* keys and tokens, and sudo logs its whole argv. Such a server is added from
|
||||
* the member's own terminal instead.
|
||||
*
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -174,15 +180,34 @@ function headerFlags(headers) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* S3, a member's own config: `user` scope only, and no env values or headers
|
||||
* — they would cross sudo's argv, which sudo logs.
|
||||
*/
|
||||
function assertMemberMcp({ scope, headers, env, acct }) {
|
||||
if (!acct) return;
|
||||
if (scope && scope !== "user") {
|
||||
throw makeError("EBADARG", "only scope user can be changed for your own config");
|
||||
}
|
||||
const has = (o) => o && typeof o === "object" && Object.keys(o).length > 0;
|
||||
if (has(headers) || has(env)) {
|
||||
throw makeError(
|
||||
"EBADARG",
|
||||
"env values and headers would be logged by sudo: add this server with `claude mcp add` in your own terminal"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `claude mcp add` has two mutually exclusive forms, matching the real CLI
|
||||
* (not a `--url` flag — see the file header): an http/sse server (`name`,
|
||||
* positional `url`, optional `--header`) or a stdio server (`name`, optional
|
||||
* `-e`, `-- command args…`).
|
||||
*/
|
||||
async function mcpAdd({ name, scope, transport, command, args = [], url, headers, env }) {
|
||||
async function mcpAdd({ name, scope, transport, command, args = [], url, headers, env, acct }) {
|
||||
const n = assertName(name);
|
||||
const sc = assertMcpScope(scope, { required: true });
|
||||
assertMemberMcp({ scope: sc, headers, env, acct });
|
||||
if (url && command) {
|
||||
throw makeError("EBADARG", "mcpAdd accepts either url or command, not both");
|
||||
}
|
||||
@@ -201,8 +226,9 @@ async function mcpAdd({ name, scope, transport, command, args = [], url, headers
|
||||
}
|
||||
const t = transport === "sse" ? "sse" : "http";
|
||||
const hFlags = headerFlags(headers);
|
||||
return serialize(() =>
|
||||
runClaude(["mcp", "add", "--scope", sc, "--transport", t, n, u, ...hFlags])
|
||||
return serialize(
|
||||
() => runClaude(["mcp", "add", "--scope", sc, "--transport", t, n, u, ...hFlags], { acct }),
|
||||
acct
|
||||
);
|
||||
}
|
||||
|
||||
@@ -216,16 +242,18 @@ async function mcpAdd({ name, scope, transport, command, args = [], url, headers
|
||||
// env/header values, applied because of position rather than KEY=VALUE.
|
||||
const cmdArgs = args.map((a, i) => assertEmbeddedValue(a, `args[${i}]`));
|
||||
const eFlags = envFlags(env);
|
||||
return serialize(() =>
|
||||
runClaude(["mcp", "add", "--scope", sc, n, ...eFlags, "--", cmd, ...cmdArgs])
|
||||
return serialize(
|
||||
() => runClaude(["mcp", "add", "--scope", sc, n, ...eFlags, "--", cmd, ...cmdArgs], { acct }),
|
||||
acct
|
||||
);
|
||||
}
|
||||
|
||||
async function mcpRemove({ name, scope }) {
|
||||
async function mcpRemove({ name, scope, acct }) {
|
||||
const n = assertName(name);
|
||||
const sc = assertMcpScope(scope);
|
||||
assertMemberMcp({ scope: sc, acct });
|
||||
const scopeFlags = sc ? ["--scope", sc] : [];
|
||||
return serialize(() => runClaude(["mcp", "remove", n, ...scopeFlags]));
|
||||
return serialize(() => runClaude(["mcp", "remove", n, ...scopeFlags], { acct }), acct);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user