diff --git a/cli/package.json b/cli/package.json index 10dfa3ea..667b3190 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.2.58", + "version": "0.2.59", "type": "module", "bin": { "spawn": "cli.js" diff --git a/cli/src/__tests__/cmdlist-filter-resolution.test.ts b/cli/src/__tests__/cmdlist-filter-resolution.test.ts index 34ca59ca..6dbd726b 100644 --- a/cli/src/__tests__/cmdlist-filter-resolution.test.ts +++ b/cli/src/__tests__/cmdlist-filter-resolution.test.ts @@ -308,6 +308,66 @@ describe("cmdList filter resolution via display names", () => { }); }); + // ── Bare positional arg: auto-detect cloud vs agent ─────────────────────── + + describe("bare positional arg reclassified as cloud filter when appropriate", () => { + it("should reclassify 'hetzner' from agentFilter to cloudFilter", async () => { + await setManifest(mockManifest); + writeHistory(sampleRecords); + + // "hetzner" passed as agentFilter (bare positional), should be reclassified + await cmdList("hetzner"); + + const output = consoleOutput(); + // Should find 2 records on hetzner (aider + claude), not 0 + expect(output).toContain("2 of 4"); + }); + + it("should reclassify 'sprite' from agentFilter to cloudFilter", async () => { + await setManifest(mockManifest); + writeHistory(sampleRecords); + + await cmdList("sprite"); + + const output = consoleOutput(); + // Should find 2 records on sprite + expect(output).toContain("2 of 4"); + }); + + it("should reclassify cloud display name 'Hetzner Cloud' to cloudFilter", async () => { + await setManifest(mockManifest); + writeHistory(sampleRecords); + + await cmdList("Hetzner Cloud"); + + const output = consoleOutput(); + expect(output).toContain("2 of 4"); + }); + + it("should NOT reclassify when agentFilter resolves to an agent", async () => { + await setManifest(mockManifest); + writeHistory(sampleRecords); + + await cmdList("claude"); + + const output = consoleOutput(); + // "claude" is a valid agent, should filter by agent + expect(output).toContain("2 of 4"); + }); + + it("should NOT reclassify when explicit cloudFilter is already set", async () => { + await setManifest(mockManifest); + writeHistory(sampleRecords); + + // When both are set, don't reclassify + await cmdList("unknown-thing", "hetzner"); + + const info = logInfoOutput(); + // Should show "no spawns" since agent=unknown-thing finds nothing + expect(info).toContain("No spawns found matching"); + }); + }); + // ── Key that matches directly vs display name ────────────────────────────── describe("direct key match takes precedence over display name", () => { diff --git a/cli/src/commands.ts b/cli/src/commands.ts index af7a8ea4..5190d8cc 100644 --- a/cli/src/commands.ts +++ b/cli/src/commands.ts @@ -933,7 +933,8 @@ function buildRecordHint(r: SpawnRecord): string { return when; } -/** Try to load manifest and resolve filter display names to keys */ +/** Try to load manifest and resolve filter display names to keys. + * When a bare positional filter doesn't match an agent, try it as a cloud. */ async function resolveListFilters( agentFilter?: string, cloudFilter?: string @@ -947,7 +948,16 @@ async function resolveListFilters( if (manifest && agentFilter) { const resolved = resolveAgentKey(manifest, agentFilter); - if (resolved) agentFilter = resolved; + if (resolved) { + agentFilter = resolved; + } else if (!cloudFilter) { + // Bare positional arg didn't match an agent -- try as a cloud filter + const resolvedCloud = resolveCloudKey(manifest, agentFilter); + if (resolvedCloud) { + cloudFilter = resolvedCloud; + agentFilter = undefined; + } + } } if (manifest && cloudFilter) { const resolved = resolveCloudKey(manifest, cloudFilter);