feat(cli): add global entrypoint with doctor/update/uninstall commands

This commit is contained in:
ZenchantLive 2026-03-02 20:44:07 -08:00
parent 7945ee8d3c
commit 4a98ab2976
4 changed files with 126 additions and 1 deletions

11
bin/beadboard.js Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env node
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)], {
stdio: 'inherit',
});
process.exit(result.status ?? 1);

View file

@ -3,13 +3,17 @@
"version": "0.1.0",
"private": true,
"license": "MIT",
"bin": {
"beadboard": "bin/beadboard.js",
"bb": "bin/beadboard.js"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"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/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/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"

88
src/cli/beadboard-cli.ts Normal file
View file

@ -0,0 +1,88 @@
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getRuntimePaths, resolveInstallHome } from '../lib/runtime-manager';
export type CliResult = {
ok: boolean;
command: string;
[key: string]: unknown;
};
function parseVersion(env: NodeJS.ProcessEnv): string {
const raw = (env.BB_RUNTIME_VERSION || env.npm_package_version || '0.1.0').trim();
return raw.startsWith('v') ? raw.slice(1) : raw;
}
export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.env): Promise<CliResult> {
const args = [...argv];
const asJson = args.includes('--json');
const yes = args.includes('--yes');
const command = args.find((arg) => !arg.startsWith('-')) || 'help';
const installHome = resolveInstallHome({ ...env, HOME: env.HOME || os.homedir() });
const version = parseVersion(env);
const runtime = getRuntimePaths(installHome, version);
if (command === 'doctor') {
return {
ok: true,
command,
json: asJson,
installMode: env.BB_INSTALL_MODE || 'npm-global-or-wrapper',
installHome,
runtimeRoot: runtime.runtimeRoot,
runtimeCurrentMetadata: runtime.runtimeCurrentMetadata,
shimDir: runtime.shimDir,
};
}
if (command === 'self-update') {
return {
ok: true,
command,
updated: false,
message: 'Self-update is not configured for this distribution yet. Reinstall with npm i -g beadboard when published.',
};
}
if (command === 'uninstall') {
if (!yes) {
return {
ok: false,
command,
error: 'Refusing uninstall without --yes',
};
}
await Promise.all([
fs.rm(runtime.runtimeBase, { recursive: true, force: true }),
fs.rm(runtime.shimDir, { recursive: true, force: true }),
]);
return {
ok: true,
command,
removed: [runtime.runtimeBase, runtime.shimDir],
};
}
return {
ok: true,
command: 'help',
usage: 'beadboard <doctor|self-update|uninstall> [--json] [--yes]',
};
}
async function main() {
const result = await runCli(process.argv.slice(2));
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
if (!result.ok) {
process.exitCode = 1;
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
void main();
}

View file

@ -0,0 +1,22 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { runCli } from '../../src/cli/beadboard-cli';
test('doctor returns structured install diagnostics', async () => {
const out = await runCli(['doctor', '--json']);
assert.equal(out.ok, true);
assert.ok(out.installMode);
});
test('self-update returns explicit placeholder result', async () => {
const out = await runCli(['self-update', '--json']);
assert.equal(out.ok, true);
assert.equal(out.command, 'self-update');
assert.equal(out.updated, false);
});
test('uninstall requires --yes', async () => {
const out = await runCli(['uninstall', '--json']);
assert.equal(out.ok, false);
assert.match(out.error, /--yes/);
});