diff --git a/cli/package.json b/cli/package.json index 8e5e66d0..dfd62d72 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.2.65", + "version": "0.2.66", "type": "module", "bin": { "spawn": "cli.js" diff --git a/cli/src/__tests__/cli-entry-edge-cases.test.ts b/cli/src/__tests__/cli-entry-edge-cases.test.ts index 5d448d05..3f0208fc 100644 --- a/cli/src/__tests__/cli-entry-edge-cases.test.ts +++ b/cli/src/__tests__/cli-entry-edge-cases.test.ts @@ -439,7 +439,7 @@ describe("fuzzy matching edge cases in showInfoOrError", () => { // "abcdefgh" is far from any agent/cloud const result = runCli(["abcdefgh"]); const out = output(result); - expect(out).toContain("Unknown command"); + expect(out).toContain("Unknown agent or cloud"); expect(out).not.toContain("Did you mean"); }); diff --git a/cli/src/__tests__/list-empty-footer.test.ts b/cli/src/__tests__/list-empty-footer.test.ts index d33bd207..46603733 100644 --- a/cli/src/__tests__/list-empty-footer.test.ts +++ b/cli/src/__tests__/list-empty-footer.test.ts @@ -271,7 +271,7 @@ function buildUnknownCommandError( if (cloudMatch) suggestions.push(`${cloudMatch} (cloud: ${manifest.clouds[cloudMatch].name})`); return { - errorMessage: `Unknown command: ${name}`, + errorMessage: `Unknown agent or cloud: ${name}`, hasSuggestion: suggestions.length > 0, suggestion: suggestions.length > 0 ? `Did you mean ${suggestions.join(" or ")}?` : undefined, }; @@ -662,9 +662,9 @@ describe("showUnknownCommandError logic", () => { }); describe("error message format", () => { - it("should always include Unknown command prefix", () => { + it("should always include Unknown agent or cloud prefix", () => { const result = buildUnknownCommandError("foo", mockManifest); - expect(result.errorMessage).toStartWith("Unknown command:"); + expect(result.errorMessage).toStartWith("Unknown agent or cloud:"); }); it("should include the actual input in error message", () => { diff --git a/cli/src/__tests__/script-failure-guidance.test.ts b/cli/src/__tests__/script-failure-guidance.test.ts index daccc033..3d656044 100644 --- a/cli/src/__tests__/script-failure-guidance.test.ts +++ b/cli/src/__tests__/script-failure-guidance.test.ts @@ -468,18 +468,24 @@ describe("buildRetryCommand", () => { ); }); - it("should truncate long prompts to 60 characters", () => { + it("should suggest --prompt-file for long prompts instead of truncating", () => { const longPrompt = "A".repeat(100); const result = buildRetryCommand("claude", "sprite", longPrompt); - expect(result).toContain("A".repeat(60) + "..."); - expect(result).not.toContain("A".repeat(61)); + expect(result).toBe("spawn claude sprite --prompt-file "); + expect(result).not.toContain("A"); // no truncated prompt content }); - it("should not truncate prompts at exactly 60 characters", () => { - const exactPrompt = "B".repeat(60); + it("should include full prompt at exactly 80 characters", () => { + const exactPrompt = "B".repeat(80); const result = buildRetryCommand("aider", "hetzner", exactPrompt); expect(result).toBe(`spawn aider hetzner --prompt "${exactPrompt}"`); - expect(result).not.toContain("..."); + expect(result).not.toContain("prompt-file"); + }); + + it("should suggest --prompt-file for prompts over 80 characters", () => { + const longPrompt = "C".repeat(81); + const result = buildRetryCommand("aider", "hetzner", longPrompt); + expect(result).toBe("spawn aider hetzner --prompt-file "); }); it("should escape double quotes in prompt", () => { diff --git a/cli/src/__tests__/show-info-or-error.test.ts b/cli/src/__tests__/show-info-or-error.test.ts index 4f1a5ed0..9d98e7b5 100644 --- a/cli/src/__tests__/show-info-or-error.test.ts +++ b/cli/src/__tests__/show-info-or-error.test.ts @@ -9,7 +9,7 @@ import { resolve } from "path"; * case where a user types "spawn " and the name could be: * - A valid agent key -> shows agent info (cmdAgentInfo) * - A valid cloud key -> shows cloud info (cmdCloudInfo) - * - An unknown name -> shows "Unknown command" with fuzzy suggestions + * - An unknown name -> shows "Unknown agent or cloud" with fuzzy suggestions * * Since showInfoOrError is not exported and calls loadManifest + process.exit, * we test it by spawning bun subprocesses (same approach as index-main-routing.test.ts). @@ -122,13 +122,13 @@ describe("showInfoOrError - single argument routing", () => { }); }); - // ── Unknown command: error output ────────────────────────────────────── + // ── Unknown agent or cloud: error output ────────────────────────────────────── describe("unknown single argument", () => { - it("should show 'Unknown command' for an unrecognized name", () => { + it("should show 'Unknown agent or cloud' for an unrecognized name", () => { const result = runCli(["xyzzyplugh"]); const output = result.stdout + result.stderr; - expect(output).toContain("Unknown command"); + expect(output).toContain("Unknown agent or cloud"); expect(result.exitCode).not.toBe(0); }); @@ -185,7 +185,7 @@ describe("showInfoOrError - single argument routing", () => { // "kubernetes" is far from any agent or cloud name const result = runCli(["kubernetes"]); const output = result.stdout + result.stderr; - expect(output).toContain("Unknown command"); + expect(output).toContain("Unknown agent or cloud"); expect(output).not.toContain("Did you mean"); }); @@ -230,14 +230,14 @@ describe("showInfoOrError - single argument routing", () => { it("should not treat numeric-only input as a valid agent or cloud", () => { const result = runCli(["12345"]); const output = result.stdout + result.stderr; - expect(output).toContain("Unknown command"); + expect(output).toContain("Unknown agent or cloud"); expect(result.exitCode).not.toBe(0); }); it("should handle hyphenated names that are not real entries", () => { const result = runCli(["not-a-real-entry"]); const output = result.stdout + result.stderr; - expect(output).toContain("Unknown command"); + expect(output).toContain("Unknown agent or cloud"); expect(result.exitCode).not.toBe(0); }); diff --git a/cli/src/commands.ts b/cli/src/commands.ts index 2059e81a..8cae9ff1 100644 --- a/cli/src/commands.ts +++ b/cli/src/commands.ts @@ -623,9 +623,12 @@ export function getScriptFailureGuidance(exitCode: number | null, cloud: string, export function buildRetryCommand(agent: string, cloud: string, prompt?: string): string { if (!prompt) return `spawn ${agent} ${cloud}`; - const short = prompt.length > 60 ? prompt.slice(0, 60) + "..." : prompt; - const safe = short.replace(/"/g, '\\"'); - return `spawn ${agent} ${cloud} --prompt "${safe}"`; + if (prompt.length <= 80) { + const safe = prompt.replace(/"/g, '\\"'); + return `spawn ${agent} ${cloud} --prompt "${safe}"`; + } + // Long prompts: suggest --prompt-file instead of truncating into a broken command + return `spawn ${agent} ${cloud} --prompt-file `; } function reportScriptFailure(errMsg: string, cloud: string, agent: string, authHint?: string, prompt?: string): never { diff --git a/cli/src/index.ts b/cli/src/index.ts index 532924fb..cccf20cf 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -90,7 +90,7 @@ function showUnknownCommandError(name: string, manifest: { agents: Record manifest.agents[k].name); const cloudMatch = findClosestKeyByNameOrKey(name, cloudKeys(manifest), (k) => manifest.clouds[k].name); - console.error(pc.red(`Unknown command: ${pc.bold(name)}`)); + console.error(pc.red(`Unknown agent or cloud: ${pc.bold(name)}`)); console.error(); if (agentMatch || cloudMatch) { const suggestions: string[] = [];