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:
parent
2ba148be12
commit
a7b56a0391
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -19,7 +19,6 @@ function createBaseResult(): DoctorResult {
|
||||
configValid: true,
|
||||
isLocalDev: false,
|
||||
},
|
||||
providers: [],
|
||||
tools: {
|
||||
lspInstalled: 0,
|
||||
lspTotal: 0,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user