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
+4 -4
View File
@@ -461,7 +461,7 @@ export function Workspace() {
}
};
// Mirrors `ccam lanes pipeline <template> <id>`: same PATCH, same
// Mirrors `agenthub lanes pipeline <template> <id>`: same PATCH, same
// stage-mismatch warning when the current declared stage matches no node
// in the newly chosen template.
const handlePipelineChange = async (id: number, pipeline: string) => {
@@ -470,7 +470,7 @@ export function Workspace() {
const { lane } = await api.lanes.update(id, { pipeline });
await refreshLanes();
// `stage` defaults to the DB sentinel "idle" until a driving session ever
// calls `ccam stage` — that's the normal state for most lanes and never
// calls `agenthub stage` — that's the normal state for most lanes and never
// matches a real pipeline node, so it isn't a mismatch worth surfacing.
if (lane.stage !== "idle" && !lane.pipeline_nodes.some((n) => n.state === "current")) {
setLaneActionError(
@@ -558,7 +558,7 @@ export function Workspace() {
))}
{!lanes.length && (
<p className="text-sm text-fg-muted">
{tLanes("emptyState")} <code>ccam lanes add --cwd $(pwd)</code>
{tLanes("emptyState")} <code>agenthub lanes add --cwd $(pwd)</code>
</p>
)}
</div>
@@ -636,7 +636,7 @@ export function Workspace() {
))}
</select>
{/* `stage` defaults to the DB sentinel "idle" until the driving
session ever calls `ccam stage` — that string collides with
session ever calls `agenthub stage` — that string collides with
`status`'s own "idle"/"running" vocabulary, so a lane that is
actively running but has never declared a stage read as if it
were sitting idle. Show a distinct label instead of the raw
@@ -96,7 +96,7 @@ vi.mock("../../lib/api", async (importOriginal) => {
return { available: false };
}),
// Same for the lane's own application stack: nothing here asserts on it,
// so report the "no .ccam/profile" shape, which is the common case.
// so report the "no .agenthub/profile" shape, which is the common case.
runtime: vi.fn().mockImplementation(async (id: number) => {
recordCall("GET", `/api/lanes/${id}/runtime`);
return { available: false };
@@ -692,7 +692,7 @@ describe("split terminal view", () => {
it("falls back to unselected when a persisted lane id no longer exists", async () => {
localStorage.setItem(
"ccam.workspace.splitView",
"agenthub.workspace.splitView",
JSON.stringify({ layout: 2, paneLaneIds: [9999, null] })
);
await renderWorkspace();
@@ -5086,7 +5086,7 @@ exports[`screen snapshots > Run 1`] = `
No lanes yet. Create one from a working directory:
<code>
ccam lanes add --cwd $(pwd)
agenthub lanes add --cwd $(pwd)
</code>
</p>
</div>
@@ -10368,7 +10368,7 @@ exports[`screen snapshots > Workflows 1`] = `
No lane has recorded a stage yet. Create one with
<code>
ccam lanes add --cwd $(pwd)
agenthub lanes add --cwd $(pwd)
</code>
</p>
</div>
@@ -31,12 +31,12 @@ vi.mock("../../lib/api", async (importOriginal) => {
// One installed plugin that actually contributes something, so the Claude Code
// tab renders PluginCard's contribution chips instead of an empty list.
const demoPlugin = {
key: "ccam@sg",
name: "ccam",
key: "agenthub@sg",
name: "agenthub",
marketplace: "sg",
scope: "user",
version: "1.4.6",
installPath: "/home/test/.claude/plugins/ccam",
installPath: "/home/test/.claude/plugins/agenthub",
installedAt: "2026-05-01T12:00:00.000Z",
lastUpdated: "2026-06-01T09:30:00.000Z",
gitCommitSha: "a1b2c3d",
@@ -53,31 +53,31 @@ vi.mock("../../lib/api", async (importOriginal) => {
{
name: "ship-feature",
description: "Implement a feature safely end-to-end.",
file: "/home/test/.claude/plugins/ccam/skills/ship-feature/SKILL.md",
file: "/home/test/.claude/plugins/agenthub/skills/ship-feature/SKILL.md",
},
{
name: "file-headers",
description: "Authorship header policy.",
file: "/home/test/.claude/plugins/ccam/skills/file-headers/SKILL.md",
file: "/home/test/.claude/plugins/agenthub/skills/file-headers/SKILL.md",
},
],
agents: [
{
name: "qc-local",
description: "Local browser QC.",
file: "/home/test/.claude/plugins/ccam/agents/qc-local.md",
file: "/home/test/.claude/plugins/agenthub/agents/qc-local.md",
},
],
commands: [
{
name: "ccam-doctor",
name: "agenthub-doctor",
description: null,
file: "/home/test/.claude/plugins/ccam/plugins/ccam/commands/ccam-doctor.md",
file: "/home/test/.claude/plugins/agenthub/plugins/agenthub/commands/agenthub-doctor.md",
},
],
outputStyles: [],
},
pluginJson: { name: "ccam", description: "The dashboard as a plugin.", version: "1.4.6" },
pluginJson: { name: "agenthub", description: "The dashboard as a plugin.", version: "1.4.6" },
},
};
@@ -614,7 +614,7 @@ describe("plugin contribution chips", () => {
it("expands a plugin's skills by name on click, and collapses again", async () => {
await openPluginsTab();
const chip = screen.getByTestId("plugin-contrib-ccam@sg-skills");
const chip = screen.getByTestId("plugin-contrib-agenthub@sg-skills");
expect(chip).toHaveAttribute("aria-expanded", "false");
expect(screen.queryByText("ship-feature")).toBeNull();
@@ -631,16 +631,16 @@ describe("plugin contribution chips", () => {
it("shows only one surface at a time, and prefixes a command with its slash", async () => {
await openPluginsTab();
fireEvent.click(screen.getByTestId("plugin-contrib-ccam@sg-skills"));
fireEvent.click(screen.getByTestId("plugin-contrib-ccam@sg-commands"));
fireEvent.click(screen.getByTestId("plugin-contrib-agenthub@sg-skills"));
fireEvent.click(screen.getByTestId("plugin-contrib-agenthub@sg-commands"));
expect(screen.queryByText("ship-feature")).toBeNull();
expect(screen.getByText("/ccam-doctor")).toBeInTheDocument();
expect(screen.getByText("/agenthub-doctor")).toBeInTheDocument();
});
it("leaves hooks as a plain chip - they have no per-item list to open", async () => {
await openPluginsTab();
expect(screen.queryByTestId("plugin-contrib-ccam@sg-hooks")).toBeNull();
expect(screen.queryByTestId("plugin-contrib-agenthub@sg-hooks")).toBeNull();
});
});