fix(doctor): oMoMoMoMo branding, remove providers check, fix comment-checker detection

Rename header to oMoMoMoMo Doctor to match installation guide branding.
Remove providers check entirely — no longer meaningful for diagnostics.
Fix comment-checker detection by resolving @code-yeongyu/comment-checker package path
in addition to PATH lookup.
This commit is contained in:
YeonGyu-Kim 2026-02-13 17:35:36 +09:00
parent 2ba148be12
commit a7b56a0391
12 changed files with 36 additions and 188 deletions

View File

@ -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<DependencyInfo> {
}
}
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<DependencyInfo> {
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<DependencyInfo> {
}
}
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,
}
}

View File

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

View File

@ -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<OpenCodeConfigShape>(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<CheckResult> {
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,
}
}

View File

@ -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<string, string> = {
[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<string, string[]> = {
anthropic: ["ANTHROPIC_API_KEY"],
openai: ["OPENAI_API_KEY"],
google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
} as const
export const AUTH_PLUGINS: Record<string, { plugin: string; name: string }> = {
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,

View File

@ -19,7 +19,6 @@ function createBaseResult(): DoctorResult {
configValid: true,
isLocalDev: false,
},
providers: [],
tools: {
lspInstalled: 0,
lspTotal: 0,

View File

@ -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 {

View File

@ -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)

View File

@ -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`)

View File

@ -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,

View File

@ -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)

View File

@ -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<DoctorResult> {
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<DoctorResult> {
const doctorResult: DoctorResult = {
results,
systemInfo,
providers,
tools,
summary,
exitCode,

View File

@ -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