Files
nntrivi2001 57dc91585d 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.
2026-07-30 14:39:03 +07:00

136 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Clears all sessions, agents, events, and token usage from the database.
* Destructive — requires explicit confirmation.
*
* Usage:
* node scripts/clear-data.js --yes Wipe everything (irrevocable)
* node scripts/clear-data.js --yes --backup Snapshot DB to data/backups/ first
* node scripts/clear-data.js --demo-only --yes Delete only seed-fixture rows
* node scripts/clear-data.js Dry run — print counts, do nothing
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
let Database;
try {
Database = require("better-sqlite3");
} catch {
try {
Database = require("../server/compat-sqlite");
} catch {
console.error(
"Error: No SQLite backend available. Upgrade to Node.js 22+ or install build tools."
);
process.exit(1);
}
}
const fs = require("fs");
const path = require("path");
const { getDataDir } = require("../server/lib/claude-home");
const args = new Set(process.argv.slice(2));
const CONFIRMED = args.has("--yes") || args.has("-y");
const BACKUP = args.has("--backup");
const DEMO_ONLY = args.has("--demo-only");
const DRY_RUN = args.has("--dry-run") || !CONFIRMED;
// Mirror server/db.js resolution so we clear the same shared database the
// servers actually use (DASHBOARD_DB_PATH override → shared data dir).
const DB_PATH = process.env.DASHBOARD_DB_PATH || path.join(getDataDir(), "dashboard.db");
if (!fs.existsSync(DB_PATH)) {
console.error(`No database at ${DB_PATH} — nothing to clear.`);
process.exit(0);
}
const db = new Database(DB_PATH);
db.pragma("foreign_keys = OFF");
const counts = {
token_usage: db.prepare("SELECT COUNT(*) as n FROM token_usage").get()?.n ?? 0,
events: db.prepare("SELECT COUNT(*) as n FROM events").get()?.n ?? 0,
agents: db.prepare("SELECT COUNT(*) as n FROM agents").get()?.n ?? 0,
sessions: db.prepare("SELECT COUNT(*) as n FROM sessions").get()?.n ?? 0,
};
const totalRows = counts.sessions + counts.agents + counts.events + counts.token_usage;
console.log("");
console.log(`Target DB: ${DB_PATH}`);
console.log("Current row counts:");
console.log(` Sessions: ${counts.sessions.toLocaleString()}`);
console.log(` Agents: ${counts.agents.toLocaleString()}`);
console.log(` Events: ${counts.events.toLocaleString()}`);
console.log(` Tokens: ${counts.token_usage.toLocaleString()}`);
console.log("");
if (DRY_RUN) {
db.close();
console.log("⚠️ DRY RUN — no data was deleted.");
console.log("");
console.log("This is a DESTRUCTIVE operation. To actually wipe the database,");
console.log("re-run with --yes:");
console.log("");
console.log(" node scripts/clear-data.js --yes");
console.log("");
console.log("Strongly recommended: also pass --backup to snapshot the DB first:");
console.log("");
console.log(" node scripts/clear-data.js --yes --backup");
console.log("");
if (DEMO_ONLY) {
console.log("(--demo-only would delete only rows tagged as seed fixtures.)");
}
process.exit(0);
}
// Confirmed path — actually delete.
if (BACKUP) {
const backupDir = path.join(path.dirname(DB_PATH), "backups");
fs.mkdirSync(backupDir, { recursive: true });
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
const backupPath = path.join(backupDir, `dashboard.${stamp}.db`);
// Use SQLite VACUUM INTO for a consistent snapshot
db.exec(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`);
console.log(`📦 Backup written: ${backupPath}`);
}
if (DEMO_ONLY) {
// Delete only fixture rows. These IDs are stable across seed runs.
const FIXTURE_SESSION_IDS = [
"demo-solo-0001-0001-0001-000000000001",
"demo-nested-0001-0001-0001-000000000001",
];
const placeholders = FIXTURE_SESSION_IDS.map(() => "?").join(",");
const tx = db.transaction(() => {
db.prepare(`DELETE FROM events WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM agents WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM token_usage WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM sessions WHERE id IN (${placeholders})`).run(...FIXTURE_SESSION_IDS);
});
tx();
console.log(
`Cleared demo fixture rows (${FIXTURE_SESSION_IDS.length} sessions and their children).`
);
} else {
console.log(`⚠️ Wiping ${totalRows.toLocaleString()} rows…`);
db.exec("DELETE FROM token_usage; DELETE FROM events; DELETE FROM agents; DELETE FROM sessions;");
console.log("Database cleared.");
}
db.pragma("foreign_keys = ON");
db.close();
console.log("");
console.log(
"Tip: run `npm run import-history` to restore sessions from ~/.claude/ JSONL transcripts."
);