Files
oh-my-openagent/src/cli/doctor/checks/auth.ts
Sisyphus b8efd3c771 feat(cli): add doctor command for installation health checks (#334)
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>
2025-12-30 15:06:41 +09:00

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,
},
]
}