import { spawn } from "node:child_process"; function formatSpawnError(error: Error & { code?: unknown }): string { const code = typeof error.code === "string" ? `${error.code}: ` : ""; return `spawn error: ${code}${error.message}`.trim(); } export async function runCursorCommand(binary: string, args: string[], timeoutMs: number): Promise<{ code: number | null; stdout: string; stderr: string }> { return new Promise((resolve) => { let stdout = ""; let stderr = ""; let settled = false; let timer: NodeJS.Timeout | undefined; const finish = (result: { code: number | null; stdout: string; stderr: string }) => { if (settled) return; settled = true; if (timer) clearTimeout(timer); resolve(result); }; /* FNXC:CursorCli 2026-07-02-00:00: Windows Cursor installers and npm-style shims can expose `cursor-agent.cmd` or `cursor.cmd` on PATH, and Node cannot direct-spawn those batch wrappers without the command shell. Keep Unix/macOS on direct spawn so only the known Cursor CLI probe/discovery seam uses shell resolution where Windows requires it. */ const child = spawn(binary, args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32", }); timer = setTimeout(() => { try { child.kill("SIGKILL"); } catch { // best effort } finish({ code: 124, stdout, stderr }); }, timeoutMs); child.stdout?.on("data", (c: Buffer) => { stdout += c.toString("utf-8"); }); child.stderr?.on("data", (c: Buffer) => { stderr += c.toString("utf-8"); }); child.once("error", (error: Error & { code?: unknown }) => { const diagnostic = formatSpawnError(error); stderr = stderr ? `${stderr}\n${diagnostic}` : diagnostic; finish({ code: 127, stdout, stderr }); }); child.once("close", (code) => { finish({ code, stdout, stderr }); }); }); }