--- phase: 35-npx-buildthis-cli plan: 01 type: execute wave: 1 depends_on: [] files_modified: - packages/buildthis/package.json - packages/buildthis/tsconfig.json - packages/buildthis/esbuild.config.mjs - packages/buildthis/vitest.config.ts - packages/buildthis/src/index.ts - packages/buildthis/src/bootstrap.ts - packages/buildthis/src/hardware.ts - packages/buildthis/src/banner.ts - packages/buildthis/src/__tests__/bootstrap.test.ts - packages/buildthis/src/__tests__/hardware.test.ts autonomous: true requirements: - CLI-01 - CLI-02 must_haves: truths: - "Running `npx buildthis` on a machine with Nexus running opens the browser to the Nexus UI" - "Running `npx buildthis` on a machine without Nexus shows hardware tier and provider recommendations" - "CLI detects Apple Silicon / GPU / CPU-only with the same logic as the web onboarding" - "Non-TTY environments get a clean non-interactive output and exit 0" artifacts: - path: "packages/buildthis/package.json" provides: "npm package config with bin.buildthis pointing to dist/index.js" contains: "\"buildthis\"" - path: "packages/buildthis/src/index.ts" provides: "CLI entry point with commander setup" contains: "program" - path: "packages/buildthis/src/bootstrap.ts" provides: "Two-path bootstrap logic: probe running instance vs guide install" exports: ["bootstrap"] - path: "packages/buildthis/src/hardware.ts" provides: "Hardware tier detection matching server/src/services/hardware.ts" exports: ["detectHardware", "HardwareTier"] - path: "packages/buildthis/src/__tests__/hardware.test.ts" provides: "Unit tests for hardware detection tiers" min_lines: 30 - path: "packages/buildthis/src/__tests__/bootstrap.test.ts" provides: "Unit tests for probe and bootstrap paths" min_lines: 30 key_links: - from: "packages/buildthis/src/index.ts" to: "packages/buildthis/src/bootstrap.ts" via: "import and call bootstrap()" pattern: "import.*bootstrap" - from: "packages/buildthis/src/bootstrap.ts" to: "packages/buildthis/src/hardware.ts" via: "import detectHardware for fresh-install path" pattern: "import.*detectHardware" - from: "packages/buildthis/src/bootstrap.ts" to: "http://127.0.0.1:3100/api/health" via: "fetch probe for running-instance detection" pattern: "api/health" --- Create the `packages/buildthis/` npm package — a standalone CLI bootstrapper that a developer runs via `npx buildthis` on a fresh machine. Two paths: (1) if Nexus is already running on port 3100, open the browser; (2) if not, detect hardware tier and present provider recommendations matching the web onboarding, then print next-step install instructions. Purpose: Final phase of v1.5 — gives developers a single command to get started with Nexus. Output: Working `buildthis` package with tests, buildable via esbuild. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/35-npx-buildthis-cli/35-RESEARCH.md @cli/esbuild.config.mjs @cli/package.json @cli/tsconfig.json @cli/src/utils/banner.ts @server/src/services/hardware.ts @pnpm-workspace.yaml Task 1: Scaffold buildthis package with hardware detection and tests packages/buildthis/package.json, packages/buildthis/tsconfig.json, packages/buildthis/esbuild.config.mjs, packages/buildthis/vitest.config.ts, packages/buildthis/src/hardware.ts, packages/buildthis/src/banner.ts, packages/buildthis/src/__tests__/hardware.test.ts @server/src/services/hardware.ts — copy-adapt the detection logic (Apple Silicon via os.cpus + platform, GPU via si.graphics with 3s timeout, cpu_only fallback) @cli/esbuild.config.mjs — mirror the esbuild pattern (banner shebang, node20 target, esm format, external native deps) @cli/package.json — reference dependency versions for consistency @cli/tsconfig.json — mirror the tsconfig extends pattern @packages/branding/vitest.config.ts — mirror vitest config pattern - detectHardware() returns { tier: "apple_silicon", totalGb } when process.platform === "darwin" AND os.cpus()[0].model starts with "Apple" - detectHardware() returns { tier: "gpu", totalGb, gpuName, gpuVramGb } when si.graphics() resolves with a controller having vram >= 4096 MB within 3 seconds - detectHardware() returns { tier: "cpu_only", totalGb } when si.graphics() throws or times out after 3 seconds - detectHardware() returns { tier: "cpu_only", totalGb } when GPU VRAM is below 4 GB 1. Create `packages/buildthis/package.json`: - name: "buildthis", version: "0.1.0", type: "module" - bin: { "buildthis": "./dist/index.js" } - files: ["dist"] - publishConfig: { access: "public" } - engines: { node: ">=20" } - scripts: dev ("tsx src/index.ts"), build (esbuild + chmod), clean, typecheck, test ("vitest run") - dependencies: @clack/prompts ^0.10.0, commander ^13.1.0, picocolors ^1.1.1, open ^11.0.0, systeminformation 5 - devDependencies: @types/node ^22.12.0, tsx ^4.19.2, typescript ^5.7.3, vitest ^2.0.0, esbuild (latest) 2. Create `packages/buildthis/tsconfig.json` extending `../../tsconfig.base.json` with outDir "dist", rootDir "src", include ["src"]. 3. Create `packages/buildthis/esbuild.config.mjs` — simple config: - entryPoints: ["src/index.ts"], bundle: true, platform: "node", target: "node20", format: "esm" - outfile: "dist/index.js" - banner: { js: "#!/usr/bin/env node" } - external: ["systeminformation", "open", "@clack/prompts", "commander", "picocolors"] - treeShaking: true, sourcemap: true 4. Create `packages/buildthis/vitest.config.ts` matching the branding package pattern: defineConfig with test.include ["src/**/*.test.ts"]. 5. Create `packages/buildthis/src/hardware.ts`: - Export type HardwareTier = "gpu" | "apple_silicon" | "cpu_only" - Export interface HardwareResult { tier: HardwareTier; totalGb: number; gpuName?: string; gpuVramGb?: number } - Export async function detectHardware(): Promise - Apple Silicon path: process.platform === "darwin" && os.cpus()[0]?.model?.startsWith("Apple") -> return apple_silicon - GPU path: Promise.race([si.graphics(), 3s timeout]), check controllers[0].vram >= 4096 (MB), return gpu with gpuName + gpuVramGb - Fallback: cpu_only - Copy logic from server/src/services/hardware.ts but simplified (no cache, no freeGb/usableGb — not needed for CLI display) 6. Create `packages/buildthis/src/banner.ts`: - Export function printBuildthisBanner(): void - Print a compact "buildthis" ASCII art header using picocolors (simpler than the full Nexus banner — just the word "buildthis" stylized, or reuse NEXUS_ART from cli/src/utils/banner.ts with a different tagline like "Bootstrap your AI workspace") 7. Create `packages/buildthis/src/__tests__/hardware.test.ts`: - Mock os.cpus, os.totalmem, process.platform, si.graphics - Test apple_silicon detection (darwin + Apple M4) - Test gpu detection (linux + NVIDIA with 8GB VRAM) - Test cpu_only fallback (si.graphics timeout) - Test cpu_only when VRAM < 4GB 8. Run `pnpm install` from repo root to link the new workspace package. 9. Run `npx vitest run packages/buildthis/src/__tests__/hardware.test.ts` — all tests pass. cd /opt/nexus && npx vitest run packages/buildthis/src/__tests__/hardware.test.ts - grep -q '"buildthis"' packages/buildthis/package.json - grep -q 'dist/index.js' packages/buildthis/package.json - grep -q 'HardwareTier' packages/buildthis/src/hardware.ts - grep -q 'detectHardware' packages/buildthis/src/hardware.ts - grep -q 'apple_silicon' packages/buildthis/src/__tests__/hardware.test.ts - grep -q 'cpu_only' packages/buildthis/src/__tests__/hardware.test.ts - grep -q 'banner' packages/buildthis/esbuild.config.mjs - test -f packages/buildthis/vitest.config.ts Package scaffolded with package.json, tsconfig, esbuild config, vitest config, hardware detection module, banner module, and passing hardware detection unit tests covering all three tiers. Task 2: Implement bootstrap logic, CLI entry point, and integration tests packages/buildthis/src/bootstrap.ts, packages/buildthis/src/index.ts, packages/buildthis/src/__tests__/bootstrap.test.ts @packages/buildthis/src/hardware.ts — use detectHardware() for fresh-install path (created in Task 1) @packages/buildthis/src/banner.ts — call printBuildthisBanner() at startup (created in Task 1) @server/src/services/hardware.ts — reference HardwareInfo interface for the server probe path @cli/src/commands/onboard.ts — reference the health-check pattern and non-TTY guard (line ~395) - probeRunningInstance(3100) returns true when fetch to http://127.0.0.1:3100/api/health responds 200 within 2 seconds - probeRunningInstance(3100) returns false when fetch throws (connection refused) or times out - bootstrap() in TTY mode with running instance: opens browser and exits - bootstrap() in TTY mode without running instance: detects hardware, shows provider options via clack select, prints next-step instructions - bootstrap() in non-TTY mode: prints non-interactive instructions and exits 0 - Provider options include "Local AI (Ollama)" only when tier is NOT cpu_only 1. Create `packages/buildthis/src/bootstrap.ts`: - Export async function probeRunningInstance(port: number): Promise - fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(2000) }) - Return res.ok on success, false on any error - Export async function bootstrap(): Promise - Import printBuildthisBanner from ./banner.ts, call it first - Non-TTY guard: if !process.stdin.isTTY || !process.stdout.isTTY, print "Run this command in an interactive terminal" with the URL and exit 0 - const port = 3100 (hardcoded default — no config reading needed) - Path 1 (running): if await probeRunningInstance(port), use clack spinner "Opening Nexus...", import open, await open(`http://127.0.0.1:${port}`), log success, return - Path 2 (fresh install): - Use clack spinner "Detecting hardware..." - const hw = await detectHardware() - p.log.info with hardware tier and RAM - If tier is apple_silicon: note "Apple Silicon detected — unified memory, runs entirely on your machine" - If tier is gpu: note GPU name and VRAM - If tier is cpu_only: note "Cloud AI recommended for best experience" - p.select for provider choice: - puter: "Puter -- free, zero-config" (hint: "No API key needed") - google: "Google -- Gemini free tier" (hint: "Sign in with Google") - apikey: "API key -- subscription provider" (hint: "OpenAI, Anthropic, Groq") - local (only if tier !== "cpu_only"): "Local AI (Ollama)" (hint: "Private, offline") - skip: "Skip for now" - Handle p.isCancel(provider) — call p.cancel("Cancelled") and process.exit(0) - Print next-step instructions based on selection: - All paths: "To install and start Nexus:\n npx paperclipai@latest onboard" - If provider is "local": also print "Make sure Ollama is installed: https://ollama.com" - If provider is "puter": also print "Puter provides free AI — no setup needed" - p.outro with "Happy building!" 2. Create `packages/buildthis/src/index.ts`: - #!/usr/bin/env node (handled by esbuild banner, but add comment) - Import { program } from "commander" - Import { bootstrap } from "./bootstrap.js" - program.name("buildthis").description("Bootstrap your AI workspace").version("0.1.0") - program.action(async () => { await bootstrap(); }) - program.parse() 3. Create `packages/buildthis/src/__tests__/bootstrap.test.ts`: - Mock global fetch for probeRunningInstance tests - Test probeRunningInstance returns true on 200 response - Test probeRunningInstance returns false on connection error - Test probeRunningInstance returns false on timeout - Test that provider options array includes "local" when tier is not cpu_only - Test that provider options array excludes "local" when tier is cpu_only - Export a helper function getProviderOptions(tier: HardwareTier) from bootstrap.ts to make this testable — extract the options-building logic into a pure function 4. Run the full test suite: `npx vitest run packages/buildthis/src/__tests__/` 5. Run `pnpm --filter buildthis build` to verify esbuild produces dist/index.js with shebang. 6. Verify the built file: head -1 packages/buildthis/dist/index.js should show "#!/usr/bin/env node". cd /opt/nexus && npx vitest run packages/buildthis/src/__tests__/ && pnpm --filter buildthis build && head -1 packages/buildthis/dist/index.js | grep -q "#!/usr/bin/env node" - grep -q 'probeRunningInstance' packages/buildthis/src/bootstrap.ts - grep -q 'api/health' packages/buildthis/src/bootstrap.ts - grep -q 'detectHardware' packages/buildthis/src/bootstrap.ts - grep -q 'isTTY' packages/buildthis/src/bootstrap.ts - grep -q 'getProviderOptions' packages/buildthis/src/bootstrap.ts - grep -q 'cpu_only' packages/buildthis/src/bootstrap.ts - grep -q 'npx paperclipai' packages/buildthis/src/bootstrap.ts - grep -q 'program' packages/buildthis/src/index.ts - grep -q 'bootstrap' packages/buildthis/src/index.ts - grep -q 'probeRunningInstance' packages/buildthis/src/__tests__/bootstrap.test.ts - grep -q 'getProviderOptions' packages/buildthis/src/__tests__/bootstrap.test.ts - test -f packages/buildthis/dist/index.js CLI entry point, bootstrap logic with two-path flow (running instance vs fresh install), provider selection with hardware-aware options, and all unit tests pass. Build produces dist/index.js with correct shebang. 1. All tests pass: `npx vitest run packages/buildthis/src/__tests__/` 2. Build succeeds: `pnpm --filter buildthis build` 3. Built file has shebang: `head -1 packages/buildthis/dist/index.js` starts with `#!/usr/bin/env node` 4. TypeScript compiles: `pnpm --filter buildthis typecheck` 5. Package.json has correct bin field: `grep buildthis packages/buildthis/package.json` - `packages/buildthis/` is a complete npm package with bin entry, esbuild config, and vitest config - Hardware detection covers all three tiers (apple_silicon, gpu, cpu_only) with 3-second GPU timeout - Bootstrap probes port 3100, opens browser if running, guides install if not - Provider options exclude local AI for cpu_only tier - Non-TTY guard prevents hanging in CI/piped environments - All unit tests pass, build produces valid CLI binary with shebang After completion, create `.planning/phases/35-npx-buildthis-cli/35-01-SUMMARY.md`