import { spawnWithTimeout } from "../spawn-with-timeout" export interface GhCliInfo { installed: boolean version: string | null path: string | null authenticated: boolean username: string | null scopes: string[] error: string | null } async function checkBinaryExists(binary: string): Promise<{ exists: boolean; path: string | null }> { try { const binaryPath = Bun.which(binary) return { exists: Boolean(binaryPath), path: binaryPath ?? null } } catch { return { exists: false, path: null } } } async function getGhVersion(): Promise { try { const result = await spawnWithTimeout(["gh", "--version"], { stdout: "pipe", stderr: "pipe" }) if (result.timedOut || result.exitCode !== 0) return null const matchedVersion = result.stdout.match(/gh version (\S+)/) return matchedVersion?.[1] ?? result.stdout.trim().split("\n")[0] ?? null } catch { return null } } async function getGhAuthStatus(): Promise<{ authenticated: boolean username: string | null scopes: string[] error: string | null }> { try { const result = await spawnWithTimeout( ["gh", "auth", "status"], { stdout: "pipe", stderr: "pipe", env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" } } ) if (result.timedOut) { return { authenticated: false, username: null, scopes: [], error: "gh auth status timed out" } } const output = result.stderr || result.stdout if (result.exitCode === 0) { const usernameMatch = output.match(/Logged in to github\.com account (\S+)/) const scopesMatch = output.match(/Token scopes?:\s*(.+)/i) return { authenticated: true, username: usernameMatch?.[1]?.replace(/[()]/g, "") ?? null, scopes: scopesMatch?.[1]?.split(/,\s*/).map((scope) => scope.trim()).filter(Boolean) ?? [], error: null, } } const errorMatch = output.match(/error[:\s]+(.+)/i) return { authenticated: false, username: null, scopes: [], error: errorMatch?.[1]?.trim() ?? "Not authenticated", } } catch (error) { return { authenticated: false, username: null, scopes: [], error: error instanceof Error ? error.message : "Failed to check auth status", } } } export async function getGhCliInfo(): Promise { const binaryStatus = await checkBinaryExists("gh") if (!binaryStatus.exists) { const version = await getGhVersion() if (version) { const authStatus = await getGhAuthStatus() return { installed: true, version, path: null, authenticated: authStatus.authenticated, username: authStatus.username, scopes: authStatus.scopes, error: authStatus.error, } } return { installed: false, version: null, path: null, authenticated: false, username: null, scopes: [], error: null, } } const [version, authStatus] = await Promise.all([getGhVersion(), getGhAuthStatus()]) return { installed: true, version, path: binaryStatus.path, authenticated: authStatus.authenticated, username: authStatus.username, scopes: authStatus.scopes, error: authStatus.error, } }