import { execFileSync } from "node:child_process"; import { accessSync, constants } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; function isExecutable(path: string): boolean { try { accessSync(path, constants.X_OK); return true; } catch { return false; } } function findOnPath(name: string): string | undefined { try { const command = process.platform === "win32" ? "where" : "which"; const output = execFileSync(command, [name], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5_000, }); return output .split(/\r?\n/) .map((line) => line.trim()) .find((path) => path.length > 0 && isExecutable(path)); } catch { return undefined; } } export function resolveGrokBinary(env: NodeJS.ProcessEnv = process.env): string { const configured = env.PI_GROK_BUILD_BIN?.trim() || env.GROK_BIN?.trim(); if (configured) { if (!isExecutable(configured)) throw new Error(`Configured Grok binary is not executable: ${configured}`); return configured; } const onPath = findOnPath("grok"); if (onPath) return onPath; const standardPath = join(homedir(), ".grok", "bin", process.platform === "win32" ? "grok.exe" : "grok"); if (isExecutable(standardPath)) return standardPath; throw new Error("Grok Build CLI not found. Install it from https://x.ai/cli and run `grok login`."); } export function getGrokVersion(binary: string): string { try { return execFileSync(binary, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 8_000, }).trim(); } catch { return "unknown"; } } export function discoverGrokModelIds(binary: string): string[] { try { const output = execFileSync(binary, ["models"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 20_000, }); const ids: string[] = []; for (const line of output.split(/\r?\n/)) { const match = line.match(/^\s*[*•-]\s+(\S+)/); const id = match?.[1]?.replace(/[(),]+$/g, ""); if (id && !ids.includes(id)) ids.push(id); } return ids; } catch { return []; } }