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:
+144
-138
@@ -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;
|
||||
Reference in New Issue
Block a user