diff --git a/src/commands/init/agent-definitions.test.ts b/src/commands/init/agent-definitions.test.ts index db42c53d..e368e625 100644 --- a/src/commands/init/agent-definitions.test.ts +++ b/src/commands/init/agent-definitions.test.ts @@ -220,6 +220,97 @@ describe("detection configuration", () => { expect(await agent.detectBinary!(exec)).toBe(false); }); + it("claude-code detectBinary returns true when binary found", async () => { + const exec = createMockExecService({ + exec: mock(async () => ({ + exitCode: 0, + stdout: "/usr/bin/claude\n", + stderr: "", + })), + }); + const agent = agentDefinitions.find((a) => a.id === "claude-code")!; + expect(await agent.detectBinary!(exec)).toBe(true); + }); + + it("binary detectors use correct command and binary name on linux", async () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + value: "linux", + configurable: true, + }); + + try { + const testCases = [ + { id: "claude-code", binary: "claude" }, + { id: "codex-cli", binary: "codex" }, + { id: "gemini-cli", binary: "gemini" }, + { id: "opencode", binary: "opencode" }, + ]; + + for (const testCase of testCases) { + const exec = createMockExecService({ + exec: mock(async () => ({ + exitCode: 0, + stdout: "/usr/bin/mock\n", + stderr: "", + })), + }); + const agent = agentDefinitions.find((a) => a.id === testCase.id)!; + expect(await agent.detectBinary!(exec)).toBe(true); + expect(exec.exec).toHaveBeenCalledWith("which", [testCase.binary]); + } + } finally { + Object.defineProperty(process, "platform", { + value: originalPlatform, + configurable: true, + }); + } + }); + + it("binary detectors use where on win32", async () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + value: "win32", + configurable: true, + }); + + try { + const exec = createMockExecService({ + exec: mock(async () => ({ + exitCode: 0, + stdout: "C:\\Program Files\\Claude\\claude.exe\n", + stderr: "", + })), + }); + const agent = agentDefinitions.find((a) => a.id === "claude-code")!; + expect(await agent.detectBinary!(exec)).toBe(true); + expect(exec.exec).toHaveBeenCalledWith("where", ["claude"]); + } finally { + Object.defineProperty(process, "platform", { + value: originalPlatform, + configurable: true, + }); + } + }); + + it("claude-code detectBinary returns false when binary not found", async () => { + const exec = createMockExecService({ + exec: mock(async () => ({ exitCode: 1, stdout: "", stderr: "" })), + }); + const agent = agentDefinitions.find((a) => a.id === "claude-code")!; + expect(await agent.detectBinary!(exec)).toBe(false); + }); + + it("claude-code detectBinary returns false on exec error", async () => { + const exec = createMockExecService({ + exec: mock(async () => { + throw new Error("spawn ENOENT"); + }), + }); + const agent = agentDefinitions.find((a) => a.id === "claude-code")!; + expect(await agent.detectBinary!(exec)).toBe(false); + }); + it("path-detected agents use FileSystemService.getHomeDir (not hardcoded)", () => { const originalPlatform = process.platform; const originalAppdata = process.env.APPDATA; @@ -851,6 +942,23 @@ describe("scanAgents", () => { expect(result.notDetected.some((a) => a.id === "opencode")).toBe(false); }); + it("detects codex via detectBinary when directory does not exist", async () => { + const lookupCmd = lookupCommandFor(); + const { fs, execService } = createScanMocks({ + detectedDirs: [], + execResults: { + [`${lookupCmd} codex`]: { + exitCode: 0, + stdout: "/usr/bin/codex\n", + stderr: "", + }, + }, + }); + const result = await scanAgents(agentDefinitions, fs, execService); + expect(result.needsSetup.some((a) => a.id === "codex-cli")).toBe(true); + expect(result.notDetected.some((a) => a.id === "codex-cli")).toBe(false); + }); + it("does not detect opencode from config directory when binary is missing", async () => { const originalPlatform = process.platform; Object.defineProperty(process, "platform", { diff --git a/src/commands/init/agent-definitions.ts b/src/commands/init/agent-definitions.ts index faf1695d..a7712206 100644 --- a/src/commands/init/agent-definitions.ts +++ b/src/commands/init/agent-definitions.ts @@ -370,6 +370,7 @@ const openCode: AgentDefinition = { /** * All supported agent definitions, ordered by popularity/likelihood. * New agents should be added here. + * @deprecated Use scanAgents() instead, which also checks configuration status. */ export const agentDefinitions: AgentDefinition[] = [ claudeCode,