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:
@@ -3,7 +3,7 @@
|
||||
* makes a plugin install self-sufficient. Covers the fast path, the Node
|
||||
* version gate, stale-lock reclaim, legacy hook removal (the duplicate-hook
|
||||
* double-counting bug) and the CLI launcher's refusal to clobber a foreign
|
||||
* `ccam`. Every path is exercised against injected temp directories — nothing
|
||||
* `agenthub`. Every path is exercised against injected temp directories — nothing
|
||||
* here touches the real $HOME.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const TMP_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-bootstrap-"));
|
||||
const TMP_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-bootstrap-"));
|
||||
process.env.CLAUDE_HOME = TMP_HOME;
|
||||
delete process.env.DASHBOARD_DATA_DIR;
|
||||
|
||||
@@ -135,17 +135,17 @@ describe("skill-plugin auto-install gate", () => {
|
||||
// Both cases here must return WITHOUT reaching server/lib/skill-plugins.js —
|
||||
// that module shells out to `claude plugin install`, and no test may.
|
||||
beforeEach(resetRuntime);
|
||||
after(() => delete process.env.CCAM_AUTO_INSTALL_PLUGINS);
|
||||
after(() => delete process.env.AGENTHUB_AUTO_INSTALL_PLUGINS);
|
||||
|
||||
for (const flag of ["0", "false", "off"]) {
|
||||
it(`is off when CCAM_AUTO_INSTALL_PLUGINS=${flag}`, () => {
|
||||
process.env.CCAM_AUTO_INSTALL_PLUGINS = flag;
|
||||
it(`is off when AGENTHUB_AUTO_INSTALL_PLUGINS=${flag}`, () => {
|
||||
process.env.AGENTHUB_AUTO_INSTALL_PLUGINS = flag;
|
||||
assert.equal(boot.autoInstallSkillPlugins(RT), "off");
|
||||
});
|
||||
}
|
||||
|
||||
it("runs once: a settled state is returned verbatim, not re-attempted", () => {
|
||||
delete process.env.CCAM_AUTO_INSTALL_PLUGINS;
|
||||
delete process.env.AGENTHUB_AUTO_INSTALL_PLUGINS;
|
||||
boot.writeState({ skillPluginsSettled: "installed" }, RT);
|
||||
|
||||
// Verbatim matters: writeState records whatever comes back, so a marker
|
||||
@@ -153,8 +153,8 @@ describe("skill-plugin auto-install gate", () => {
|
||||
assert.equal(boot.autoInstallSkillPlugins(RT), "installed");
|
||||
});
|
||||
|
||||
it("is off under the flag even when forced, so /ccam-update honours the opt-out", () => {
|
||||
process.env.CCAM_AUTO_INSTALL_PLUGINS = "0";
|
||||
it("is off under the flag even when forced, so /agenthub-update honours the opt-out", () => {
|
||||
process.env.AGENTHUB_AUTO_INSTALL_PLUGINS = "0";
|
||||
boot.writeState({ skillPluginsSettled: "failed" }, RT);
|
||||
assert.equal(boot.autoInstallSkillPlugins(RT, { force: true }), "off");
|
||||
});
|
||||
@@ -200,7 +200,7 @@ describe("legacy hook cleanup", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fs.rmSync(settings, { force: true });
|
||||
fs.rmSync(`${settings}.ccam-bak`, { force: true });
|
||||
fs.rmSync(`${settings}.agenthub-bak`, { force: true });
|
||||
});
|
||||
|
||||
it("removes checkout-installed hook entries and backs the file up first", () => {
|
||||
@@ -234,7 +234,7 @@ describe("legacy hook cleanup", () => {
|
||||
assert.equal(after.hooks.PreToolUse.length, 1, "unrelated hooks must survive");
|
||||
assert.match(JSON.stringify(after.hooks.PreToolUse), /other\/tool\.js/);
|
||||
assert.equal(after.hooks.SessionStart, undefined, "an emptied list is dropped");
|
||||
assert.ok(fs.existsSync(`${settings}.ccam-bak`), "must back up before writing");
|
||||
assert.ok(fs.existsSync(`${settings}.agenthub-bak`), "must back up before writing");
|
||||
});
|
||||
|
||||
it("never removes the plugin's own cache-resolved hook entries", () => {
|
||||
@@ -249,7 +249,7 @@ describe("legacy hook cleanup", () => {
|
||||
"plugins",
|
||||
"cache",
|
||||
"claude-code-agent-monitor-plugins",
|
||||
"ccam",
|
||||
"agenthub",
|
||||
"abc123",
|
||||
"scripts",
|
||||
"hook-handler.js"
|
||||
@@ -280,10 +280,10 @@ describe("legacy hook cleanup", () => {
|
||||
assert.match(JSON.stringify(after.hooks.PreToolUse), /plugins.*cache/);
|
||||
});
|
||||
|
||||
it("leaves a file with no CCAM hooks untouched and writes no backup", () => {
|
||||
it("leaves a file with no AgentHub hooks untouched and writes no backup", () => {
|
||||
fs.writeFileSync(settings, JSON.stringify({ hooks: { Stop: [{ hooks: [] }] } }));
|
||||
assert.equal(boot.stripLegacyHooks(settings), 0);
|
||||
assert.equal(fs.existsSync(`${settings}.ccam-bak`), false);
|
||||
assert.equal(fs.existsSync(`${settings}.agenthub-bak`), false);
|
||||
});
|
||||
|
||||
it("is a no-op when no settings file exists", () => {
|
||||
@@ -291,17 +291,17 @@ describe("legacy hook cleanup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ccam CLI launcher", () => {
|
||||
describe("agenthub CLI launcher", () => {
|
||||
const bin = path.join(TMP_HOME, "bin");
|
||||
const target = path.join(bin, process.platform === "win32" ? "ccam.cmd" : "ccam");
|
||||
const target = path.join(bin, process.platform === "win32" ? "agenthub.cmd" : "agenthub");
|
||||
|
||||
beforeEach(() => {
|
||||
fs.rmSync(bin, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("writes a launcher that points at the plugin's bin/ccam.js", () => {
|
||||
it("writes a launcher that points at the plugin's bin/agenthub.js", () => {
|
||||
assert.equal(boot.linkCli(bin), "written");
|
||||
assert.match(fs.readFileSync(target, "utf8"), /bin[/\\]ccam\.js/);
|
||||
assert.match(fs.readFileSync(target, "utf8"), /bin[/\\]agenthub\.js/);
|
||||
});
|
||||
|
||||
it("is idempotent", () => {
|
||||
@@ -309,9 +309,9 @@ describe("ccam CLI launcher", () => {
|
||||
assert.equal(boot.linkCli(bin), "exists");
|
||||
});
|
||||
|
||||
it("never clobbers a ccam it did not write (a linked checkout keeps winning)", () => {
|
||||
it("never clobbers a agenthub it did not write (a linked checkout keeps winning)", () => {
|
||||
fs.mkdirSync(bin, { recursive: true });
|
||||
fs.writeFileSync(target, '#!/bin/sh\nexec node /my/checkout/bin/ccam.js "$@"\n');
|
||||
fs.writeFileSync(target, '#!/bin/sh\nexec node /my/checkout/bin/agenthub.js "$@"\n');
|
||||
assert.equal(boot.linkCli(bin), "foreign");
|
||||
assert.match(fs.readFileSync(target, "utf8"), /my\/checkout/);
|
||||
});
|
||||
@@ -319,10 +319,10 @@ describe("ccam CLI launcher", () => {
|
||||
|
||||
describe("plugin-open / plugin-bootstrap require cycle", () => {
|
||||
// bootstrap() lazily requires plugin-open for buildClient, and plugin-open
|
||||
// requires bootstrap at load. When plugin-open is the entry point (/ccam-open)
|
||||
// requires bootstrap at load. When plugin-open is the entry point (/agenthub-open)
|
||||
// that lazy require resolves to whatever sits on its module.exports at that
|
||||
// instant — so the assignment must come before the entry block that calls
|
||||
// bootstrap(), or /ccam-open dies with "buildClient is not a function".
|
||||
// bootstrap(), or /agenthub-open dies with "buildClient is not a function".
|
||||
const src = fs.readFileSync(
|
||||
path.join(__dirname, "..", "..", "scripts", "plugin-open.js"),
|
||||
"utf8"
|
||||
|
||||
Reference in New Issue
Block a user