diff --git a/src/cli/doctor/checks/dependencies.ts b/src/cli/doctor/checks/dependencies.ts index 52eb8c9c..da22afcf 100644 --- a/src/cli/doctor/checks/dependencies.ts +++ b/src/cli/doctor/checks/dependencies.ts @@ -1,3 +1,7 @@ +import { existsSync } from "node:fs" +import { createRequire } from "node:module" +import { dirname, join } from "node:path" + import type { DependencyInfo } from "../types" async function checkBinaryExists(binary: string): Promise<{ exists: boolean; path: string | null }> { @@ -98,10 +102,24 @@ export async function checkAstGrepNapi(): Promise { } } +function findCommentCheckerPackageBinary(): string | null { + const binaryName = process.platform === "win32" ? "comment-checker.exe" : "comment-checker" + try { + const require = createRequire(import.meta.url) + const pkgPath = require.resolve("@code-yeongyu/comment-checker/package.json") + const binaryPath = join(dirname(pkgPath), "bin", binaryName) + if (existsSync(binaryPath)) return binaryPath + } catch { + // intentionally empty - package not installed + } + return null +} + export async function checkCommentChecker(): Promise { const binaryCheck = await checkBinaryExists("comment-checker") + const resolvedPath = binaryCheck.exists ? binaryCheck.path : findCommentCheckerPackageBinary() - if (!binaryCheck.exists) { + if (!resolvedPath) { return { name: "Comment Checker", required: false, @@ -112,14 +130,14 @@ export async function checkCommentChecker(): Promise { } } - const version = await getBinaryVersion("comment-checker") + const version = await getBinaryVersion(resolvedPath) return { name: "Comment Checker", required: false, installed: true, version, - path: binaryCheck.path, + path: resolvedPath, } } diff --git a/src/cli/doctor/checks/index.ts b/src/cli/doctor/checks/index.ts index 20960d7b..0ad6821f 100644 --- a/src/cli/doctor/checks/index.ts +++ b/src/cli/doctor/checks/index.ts @@ -2,13 +2,12 @@ import type { CheckDefinition } from "../types" import { CHECK_IDS, CHECK_NAMES } from "../constants" import { checkSystem, gatherSystemInfo } from "./system" import { checkConfig } from "./config" -import { checkProviders, gatherProviderStatuses } from "./providers" import { checkTools, gatherToolsSummary } from "./tools" import { checkModels } from "./model-resolution" export type { CheckDefinition } export * from "./model-resolution-types" -export { gatherSystemInfo, gatherProviderStatuses, gatherToolsSummary } +export { gatherSystemInfo, gatherToolsSummary } export function getAllCheckDefinitions(): CheckDefinition[] { return [ @@ -23,11 +22,6 @@ export function getAllCheckDefinitions(): CheckDefinition[] { name: CHECK_NAMES[CHECK_IDS.CONFIG], check: checkConfig, }, - { - id: CHECK_IDS.PROVIDERS, - name: CHECK_NAMES[CHECK_IDS.PROVIDERS], - check: checkProviders, - }, { id: CHECK_IDS.TOOLS, name: CHECK_NAMES[CHECK_IDS.TOOLS], diff --git a/src/cli/doctor/checks/providers.ts b/src/cli/doctor/checks/providers.ts deleted file mode 100644 index c34ebdb3..00000000 --- a/src/cli/doctor/checks/providers.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { existsSync, readFileSync } from "node:fs" - -import { AGENT_MODEL_REQUIREMENTS } from "../../../shared/model-requirements" -import { getOpenCodeConfigPaths, parseJsonc } from "../../../shared" -import { AUTH_ENV_VARS, AUTH_PLUGINS, CHECK_IDS, CHECK_NAMES } from "../constants" -import type { CheckResult, DoctorIssue, ProviderStatus } from "../types" - -interface OpenCodeConfigShape { - plugin?: string[] -} - -function loadOpenCodePlugins(): string[] { - const configPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null }) - const targetPath = existsSync(configPaths.configJsonc) - ? configPaths.configJsonc - : configPaths.configJson - - if (!existsSync(targetPath)) return [] - - try { - const content = readFileSync(targetPath, "utf-8") - const parsed = parseJsonc(content) - return parsed.plugin ?? [] - } catch { - return [] - } -} - -function hasProviderPlugin(plugins: string[], providerId: string): boolean { - const definition = AUTH_PLUGINS[providerId] - if (!definition) return false - if (definition.plugin === "builtin") return true - return plugins.some((plugin) => plugin === definition.plugin || plugin.startsWith(`${definition.plugin}@`)) -} - -function hasProviderEnvVar(providerId: string): boolean { - const envVarNames = AUTH_ENV_VARS[providerId] ?? [] - return envVarNames.some((envVarName) => Boolean(process.env[envVarName])) -} - -function getAffectedAgents(providerId: string): string[] { - const affectedAgents: string[] = [] - - for (const [agentName, requirement] of Object.entries(AGENT_MODEL_REQUIREMENTS)) { - const usesProvider = requirement.fallbackChain.some((entry) => entry.providers.includes(providerId)) - if (usesProvider) { - affectedAgents.push(agentName) - } - } - - return affectedAgents -} - -export function gatherProviderStatuses(): ProviderStatus[] { - const plugins = loadOpenCodePlugins() - - return Object.entries(AUTH_PLUGINS).map(([providerId, definition]) => { - const hasPlugin = hasProviderPlugin(plugins, providerId) - const hasEnvVar = hasProviderEnvVar(providerId) - return { - id: providerId, - name: definition.name, - available: hasPlugin && hasEnvVar, - hasPlugin, - hasEnvVar, - } - }) -} - -export async function checkProviders(): Promise { - const statuses = gatherProviderStatuses() - const issues: DoctorIssue[] = [] - - for (const status of statuses) { - if (status.available) continue - - const missingParts: string[] = [] - if (!status.hasPlugin) missingParts.push("auth plugin") - if (!status.hasEnvVar) missingParts.push("environment variable") - - issues.push({ - title: `${status.name} authentication missing`, - description: `Missing ${missingParts.join(" and ")} for ${status.name}.`, - fix: `Configure ${status.name} provider in OpenCode and set ${(AUTH_ENV_VARS[status.id] ?? []).join(" or ")}`, - affects: getAffectedAgents(status.id), - severity: "warning", - }) - } - - const status = issues.length === 0 ? "pass" : "warn" - return { - name: CHECK_NAMES[CHECK_IDS.PROVIDERS], - status, - message: issues.length === 0 ? "All provider auth checks passed" : `${issues.length} provider issue(s) detected`, - details: statuses.map( - (providerStatus) => - `${providerStatus.name}: plugin=${providerStatus.hasPlugin ? "yes" : "no"}, env=${providerStatus.hasEnvVar ? "yes" : "no"}` - ), - issues, - } -} diff --git a/src/cli/doctor/constants.ts b/src/cli/doctor/constants.ts index bfd66afe..ff41f836 100644 --- a/src/cli/doctor/constants.ts +++ b/src/cli/doctor/constants.ts @@ -20,7 +20,6 @@ export const STATUS_COLORS = { export const CHECK_IDS = { SYSTEM: "system", CONFIG: "config", - PROVIDERS: "providers", TOOLS: "tools", MODELS: "models", } as const @@ -28,23 +27,10 @@ export const CHECK_IDS = { export const CHECK_NAMES: Record = { [CHECK_IDS.SYSTEM]: "System", [CHECK_IDS.CONFIG]: "Configuration", - [CHECK_IDS.PROVIDERS]: "Providers", [CHECK_IDS.TOOLS]: "Tools", [CHECK_IDS.MODELS]: "Models", } as const -export const AUTH_ENV_VARS: Record = { - anthropic: ["ANTHROPIC_API_KEY"], - openai: ["OPENAI_API_KEY"], - google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"], -} as const - -export const AUTH_PLUGINS: Record = { - anthropic: { plugin: "builtin", name: "Anthropic" }, - openai: { plugin: "opencode-openai-codex-auth", name: "OpenAI" }, - google: { plugin: "opencode-antigravity-auth", name: "Google" }, -} as const - export const EXIT_CODES = { SUCCESS: 0, FAILURE: 1, diff --git a/src/cli/doctor/format-default.test.ts b/src/cli/doctor/format-default.test.ts index 60b09674..76b85169 100644 --- a/src/cli/doctor/format-default.test.ts +++ b/src/cli/doctor/format-default.test.ts @@ -19,7 +19,6 @@ function createBaseResult(): DoctorResult { configValid: true, isLocalDev: false, }, - providers: [], tools: { lspInstalled: 0, lspTotal: 0, diff --git a/src/cli/doctor/format-shared.ts b/src/cli/doctor/format-shared.ts index b6c37bc5..401533dc 100644 --- a/src/cli/doctor/format-shared.ts +++ b/src/cli/doctor/format-shared.ts @@ -27,7 +27,7 @@ export function stripAnsi(str: string): string { } export function formatHeader(): string { - return `\n${color.bgMagenta(color.white(" oMo Doctor "))}\n` + return `\n${color.bgMagenta(color.white(" oMoMoMoMo Doctor "))}\n` } export function formatIssue(issue: DoctorIssue, index: number): string { diff --git a/src/cli/doctor/format-status.ts b/src/cli/doctor/format-status.ts index c73a11dc..6b4930a1 100644 --- a/src/cli/doctor/format-status.ts +++ b/src/cli/doctor/format-status.ts @@ -7,7 +7,7 @@ export function formatStatus(result: DoctorResult): string { lines.push(formatHeader()) - const { systemInfo, providers, tools } = result + const { systemInfo, tools } = result const padding = " " const opencodeVer = systemInfo.opencodeVersion ?? "unknown" @@ -19,12 +19,6 @@ export function formatStatus(result: DoctorResult): string { const configStatus = systemInfo.configValid ? color.green("(valid)") : color.red("(invalid)") lines.push(` ${padding}Config ${configPath} ${configStatus}`) - const providerParts = providers.map((p) => { - const mark = formatStatusMark(p.available) - return `${p.name}${mark}` - }) - lines.push(` ${padding}Providers ${providerParts.join(" ")}`) - const lspText = `LSP ${tools.lspInstalled}/${tools.lspTotal}` const astGrepMark = formatStatusMark(tools.astGrepCli) const ghMark = formatStatusMark(tools.ghCli.installed && tools.ghCli.authenticated) diff --git a/src/cli/doctor/format-verbose.ts b/src/cli/doctor/format-verbose.ts index b491651a..f965b675 100644 --- a/src/cli/doctor/format-verbose.ts +++ b/src/cli/doctor/format-verbose.ts @@ -7,7 +7,7 @@ export function formatVerbose(result: DoctorResult): string { lines.push(formatHeader()) - const { systemInfo, providers, tools, results, summary } = result + const { systemInfo, tools, results, summary } = result lines.push(`${color.bold("System Information")}`) lines.push(`${color.dim("\u2500".repeat(40))}`) @@ -31,16 +31,6 @@ export function formatVerbose(result: DoctorResult): string { lines.push(` ${formatStatusSymbol(systemInfo.configValid ? "pass" : "fail")} ${systemInfo.configPath ?? "unknown"} (${configStatus})`) lines.push("") - lines.push(`${color.bold("Providers")}`) - lines.push(`${color.dim("\u2500".repeat(40))}`) - for (const provider of providers) { - const availableMark = provider.available ? color.green("✓") : color.red("✗") - const pluginMark = provider.hasPlugin ? color.green("plugin") : color.dim("no plugin") - const envMark = provider.hasEnvVar ? color.green("env") : color.dim("no env") - lines.push(` ${availableMark} ${provider.name} ${pluginMark} · ${envMark}`) - } - lines.push("") - lines.push(`${color.bold("Tools")}`) lines.push(`${color.dim("\u2500".repeat(40))}`) lines.push(` ${formatStatusSymbol("pass")} LSP ${tools.lspInstalled}/${tools.lspTotal} installed`) diff --git a/src/cli/doctor/formatter.test.ts b/src/cli/doctor/formatter.test.ts index a14104d0..5884997a 100644 --- a/src/cli/doctor/formatter.test.ts +++ b/src/cli/doctor/formatter.test.ts @@ -17,7 +17,6 @@ function createDoctorResult(): DoctorResult { configValid: true, isLocalDev: false, }, - providers: [{ id: "anthropic", name: "Anthropic", available: true, hasEnvVar: true, hasPlugin: true }], tools: { lspInstalled: 2, lspTotal: 4, diff --git a/src/cli/doctor/runner.test.ts b/src/cli/doctor/runner.test.ts index 2e65f323..ca96b079 100644 --- a/src/cli/doctor/runner.test.ts +++ b/src/cli/doctor/runner.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, mock } from "bun:test" -import type { CheckDefinition, CheckResult, DoctorResult, ProviderStatus, SystemInfo, ToolsSummary } from "./types" +import type { CheckDefinition, CheckResult, DoctorResult, SystemInfo, ToolsSummary } from "./types" function createSystemInfo(): SystemInfo { return { @@ -14,13 +14,6 @@ function createSystemInfo(): SystemInfo { } } -function createProviders(): ProviderStatus[] { - return [ - { id: "anthropic", name: "Anthropic", available: true, hasEnvVar: true, hasPlugin: true }, - { id: "openai", name: "OpenAI", available: false, hasEnvVar: false, hasPlugin: false }, - ] -} - function createTools(): ToolsSummary { return { lspInstalled: 1, @@ -143,7 +136,6 @@ describe("runner", () => { const deferredTwo = createDeferred() const deferredThree = createDeferred() const deferredFour = createDeferred() - const deferredFive = createDeferred() const checks: CheckDefinition[] = [ { @@ -162,20 +154,12 @@ describe("runner", () => { return deferredTwo.promise }, }, - { - id: "providers", - name: "Providers", - check: async () => { - startedChecks.push("providers") - return deferredThree.promise - }, - }, { id: "tools", name: "Tools", check: async () => { startedChecks.push("tools") - return deferredFour.promise + return deferredThree.promise }, }, { @@ -183,7 +167,7 @@ describe("runner", () => { name: "Models", check: async () => { startedChecks.push("models") - return deferredFive.promise + return deferredFour.promise }, }, ] @@ -192,16 +176,14 @@ describe("runner", () => { results: [ createPassResult("System"), createPassResult("Configuration"), - createPassResult("Providers"), createPassResult("Tools"), createPassResult("Models"), ], systemInfo: createSystemInfo(), - providers: createProviders(), tools: createTools(), summary: { - total: 5, - passed: 5, + total: 4, + passed: 4, failed: 0, warnings: 0, skipped: 0, @@ -216,7 +198,6 @@ describe("runner", () => { mock.module("./checks", () => ({ getAllCheckDefinitions: () => checks, gatherSystemInfo: async () => expectedResult.systemInfo, - gatherProviderStatuses: () => expectedResult.providers, gatherToolsSummary: async () => expectedResult.tools, })) mock.module("./formatter", () => ({ @@ -236,15 +217,14 @@ describe("runner", () => { const startedBeforeResolve = [...startedChecks] deferredOne.resolve(createPassResult("System")) deferredTwo.resolve(createPassResult("Configuration")) - deferredThree.resolve(createPassResult("Providers")) - deferredFour.resolve(createPassResult("Tools")) - deferredFive.resolve(createPassResult("Models")) + deferredThree.resolve(createPassResult("Tools")) + deferredFour.resolve(createPassResult("Models")) const result = await runPromise //#then console.log = originalLog - expect(startedBeforeResolve.sort()).toEqual(["config", "models", "providers", "system", "tools"]) - expect(result.results.length).toBe(5) + expect(startedBeforeResolve.sort()).toEqual(["config", "models", "system", "tools"]) + expect(result.results.length).toBe(4) expect(result.exitCode).toBe(0) expect(formatDoctorOutputMock).toHaveBeenCalledTimes(1) expect(formatJsonOutputMock).toHaveBeenCalledTimes(0) diff --git a/src/cli/doctor/runner.ts b/src/cli/doctor/runner.ts index ec9e0152..75342bec 100644 --- a/src/cli/doctor/runner.ts +++ b/src/cli/doctor/runner.ts @@ -1,5 +1,5 @@ import type { DoctorOptions, DoctorResult, CheckDefinition, CheckResult, DoctorSummary } from "./types" -import { getAllCheckDefinitions, gatherSystemInfo, gatherProviderStatuses, gatherToolsSummary } from "./checks" +import { getAllCheckDefinitions, gatherSystemInfo, gatherToolsSummary } from "./checks" import { EXIT_CODES } from "./constants" import { formatDoctorOutput, formatJsonOutput } from "./formatter" @@ -39,10 +39,9 @@ export async function runDoctor(options: DoctorOptions): Promise { const start = performance.now() const allChecks = getAllCheckDefinitions() - const [results, systemInfo, providers, tools] = await Promise.all([ + const [results, systemInfo, tools] = await Promise.all([ Promise.all(allChecks.map(runCheck)), gatherSystemInfo(), - Promise.resolve(gatherProviderStatuses()), gatherToolsSummary(), ]) @@ -53,7 +52,6 @@ export async function runDoctor(options: DoctorOptions): Promise { const doctorResult: DoctorResult = { results, systemInfo, - providers, tools, summary, exitCode, diff --git a/src/cli/doctor/types.ts b/src/cli/doctor/types.ts index cb725c42..4c165566 100644 --- a/src/cli/doctor/types.ts +++ b/src/cli/doctor/types.ts @@ -46,14 +46,6 @@ export interface SystemInfo { isLocalDev: boolean } -export interface ProviderStatus { - id: string - name: string - available: boolean - hasEnvVar: boolean - hasPlugin: boolean -} - export interface ToolsSummary { lspInstalled: number lspTotal: number @@ -77,7 +69,6 @@ export interface DoctorSummary { export interface DoctorResult { results: CheckResult[] systemInfo: SystemInfo - providers: ProviderStatus[] tools: ToolsSummary summary: DoctorSummary exitCode: number