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
@@ -93,7 +93,7 @@ export function LanePipelinePanel() {
t("lanes.emptyLane")
) : (
<>
{t("lanes.empty")} <code>ccam lanes add --cwd $(pwd)</code>
{t("lanes.empty")} <code>agenthub lanes add --cwd $(pwd)</code>
</>
)}
</p>
@@ -132,7 +132,7 @@ describe("LanePipelinePanel", () => {
statsMock.mockResolvedValue({ pipelines: [], lanes: [] });
render(<LanePipelinePanel />);
expect(await screen.findByText(/ccam lanes add/)).toBeInTheDocument();
expect(await screen.findByText(/agenthub lanes add/)).toBeInTheDocument();
});
it("refetches narrowed to the lane picked, and falls back to a cwd for an untitled one", async () => {
@@ -162,7 +162,7 @@ describe("LanePipelinePanel", () => {
// the whole subtree, so a handle grabbed before it is already detached.
await waitFor(() => expect(screen.queryByTestId("pipeline-stat-plan")).not.toBeInTheDocument());
expect(screen.getByTestId("pipeline-stats-lane-picker")).toBeInTheDocument();
expect(screen.queryByText(/ccam lanes add/)).not.toBeInTheDocument();
expect(screen.queryByText(/agenthub lanes add/)).not.toBeInTheDocument();
});
it("surfaces a failed fetch", async () => {
@@ -18,10 +18,10 @@ vi.mock("../../../lib/api", async (importOriginal) => {
const USAGE = {
skills: [
{ name: "ccam:ship-feature", plugin: "ccam", count: 12, sessions: 5 },
{ name: "agenthub:ship-feature", plugin: "agenthub", count: 12, sessions: 5 },
{ name: "daily-report", plugin: null, count: 3, sessions: 3 },
],
plugins: [{ name: "ccam", skills: 1, skillCalls: 12, mcpCalls: 4 }],
plugins: [{ name: "agenthub", skills: 1, skillCalls: 12, mcpCalls: 4 }],
};
describe("SkillUsagePanel", () => {
@@ -29,14 +29,14 @@ describe("SkillUsagePanel", () => {
usageMock.mockResolvedValue(USAGE);
render(<SkillUsagePanel statusFilter="all" />);
expect(await screen.findByTestId("plugin-usage-ccam")).toHaveTextContent("16");
expect(await screen.findByTestId("plugin-usage-agenthub")).toHaveTextContent("16");
});
it("lists each skill with its call and session counts", async () => {
usageMock.mockResolvedValue(USAGE);
render(<SkillUsagePanel statusFilter="all" />);
const row = await screen.findByTestId("skill-usage-ccam:ship-feature");
const row = await screen.findByTestId("skill-usage-agenthub:ship-feature");
expect(row).toHaveTextContent("12");
expect(row).toHaveTextContent("5");
});
@@ -45,7 +45,7 @@ describe("SkillUsagePanel", () => {
usageMock.mockResolvedValue(USAGE);
render(<SkillUsagePanel statusFilter="active" />);
await screen.findByTestId("plugin-usage-ccam");
await screen.findByTestId("plugin-usage-agenthub");
expect(usageMock).toHaveBeenCalledWith("active");
});