diff --git a/cli/package-lock.json b/cli/package-lock.json new file mode 100644 index 00000000..3417fc66 --- /dev/null +++ b/cli/package-lock.json @@ -0,0 +1,84 @@ +{ + "name": "@openrouter/spawn", + "version": "0.2.82", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@openrouter/spawn", + "version": "0.2.82", + "dependencies": { + "@clack/prompts": "^1.0.0", + "picocolors": "^1.1.1" + }, + "bin": { + "spawn": "cli.js" + }, + "devDependencies": { + "@types/bun": "^1.3.8" + } + }, + "node_modules/@clack/core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.1.tgz", + "integrity": "sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.1.tgz", + "integrity": "sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==", + "dependencies": { + "@clack/core": "1.0.1", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@types/bun": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.9.tgz", + "integrity": "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==", + "dev": true, + "dependencies": { + "bun-types": "1.3.9" + } + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "dev": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/bun-types": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.9.tgz", + "integrity": "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true + } + } +} diff --git a/cli/src/commands.ts b/cli/src/commands.ts index 56c65538..f0d5d7d4 100644 --- a/cli/src/commands.ts +++ b/cli/src/commands.ts @@ -1663,6 +1663,38 @@ function printGroupedList( // ── Agent Info ───────────────────────────────────────────────────────────────── +function printAgentCloudsList( + sortedClouds: string[], + manifest: Manifest, + agentKey: string, + allClouds: string[], + credCount: number +): void { + console.log(); + console.log(pc.bold(`Available clouds:`) + pc.dim(` ${sortedClouds.length} of ${allClouds.length}`)); + if (credCount > 0) { + console.log(pc.dim(` ${credCount} cloud${credCount > 1 ? "s" : ""} with credentials detected (shown first)`)); + } + console.log(); + + if (sortedClouds.length === 0) { + console.log(pc.dim(" No implemented clouds yet.")); + console.log(); + return; + } + + const byType = groupByType(sortedClouds, (c) => manifest.clouds[c].type); + printGroupedList( + byType, + (c) => manifest.clouds[c].name, + (c) => { + const hint = `spawn ${agentKey} ${c}`; + return hasCloudCredentials(manifest.clouds[c].auth) ? `${hint} ${pc.green("(credentials detected)")}` : hint; + } + ); + console.log(); +} + export async function cmdAgentInfo(agent: string, preloadedManifest?: Manifest): Promise { const [manifest, agentKey] = preloadedManifest ? [preloadedManifest, agent] @@ -1691,29 +1723,7 @@ export async function cmdAgentInfo(agent: string, preloadedManifest?: Manifest): }); } - console.log(); - console.log(pc.bold(`Available clouds:`) + pc.dim(` ${sortedClouds.length} of ${allClouds.length}`)); - if (credCount > 0) { - console.log(pc.dim(` ${credCount} cloud${credCount > 1 ? "s" : ""} with credentials detected (shown first)`)); - } - console.log(); - - if (sortedClouds.length === 0) { - console.log(pc.dim(" No implemented clouds yet.")); - console.log(); - return; - } - - const byType = groupByType(sortedClouds, (c) => manifest.clouds[c].type); - printGroupedList( - byType, - (c) => manifest.clouds[c].name, - (c) => { - const hint = `spawn ${agentKey} ${c}`; - return hasCloudCredentials(manifest.clouds[c].auth) ? `${hint} ${pc.green("(credentials detected)")}` : hint; - } - ); - console.log(); + printAgentCloudsList(sortedClouds, manifest, agentKey, allClouds, credCount); } /** Print quick-start instructions showing credential status and example spawn command */ @@ -1859,11 +1869,8 @@ export async function cmdUpdate(): Promise { // ── Help ─────────────────────────────────────────────────────────────────────── -export function cmdHelp(): void { - console.log(` -${pc.bold("spawn")} -- Launch any AI coding agent on any cloud - -${pc.bold("USAGE")} +function getHelpUsageSection(): string { + return `${pc.bold("USAGE")} spawn Interactive agent + cloud picker spawn Launch agent on cloud directly spawn --dry-run Preview what would be provisioned (or -n) @@ -1883,9 +1890,11 @@ ${pc.bold("USAGE")} spawn clouds List all cloud providers spawn update Check for CLI updates spawn version Show version (or --version, -v) - spawn help Show this help message (or --help, -h) + spawn help Show this help message (or --help, -h)`; +} -${pc.bold("EXAMPLES")} +function getHelpExamplesSection(): string { + return `${pc.bold("EXAMPLES")} spawn ${pc.dim("# Pick interactively")} spawn claude sprite ${pc.dim("# Launch Claude Code on Sprite")} spawn aider hetzner ${pc.dim("# Launch Aider on Hetzner Cloud")} @@ -1899,9 +1908,11 @@ ${pc.bold("EXAMPLES")} spawn hetzner ${pc.dim("# Show which agents run on Hetzner")} spawn list ${pc.dim("# Browse history and pick one to rerun")} spawn list claude ${pc.dim("# Filter history by agent name")} - spawn matrix ${pc.dim("# See the full agent x cloud matrix")} + spawn matrix ${pc.dim("# See the full agent x cloud matrix")}`; +} -${pc.bold("AUTHENTICATION")} +function getHelpAuthSection(): string { + return `${pc.bold("AUTHENTICATION")} All agents use OpenRouter for LLM access. Get your API key at: ${pc.cyan("https://openrouter.ai/settings/keys")} @@ -1909,29 +1920,58 @@ ${pc.bold("AUTHENTICATION")} ${pc.dim("OPENROUTER_API_KEY")}=sk-or-v1-... spawn claude sprite Each cloud provider has its own auth requirements. - Run ${pc.cyan("spawn ")} to see setup instructions for a specific provider. + Run ${pc.cyan("spawn ")} to see setup instructions for a specific provider.`; +} -${pc.bold("INSTALL")} - curl -fsSL ${RAW_BASE}/cli/install.sh | bash +function getHelpInstallSection(): string { + return `${pc.bold("INSTALL")} + curl -fsSL ${RAW_BASE}/cli/install.sh | bash`; +} -${pc.bold("TROUBLESHOOTING")} +function getHelpTroubleshootingSection(): string { + return `${pc.bold("TROUBLESHOOTING")} ${pc.dim("*")} Script not found: Run ${pc.cyan("spawn matrix")} to verify the combination exists ${pc.dim("*")} Missing credentials: Run ${pc.cyan("spawn ")} to see setup instructions ${pc.dim("*")} Update issues: Try ${pc.cyan("spawn update")} or reinstall manually ${pc.dim("*")} Garbled unicode: Set ${pc.cyan("SPAWN_NO_UNICODE=1")} for ASCII-only output ${pc.dim("*")} Missing unicode over SSH: Set ${pc.cyan("SPAWN_UNICODE=1")} to force unicode on - ${pc.dim("*")} Slow startup: Set ${pc.cyan("SPAWN_NO_UPDATE_CHECK=1")} to skip auto-update + ${pc.dim("*")} Slow startup: Set ${pc.cyan("SPAWN_NO_UPDATE_CHECK=1")} to skip auto-update`; +} -${pc.bold("ENVIRONMENT VARIABLES")} +function getHelpEnvVarsSection(): string { + return `${pc.bold("ENVIRONMENT VARIABLES")} ${pc.cyan("OPENROUTER_API_KEY")} OpenRouter API key (all agents require this) ${pc.cyan("SPAWN_NO_UPDATE_CHECK=1")} Skip auto-update check on startup ${pc.cyan("SPAWN_NO_UNICODE=1")} Force ASCII output (no unicode symbols) ${pc.cyan("SPAWN_UNICODE=1")} Force Unicode output (override auto-detection) ${pc.cyan("SPAWN_HOME")} Override spawn data directory (default: ~/.spawn) - ${pc.cyan("SPAWN_DEBUG=1")} Show debug output (unicode detection, etc.) - -${pc.bold("MORE INFO")} - Repository: https://github.com/${REPO} - OpenRouter: https://openrouter.ai -`); + ${pc.cyan("SPAWN_DEBUG=1")} Show debug output (unicode detection, etc.)`; +} + +function getHelpFooterSection(): string { + return `${pc.bold("MORE INFO")} + Repository: https://github.com/${REPO} + OpenRouter: https://openrouter.ai`; +} + +export function cmdHelp(): void { + const sections = [ + "", + `${pc.bold("spawn")} -- Launch any AI coding agent on any cloud`, + "", + getHelpUsageSection(), + "", + getHelpExamplesSection(), + "", + getHelpAuthSection(), + "", + getHelpInstallSection(), + "", + getHelpTroubleshootingSection(), + "", + getHelpEnvVarsSection(), + "", + getHelpFooterSection(), + ]; + console.log(sections.join("\n")); }