feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file Starts the npm-managed Prometheus + Grafana stack (no Docker / no Brew).
|
||||
* Downloads binaries on first run if monitoring:setup has not been run yet.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
const fs = require("node:fs");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const path = require("node:path");
|
||||
const {
|
||||
PROMETHEUS_CONFIG,
|
||||
PROMETHEUS_DATA,
|
||||
GRAFANA_DATA,
|
||||
RUNTIME_PROVISIONING,
|
||||
prometheusBinary,
|
||||
prometheusServerArgs,
|
||||
PROMETHEUS_CONSOLES_URL,
|
||||
grafanaBinary,
|
||||
grafanaHome,
|
||||
grafanaAdminEnv,
|
||||
grafanaLoginLabel,
|
||||
binariesReady,
|
||||
} = require("./paths");
|
||||
const {
|
||||
readPid,
|
||||
isRunning,
|
||||
writePid,
|
||||
writeGrafanaProvisioning,
|
||||
spawnDetached,
|
||||
spawnForeground,
|
||||
waitForHttp,
|
||||
killProcess,
|
||||
PROMETHEUS_PID,
|
||||
GRAFANA_PID,
|
||||
PROMETHEUS_LOG,
|
||||
GRAFANA_LOG,
|
||||
} = require("./lib");
|
||||
|
||||
const detached = process.argv.includes("--detach") || process.argv.includes("-d");
|
||||
const foreground = process.argv.includes("--foreground") || process.argv.includes("-f");
|
||||
|
||||
async function ensureBinaries() {
|
||||
if (binariesReady()) return;
|
||||
console.log("Monitoring binaries not found — running setup…");
|
||||
const script = path.join(__dirname, "ensure-binaries.js");
|
||||
const result = spawnSync(process.execPath, [script], { stdio: "inherit" });
|
||||
if (result.status !== 0) process.exit(result.status || 1);
|
||||
}
|
||||
|
||||
function assertNotRunning() {
|
||||
for (const [file, label] of [
|
||||
[PROMETHEUS_PID, "Prometheus"],
|
||||
[GRAFANA_PID, "Grafana"],
|
||||
]) {
|
||||
const pid = readPid(file);
|
||||
if (isRunning(pid)) {
|
||||
console.error(`${label} is already running (pid ${pid}). Run: npm run monitoring:down`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startPrometheus() {
|
||||
fs.mkdirSync(PROMETHEUS_DATA, { recursive: true });
|
||||
const args = prometheusServerArgs(PROMETHEUS_CONFIG);
|
||||
const binary = prometheusBinary();
|
||||
if (detached || (!foreground && !process.stdout.isTTY)) {
|
||||
const pid = spawnDetached(binary, args, {}, PROMETHEUS_LOG);
|
||||
writePid(PROMETHEUS_PID, pid);
|
||||
return pid;
|
||||
}
|
||||
return spawnForeground(binary, args, {});
|
||||
}
|
||||
|
||||
function startGrafana() {
|
||||
fs.mkdirSync(GRAFANA_DATA, { recursive: true });
|
||||
writeGrafanaProvisioning();
|
||||
const env = {
|
||||
GF_PATHS_HOME: grafanaHome(),
|
||||
GF_PATHS_DATA: GRAFANA_DATA,
|
||||
GF_PATHS_PROVISIONING: RUNTIME_PROVISIONING,
|
||||
...grafanaAdminEnv(),
|
||||
};
|
||||
const args = ["server", "--homepath", grafanaHome()];
|
||||
const binary = grafanaBinary();
|
||||
if (detached || (!foreground && !process.stdout.isTTY)) {
|
||||
const pid = spawnDetached(binary, args, env, GRAFANA_LOG);
|
||||
writePid(GRAFANA_PID, pid);
|
||||
return pid;
|
||||
}
|
||||
return spawnForeground(binary, args, env);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await ensureBinaries();
|
||||
assertNotRunning();
|
||||
|
||||
console.log("Starting CCAM monitoring stack…");
|
||||
console.log(` Prometheus config: ${PROMETHEUS_CONFIG}`);
|
||||
console.log(` Grafana home: ${grafanaHome()}`);
|
||||
|
||||
if (detached || (!foreground && !process.stdout.isTTY)) {
|
||||
const promPid = startPrometheus();
|
||||
const grafPid = startGrafana();
|
||||
const promOk = await waitForHttp("http://127.0.0.1:9090/-/ready");
|
||||
const grafOk = await waitForHttp("http://127.0.0.1:3000/api/health");
|
||||
console.log("");
|
||||
console.log(
|
||||
`Prometheus http://localhost:9090 (pid ${promPid})${promOk ? "" : " [still starting]"}`
|
||||
);
|
||||
console.log(` CCAM console: ${PROMETHEUS_CONSOLES_URL}`);
|
||||
console.log(
|
||||
`Grafana http://localhost:3000 (pid ${grafPid}) ${grafanaLoginLabel()}${grafOk ? "" : " [still starting]"}`
|
||||
);
|
||||
console.log(
|
||||
"CCAM dashboards auto-provisioned (Overview, Sessions & Agents, Tokens & Events, Platform Health)."
|
||||
);
|
||||
console.log("Stop with: npm run monitoring:down");
|
||||
if (!promOk || !grafOk) {
|
||||
console.log(`Logs: ${PROMETHEUS_LOG} ${GRAFANA_LOG}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Foreground: Prometheus in background, Grafana in foreground (Grafana owns the tty).
|
||||
const promPid = spawnDetached(
|
||||
prometheusBinary(),
|
||||
prometheusServerArgs(PROMETHEUS_CONFIG),
|
||||
{},
|
||||
PROMETHEUS_LOG
|
||||
);
|
||||
writePid(PROMETHEUS_PID, promPid);
|
||||
console.log(`Prometheus running at http://localhost:9090 (pid ${promPid})`);
|
||||
console.log(` CCAM console: ${PROMETHEUS_CONSOLES_URL}`);
|
||||
console.log(
|
||||
`Grafana starting at http://localhost:3000 (${grafanaLoginLabel()}) — Ctrl+C stops both`
|
||||
);
|
||||
const graf = startGrafana();
|
||||
const shutdown = () => {
|
||||
killProcess(promPid);
|
||||
process.exit(0);
|
||||
};
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
graf.on("exit", (code) => {
|
||||
shutdown();
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`monitoring:start failed: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user