UNPKG

framework

Version:

The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.

91 lines 4.62 kB
import { execFile } from 'node:child_process'; import { DRIVER_SPECS } from './driver-cli.js'; function defaultProbe(bin, args) { return new Promise(resolvePromise => { execFile(bin, [...args], { timeout: 10_000 }, (err, stdout, stderr) => { resolvePromise({ ok: !err, output: `${String(stdout)}${String(stderr)}` }); }); }); } /** Whether this process runs as root. Windows has no uid, and is never root by this test. */ function runningAsRoot() { return process.getuid?.() === 0; } /** * Run the preflight checks: Node is implicit (we are running), the picked driver's CLI must be * installed, and it must be logged in. Returns every check plus an overall `ok`, which counts * failures only, so a warning travels without blocking anything. * * The auth probe is skipped when the CLI is missing: a binary that does not resolve cannot * answer a second question, and one "not found" beats two lines saying the same thing. */ export async function preflight(opts = {}) { const agent = opts.driver ?? 'claude'; const spec = DRIVER_SPECS[agent]; const bin = opts.bin ?? spec.bin; const probe = opts.probe ?? defaultProbe; const checks = [{ name: 'node', ok: true, detail: process.version }]; const cli = await probe(bin, ['--version']); checks.push(cli.ok ? { name: agent, ok: true, detail: cli.output.trim() || 'installed' } : { name: agent, ok: false, detail: `\`${bin}\` not found — ${spec.installHint}` }); if (cli.ok) { const loggedIn = spec.auth.loggedIn(await probe(bin, spec.auth.args)); // Only an explicit no fails. `undefined` means the CLI would not say, and an agent that might // work is worth more than a warning we cannot stand behind. if (loggedIn === false) { checks.push({ name: `${agent} auth`, ok: false, detail: `\`${bin}\` is not logged in. Run \`${spec.auth.fix}\`, then start the session again.`, }); } else if (loggedIn === true) { checks.push({ name: `${agent} auth`, ok: true, detail: 'logged in' }); } } // The publish half (#1419): an armed PR/merge fires `gh` at the finish, hours after the Start // that could have said it will not work. Warnings, not failures — the session's own work needs // no gh, and the push rung is plain git, so the agent is worth starting either way. if (opts.publish) { const ghCli = await probe('gh', ['--version']); if (!ghCli.ok) { checks.push({ name: 'gh', ok: true, warn: true, detail: '`gh` not found — the armed PR cannot be opened, so publishing stops at the pushed branch. Install the GitHub CLI (`brew install gh`, or https://cli.github.com) and run `gh auth login`.', }); } else if (!(await probe('gh', ['auth', 'status'])).ok) { // `gh auth status` exits non-zero when no host is logged in, and says so on stderr — // which the probe folds into `output`, though the exit code alone decides here. checks.push({ name: 'gh auth', ok: true, warn: true, detail: '`gh` is not logged in — the armed PR cannot be opened, so publishing stops at the pushed branch. Run `gh auth login`, then start the session.', }); } } // Root is the sudo-HOME trap (#1323), and it fails every agent identically: `sudo` moves `HOME`, the CLI // looks for its credentials under root's home instead of the user's, finds none, and every agent // dies the same way with the same empty log. Named here because the failure it causes says // nothing at all about its cause. if ((opts.isRoot ?? runningAsRoot)()) { const sudoUser = opts.sudoUser !== undefined ? opts.sudoUser : process.env.SUDO_USER; const asUser = sudoUser ? `\`${sudoUser}\`` : 'your own user'; checks.push({ name: 'root', ok: true, warn: true, detail: `running as root, so the agent looks for credentials under root's home and will not find yours. Restart the daemon as ${asUser}, without \`sudo\`.`, }); } return { ok: checks.every(c => c.ok), checks }; } /** The failing checks, one line each, the way a user is told what to fix. */ export function preflightProblems(result) { return result.checks.filter(c => !c.ok).map(c => `${c.name}: ${c.detail}`); } //# sourceMappingURL=preflight.js.map