mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 16:39:50 +00:00
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:
parent
bc89a2821b
commit
fa516bad89
4 changed files with 27 additions and 19 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.2.45",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(" "));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue