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:
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file Pre-build guard.
|
||||
*
|
||||
* Ensures the desktop bundle has everything it needs before TypeScript
|
||||
* compiles. Specifically:
|
||||
* 1. The root repo's node_modules exists (Express + friends).
|
||||
* 2. The client has been built (client/dist exists). In production mode the
|
||||
* Express server serves the SPA from client/dist; if it's missing the
|
||||
* DMG would ship a 404-only dashboard.
|
||||
* 3. Asset PNGs exist (or we leave a clear warning — icons can be
|
||||
* regenerated via scripts/build-icons.sh).
|
||||
* 4. The desktop-local better-sqlite3 native binary matches this machine's
|
||||
* CPU architecture. A prior `electron-builder --mac --x64/--arm64` build
|
||||
* rebuilds it for the target arch; left mismatched it breaks `desktop:dev`
|
||||
* and `desktop:test` with ERR_DLOPEN_FAILED. We rebuild it if so.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const { hasBetterSqliteBinary, printNativeDepHelp } = require("./preflight");
|
||||
|
||||
const desktopRoot = path.resolve(__dirname, "..");
|
||||
const repoRoot = path.resolve(__dirname, "..", "..");
|
||||
const clientDist = path.join(repoRoot, "client", "dist");
|
||||
const rootNodeModules = path.join(repoRoot, "node_modules");
|
||||
const assets = path.join(desktopRoot, "assets");
|
||||
|
||||
function run(cmd, args, opts = {}) {
|
||||
// On Windows `npm`/`npx` are `.cmd` shims that `spawnSync` can only launch
|
||||
// through a shell; without this it fails with ENOENT. POSIX is unaffected.
|
||||
const result = spawnSync(cmd, args, {
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
...opts,
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`${cmd} ${args.join(" ")} failed with exit ${result.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rootNodeModules)) {
|
||||
console.log("[prebuild] installing root dependencies…");
|
||||
run("npm", ["ci"], { cwd: repoRoot });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(clientDist) || !fs.existsSync(path.join(clientDist, "index.html"))) {
|
||||
console.log("[prebuild] building client (client/dist missing)…");
|
||||
run("npm", ["ci"], { cwd: path.join(repoRoot, "client") });
|
||||
run("npm", ["run", "build"], { cwd: repoRoot });
|
||||
}
|
||||
|
||||
const trayIcon = path.join(assets, "tray-icon-Template.png");
|
||||
if (!fs.existsSync(trayIcon)) {
|
||||
console.warn(
|
||||
"[prebuild] WARN: tray-icon-Template.png missing. Run `npm run build:icons` to regenerate from assets/icon.svg."
|
||||
);
|
||||
}
|
||||
|
||||
// Heal a better-sqlite3 native binary left built for the wrong CPU arch by a
|
||||
// prior `electron-builder --mac --x64/--arm64` run. Without this, `desktop:dev`
|
||||
// and `desktop:test` fail to load the module (ERR_DLOPEN_FAILED) until the
|
||||
// contributor manually re-runs `npm run desktop:install`.
|
||||
if (process.platform === "darwin") {
|
||||
const bsNode = path.join(
|
||||
desktopRoot,
|
||||
"node_modules",
|
||||
"better-sqlite3",
|
||||
"build",
|
||||
"Release",
|
||||
"better_sqlite3.node"
|
||||
);
|
||||
if (fs.existsSync(bsNode)) {
|
||||
const desc = spawnSync("file", ["-b", bsNode], { encoding: "utf8" }).stdout || "";
|
||||
// A universal binary works on both arches; only act on a clear mismatch.
|
||||
const universal = /universal/i.test(desc);
|
||||
const wrongArch =
|
||||
!universal &&
|
||||
((process.arch === "arm64" && !/arm64/.test(desc)) ||
|
||||
(process.arch === "x64" && !/x86_64/.test(desc)));
|
||||
if (wrongArch) {
|
||||
console.log(
|
||||
"[prebuild] better-sqlite3 is built for the wrong CPU arch (a prior DMG build left it that way) — rebuilding for this machine…"
|
||||
);
|
||||
run("npx", ["electron-builder", "install-app-deps"], { cwd: desktopRoot });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The embedded server `require`s better-sqlite3 at boot; without its native
|
||||
// binary the desktop app dies with a fatal dialog after compiling cleanly.
|
||||
// Catch it here (a build-time, copy-pasteable failure) rather than at runtime.
|
||||
if (!hasBetterSqliteBinary()) {
|
||||
printNativeDepHelp("The desktop-local better-sqlite3 native binary is missing.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("[prebuild] ok");
|
||||
Reference in New Issue
Block a user