diff --git a/packages/cli/src/commands/doctor.ts b/packages/cli/src/commands/doctor.ts index b65e7562f..1427c8d72 100644 --- a/packages/cli/src/commands/doctor.ts +++ b/packages/cli/src/commands/doctor.ts @@ -16,11 +16,11 @@ interface DoctorRow { function statusIndicator(status: DoctorCheckResult['status']): string { switch (status) { case 'ok': - return 'ok' + return '✓ ok' case 'warn': - return 'warn' + return '⚠ warn' case 'error': - return 'error' + return '✗ error' } } @@ -41,9 +41,10 @@ function createDoctorSchema(report: DoctorReport): OutputSchema { header: 'STATUS', field: 'status', color: (value) => { - if (value === 'ok') return 'green' - if (value === 'warn') return 'yellow' - if (value === 'error') return 'red' + const v = typeof value === 'string' ? value : '' + if (v.includes('ok')) return 'green' + if (v.includes('warn')) return 'yellow' + if (v.includes('error')) return 'red' return undefined }, }, diff --git a/packages/server/src/server/doctor/checks/provider-checks.ts b/packages/server/src/server/doctor/checks/provider-checks.ts index 9fb9814fa..15635a6c7 100644 --- a/packages/server/src/server/doctor/checks/provider-checks.ts +++ b/packages/server/src/server/doctor/checks/provider-checks.ts @@ -14,9 +14,12 @@ const PROVIDERS: ProviderDef[] = [ { name: "opencode", command: "opencode", label: "OpenCode CLI" }, ]; +const EXEC_TIMEOUT_MS = 5000; + function whichCommand(command: string): string | null { + const whichBin = process.platform === "win32" ? "where" : "which"; try { - return execFileSync("which", [command], { encoding: "utf8" }).trim() || null; + return execFileSync(whichBin, [command], { encoding: "utf8", timeout: EXEC_TIMEOUT_MS }).trim() || null; } catch { return null; } @@ -24,14 +27,13 @@ function whichCommand(command: string): string | null { function getVersion(binaryPath: string): string | null { try { - return execFileSync(binaryPath, ["--version"], { encoding: "utf8" }).trim() || null; + return execFileSync(binaryPath, ["--version"], { encoding: "utf8", timeout: EXEC_TIMEOUT_MS }).trim() || null; } catch { return null; } } -function checkBinary(provider: ProviderDef): DoctorCheckResult { - const binaryPath = whichCommand(provider.command); +function checkBinary(provider: ProviderDef, binaryPath: string | null): DoctorCheckResult { if (binaryPath) { return { id: `provider.${provider.name}.binary`, @@ -48,8 +50,7 @@ function checkBinary(provider: ProviderDef): DoctorCheckResult { }; } -function checkVersion(provider: ProviderDef): DoctorCheckResult { - const binaryPath = whichCommand(provider.command); +function checkVersion(provider: ProviderDef, binaryPath: string | null): DoctorCheckResult { if (!binaryPath) { return { id: `provider.${provider.name}.version`, @@ -80,8 +81,9 @@ function checkVersion(provider: ProviderDef): DoctorCheckResult { export async function runProviderChecks(): Promise { const results: DoctorCheckResult[] = []; for (const provider of PROVIDERS) { - results.push(checkBinary(provider)); - results.push(checkVersion(provider)); + const binaryPath = whichCommand(provider.command); + results.push(checkBinary(provider, binaryPath)); + results.push(checkVersion(provider, binaryPath)); } return results; }