refactor!: rebrand CCAM to AgentHub

Clean break, no compatibility alias. Renames the product name, the CLI
binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` ->
`AGENTHUB_`), the Prometheus metric namespace (`ccam_*` ->
`agenthub_*`), the tmux session prefix (`ccam-lane-<id>` ->
`agenthub-lane-<id>`), the per-repo runtime profile directory
(`.ccam/profile` -> `.agenthub/profile`), the lanes state root
(`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled
plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash
commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` ->
`/agenthub:*`).

Because there is no alias, an existing install has to be updated by
hand: rename its `.ccam/profile` directory and any `CCAM_*` variables,
and reinstall the plugin under its new name.

Two other name families are deliberately left alone here, since they are
not just strings: the `Claude-Code-Monitor` repository/marketplace name,
and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard`
runtime directory, which holds the live database and needs a migration
rather than a rename.

Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across
tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
This commit is contained in:
2026-09-07 12:09:15 +07:00
parent ae9b3dbcd5
commit 17d56d0e01
345 changed files with 2705 additions and 2727 deletions
+144 -138
View File
@@ -1,13 +1,13 @@
#!/usr/bin/env node
/**
* @file ccam — the Claude Code Agent Monitor command-line interface.
* @file agenthub — the AgentHub command-line interface.
*
* A dependency-free umbrella CLI that brings the full dashboard feature
* surface to the terminal. After the normal project setup (`npm run setup`,
* which links this binary via `npm link`), every command is available
* directly from any shell as `ccam <command>`, or interactively at the
* `ccam ›` prompt via `ccam repl`.
* directly from any shell as `agenthub <command>`, or interactively at the
* `agenthub ›` prompt via `agenthub repl`.
*
* Server status · start · service (install/uninstall/status) · repl
* Monitoring health · stats · kanban · tail
@@ -20,8 +20,8 @@
* Administration doctor · info · export · cleanup · reinstall-hooks ·
* update-check · clear-data --yes · open · version
*
* The REPL (`ccam repl`, aliases `shell` / `i`) runs each entered line as a
* short-lived child `ccam` process, so its behavior is identical to the
* The REPL (`agenthub repl`, aliases `shell` / `i`) runs each entered line as a
* short-lived child `agenthub` process, so its behavior is identical to the
* one-shot CLI and no single command — a non-zero exit, a blocking `tail`, an
* offline refusal — can take the shell down with it. It adds tab-completion,
* persisted arrow-key history, and a live server-status prompt.
@@ -43,7 +43,7 @@ const fs = require("node:fs");
const { spawn } = require("node:child_process");
// Resolve the repo root relative to this script's REAL location so the CLI
// works both from a checkout (./bin/ccam.js) and through the global symlink
// works both from a checkout (./bin/agenthub.js) and through the global symlink
// `npm link` creates (which points back into the checkout).
const REPO_ROOT = path.resolve(path.dirname(fs.realpathSync(__filename)), "..");
@@ -51,14 +51,14 @@ const REPO_ROOT = path.resolve(path.dirname(fs.realpathSync(__filename)), "..");
// Styling follows the informal CLI conventions: colors are enabled on a TTY,
// disabled when output is piped or redirected, force-disabled by the NO_COLOR
// env var (https://no-color.org) or a --no-color flag anywhere on the command
// line, and force-enabled by FORCE_COLOR / CCAM_COLOR=1 (useful for `watch`
// line, and force-enabled by FORCE_COLOR / AGENTHUB_COLOR=1 (useful for `watch`
// or CI logs that render ANSI). Every helper degrades to plain text, so piped
// output stays grep/script-friendly byte-for-byte.
const useColor = (() => {
if (process.env.NO_COLOR || process.argv.includes("--no-color")) return false;
const force = process.env.FORCE_COLOR;
if (force != null && force !== "" && force !== "0" && force !== "false") return true;
if (process.env.CCAM_COLOR === "1") return true;
if (process.env.AGENTHUB_COLOR === "1") return true;
return Boolean(process.stdout.isTTY);
})();
@@ -197,15 +197,15 @@ const patch = (p, b, options) => api("PATCH", p, b, options);
/**
* Print the standard "server is not running" indicator and exit 1. Every
* command that needs the API funnels through this, so the guidance is
* identical everywhere: the ccam CLI talks to the local dashboard server,
* and `ccam start` (or npm run dev / npm start) brings one up.
* identical everywhere: the agenthub CLI talks to the local dashboard server,
* and `agenthub start` (or npm run dev / npm start) brings one up.
*/
function serverDownExit(reason) {
console.error(`${c.red("○ Dashboard server is NOT running")} ${c.dim(`(tried ${baseUrl()})`)}`);
if (reason) console.error(c.dim(` No offline fallback for this command: ${reason}.`));
console.error(c.dim(" This command needs the server. Start it with one of:"));
console.error(
` ${c.bold("ccam start")} ${c.dim("# production server in the background")}`
` ${c.bold("agenthub start")} ${c.dim("# production server in the background")}`
);
console.error(
` ${c.bold("npm run dev")} ${c.dim("# dev mode (hot reload), foreground")}`
@@ -388,7 +388,7 @@ function offlineBanner() {
);
console.log(
c.dim(" Data is as of the last capture — live capture and full features need the server: ") +
c.bold("ccam start")
c.bold("agenthub start")
);
console.log();
}
@@ -733,7 +733,7 @@ async function cmdSessions(flags) {
async function cmdSession(positional) {
const id = positional[0];
if (!id) {
console.error(c.red("✖ Usage: ccam session <session-id>"));
console.error(c.red("✖ Usage: agenthub session <session-id>"));
process.exit(1);
}
const d = await get(`/api/sessions/${encodeURIComponent(id)}`);
@@ -941,7 +941,7 @@ async function cmdCost(flags) {
(u.cache_write_tokens || 0);
console.log(` ${c.bold(u.model)} ${c.dim(`${fmtTokens(tokens)} tokens`)}`);
}
console.log(c.dim(" Add a rule with: ccam pricing set <pattern> --input N --output N"));
console.log(c.dim(" Add a rule with: agenthub pricing set <pattern> --input N --output N"));
}
}
@@ -1012,7 +1012,7 @@ async function cmdWebhooks(flags, positional) {
// ── Remote data sources ─────────────────────────────────────────────────────
/**
* `ccam remote-sources [list|add|test|sync|rm]` — manage the SSH machines this
* `agenthub remote-sources [list|add|test|sync|rm]` — manage the SSH machines this
* dashboard pulls Claude Code history from (server/routes/remote-sources.js).
* No secrets are handled here; auth defers to the host's SSH stack.
*/
@@ -1024,7 +1024,7 @@ async function cmdRemoteSources(flags, positional) {
console.error(c.red("✖ add requires --label and --host"));
console.error(
c.dim(
" e.g. ccam remote-sources add --label 'Dev box' --host son@dev --port 22 --identity ~/.ssh/id_ed25519"
" e.g. agenthub remote-sources add --label 'Dev box' --host son@dev --port 22 --identity ~/.ssh/id_ed25519"
)
);
process.exit(1);
@@ -1101,7 +1101,9 @@ async function cmdRemoteSources(flags, positional) {
)
);
} else {
console.log(c.dim(" No remote sources configured. Add one: ccam remote-sources add --help"));
console.log(
c.dim(" No remote sources configured. Add one: agenthub remote-sources add --help")
);
}
}
@@ -1233,7 +1235,7 @@ async function cmdImport(flags, positional) {
);
return;
}
console.error(c.red("✖ Usage: ccam import rescan | ccam import path <dir>"));
console.error(c.red("✖ Usage: agenthub import rescan | agenthub import path <dir>"));
process.exit(1);
}
@@ -1242,7 +1244,7 @@ async function cmdImport(flags, positional) {
async function cmdDoctor() {
let failed = false;
await get("/api/health");
heading("ccam doctor", baseUrl());
heading("agenthub doctor", baseUrl());
console.log(`${c.green("✔")} API reachable ${baseUrl()}`);
const info = await get("/api/settings/info");
const hooks = info.hooks || {};
@@ -1302,7 +1304,7 @@ async function cmdInfo() {
}
async function cmdExport(positional) {
const file = positional[0] || `ccam-export-${new Date().toISOString().slice(0, 10)}.json`;
const file = positional[0] || `agenthub-export-${new Date().toISOString().slice(0, 10)}.json`;
const data = await get("/api/settings/export");
fs.writeFileSync(file, JSON.stringify(data, null, 2));
console.log(
@@ -1310,13 +1312,13 @@ async function cmdExport(positional) {
);
}
// Restore a bundle produced by `ccam export` (or the dashboard's Export data).
// Restore a bundle produced by `agenthub export` (or the dashboard's Export data).
// The local server reads the file from disk, so we pass an absolute path rather
// than uploading — idempotent and non-destructive (existing sessions skipped).
async function cmdImportData(positional) {
const target = positional[0];
if (!target) {
console.error(c.red("✖ Usage: ccam import-data <export.json>"));
console.error(c.red("✖ Usage: agenthub import-data <export.json>"));
process.exit(1);
}
const abs = path.resolve(process.cwd(), target);
@@ -1338,7 +1340,7 @@ async function cmdCleanup(flags) {
if (flags.hours) body.abandon_hours = Number(flags.hours);
if (flags.days) body.purge_days = Number(flags.days);
if (!flags.hours && !flags.days) {
console.error(c.red("✖ Usage: ccam cleanup --hours <N> and/or --days <M>"));
console.error(c.red("✖ Usage: agenthub cleanup --hours <N> and/or --days <M>"));
console.error(c.dim(" --hours N abandon active sessions idle for N hours"));
console.error(c.dim(" --days M purge completed sessions older than M days"));
process.exit(1);
@@ -1350,7 +1352,7 @@ async function cmdCleanup(flags) {
async function cmdClearData(flags) {
if (flags.yes !== true) {
console.error(c.red("✖ clear-data deletes ALL sessions, agents, events, and token usage."));
console.error(c.dim(" Re-run with --yes to confirm: ccam clear-data --yes"));
console.error(c.dim(" Re-run with --yes to confirm: agenthub clear-data --yes"));
process.exit(1);
}
await post("/api/settings/clear-data");
@@ -1366,7 +1368,7 @@ async function cmdReinstallHooks() {
* Start the dashboard server in the background (production mode, serving the
* built client) and wait until /api/health answers. No-ops with a pointer to
* the live URL when a server is already up. The child is fully detached with
* its output appended to data/ccam-server.log, so closing this terminal does
* its output appended to data/agenthub-server.log, so closing this terminal does
* not stop the dashboard; stop it later with `kill <pid>` (the PID is
* printed and registered in ~/.claude/.agent-dashboard.json).
*/
@@ -1378,13 +1380,13 @@ async function cmdStart(flags) {
const clientDist = path.join(REPO_ROOT, "client", "dist", "index.html");
if (!fs.existsSync(clientDist)) {
console.error(c.red("✖ client/dist is missing — the production server needs a built client."));
console.error(c.dim(" Build it once with: npm run build (then re-run: ccam start)"));
console.error(c.dim(" Build it once with: npm run build (then re-run: agenthub start)"));
console.error(c.dim(" Or run the dev servers instead: npm run dev"));
process.exit(1);
}
const logDir = path.join(REPO_ROOT, "data");
fs.mkdirSync(logDir, { recursive: true });
const logFile = path.join(logDir, "ccam-server.log");
const logFile = path.join(logDir, "agenthub-server.log");
const out = fs.openSync(logFile, "a");
const env = { ...process.env, NODE_ENV: "production" };
if (flags.port) env.DASHBOARD_PORT = String(flags.port);
@@ -1436,18 +1438,18 @@ async function cmdStart(flags) {
/**
* Installs/removes a systemd `--user` service so the dashboard survives
* reboots without anything (Claude Code included) needing to start it first.
* Linux/systemd only; other platforms are told to keep using `ccam start`.
* Linux/systemd only; other platforms are told to keep using `agenthub start`.
*/
async function cmdService(sub, flags) {
const svc = require(path.join(REPO_ROOT, "scripts", "systemd-service.js"));
if (!svc.systemctlAvailable()) {
console.error(c.red("✖ systemd --user is not available on this machine."));
console.error(
c.dim(" Auto-start on boot needs Linux + systemd. Use `ccam start` manually instead.")
c.dim(" Auto-start on boot needs Linux + systemd. Use `agenthub start` manually instead.")
);
process.exit(1);
}
const name = "ccam";
const name = "agenthub";
if (sub === "install") {
const clientDist = path.join(REPO_ROOT, "client", "dist", "index.html");
if (!fs.existsSync(clientDist)) {
@@ -1461,16 +1463,18 @@ async function cmdService(sub, flags) {
if (flags.port) env.DASHBOARD_PORT = String(flags.port);
const result = svc.installUserService({
name,
description: "CCAM dashboard server",
description: "AgentHub dashboard server",
workingDir: REPO_ROOT,
execStart: `${process.execPath} ${path.join(REPO_ROOT, "server", "index.js")}`,
env,
});
if (result === "foreign") {
console.error(
c.red(`✖ ~/.config/systemd/user/${name}.service already exists and wasn't written by ccam.`)
c.red(
`✖ ~/.config/systemd/user/${name}.service already exists and wasn't written by agenthub.`
)
);
console.error(c.dim(" Remove or rename it, then re-run `ccam service install`."));
console.error(c.dim(" Remove or rename it, then re-run `agenthub service install`."));
process.exit(1);
}
if (result === "failed") {
@@ -1501,7 +1505,9 @@ async function cmdService(sub, flags) {
const result = svc.uninstallUserService(name);
if (result === "foreign") {
console.error(
c.red(`✖ ~/.config/systemd/user/${name}.service wasn't written by ccam — not touching it.`)
c.red(
`✖ ~/.config/systemd/user/${name}.service wasn't written by agenthub — not touching it.`
)
);
process.exit(1);
}
@@ -1514,7 +1520,7 @@ async function cmdService(sub, flags) {
spawn("systemctl", ["--user", "status", `${name}.service`, "--no-pager"], { stdio: "inherit" });
return;
}
console.error(c.red("✖ Usage: ccam service install|uninstall|status [--port N]"));
console.error(c.red("✖ Usage: agenthub service install|uninstall|status [--port N]"));
process.exit(1);
}
@@ -1529,7 +1535,7 @@ async function cmdStatus() {
console.log(
`${c.red("○")} Dashboard server is ${c.bold("NOT running")} ${c.dim(`(tried ${baseUrl()})`)}`
);
console.log(c.dim(" Start it with: ccam start (or npm run dev / npm start)"));
console.log(c.dim(" Start it with: agenthub start (or npm run dev / npm start)"));
process.exit(1);
}
}
@@ -1547,7 +1553,7 @@ function cmdOpen() {
}
/**
* `ccam lanes add` — adopt a working directory or provision a managed worktree.
* `agenthub lanes add` — adopt a working directory or provision a managed worktree.
*/
async function cmdLanesAdd(args) {
const flag = (name) => {
@@ -1563,7 +1569,7 @@ async function cmdLanesAdd(args) {
if (repo) {
if (cwd || pipeline) {
console.error(
"usage: ccam lanes add --repo <path> [--title <text>] [--base <branch>] [--slug <slug>]"
"usage: agenthub lanes add --repo <path> [--title <text>] [--base <branch>] [--slug <slug>]"
);
process.exitCode = 1;
return;
@@ -1596,7 +1602,7 @@ async function cmdLanesAdd(args) {
return;
}
if (!cwd || !title) {
console.error("usage: ccam lanes add --cwd <path> --title <text> [--pipeline <id>]");
console.error("usage: agenthub lanes add --cwd <path> --title <text> [--pipeline <id>]");
process.exitCode = 1;
return;
}
@@ -1607,7 +1613,7 @@ async function cmdLanesAdd(args) {
}
/**
* `ccam skills install` — copy this checkout's `skills/ship-feature-lane/`
* `agenthub skills install` — copy this checkout's `skills/ship-feature-lane/`
* into `~/.claude/skills/ship-feature-lane/`, so `/ship-feature-lane` is
* discoverable from a session running inside any lane's own working
* directory, not just inside this repo. Pure filesystem action; does not
@@ -1628,7 +1634,7 @@ function cmdSkillsInstall() {
}
/**
* `ccam lanes gc [--dry-run]` — reap orphaned Playwright MCP processes
* `agenthub lanes gc [--dry-run]` — reap orphaned Playwright MCP processes
* (owning session died) and cap oversized hook logs, across every lane on
* this machine. Machine-wide, not one lane's; pure local process/file
* action, no HTTP round-trip.
@@ -1658,15 +1664,15 @@ function cmdLanesGc(args) {
}
/**
* `ccam lanes profile init <repo>` — detect a Node.js project and scaffold
* `.ccam/profile/`. Pure filesystem action against the SOURCE repo; does not
* `agenthub lanes profile init <repo>` — detect a Node.js project and scaffold
* `.agenthub/profile/`. Pure filesystem action against the SOURCE repo; does not
* talk to the dashboard server at all.
*/
function cmdLanesProfileInit(args) {
const repo = args.find((arg) => !arg.startsWith("--"));
const force = args.includes("--force");
if (!repo) {
console.error("usage: ccam lanes profile init <repo> [--force]");
console.error("usage: agenthub lanes profile init <repo> [--force]");
process.exitCode = 1;
return;
}
@@ -1684,7 +1690,7 @@ function cmdLanesProfileInit(args) {
`✖ No detectable Node.js project at ${resolved} (looked for backend/package.json +\n` +
" frontend/package.json, or a root package.json).\n" +
" Auto-scaffolding currently supports Node.js repos in that layout only.\n" +
" Write .ccam/profile/ by hand — see docs/LANES.md."
" Write .agenthub/profile/ by hand — see docs/LANES.md."
);
process.exitCode = 1;
return;
@@ -1702,17 +1708,17 @@ function cmdLanesProfileInit(args) {
throw err;
}
console.log(`${c.green("✔")} Scaffolded .ccam/profile/ at ${resolved} (${facts.layout})`);
console.log(`${c.green("✔")} Scaffolded .agenthub/profile/ at ${resolved} (${facts.layout})`);
for (const file of result.written) console.log(` wrote ${file}`);
if (result.todos.length) {
console.log(`\n${c.yellow(`⚠ ${result.todos.length} item(s) need manual attention:`)}`);
for (const todo of result.todos) console.log(` ${todo}`);
}
console.log(`\nNext: ccam lanes profile check ${repo}`);
console.log(`\nNext: agenthub lanes profile check ${repo}`);
}
/**
* `ccam lanes profile check [<path>]` — validate a profile without needing a
* `agenthub lanes profile check [<path>]` — validate a profile without needing a
* lane to exist for it yet. Defaults to the current directory, NOT lane-id
* resolution (unlike every other `lanes` subcommand) — this is meant to run
* against a bare repo right after `profile init`.
@@ -1751,13 +1757,13 @@ function printLaneFacts(title, facts, fields) {
}
}
/** `ccam lanes reset|remove|purge` — destructive work only after a fact check. */
/** `agenthub lanes reset|remove|purge` — destructive work only after a fact check. */
async function cmdLanesLifecycle(action, args) {
const id = args.find((arg) => !arg.startsWith("--"));
const fields = LANE_PREFLIGHT_FIELDS[action];
if (!id) {
const extra = action === "reset" ? " [--keep-db]" : "";
console.error(`usage: ccam lanes ${action} <id> [--force]${extra} --yes`);
console.error(`usage: agenthub lanes ${action} <id> [--force]${extra} --yes`);
process.exitCode = 1;
return;
}
@@ -1789,7 +1795,7 @@ async function cmdLanesLifecycle(action, args) {
preflight.blocked.includes("adopted")
) {
console.error(
`Lane #${id} points at a directory you own. It cannot be reset; use \`ccam lanes remove ${id} --yes\` to drop only the dashboard's record of it.`
`Lane #${id} points at a directory you own. It cannot be reset; use \`agenthub lanes remove ${id} --yes\` to drop only the dashboard's record of it.`
);
process.exitCode = 1;
return;
@@ -1822,7 +1828,7 @@ async function cmdLanesLifecycle(action, args) {
}
/**
* `ccam lanes` — one row per lane: what it is, where it is in its pipeline, and
* `agenthub lanes` — one row per lane: what it is, where it is in its pipeline, and
* whether the driving session is still breathing.
*/
/**
@@ -1856,7 +1862,7 @@ function detectionLeadsDeclaration(l) {
async function cmdLanes() {
const { lanes, counts } = await get("/api/lanes");
if (!lanes.length) {
console.log("no lanes yet — create one with: ccam lanes add --cwd <path> --title <text>");
console.log("no lanes yet — create one with: agenthub lanes add --cwd <path> --title <text>");
return;
}
for (const l of lanes) {
@@ -1879,14 +1885,14 @@ async function cmdLanes() {
* marker (it only READS CODEX_HOME, and sets CODEX_SANDBOX* under a sandbox
* policy), so sniffing a CODEX_ prefix would be a guess. A codex session says
* so with `--tool codex`, or needs nothing at all inside a pane the dashboard
* started — that pane carries CCAM_LANE_ID.
* started — that pane carries AGENTHUB_LANE_ID.
*/
function toolFromEnv() {
return process.env.CLAUDECODE || process.env.CLAUDE_CODE_ENTRYPOINT ? "claude" : null;
}
/**
* Which lane a command is about, in order: an explicit id, the CCAM_LANE_ID of
* Which lane a command is about, in order: an explicit id, the AGENTHUB_LANE_ID of
* a dashboard-started pane, then the lane owning the working directory (longest
* path-boundary match, the same rule the server uses). A session running inside
* a lane never has to know its own id.
@@ -1899,7 +1905,7 @@ function toolFromEnv() {
*
* Only the FIRST positional counts as an id, and only when it is all digits.
* Scanning the whole argv for a number would swallow flag values —
* `ccam lanes logs web --tail 4096` would have addressed lane 4096.
* `agenthub lanes logs web --tail 4096` would have addressed lane 4096.
*
* @param {string[]} args - Arguments after the subcommand.
* @returns {Promise<{laneId: string|number, rest: string[]}|null>} null once an error is printed.
@@ -1918,12 +1924,12 @@ async function resolveLaneArg(args) {
const cwd = require("path").resolve(flagValue("cwd") || process.cwd());
const { lanes } = await get("/api/lanes");
// A pane's CCAM_LANE_ID beats cwd matching, but only after it is checked
// A pane's AGENTHUB_LANE_ID beats cwd matching, but only after it is checked
// against the live lane list AND the directory: a pane outlives the lane it
// was started for, ids are reused after a delete, and an agent that cd'd into
// another repo is no longer working in its pane's lane. Trusting the number
// blind would write this session's stage onto whatever lane inherited the id.
const fromPane = process.env.CCAM_LANE_ID;
const fromPane = process.env.AGENTHUB_LANE_ID;
if (fromPane && /^\d+$/.test(fromPane)) {
const paneLane = lanes.find((l) => String(l.id) === fromPane);
if (paneLane && (cwd === paneLane.cwd || cwd.startsWith(`${paneLane.cwd}/`))) {
@@ -1934,7 +1940,7 @@ async function resolveLaneArg(args) {
.filter((l) => cwd === l.cwd || cwd.startsWith(`${l.cwd}/`))
.sort((a, b) => b.cwd.length - a.cwd.length);
if (!owning.length) {
console.error(`no lane owns ${cwd} — create one with: ccam lanes add --cwd ${cwd}`);
console.error(`no lane owns ${cwd} — create one with: agenthub lanes add --cwd ${cwd}`);
process.exitCode = 1;
return null;
}
@@ -1968,12 +1974,12 @@ async function resolveLaneArg(args) {
/** One line per declared port: name, number, listening, and any base drift. */
function printRuntime(runtime) {
if (!runtime.available) {
console.log("no .ccam/profile for this lane — nothing to run.");
console.log("no .agenthub/profile for this lane — nothing to run.");
if (runtime.searched) for (const p of runtime.searched) console.log(` looked in ${p}`);
return;
}
if (!runtime.provisioned) {
console.log("profile found, runtime not provisioned yet — run: ccam lanes up");
console.log("profile found, runtime not provisioned yet — run: agenthub lanes up");
return;
}
console.log(`slot ${runtime.slot} · profile ${runtime.profileDir}`);
@@ -2003,7 +2009,7 @@ function printRuntime(runtime) {
}
/**
* `ccam lanes up|down|runtime|logs|hook` — a lane's own application stack, as
* `agenthub lanes up|down|runtime|logs|hook` — a lane's own application stack, as
* opposed to `start`/`stop`, which drive its Claude run. Two lifecycles, one
* lane id.
*
@@ -2072,7 +2078,7 @@ async function cmdLanesRuntime(sub, args) {
if (sub === "logs") {
const svc = laneArgs.find((arg) => !arg.startsWith("--"));
if (!svc) {
console.error("usage: ccam lanes logs [<id>] <service> [--tail bytes]");
console.error("usage: agenthub lanes logs [<id>] <service> [--tail bytes]");
process.exitCode = 1;
return;
}
@@ -2091,7 +2097,7 @@ async function cmdLanesRuntime(sub, args) {
if (sub === "hook") {
const name = laneArgs[0];
if (!name || name.startsWith("--")) {
console.error("usage: ccam lanes hook [<id>] <name> [args…]");
console.error("usage: agenthub lanes hook [<id>] <name> [args…]");
process.exitCode = 1;
return;
}
@@ -2106,7 +2112,7 @@ async function cmdLanesRuntime(sub, args) {
return;
}
console.log(
`lane #${laneId} running hook ${name} — follow it with: ccam lanes logs ${laneId} ${name}`
`lane #${laneId} running hook ${name} — follow it with: agenthub lanes logs ${laneId} ${name}`
);
}
@@ -2142,7 +2148,7 @@ async function cmdLanesRuntime(sub, args) {
console.error(` conflicted: ${result.conflictedFiles.join(", ")}`);
console.error(" resolve, then: git add <resolved files> && git commit --no-edit");
console.error(
` then: ccam lanes sync-base --continue ${branch ? branch + " " : ""}${laneId}`
` then: agenthub lanes sync-base --continue ${branch ? branch + " " : ""}${laneId}`
);
process.exitCode = 4;
return;
@@ -2206,7 +2212,7 @@ function fmtLockRow(lock) {
return `${lock.name.padEnd(20)} held by ${lock.holder.padEnd(10)} for ${mins}m`;
}
/** `ccam lock status [<name>]` — one lock, or every held lock. */
/** `agenthub lock status [<name>]` — one lock, or every held lock. */
async function cmdLockStatus(args) {
const name = args.find((arg) => !arg.startsWith("--"));
if (name) {
@@ -2224,7 +2230,7 @@ async function cmdLockStatus(args) {
}
/**
* `ccam lock acquire <name> [--holder X] [--timeout N]` — polls until the
* `agenthub lock acquire <name> [--holder X] [--timeout N]` — polls until the
* lock is free (or `--timeout` seconds elapse). Prints a status line every
* ~60s of continued waiting so a long wait never reads as a hung command —
* this is the CLI-side "heartbeat" the design calls for; it is terminal
@@ -2233,7 +2239,7 @@ async function cmdLockStatus(args) {
async function cmdLockAcquire(args) {
const name = args.find((arg) => !arg.startsWith("--"));
if (!name) {
console.error("usage: ccam lock acquire <name> [--holder X] [--timeout seconds]");
console.error("usage: agenthub lock acquire <name> [--holder X] [--timeout seconds]");
process.exitCode = 1;
return;
}
@@ -2277,11 +2283,11 @@ async function cmdLockAcquire(args) {
}
}
/** `ccam lock release <name> [--holder X]`. */
/** `agenthub lock release <name> [--holder X]`. */
async function cmdLockRelease(args) {
const name = args.find((arg) => !arg.startsWith("--"));
if (!name) {
console.error("usage: ccam lock release <name> [--holder X]");
console.error("usage: agenthub lock release <name> [--holder X]");
process.exitCode = 1;
return;
}
@@ -2303,7 +2309,7 @@ async function cmdLockRelease(args) {
}
/**
* `ccam stage <stage> [flags]` — the lane equivalent of Shipyard's
* `agenthub stage <stage> [flags]` — the lane equivalent of Shipyard's
* `state.sh N set stage=…`. A skill calls this at each phase boundary so the
* dashboard shows a declared stage instead of an inferred one.
*/
@@ -2314,23 +2320,23 @@ function fmtFeatureRow(f) {
}`;
}
/** `ccam feature list [<id>] [--cwd path]` — every feature this lane has activated. */
/** `agenthub feature list [<id>] [--cwd path]` — every feature this lane has activated. */
async function cmdFeatureList(args) {
const resolved = await resolveLaneArg(args);
if (!resolved) return;
const { features } = await get(`/api/lanes/${resolved.laneId}/features`);
if (!features.length) {
console.log("no features activated yet — start one with: ccam feature activate <slug>");
console.log("no features activated yet — start one with: agenthub feature activate <slug>");
return;
}
for (const f of features) console.log(fmtFeatureRow(f));
}
/** `ccam feature activate <slug> [--title X] [<id>] [--cwd path]`. */
/** `agenthub feature activate <slug> [--title X] [<id>] [--cwd path]`. */
async function cmdFeatureActivate(args) {
const slug = args.find((arg) => !arg.startsWith("--"));
if (!slug) {
console.error("usage: ccam feature activate <slug> [--title text]");
console.error("usage: agenthub feature activate <slug> [--title text]");
process.exitCode = 1;
return;
}
@@ -2349,8 +2355,8 @@ async function cmdFeatureActivate(args) {
);
}
/** `ccam feature show <slug> [<id>] [--cwd path]` — one feature's saved pipeline. */
/** `ccam lanes proof-link [<id>] [--cwd path]` — converge the clone-root
/** `agenthub feature show <slug> [<id>] [--cwd path]` — one feature's saved pipeline. */
/** `agenthub lanes proof-link [<id>] [--cwd path]` — converge the clone-root
* `proof/` onto `.playwright-mcp/proof` (idempotent). Never run automatically
* by anything else in this codebase; a session or hook calls it explicitly. */
async function cmdLanesProofLink(args) {
@@ -2364,7 +2370,7 @@ async function cmdLanesProofLink(args) {
);
}
/** `ccam lanes agents install [<id>] [--cwd path]` — write the ship-feature-lane
/** `agenthub lanes agents install [<id>] [--cwd path]` — write the ship-feature-lane
* agent templates (qc-local, senior-gate-reviewer) into <lane>/.claude/agents/,
* git-excluded. Never run automatically; a session calls it explicitly. */
async function cmdLanesAgentsInstall(args) {
@@ -2383,7 +2389,7 @@ async function cmdLanesAgentsInstall(args) {
console.log(`${c.green("✔")} installed: ${result.installed.join(", ")}`);
}
/** `ccam lanes mcp sync [<id>] [--cwd path]` — relocate the source repo's
/** `agenthub lanes mcp sync [<id>] [--cwd path]` — relocate the source repo's
* already-configured MCP servers into <lane>/.mcp.json and seed Chromium
* profiles. Never run automatically; a session calls it explicitly. */
async function cmdLanesMcpSync(args) {
@@ -2401,13 +2407,13 @@ async function cmdLanesMcpSync(args) {
}
}
/** `ccam lanes integration <name> [<id>]` — check whether a named
/** `agenthub lanes integration <name> [<id>]` — check whether a named
* integration (tracker, dev_qc, ci_wait) is turned on for this lane, per
* its .ccam/profile/integrations.env. Exit 0 = on, 1 = off. */
* its .agenthub/profile/integrations.env. Exit 0 = on, 1 = off. */
async function cmdLanesIntegration(args) {
const name = args.find((arg) => !arg.startsWith("--"));
if (!name) {
console.error("usage: ccam lanes integration <name> [<id>]");
console.error("usage: agenthub lanes integration <name> [<id>]");
process.exitCode = 1;
return;
}
@@ -2420,19 +2426,19 @@ async function cmdLanesIntegration(args) {
process.exitCode = enabled ? 0 : 1;
}
/** `ccam lanes ci status <branch-or-sha> [<lane-id>]` — one-shot GitHub
/** `agenthub lanes ci status <branch-or-sha> [<lane-id>]` — one-shot GitHub
* Actions status snapshot for a ref, via GET /:id/ci/status. Never polls;
* the caller (ship-feature-lane Stage 13) owns the wait loop. Exit code
* mirrors the derived state: 0 success, 1 failure or any error, 3 pending. */
async function cmdLanesCi(args) {
if (args[0] !== "status") {
console.error("usage: ccam lanes ci status <branch-or-sha> [<lane-id>]");
console.error("usage: agenthub lanes ci status <branch-or-sha> [<lane-id>]");
process.exitCode = 1;
return;
}
const ref = args[1];
if (!ref) {
console.error("usage: ccam lanes ci status <branch-or-sha> [<lane-id>]");
console.error("usage: agenthub lanes ci status <branch-or-sha> [<lane-id>]");
process.exitCode = 1;
return;
}
@@ -2453,7 +2459,7 @@ async function cmdLanesCi(args) {
}
/**
* `ccam lanes node [<node-id>] [extra…] [<id>] [--print]` — run ONE node of the
* `agenthub lanes node [<node-id>] [extra…] [<id>] [--print]` — run ONE node of the
* lane's pipeline instead of the whole thing.
*
* Every node already carries the one command that drives it (`command`, added
@@ -2462,15 +2468,15 @@ async function cmdLanesCi(args) {
* pasted it into a session by hand. This runs it, in the lane's own cwd:
*
* - a slash command (`/superpowers:brainstorming`) starts `claude` with it
* as the prompt, exactly like `ccam ship-full` does with the whole skill;
* - anything else (`npm test`, `ccam lanes hook ci-gate`) is spawned directly.
* as the prompt, exactly like `agenthub ship-full` does with the whole skill;
* - anything else (`npm test`, `agenthub lanes hook ci-gate`) is spawned directly.
*
* With no node id it lists the nodes and their commands, which is also the
* error path's answer. `--print` shows the argv and runs nothing.
*
* The node is matched by id OR alias, the same way `ccam stage` matches one:
* The node is matched by id OR alias, the same way `agenthub stage` matches one:
* the two shipped templates name the same step `implement` and `implementing`,
* and `/ccam:implement` must not depend on which one the lane happens to be on.
* and `/agenthub:implement` must not depend on which one the lane happens to be on.
*
* The command is split on whitespace and spawned WITHOUT a shell: a template
* is data, and templates in `DASHBOARD_PIPELINES_DIR` are files anyone can
@@ -2479,7 +2485,7 @@ async function cmdLanesCi(args) {
*
* It does NOT declare the stage. Running a node's command is not proof the
* node passed — same boundary as the console never writing a lane's stage.
* Declare with `ccam stage <node>` when the work is actually done.
* Declare with `agenthub stage <node>` when the work is actually done.
*/
async function cmdLanesNode(args) {
const { flags, positional } = parseArgs(args);
@@ -2509,7 +2515,7 @@ async function cmdLanesNode(args) {
if (!key) {
listNodes();
console.error(`\nusage: ccam lanes node <node-id> [extra args…] [<id>] [--print]`);
console.error(`\nusage: agenthub lanes node <node-id> [extra args…] [<id>] [--print]`);
process.exitCode = 1;
return;
}
@@ -2563,8 +2569,8 @@ async function cmdLanesNode(args) {
}
/**
* `ccam lanes pipeline [<template-id>] [<id>]` — show or switch which pipeline
* template a lane renders against. `ccam lanes add --pipeline` could only set
* `agenthub lanes pipeline [<template-id>] [<id>]` — show or switch which pipeline
* template a lane renders against. `agenthub lanes add --pipeline` could only set
* this at creation time, so every lane added from the dashboard's "+ Add lane"
* was stuck on `default` with no way back to a 16-node template.
*/
@@ -2589,7 +2595,7 @@ async function cmdLanesPipeline(args) {
);
// Switching templates re-resolves the SAME declared stage string against a
// different node list, so a stage that meant something in the old pipeline
// can land nowhere in the new one. Same warning as `ccam stage`, same reason.
// can land nowhere in the new one. Same warning as `agenthub stage`, same reason.
if (!lane.pipeline_nodes.some((n) => n.state === "current")) {
console.error(
c.yellow(
@@ -2611,7 +2617,7 @@ async function cmdLanesShell(args) {
const resolved = await resolveLaneArg(args);
if (!resolved) return;
const { lane } = await get(`/api/lanes/${resolved.laneId}`);
const sessionName = `ccam-lane-${lane.id}`;
const sessionName = `agenthub-lane-${lane.id}`;
const child = spawn("tmux", ["new-session", "-A", "-s", sessionName, "-c", lane.cwd], {
stdio: "inherit",
});
@@ -2629,10 +2635,10 @@ async function cmdLanesShell(args) {
* Start a `claude` session in this lane driven by a pipeline's own skill.
* Two commands, two depths of work:
*
* ccam ship-full → the 16-node `ship-feature` template, `/ccam:ship-feature-lane`
* agenthub ship-full → the 16-node `ship-feature` template, `/agenthub:ship-feature-lane`
* — autonomous to a published PR, then watched until a
* human merges it.
* ccam ship-quick → the 8-node `default` template, `/ccam:ship-feature`
* agenthub ship-quick → the 8-node `default` template, `/agenthub:ship-feature`
* — in-repo feature work that stops at review/gate.
*
* Each command puts the lane on its own template, so the choice is the command
@@ -2676,7 +2682,7 @@ async function cmdShip(command, args) {
if (flags.print || !requirement) {
// `--print` shows what would run, so it switches nothing.
if (!requirement) console.error(`usage: ccam ${command} "<requirement>" [<id>] [--print]`);
if (!requirement) console.error(`usage: agenthub ${command} "<requirement>" [<id>] [--print]`);
console.log(`cd ${lane.cwd} && claude ${JSON.stringify(prompt)}`);
if (!requirement) process.exitCode = 1;
return;
@@ -2710,7 +2716,7 @@ async function cmdShip(command, args) {
async function cmdFeatureShow(args) {
const slug = args.find((arg) => !arg.startsWith("--"));
if (!slug) {
console.error("usage: ccam feature show <slug>");
console.error("usage: agenthub feature show <slug>");
process.exitCode = 1;
return;
}
@@ -2736,7 +2742,7 @@ async function cmdStage(args) {
const stage = args[0];
if (!stage || stage.startsWith("--")) {
console.error(
"usage: ccam stage <stage> [--lane <id>] [--cwd <path>] [--tool claude|codex] [--status <s>] [--evidence <text>] [--note <text>] [--result pass|fail]"
"usage: agenthub stage <stage> [--lane <id>] [--cwd <path>] [--tool claude|codex] [--status <s>] [--evidence <text>] [--note <text>] [--result pass|fail]"
);
process.exitCode = 1;
return;
@@ -2884,7 +2890,7 @@ const COMMAND_GROUPS = [
[
"lanes profile init",
"<repo> [--force]",
"Detect a Node.js project and scaffold .ccam/profile/",
"Detect a Node.js project and scaffold .agenthub/profile/",
],
[
"lanes profile check",
@@ -3003,7 +3009,7 @@ const COMMAND_GROUPS = [
["update-check", "", "Check whether the dashboard checkout is behind upstream"],
["clear-data", "--yes", "Delete ALL data (requires --yes)"],
["open", "", "Open the dashboard in your browser"],
["version", "", "Print the ccam version (also: --version, -v)"],
["version", "", "Print the agenthub version (also: --version, -v)"],
],
],
];
@@ -3023,11 +3029,11 @@ otherwise the live server is found via ~/.claude/.agent-dashboard.json,
falling back to http://127.0.0.1:4820.
${c.bold("Output:")} colors auto-enable on a TTY and turn off when piped.
Disable with --no-color or NO_COLOR=1; force with FORCE_COLOR=1 / CCAM_COLOR=1.
Disable with --no-color or NO_COLOR=1; force with FORCE_COLOR=1 / AGENTHUB_COLOR=1.
${c.bold("Note:")} ccam talks to the local dashboard server — API-backed commands
require it to be running (${c.bold("ccam start")} brings one up in the background).
Prefer an interactive session? Run ${c.bold("ccam repl")} for a shell with completion,
${c.bold("Note:")} agenthub talks to the local dashboard server — API-backed commands
require it to be running (${c.bold("agenthub start")} brings one up in the background).
Prefer an interactive session? Run ${c.bold("agenthub repl")} for a shell with completion,
history, and a live status prompt.`;
}
@@ -3038,8 +3044,8 @@ function cmdHelp() {
`${section(group)}\n` + items.map(([n, a, d]) => catalogRow(n, a, d)).join("\n")
).join("\n");
console.log(
`${c.cyan("▍")}${c.bold("ccam")} — Claude Code Agent Monitor CLI\n\n` +
`${c.bold("Usage:")} ccam <command> [options]\n` +
`${c.cyan("▍")}${c.bold("agenthub")} — AgentHub CLI\n\n` +
`${c.bold("Usage:")} agenthub <command> [options]\n` +
body +
"\n" +
helpFooter()
@@ -3058,7 +3064,7 @@ function pkgVersion() {
/** Print the CLI/package version. */
function cmdVersion() {
const v = pkgVersion();
console.log(v ? `ccam ${v}` : "ccam (version unknown)");
console.log(v ? `agenthub ${v}` : "agenthub (version unknown)");
}
// ── Offline command handlers & dispatch ────────────────────────────────────
@@ -3115,7 +3121,7 @@ const OFFLINE_HANDLERS = {
async session(flags, positional) {
const id = positional[0];
if (!id) {
console.error(c.red("✖ Usage: ccam session <session-id>"));
console.error(c.red("✖ Usage: agenthub session <session-id>"));
process.exit(1);
}
const db = requireDb();
@@ -3187,7 +3193,7 @@ const OFFLINE_HANDLERS = {
},
async export(flags, positional) {
const db = requireDb();
const file = positional[0] || `ccam-export-${new Date().toISOString().slice(0, 10)}.json`;
const file = positional[0] || `agenthub-export-${new Date().toISOString().slice(0, 10)}.json`;
const payload = {
exported_at: new Date().toISOString(),
exported_offline: true,
@@ -3203,13 +3209,13 @@ const OFFLINE_HANDLERS = {
);
},
async doctor() {
heading("ccam doctor", "offline");
heading("agenthub doctor", "offline");
let failed = true; // offline is always a failure for live monitoring
console.log(
`${c.red("○")} Dashboard server NOT running ${c.dim(`(tried ${baseUrl()})`)} — start with: ${c.bold("ccam start")}`
`${c.red("○")} Dashboard server NOT running ${c.dim(`(tried ${baseUrl()})`)} — start with: ${c.bold("agenthub start")}`
);
console.log(
`${c.dim("·")} Remote sources require a live server (ccam remote-sources / Settings)`
`${c.dim("·")} Remote sources require a live server (agenthub remote-sources / Settings)`
);
const file = dbPath();
if (fs.existsSync(file)) {
@@ -3275,15 +3281,15 @@ function requireDb() {
if (!db) {
console.error(c.red(`✖ No readable database at ${dbPath()}`));
console.error(c.dim(" Nothing has been captured yet, or no SQLite driver is available."));
console.error(c.dim(" Start the server to begin capturing: ccam start"));
console.error(c.dim(" Start the server to begin capturing: agenthub start"));
process.exit(1);
}
return db;
}
// ── Interactive REPL (ccam shell) ───────────────────────────────────────────
// `ccam repl` opens a persistent prompt where commands are typed WITHOUT the
// `ccam` prefix. Each command runs as a short-lived child `ccam` process, so
// ── Interactive REPL (agenthub shell) ───────────────────────────────────────────
// `agenthub repl` opens a persistent prompt where commands are typed WITHOUT the
// `agenthub` prefix. Each command runs as a short-lived child `agenthub` process, so
// behavior is byte-identical to the one-shot CLI and a command that exits
// non-zero, blocks (tail), or refuses offline can never take the shell down
// with it. Colors, tab-completion, arrow-key history (persisted), and a live
@@ -3348,7 +3354,7 @@ function replCompleter(line) {
return [[], word];
}
// The CCAM word-mark, shown once when the interactive shell starts (and via
// The AgentHub word-mark, shown once when the interactive shell starts (and via
// the `banner` built-in). Kept as raw text so the backslashes render verbatim.
const BANNER_ART = String.raw`
_____ _____ _____ _____
@@ -3381,17 +3387,17 @@ function replBanner(up) {
if (termWidth() >= 100) {
console.log(c.cyan(BANNER_ART));
} else {
console.log(`\n${c.cyan("▍")}${c.bold("ccam")}`);
console.log(`\n${c.cyan("▍")}${c.bold("agenthub")}`);
}
const dot = up ? c.green("●") : c.red("○");
const where = up ? baseUrl().replace(/^https?:\/\//, "") : "offline";
console.log(
` ${c.bold("Claude Code Agent Monitor")} ${c.dim("· interactive shell")}` +
` ${c.bold("AgentHub")} ${c.dim("· interactive shell")}` +
(v ? c.dim(` · v${v}`) : "") +
` ${dot} ${c.dim(where)}`
);
console.log(
c.dim(" Type commands without the 'ccam' prefix — e.g. ") + c.bold("sessions --limit 5")
c.dim(" Type commands without the 'agenthub' prefix — e.g. ") + c.bold("sessions --limit 5")
);
console.log(
c.dim(" ") +
@@ -3406,10 +3412,10 @@ function replBanner(up) {
}
/** Categorized REPL help: shell built-ins first, then the full command
* catalog (identical groups/descriptions to `ccam help`). */
* catalog (identical groups/descriptions to `agenthub help`). */
function replHelp() {
const row = (name, desc) => ` ${c.cyan(name.padEnd(20))}${desc}`;
console.log(`\n${c.cyan("▍")}${c.bold("ccam shell")} ${c.dim("— built-ins")}`);
console.log(`\n${c.cyan("▍")}${c.bold("agenthub shell")} ${c.dim("— built-ins")}`);
console.log(row("help", "This help. " + c.dim("`help <command>` shows one command's details")));
console.log(row("commands", "Compact list of every command, grouped"));
console.log(
@@ -3460,7 +3466,7 @@ function replCommandsList() {
}
/**
* The interactive shell. Reads lines, dispatches each as a child `ccam`
* The interactive shell. Reads lines, dispatches each as a child `agenthub`
* process (inheriting stdio so tables/colors/streaming render natively),
* and keeps a persisted history. On a TTY the prompt shows live server
* status (● up / ○ offline) and the resolved host; piped input (tests,
@@ -3469,7 +3475,7 @@ function replCommandsList() {
async function cmdRepl() {
const readline = require("node:readline");
const isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
const historyFile = path.join(REPO_ROOT, "data", ".ccam_repl_history");
const historyFile = path.join(REPO_ROOT, "data", ".agenthub_repl_history");
let history = [];
try {
@@ -3503,7 +3509,7 @@ async function cmdRepl() {
const up = await serverUpCached();
const dot = up ? c.green("●") : c.red("○");
const where = up ? c.dim(baseUrl().replace(/^https?:\/\//, "")) : c.dim("offline");
rl.setPrompt(`${dot} ${c.bold("ccam")} ${where} ${c.cyan("›")} `);
rl.setPrompt(`${dot} ${c.bold("agenthub")} ${where} ${c.cyan("›")} `);
rl.prompt();
}
@@ -3519,13 +3525,13 @@ async function cmdRepl() {
renderPrompt();
});
/** Run one child `ccam` invocation with inherited stdio. */
/** Run one child `agenthub` invocation with inherited stdio. */
function runChild(argv) {
return new Promise((resolve) => {
childActive = true;
const env = { ...process.env };
if (useColor) env.FORCE_COLOR = "1";
env.CCAM_REPL = "1";
env.AGENTHUB_REPL = "1";
const child = spawn(process.execPath, [__filename, ...argv], {
stdio: "inherit",
env,
@@ -3560,7 +3566,7 @@ async function cmdRepl() {
if (isTty) process.stdout.write("\x1b[2J\x1b[H");
console.log(
c.dim(
`⟳ watch: ccam ${argv.join(" ")} — every ${Math.round(intervalMs / 1000)}s · ${new Date().toLocaleTimeString()} · Ctrl+C to stop`
`⟳ watch: agenthub ${argv.join(" ")} — every ${Math.round(intervalMs / 1000)}s · ${new Date().toLocaleTimeString()} · Ctrl+C to stop`
)
);
await runChild(argv);
@@ -3655,7 +3661,7 @@ async function cmdRepl() {
continue;
}
if (head === "repl" || head === "shell" || head === "i") {
console.log(c.dim("Already in the ccam shell."));
console.log(c.dim("Already in the agenthub shell."));
await renderPrompt();
continue;
}
@@ -3682,7 +3688,7 @@ async function cmdRepl() {
// ── Command dispatch ────────────────────────────────────────────────────────
// The single source of truth for "argv → command handler". Both the top-level
// entry point and the interactive REPL route through here, so every command
// behaves identically whether typed as `ccam <cmd>` or entered at the `ccam ›`
// behaves identically whether typed as `agenthub <cmd>` or entered at the `agenthub ›`
// prompt. Handlers that need the server throw ServerDownError; the caller
// decides whether to fall back to an offline reader or report the refusal.
@@ -3782,7 +3788,7 @@ async function runCommand(argv) {
if (rest[1] === "init") return cmdLanesProfileInit(rest.slice(2));
if (rest[1] === "check") return cmdLanesProfileCheck(rest.slice(2));
console.error(
"usage: ccam lanes profile init <repo> [--force] | ccam lanes profile check [<path>]"
"usage: agenthub lanes profile init <repo> [--force] | agenthub lanes profile check [<path>]"
);
process.exitCode = 1;
return;
@@ -3821,7 +3827,7 @@ async function runCommand(argv) {
return cmdLanes();
case "skills": {
if (rest[0] === "install") return cmdSkillsInstall();
console.error("usage: ccam skills install");
console.error("usage: agenthub skills install");
process.exitCode = 1;
return;
}
@@ -3831,7 +3837,7 @@ async function runCommand(argv) {
if (sub === "acquire") return cmdLockAcquire(rest.slice(1));
if (sub === "release") return cmdLockRelease(rest.slice(1));
console.error(
"usage: ccam lock status [<name>] | ccam lock acquire <name> [--holder X] [--timeout N] | ccam lock release <name> [--holder X]"
"usage: agenthub lock status [<name>] | agenthub lock acquire <name> [--holder X] [--timeout N] | agenthub lock release <name> [--holder X]"
);
process.exitCode = 1;
return;
@@ -3847,7 +3853,7 @@ async function runCommand(argv) {
if (sub === "activate") return cmdFeatureActivate(rest.slice(1));
if (sub === "show") return cmdFeatureShow(rest.slice(1));
console.error(
"usage: ccam feature list | ccam feature activate <slug> [--title text] | ccam feature show <slug>"
"usage: agenthub feature list | agenthub feature activate <slug> [--title text] | agenthub feature show <slug>"
);
process.exitCode = 1;
return;