diff --git a/src/opencode/process.ts b/src/opencode/process.ts index 40a41d2e..f8967ca4 100644 --- a/src/opencode/process.ts +++ b/src/opencode/process.ts @@ -46,11 +46,20 @@ export function resolveLocalOpencodeTarget(apiUrl: string): LocalOpencodeTarget } function resolveWindowsOpencodeExe(): string { - // npm on Windows usually puts opencode.cmd on PATH (not opencode.exe). - // We locate the shim and derive the real exe path from its directory. const pathEnv = process.env.PATH ?? ""; const pathEntries = pathEnv.split(path.delimiter).filter(Boolean); + // First pass: look for opencode.exe directly on PATH. + // Covers non-npm installations (install script, scoop, choco, manual download, etc.). + for (const entry of pathEntries) { + const candidateExe = path.join(entry, "opencode.exe"); + if (existsSync(candidateExe)) { + return candidateExe; + } + } + + // Second pass: look for opencode.cmd (npm global install). + // Derive the real exe path from the shim location. for (const entry of pathEntries) { const opencodeCmd = path.join(entry, "opencode.cmd"); if (!existsSync(opencodeCmd)) { diff --git a/tests/opencode/process.test.ts b/tests/opencode/process.test.ts index 0d3ef09c..920cf012 100644 --- a/tests/opencode/process.test.ts +++ b/tests/opencode/process.test.ts @@ -76,6 +76,35 @@ describe("opencode/process", () => { } }); + it("resolves opencode.exe directly from PATH when no .cmd shim exists", () => { + if (process.platform !== "win32") { + return; + } + + const originalPath = process.env.PATH; + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-telegram-bot-")); + const binDir = path.join(tempRoot, "bin"); + const exePath = path.join(binDir, "opencode.exe"); + + try { + fs.mkdirSync(binDir, { recursive: true }); + fs.writeFileSync(exePath, "", "utf8"); + + // Isolate PATH to only the temp dir — no npm .cmd shim on PATH + process.env.PATH = binDir; + + const command = createOpencodeServeSpawnCommand({ host: "localhost", port: 4987 }); + expect(command).toEqual({ + command: exePath, + args: ["serve", "--port", "4987"], + windowsHide: true, + }); + } finally { + process.env.PATH = originalPath; + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + it("uses resolved opencode.exe on Windows when opencode.cmd is on PATH and exe exists", () => { if (process.platform !== "win32") { return;