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
+12 -9
View File
@@ -11,13 +11,13 @@ const fs = require("node:fs");
const { describe, it, after } = require("node:test");
const assert = require("node:assert/strict");
const SUITE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-detect-"));
const SUITE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-detect-"));
// Must be set before the first require of anything that transitively loads
// server/lib/secrets.js (lane-detect.js does, for checkProfile) — SECRETS_PATH
// is bound once at that module's first load, and without this a test run on a
// machine with a real ~/.ccam/secrets.env would read that file instead of a
// machine with a real ~/.agenthub/secrets.env would read that file instead of a
// deterministic, isolated one.
process.env.CCAM_SECRETS_PATH = path.join(SUITE_ROOT, "secrets.env");
process.env.AGENTHUB_SECRETS_PATH = path.join(SUITE_ROOT, "secrets.env");
process.env.DASHBOARD_DB_PATH = path.join(SUITE_ROOT, "dashboard.db");
process.env.LANES_ROOT = path.join(SUITE_ROOT, "lanes");
after(() => fs.rmSync(SUITE_ROOT, { recursive: true, force: true }));
@@ -180,7 +180,7 @@ describe("scaffoldProfile", () => {
const result = detect.scaffoldProfile(repo, facts);
const profileDir = path.join(repo, ".ccam", "profile");
const profileDir = path.join(repo, ".agenthub", "profile");
const env = fs.readFileSync(path.join(profileDir, "profile.env"), "utf8");
assert.match(env, /PORTS="app"/);
assert.match(env, /PORT_BASE_app=3000/);
@@ -209,7 +209,7 @@ describe("scaffoldProfile", () => {
const result = detect.scaffoldProfile(repo, facts);
const profileDir = path.join(repo, ".ccam", "profile");
const profileDir = path.join(repo, ".agenthub", "profile");
const env = fs.readFileSync(path.join(profileDir, "profile.env"), "utf8");
assert.match(env, /DB_PREFIX=/);
assert.match(env, /REDIS=1/);
@@ -238,7 +238,10 @@ describe("scaffoldProfile", () => {
env: null,
};
const result = detect.scaffoldProfile(repo, facts);
const boot = fs.readFileSync(path.join(repo, ".ccam", "profile", "hooks", "boot.sh"), "utf8");
const boot = fs.readFileSync(
path.join(repo, ".agenthub", "profile", "hooks", "boot.sh"),
"utf8"
);
assert.match(boot, /# TODO: no start\/dev script found/);
assert.equal(result.todos.length, 1);
});
@@ -307,7 +310,7 @@ describe("checkProfile", () => {
const repo = makeRepo();
writePkg(repo, { start: "node index.js" });
detect.scaffoldProfile(repo, detect.detectNode(repo));
fs.rmSync(path.join(repo, ".ccam", "profile", "hooks", "health.sh"));
fs.rmSync(path.join(repo, ".agenthub", "profile", "hooks", "health.sh"));
const result = await detect.checkProfile(repo);
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => /health/.test(e)));
@@ -319,7 +322,7 @@ describe("checkProfile", () => {
writeCompose(repo, " postgres:\n image: postgres:16\n");
detect.scaffoldProfile(repo, detect.detectNode(repo));
// Remove the migrate/seed TODO lines so only the secrets warning is left to check.
const hooksDir = path.join(repo, ".ccam", "profile", "hooks");
const hooksDir = path.join(repo, ".agenthub", "profile", "hooks");
fs.writeFileSync(path.join(hooksDir, "migrate.sh"), "#!/usr/bin/env bash\nexit 0\n");
fs.writeFileSync(path.join(hooksDir, "seed.sh"), "#!/usr/bin/env bash\nexit 0\n");
const result = await detect.checkProfile(repo);
@@ -330,7 +333,7 @@ describe("checkProfile", () => {
const repo = makeRepo();
writePkg(repo, { start: "node index.js" });
detect.scaffoldProfile(repo, detect.detectNode(repo));
const hookPath = path.join(repo, ".ccam", "profile", "hooks", "health.sh");
const hookPath = path.join(repo, ".agenthub", "profile", "hooks", "health.sh");
fs.chmodSync(hookPath, 0o000);
try {
const result = await detect.checkProfile(repo);