feat(cli): route runtime commands and add bd diagnostics to status

This commit is contained in:
ZenchantLive 2026-03-02 21:19:12 -08:00
parent 654c8eb4c8
commit 34c2b7f4eb
5 changed files with 78 additions and 3 deletions

View file

@ -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',
});

View file

@ -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);

View file

@ -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"

View file

@ -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');
}
});

View file

@ -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');
});