fix: improve spawn list and clouds UX (#491)

- Show prompt preview in `spawn list` history for prompted runs
- Include prompt in rerun hint when last spawn used --prompt
- Show auth requirements in `spawn clouds` listing
- Change swap detection from warn to info (auto-correcting, not a warning)
- Update `spawn clouds` help text: "for setup instructions" instead of "for details"

Bump CLI version to 0.2.46.

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-02-11 10:34:28 -08:00 committed by GitHub
parent bc89a2821b
commit fa516bad89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 19 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.2.45",
"version": "0.2.46",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -359,8 +359,8 @@ describe("Commands Error Paths", () => {
await expect(cmdRun("sprite", "claude")).rejects.toThrow("process.exit");
expect(processExitSpy).toHaveBeenCalledWith(1);
const warnCalls = mockLogWarn.mock.calls.map((c: any[]) => c.join(" "));
expect(warnCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
});
it("should suggest the correct argument order when swapped", async () => {
@ -373,8 +373,8 @@ describe("Commands Error Paths", () => {
it("should suggest correct order for hetzner/aider swap", async () => {
await expect(cmdRun("hetzner", "aider")).rejects.toThrow("process.exit");
const warnCalls = mockLogWarn.mock.calls.map((c: any[]) => c.join(" "));
expect(warnCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls2 = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls2.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls.some((msg: string) => msg.includes("spawn aider hetzner"))).toBe(true);

View file

@ -113,10 +113,8 @@ describe("detectAndFixSwappedArgs via cmdRun", () => {
// May throw from script execution
}
const warnCalls = mockLogWarn.mock.calls.map((c: any[]) => c.join(" "));
expect(warnCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
expect(infoCalls.some((msg: string) => msg.includes("spawn claude sprite"))).toBe(true);
});
@ -221,8 +219,8 @@ describe("detectAndFixSwappedArgs via cmdRun", () => {
}
// Should detect the swap
const warnCalls = mockLogWarn.mock.calls.map((c: any[]) => c.join(" "));
expect(warnCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
// Should then fail at implementation check
const errorCalls = mockLogError.mock.calls.map((c: any[]) => c.join(" "));
@ -552,8 +550,8 @@ describe("prompt handling with swapped args", () => {
}
// Should detect swap
const warnCalls = mockLogWarn.mock.calls.map((c: any[]) => c.join(" "));
expect(warnCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
const infoCalls = mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
expect(infoCalls.some((msg: string) => msg.includes("swapped"))).toBe(true);
// Should show launch message with prompt
const stepCalls = mockLogStep.mock.calls.map((c: any[]) => c.join(" "));

View file

@ -319,7 +319,7 @@ function detectAndFixSwappedArgs(
cloud: string
): { agent: string; cloud: string } {
if (!manifest.agents[agent] && manifest.clouds[agent] && manifest.agents[cloud]) {
p.log.warn(`It looks like you swapped the agent and cloud arguments.`);
p.log.info(`It looks like you swapped the agent and cloud arguments.`);
p.log.info(`Running: ${pc.cyan(`spawn ${cloud} ${agent}`)}`);
return { agent: cloud, cloud: agent };
}
@ -762,18 +762,27 @@ export async function cmdList(agentFilter?: string, cloudFilter?: string): Promi
for (const r of records) {
const when = formatTimestamp(r.timestamp);
console.log(
let line =
pc.green(r.agent.padEnd(20)) +
r.cloud.padEnd(20) +
pc.dim(when)
);
pc.dim(when);
if (r.prompt) {
const preview = r.prompt.length > 40 ? r.prompt.slice(0, 40) + "..." : r.prompt;
line += pc.dim(` --prompt "${preview}"`);
}
console.log(line);
}
console.log();
// Show rerun hint for the most recent spawn (first record since list is newest-first)
const latest = records[0];
console.log(`Rerun last: ${pc.cyan(`spawn ${latest.agent} ${latest.cloud}`)}`);
if (latest.prompt) {
const shortPrompt = latest.prompt.length > 30 ? latest.prompt.slice(0, 30) + "..." : latest.prompt;
console.log(`Rerun last: ${pc.cyan(`spawn ${latest.agent} ${latest.cloud} --prompt "${shortPrompt}"`)}`);
} else {
console.log(`Rerun last: ${pc.cyan(`spawn ${latest.agent} ${latest.cloud}`)}`);
}
console.log(pc.dim(`${records.length} spawn${records.length !== 1 ? "s" : ""} recorded`));
console.log(pc.dim(`Filter: ${pc.cyan("spawn list -a <agent>")} or ${pc.cyan("spawn list -c <cloud>")}`));
@ -833,11 +842,12 @@ export async function cmdClouds(): Promise<void> {
const c = manifest.clouds[key];
const implCount = getImplementedAgents(manifest, key).length;
const countStr = `${implCount}/${allAgents.length}`;
console.log(` ${pc.green(key.padEnd(NAME_COLUMN_WIDTH))} ${c.name.padEnd(NAME_COLUMN_WIDTH)} ${pc.dim(`${countStr.padEnd(6)} ${c.description}`)}`);
const authHint = c.auth.toLowerCase() === "none" ? "" : ` auth: ${c.auth}`;
console.log(` ${pc.green(key.padEnd(NAME_COLUMN_WIDTH))} ${c.name.padEnd(NAME_COLUMN_WIDTH)} ${pc.dim(`${countStr.padEnd(6)} ${c.description}`)}${authHint ? pc.dim(authHint) : ""}`);
}
}
console.log();
console.log(pc.dim(` Run ${pc.cyan("spawn <cloud>")} for details, or ${pc.cyan("spawn <agent> <cloud>")} to launch.`));
console.log(pc.dim(` Run ${pc.cyan("spawn <cloud>")} for setup instructions, or ${pc.cyan("spawn <agent> <cloud>")} to launch.`));
console.log();
}