Implements a comprehensive 'doctor' command that diagnoses oh-my-opencode installation health with a beautiful TUI output. Checks performed: - OpenCode installation (version, path, binary) - Plugin registration in opencode.json - Configuration file validity (oh-my-opencode.json) - Auth providers (Anthropic, OpenAI, Google) - Dependencies (ast-grep CLI/NAPI, comment-checker) - LSP servers availability - MCP servers (builtin and user) - Version status and updates Features: - Beautiful TUI with symbols and colors - --verbose flag for detailed output - --json flag for machine-readable output - --category flag for running specific checks - Exit code 1 on failures for CI integration Closes #333 Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs"
|
|
import { homedir } from "node:os"
|
|
import { join } from "node:path"
|
|
import type { CheckResult, CheckDefinition, AuthProviderInfo, AuthProviderId } from "../types"
|
|
import { CHECK_IDS, CHECK_NAMES } from "../constants"
|
|
import { parseJsonc } from "../../../shared"
|
|
|
|
const OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode")
|
|
const OPENCODE_JSON = join(OPENCODE_CONFIG_DIR, "opencode.json")
|
|
const OPENCODE_JSONC = join(OPENCODE_CONFIG_DIR, "opencode.jsonc")
|
|
|
|
const AUTH_PLUGINS: Record<AuthProviderId, { plugin: string; name: string }> = {
|
|
anthropic: { plugin: "builtin", name: "Anthropic (Claude)" },
|
|
openai: { plugin: "opencode-openai-codex-auth", name: "OpenAI (ChatGPT)" },
|
|
google: { plugin: "opencode-antigravity-auth", name: "Google (Gemini)" },
|
|
}
|
|
|
|
function getOpenCodeConfig(): { plugin?: string[] } | null {
|
|
const configPath = existsSync(OPENCODE_JSONC) ? OPENCODE_JSONC : OPENCODE_JSON
|
|
if (!existsSync(configPath)) return null
|
|
|
|
try {
|
|
const content = readFileSync(configPath, "utf-8")
|
|
return parseJsonc<{ plugin?: string[] }>(content)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function isPluginInstalled(plugins: string[], pluginName: string): boolean {
|
|
if (pluginName === "builtin") return true
|
|
return plugins.some((p) => p === pluginName || p.startsWith(`${pluginName}@`))
|
|
}
|
|
|
|
export function getAuthProviderInfo(providerId: AuthProviderId): AuthProviderInfo {
|
|
const config = getOpenCodeConfig()
|
|
const plugins = config?.plugin ?? []
|
|
const authConfig = AUTH_PLUGINS[providerId]
|
|
|
|
const pluginInstalled = isPluginInstalled(plugins, authConfig.plugin)
|
|
|
|
return {
|
|
id: providerId,
|
|
name: authConfig.name,
|
|
pluginInstalled,
|
|
configured: pluginInstalled,
|
|
}
|
|
}
|
|
|
|
export async function checkAuthProvider(providerId: AuthProviderId): Promise<CheckResult> {
|
|
const info = getAuthProviderInfo(providerId)
|
|
const checkId = `auth-${providerId}` as keyof typeof CHECK_NAMES
|
|
const checkName = CHECK_NAMES[checkId] || info.name
|
|
|
|
if (!info.pluginInstalled) {
|
|
return {
|
|
name: checkName,
|
|
status: "skip",
|
|
message: "Auth plugin not installed",
|
|
details: [
|
|
`Plugin: ${AUTH_PLUGINS[providerId].plugin}`,
|
|
"Run: bunx oh-my-opencode install",
|
|
],
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: checkName,
|
|
status: "pass",
|
|
message: "Auth plugin available",
|
|
details: [
|
|
providerId === "anthropic"
|
|
? "Run: opencode auth login (select Anthropic)"
|
|
: `Plugin: ${AUTH_PLUGINS[providerId].plugin}`,
|
|
],
|
|
}
|
|
}
|
|
|
|
export async function checkAnthropicAuth(): Promise<CheckResult> {
|
|
return checkAuthProvider("anthropic")
|
|
}
|
|
|
|
export async function checkOpenAIAuth(): Promise<CheckResult> {
|
|
return checkAuthProvider("openai")
|
|
}
|
|
|
|
export async function checkGoogleAuth(): Promise<CheckResult> {
|
|
return checkAuthProvider("google")
|
|
}
|
|
|
|
export function getAuthCheckDefinitions(): CheckDefinition[] {
|
|
return [
|
|
{
|
|
id: CHECK_IDS.AUTH_ANTHROPIC,
|
|
name: CHECK_NAMES[CHECK_IDS.AUTH_ANTHROPIC],
|
|
category: "authentication",
|
|
check: checkAnthropicAuth,
|
|
critical: false,
|
|
},
|
|
{
|
|
id: CHECK_IDS.AUTH_OPENAI,
|
|
name: CHECK_NAMES[CHECK_IDS.AUTH_OPENAI],
|
|
category: "authentication",
|
|
check: checkOpenAIAuth,
|
|
critical: false,
|
|
},
|
|
{
|
|
id: CHECK_IDS.AUTH_GOOGLE,
|
|
name: CHECK_NAMES[CHECK_IDS.AUTH_GOOGLE],
|
|
category: "authentication",
|
|
check: checkGoogleAuth,
|
|
critical: false,
|
|
},
|
|
]
|
|
}
|