nexus/packages/buildthis/src/bootstrap.ts
Nexus Dev 1262e4a243 feat(35-01): buildthis CLI package — hardware detection + bootstrap
Standalone npm package at packages/buildthis/. Probes running Nexus
instance, opens browser if found, guides install with hardware-aware
provider recommendations if not. 14 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:49 +00:00

145 lines
4.1 KiB
TypeScript

import * as p from "@clack/prompts";
import pc from "picocolors";
import { detectHardware, type HardwareTier } from "./hardware.js";
import { printBuildthisBanner } from "./banner.js";
/**
* Probe whether a Nexus instance is running on the given port.
* Returns true if the health endpoint responds with HTTP 200 within 2 seconds.
*/
export async function probeRunningInstance(port: number): Promise<boolean> {
try {
const res = await fetch(`http://127.0.0.1:${port}/api/health`, {
signal: AbortSignal.timeout(2000),
});
return res.ok;
} catch {
return false;
}
}
/**
* Build the provider selection options based on hardware tier.
* Excludes local AI for cpu_only machines (too slow for practical use).
*/
export function getProviderOptions(tier: HardwareTier): Array<{ value: string; label: string; hint: string }> {
const options: Array<{ value: string; label: string; hint: string }> = [
{
value: "puter",
label: "Puter — free, zero-config",
hint: "No API key needed",
},
{
value: "google",
label: "Google — Gemini free tier",
hint: "Sign in with Google",
},
{
value: "apikey",
label: "API key — subscription provider",
hint: "OpenAI, Anthropic, Groq",
},
];
if (tier !== "cpu_only") {
options.push({
value: "local",
label: "Local AI (Ollama)",
hint: "Private, offline",
});
}
options.push({
value: "skip",
label: "Skip for now",
hint: "Set up a provider later",
});
return options;
}
/**
* Main bootstrap function.
*
* Two paths:
* 1. Nexus is already running on port 3100 — open the browser
* 2. Fresh install — detect hardware, show provider options, print next steps
*/
export async function bootstrap(): Promise<void> {
printBuildthisBanner();
const port = 3100;
// Non-TTY guard: prevent hanging in CI/piped environments
if (!process.stdin.isTTY || !process.stdout.isTTY) {
console.log(pc.bold("Run this command in an interactive terminal."));
console.log("");
console.log(" To open Nexus in your browser:");
console.log(pc.cyan(` http://127.0.0.1:${port}`));
console.log("");
console.log(" To install Nexus on this machine:");
console.log(pc.cyan(" npx paperclipai@latest onboard"));
process.exit(0);
return;
}
// Path 1: Running instance detected
const isRunning = await probeRunningInstance(port);
if (isRunning) {
const s = p.spinner();
s.start("Opening Nexus...");
const { default: open } = await import("open");
await open(`http://127.0.0.1:${port}`);
s.stop(pc.green("Nexus opened in your browser"));
return;
}
// Path 2: Fresh install — detect hardware and guide setup
const s = p.spinner();
s.start("Detecting hardware...");
const hw = await detectHardware();
s.stop("Hardware detected");
// Display hardware info
if (hw.tier === "apple_silicon") {
p.log.info(
`${pc.bold("Apple Silicon detected")} — unified memory (${hw.totalGb} GB), runs entirely on your machine`
);
} else if (hw.tier === "gpu") {
p.log.info(
`${pc.bold("GPU detected")}: ${hw.gpuName} (${hw.gpuVramGb} GB VRAM) — ${hw.totalGb} GB RAM`
);
} else {
p.log.info(
`${pc.bold("CPU-only machine")} (${hw.totalGb} GB RAM) — cloud AI recommended for best experience`
);
}
// Provider selection
const provider = await p.select({
message: "Which AI provider would you like to use?",
options: getProviderOptions(hw.tier),
});
if (p.isCancel(provider)) {
p.cancel("Cancelled");
process.exit(0);
return;
}
// Print next-step instructions
console.log("");
console.log(pc.bold("To install and start Nexus:"));
console.log(pc.cyan(" npx paperclipai@latest onboard"));
console.log("");
if (provider === "local") {
console.log(pc.dim("Make sure Ollama is installed: https://ollama.com"));
console.log("");
} else if (provider === "puter") {
console.log(pc.dim("Puter provides free AI — no setup needed"));
console.log("");
}
p.outro(pc.bold("Happy building!"));
}