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:
@@ -202,7 +202,7 @@ router.delete("/file", (req, res) => {
|
||||
// These shell out to `claude plugin …` with an argv array — never a shell
|
||||
// string — because the CLI owns the manifest + cache tree that plugin state
|
||||
// lives in. Identifiers travel in the body, not the URL, so a key like
|
||||
// `ccam@sg` needs no encoding (same reason `DELETE /file` takes a body).
|
||||
// `agenthub@sg` needs no encoding (same reason `DELETE /file` takes a body).
|
||||
|
||||
const CLI_ERR_TO_STATUS = {
|
||||
EBADARG: 400,
|
||||
|
||||
@@ -43,10 +43,10 @@ const { getClaudeHome, getProjectsDir } = require("../lib/claude-home");
|
||||
// Upload limits — deliberately generous because transcripts can be large.
|
||||
// Configurable at runtime via env for deployments that need tighter bounds.
|
||||
const MAX_UPLOAD_BYTES = parseInt(
|
||||
process.env.CCAM_IMPORT_MAX_BYTES || String(1024 * 1024 * 1024), // 1 GB default
|
||||
process.env.AGENTHUB_IMPORT_MAX_BYTES || String(1024 * 1024 * 1024), // 1 GB default
|
||||
10
|
||||
);
|
||||
const MAX_UPLOAD_FILES = parseInt(process.env.CCAM_IMPORT_MAX_FILES || "2000", 10);
|
||||
const MAX_UPLOAD_FILES = parseInt(process.env.AGENTHUB_IMPORT_MAX_FILES || "2000", 10);
|
||||
|
||||
/**
|
||||
* Lazily build a multer upload middleware. Kept lazy so the server still
|
||||
@@ -67,8 +67,8 @@ function getUploader() {
|
||||
}
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, _file, cb) => {
|
||||
if (!req._ccamUploadDir) req._ccamUploadDir = mkTempDir("ccam-upload-");
|
||||
cb(null, req._ccamUploadDir);
|
||||
if (!req._agenthubUploadDir) req._agenthubUploadDir = mkTempDir("agenthub-upload-");
|
||||
cb(null, req._agenthubUploadDir);
|
||||
},
|
||||
filename: (_req, file, cb) => {
|
||||
// Preserve the original name for kind-detection later, but prefix with
|
||||
@@ -90,8 +90,8 @@ function getUploader() {
|
||||
if (kind === "unknown") {
|
||||
// Track rejected filenames on the request so we can surface the count
|
||||
// in the response — users wonder why their upload "partially worked".
|
||||
if (!req._ccamRejected) req._ccamRejected = [];
|
||||
req._ccamRejected.push(file.originalname);
|
||||
if (!req._agenthubRejected) req._agenthubRejected = [];
|
||||
req._agenthubRejected.push(file.originalname);
|
||||
cb(null, false);
|
||||
} else {
|
||||
cb(null, true);
|
||||
@@ -296,8 +296,8 @@ router.post("/upload", uploadMiddleware, async (req, res) => {
|
||||
});
|
||||
}
|
||||
const files = Array.isArray(req.files) ? req.files : [];
|
||||
const rejectedNames = Array.isArray(req._ccamRejected) ? req._ccamRejected : [];
|
||||
const reqUploadDir = req._ccamUploadDir || null;
|
||||
const rejectedNames = Array.isArray(req._agenthubRejected) ? req._agenthubRejected : [];
|
||||
const reqUploadDir = req._agenthubUploadDir || null;
|
||||
|
||||
if (files.length === 0) {
|
||||
// Clean up the upload dir if multer created one before rejecting all files.
|
||||
@@ -314,7 +314,7 @@ router.post("/upload", uploadMiddleware, async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const workDir = mkTempDir("ccam-import-work-");
|
||||
const workDir = mkTempDir("agenthub-import-work-");
|
||||
let extractedCount = 0;
|
||||
let skippedEntries = 0;
|
||||
|
||||
|
||||
@@ -562,8 +562,8 @@ router.post("/:id/mcp/sync", sameOriginGuard, async (req, res) => {
|
||||
|
||||
/**
|
||||
* Detect a Node.js project at this lane's OWN directory and scaffold
|
||||
* .ccam/profile/ if one is found — the HTTP equivalent of
|
||||
* `ccam lanes profile init`, always targeting lane.cwd (never an arbitrary
|
||||
* .agenthub/profile/ if one is found — the HTTP equivalent of
|
||||
* `agenthub lanes profile init`, always targeting lane.cwd (never an arbitrary
|
||||
* path; the CLI's <repo> argument has no meaning here, this lane's own
|
||||
* directory is the only sensible target). "No Node.js project detected" is
|
||||
* a normal 200 outcome, not an error — most lanes won't be auto-detectable
|
||||
@@ -590,7 +590,7 @@ router.post("/:id/profile/init", sameOriginGuard, (req, res) => {
|
||||
|
||||
/**
|
||||
* Whether a named integration (tracker, dev_qc, ci_wait, ...) is turned on
|
||||
* for this lane — reads .ccam/profile/integrations.env. Read-only, no
|
||||
* for this lane — reads .agenthub/profile/integrations.env. Read-only, no
|
||||
* sameOriginGuard needed (same reasoning GET /:id/git already documents:
|
||||
* that guard exists for destructive actions).
|
||||
*/
|
||||
@@ -628,7 +628,7 @@ router.get("/:id/git", async (req, res) => {
|
||||
/**
|
||||
* A single GitHub Actions status snapshot for one ref (branch or commit sha)
|
||||
* in the lane's repo — NOT a poll loop. ship-feature-lane's Stage 13 owns
|
||||
* the wait/retry decision (docs/LANES.md "Orchestration: what CCAM does NOT
|
||||
* the wait/retry decision (docs/LANES.md "Orchestration: what AgentHub does NOT
|
||||
* do"); this route only reports what `gh` currently knows. Read-only, no
|
||||
* same-origin guard, same reasoning as GET /:id/git.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file HTTP surface for cross-lane named locks (`server/lib/named-lock.js`).
|
||||
* Every route is single-shot — this layer never blocks waiting for a lock to
|
||||
* free up. The CLI (`ccam lock acquire`) owns the polling loop; CCAM's
|
||||
* free up. The CLI (`agenthub lock acquire`) owns the polling loop; AgentHub's
|
||||
* server-side primitives stay non-orchestrating, same as every other lane
|
||||
* route in this project.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
|
||||
+10
-10
@@ -74,26 +74,26 @@ function appendMetric(out, name, help, type, samples) {
|
||||
router.get("/", (_req, res) => {
|
||||
const out = [];
|
||||
|
||||
appendMetric(out, "ccam_up", "1 when the dashboard API is serving this scrape.", "gauge", [
|
||||
appendMetric(out, "agenthub_up", "1 when the dashboard API is serving this scrape.", "gauge", [
|
||||
{ value: 1 },
|
||||
]);
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_build_info",
|
||||
"agenthub_build_info",
|
||||
"Dashboard build info; the value is always 1, the version rides on the label.",
|
||||
"gauge",
|
||||
[{ labels: { version: APP_VERSION }, value: 1 }]
|
||||
);
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_process_uptime_seconds",
|
||||
"agenthub_process_uptime_seconds",
|
||||
"Uptime of the dashboard server process in seconds.",
|
||||
"gauge",
|
||||
[{ value: Math.round(process.uptime()) }]
|
||||
);
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_process_resident_memory_bytes",
|
||||
"agenthub_process_resident_memory_bytes",
|
||||
"Resident set size (RSS) of the dashboard server process in bytes.",
|
||||
"gauge",
|
||||
[{ value: process.memoryUsage().rss }]
|
||||
@@ -103,7 +103,7 @@ router.get("/", (_req, res) => {
|
||||
const sessionCounts = new Map(stmts.sessionStatusCounts.all().map((r) => [r.status, r.count]));
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_sessions",
|
||||
"agenthub_sessions",
|
||||
"Number of sessions by lifecycle status.",
|
||||
"gauge",
|
||||
SESSION_STATUSES.map((status) => ({
|
||||
@@ -116,7 +116,7 @@ router.get("/", (_req, res) => {
|
||||
const agentCounts = new Map(stmts.agentStatusCounts.all().map((r) => [r.status, r.count]));
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_agents",
|
||||
"agenthub_agents",
|
||||
"Number of agents (main + subagents) by status.",
|
||||
"gauge",
|
||||
AGENT_STATUSES.map((status) => ({ labels: { status }, value: agentCounts.get(status) || 0 }))
|
||||
@@ -125,7 +125,7 @@ router.get("/", (_req, res) => {
|
||||
// Event throughput (monotonic — a counter).
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_events_total",
|
||||
"agenthub_events_total",
|
||||
"Total hook and synthetic events recorded since the database was created.",
|
||||
"counter",
|
||||
[{ value: stmts.countEvents.get().count }]
|
||||
@@ -140,7 +140,7 @@ router.get("/", (_req, res) => {
|
||||
}
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_websocket_clients",
|
||||
"agenthub_websocket_clients",
|
||||
"Currently connected realtime (WebSocket) dashboard clients.",
|
||||
"gauge",
|
||||
[{ value: clients }]
|
||||
@@ -158,7 +158,7 @@ router.get("/", (_req, res) => {
|
||||
}
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_remote_sources",
|
||||
"agenthub_remote_sources",
|
||||
"Configured Remote Data Sources, split by auto-sync enabled state.",
|
||||
"gauge",
|
||||
[
|
||||
@@ -181,7 +181,7 @@ router.get("/", (_req, res) => {
|
||||
.get();
|
||||
appendMetric(
|
||||
out,
|
||||
"ccam_tokens_total",
|
||||
"agenthub_tokens_total",
|
||||
"Cumulative token usage across all sessions, by kind.",
|
||||
"counter",
|
||||
[
|
||||
|
||||
@@ -10,7 +10,7 @@ const { sameOriginGuard } = require("./run");
|
||||
const router = Router();
|
||||
|
||||
/** This server's own checkout root — server/routes/skills.js is two levels
|
||||
* under it (server/routes/), same computation bin/ccam.js's REPO_ROOT
|
||||
* under it (server/routes/), same computation bin/agenthub.js's REPO_ROOT
|
||||
* already does from its own location. */
|
||||
const REPO_ROOT = path.resolve(__dirname, "..", "..");
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ router.get("/skill-usage", (req, res) => {
|
||||
for (const m of mcpCalls) {
|
||||
// mcp__plugin_<plugin>_<server>__<tool>. Both halves may contain
|
||||
// underscores, so the split is only unambiguous when the plugin names its
|
||||
// server after itself — the overwhelmingly common case (`ccam_ccam`,
|
||||
// server after itself — the overwhelmingly common case (`agenthub_agenthub`,
|
||||
// `vercel_vercel`), which the dedupe below collapses back to one name.
|
||||
// ponytail: a plugin whose server has its own name reports under
|
||||
// "<plugin>_<server>"; read the real pairing off the MCP config if that
|
||||
|
||||
Reference in New Issue
Block a user