57dc91585d
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.
155 lines
4.6 KiB
JavaScript
155 lines
4.6 KiB
JavaScript
#!/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);
|
|
});
|