From 34c2b7f4ebdbfd8416b1e7a3a7392aadd8299c6e Mon Sep 17 00:00:00 2001 From: ZenchantLive Date: Mon, 2 Mar 2026 21:19:12 -0800 Subject: [PATCH] feat(cli): route runtime commands and add bd diagnostics to status --- bin/beadboard.js | 9 ++++- install/beadboard.mjs | 38 +++++++++++++++++++ package.json | 2 +- tests/cli/beadboard-bin-routing.test.ts | 20 ++++++++++ .../beadboard-launcher-runtime.test.ts | 12 +++++- 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/cli/beadboard-bin-routing.test.ts diff --git a/bin/beadboard.js b/bin/beadboard.js index 7fdbe9b..d43faed 100644 --- a/bin/beadboard.js +++ b/bin/beadboard.js @@ -5,7 +5,14 @@ const { spawnSync } = require('node:child_process'); const path = require('node:path'); const cliPath = path.resolve(__dirname, '../src/cli/beadboard-cli.ts'); -const result = spawnSync(process.execPath, ['--import', 'tsx', cliPath, ...process.argv.slice(2)], { +const launcherPath = path.resolve(__dirname, '../install/beadboard.mjs'); +const command = process.argv[2] || 'help'; +const launcherCommands = new Set(['start', 'open', 'status']); +const targetArgs = launcherCommands.has(command) + ? [launcherPath, ...process.argv.slice(2)] + : ['--import', 'tsx', cliPath, ...process.argv.slice(2)]; + +const result = spawnSync(process.execPath, targetArgs, { stdio: 'inherit', }); diff --git a/install/beadboard.mjs b/install/beadboard.mjs index af3be63..c9607a4 100644 --- a/install/beadboard.mjs +++ b/install/beadboard.mjs @@ -68,6 +68,43 @@ function getPort() { return Number.isFinite(value) ? value : 3000; } +function splitPathVariable(value) { + if (!value) return []; + return value.split(path.delimiter).map((entry) => entry.trim()).filter(Boolean); +} + +function resolveBdPath() { + const pathEntries = splitPathVariable(process.env.PATH || ''); + const candidates = + process.platform === 'win32' + ? ['bd.cmd', 'bd.exe', 'bd.ps1', 'bd.bat', 'bd'] + : ['bd']; + for (const dir of pathEntries) { + for (const candidate of candidates) { + const fullPath = path.join(dir, candidate); + if (fs.existsSync(fullPath)) { + return fullPath; + } + } + } + return null; +} + +function getBdDiagnostics() { + const beadsDir = path.resolve(process.cwd(), '.beads'); + const dbPath = path.join(beadsDir, 'beads.db'); + const bdPath = resolveBdPath(); + return { + available: Boolean(bdPath), + path: bdPath, + project: { + cwd: process.cwd(), + hasBeadsDir: fs.existsSync(beadsDir), + hasBeadsDb: fs.existsSync(dbPath), + }, + }; +} + function statusRequest(port) { return new Promise((resolve) => { const req = http.get( @@ -140,6 +177,7 @@ async function main() { runtimeRoot: runtime.runtimeRoot, installMode: runtime.installMode, shimTarget: runtime.shimTarget, + bd: getBdDiagnostics(), }; output(payload, json); process.exit(probe.running ? 0 : 1); diff --git a/package.json b/package.json index b6ee39b..ccce92b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start": "next start", "lint": "eslint .", "typecheck": "tsc --noEmit", - "test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/runtime-manager.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/install-legacy-migration.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts && node --import tsx --test tests/cli/beadboard-cli.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts", + "test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/runtime-manager.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/install-legacy-migration.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts && node --import tsx --test tests/cli/beadboard-cli.test.ts && node --import tsx --test tests/cli/beadboard-bin-routing.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts", "video": "remotion preview src/video/index.ts", "video:render": "remotion render src/video/index.ts Main out/video.mp4", "video:thumbnail": "remotion still src/video/index.ts Main out/thumbnail.png --frame=60" diff --git a/tests/cli/beadboard-bin-routing.test.ts b/tests/cli/beadboard-bin-routing.test.ts new file mode 100644 index 0000000..5ecf0cd --- /dev/null +++ b/tests/cli/beadboard-bin-routing.test.ts @@ -0,0 +1,20 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const binPath = path.resolve('bin/beadboard.js'); + +test('bin routes status --json to launcher runtime command', async () => { + try { + const { stdout } = await execFileAsync(process.execPath, [binPath, 'status', '--json']); + const payload = JSON.parse(stdout); + assert.equal(payload.command, 'status'); + } catch (error) { + const stdout = (error as { stdout?: string }).stdout || ''; + const payload = stdout ? JSON.parse(stdout) : null; + assert.equal(payload?.command, 'status'); + } +}); diff --git a/tests/scripts/beadboard-launcher-runtime.test.ts b/tests/scripts/beadboard-launcher-runtime.test.ts index bf0f23a..36a934e 100644 --- a/tests/scripts/beadboard-launcher-runtime.test.ts +++ b/tests/scripts/beadboard-launcher-runtime.test.ts @@ -8,8 +8,18 @@ const execFileAsync = promisify(execFile); const launcherPath = path.resolve('install/beadboard.mjs'); test('status --json reports runtime root and install mode', async () => { - const { stdout } = await execFileAsync(process.execPath, [launcherPath, 'status', '--json']); + let stdout = ''; + try { + ({ stdout } = await execFileAsync(process.execPath, [launcherPath, 'status', '--json'])); + } catch (error) { + stdout = (error as { stdout?: string }).stdout || ''; + } const payload = JSON.parse(stdout); assert.ok(payload.runtimeRoot); assert.ok(payload.installMode); + assert.ok(payload.bd); + assert.equal(typeof payload.bd.available, 'boolean'); + assert.ok('path' in payload.bd); + assert.ok(payload.bd.project); + assert.equal(typeof payload.bd.project.hasBeadsDir, 'boolean'); });