diff --git a/cli/package.json b/cli/package.json index 69d3c2d7..eadf1756 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.2.14", + "version": "0.2.15", "type": "module", "bin": { "spawn": "cli.js" diff --git a/cli/src/commands.ts b/cli/src/commands.ts index d0ef16f9..f1f0d1c2 100644 --- a/cli/src/commands.ts +++ b/cli/src/commands.ts @@ -459,7 +459,7 @@ export async function cmdClouds(): Promise { console.log(` ${pc.green(key.padEnd(NAME_COLUMN_WIDTH))} ${c.name.padEnd(NAME_COLUMN_WIDTH)} ${pc.dim(`${implCount} agent${implCount !== 1 ? "s" : ""} ${c.description}`)}`); } console.log(); - console.log(pc.dim(` Run ${pc.cyan("spawn ")} to launch.`)); + console.log(pc.dim(` Run ${pc.cyan("spawn ")} for details, or ${pc.cyan("spawn ")} to launch.`)); console.log(); } @@ -491,6 +491,53 @@ export async function cmdAgentInfo(agent: string): Promise { console.log(); } +// ── Cloud Info ───────────────────────────────────────────────────────────────── + +// Validate and load cloud - consolidates the pattern used by cmdCloudInfo +async function validateAndGetCloud(cloud: string): Promise<[manifest: Manifest, cloudKey: string]> { + try { + validateIdentifier(cloud, "Cloud name"); + } catch (err) { + p.log.error(getErrorMessage(err)); + process.exit(1); + } + + validateNonEmptyString(cloud, "Cloud name", "spawn clouds"); + const manifest = await loadManifestWithSpinner(); + validateCloud(manifest, cloud); + + return [manifest, cloud]; +} + +export async function cmdCloudInfo(cloud: string): Promise { + const [manifest, cloudKey] = await validateAndGetCloud(cloud); + + const c = manifest.clouds[cloudKey]; + console.log(); + console.log(`${pc.bold(c.name)} ${pc.dim("--")} ${c.description}`); + if (c.notes) { + console.log(pc.dim(` ${c.notes}`)); + } + console.log(); + console.log(pc.bold("Available agents:")); + console.log(); + + let found = false; + for (const agent of agentKeys(manifest)) { + const status = matrixStatus(manifest, cloudKey, agent); + if (status === "implemented") { + const a = manifest.agents[agent]; + console.log(` ${pc.green(agent.padEnd(NAME_COLUMN_WIDTH))} ${a.name.padEnd(NAME_COLUMN_WIDTH)} ${pc.dim("spawn " + agent + " " + cloudKey)}`); + found = true; + } + } + + if (!found) { + console.log(pc.dim(" No implemented agents yet.")); + } + console.log(); +} + // ── Update ───────────────────────────────────────────────────────────────────── export async function cmdUpdate(): Promise { @@ -549,6 +596,7 @@ ${pc.bold("USAGE")} spawn --prompt-file Execute agent with prompt from file spawn Show available clouds for agent + spawn Show available agents for cloud spawn list Full matrix table spawn agents List all agents with descriptions spawn clouds List all cloud providers @@ -566,6 +614,7 @@ ${pc.bold("EXAMPLES")} spawn claude sprite --prompt-file instructions.txt ${pc.dim("# Read prompt from file")} spawn claude ${pc.dim("# Show which clouds support Claude")} + spawn hetzner ${pc.dim("# Show which agents run on Hetzner")} spawn list ${pc.dim("# See the full agent x cloud matrix")} ${pc.bold("AUTHENTICATION")} diff --git a/cli/src/index.ts b/cli/src/index.ts index 5cb76276..3d7be397 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -6,11 +6,13 @@ import { cmdAgents, cmdClouds, cmdAgentInfo, + cmdCloudInfo, cmdUpdate, cmdHelp, } from "./commands.js"; import pkg from "../package.json" with { type: "json" }; import { checkForUpdates } from "./update-check.js"; +import { loadManifest } from "./manifest.js"; const VERSION = pkg.version; @@ -56,7 +58,13 @@ const HELP_FLAGS = ["--help", "-h", "help"]; async function handleDefaultCommand(agent: string, cloud: string | undefined, prompt?: string): Promise { // Handle "spawn --help" / "spawn -h" / "spawn help" if (cloud && HELP_FLAGS.includes(cloud)) { - await cmdAgentInfo(agent); + // Could be "spawn --help" or "spawn --help" + const manifest = await loadManifest(); + if (!manifest.agents[agent] && manifest.clouds[agent]) { + await cmdCloudInfo(agent); + } else { + await cmdAgentInfo(agent); + } return; } if (cloud) { @@ -67,7 +75,13 @@ async function handleDefaultCommand(agent: string, cloud: string | undefined, pr console.error(`\nUsage: spawn ${agent} --prompt "your prompt here"`); process.exit(1); } - await cmdAgentInfo(agent); + // "spawn " with no second arg: show agent info, or cloud info if it's a cloud name + const manifest = await loadManifest(); + if (!manifest.agents[agent] && manifest.clouds[agent]) { + await cmdCloudInfo(agent); + } else { + await cmdAgentInfo(agent); + } } }