oh-my-opencode 3.5.3 → 3.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/agents/builtin-agents/agent-overrides.d.ts +2 -2
- package/dist/agents/builtin-agents/atlas-agent.d.ts +1 -0
- package/dist/agents/builtin-agents/resolve-file-uri.d.ts +1 -0
- package/dist/cli/doctor/checks/config.d.ts +2 -8
- package/dist/cli/doctor/checks/dependencies.d.ts +1 -5
- package/dist/cli/doctor/checks/index.d.ts +4 -16
- package/dist/cli/doctor/checks/model-resolution.d.ts +4 -4
- package/dist/cli/doctor/checks/{opencode.d.ts → system-binary.d.ts} +6 -12
- package/dist/cli/doctor/checks/system-loaded-version.d.ts +9 -0
- package/dist/cli/doctor/checks/system-plugin.d.ts +15 -0
- package/dist/cli/doctor/checks/system.d.ts +3 -0
- package/dist/cli/doctor/checks/{gh.d.ts → tools-gh.d.ts} +0 -3
- package/dist/cli/doctor/checks/tools-lsp.d.ts +6 -0
- package/dist/cli/doctor/checks/tools-mcp.d.ts +3 -0
- package/dist/cli/doctor/checks/tools.d.ts +3 -0
- package/dist/cli/doctor/constants.d.ts +4 -17
- package/dist/cli/doctor/format-default.d.ts +2 -0
- package/dist/cli/doctor/format-shared.d.ts +6 -0
- package/dist/cli/doctor/format-status.d.ts +2 -0
- package/dist/cli/doctor/format-verbose.d.ts +2 -0
- package/dist/cli/doctor/formatter.d.ts +2 -11
- package/dist/cli/doctor/index.d.ts +1 -1
- package/dist/cli/doctor/runner.d.ts +1 -3
- package/dist/cli/doctor/types.d.ts +39 -6
- package/dist/cli/index.js +946 -1142
- package/dist/cli/run/runner.d.ts +1 -0
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/config/schema/oh-my-opencode-config.d.ts +11 -11
- package/dist/config/schema/skills.d.ts +10 -10
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/constants.d.ts +1 -0
- package/dist/features/background-agent/index.d.ts +1 -0
- package/dist/features/background-agent/manager.d.ts +2 -0
- package/dist/features/background-agent/poll-running-tasks.d.ts +3 -1
- package/dist/features/background-agent/task-history.d.ts +18 -0
- package/dist/features/background-agent/task-poller.d.ts +4 -0
- package/dist/features/background-agent/types.d.ts +4 -0
- package/dist/features/claude-code-agent-loader/loader.d.ts +1 -1
- package/dist/features/claude-code-command-loader/loader.d.ts +3 -3
- package/dist/features/opencode-skill-loader/config-source-discovery.d.ts +7 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/loader.d.ts +8 -5
- package/dist/features/opencode-skill-loader/merger.d.ts +1 -1
- package/dist/features/opencode-skill-loader/skill-resolution-options.d.ts +2 -0
- package/dist/features/tmux-subagent/grid-planning.d.ts +1 -1
- package/dist/features/tmux-subagent/pane-split-availability.d.ts +3 -3
- package/dist/features/tmux-subagent/spawn-action-decider.d.ts +1 -1
- package/dist/hooks/claude-code-hooks/transcript.d.ts +8 -13
- package/dist/hooks/compaction-context-injector/hook.d.ts +2 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +2 -5
- package/dist/hooks/think-mode/switcher.d.ts +1 -2
- package/dist/hooks/todo-continuation-enforcer/constants.d.ts +1 -0
- package/dist/hooks/todo-continuation-enforcer/session-state.d.ts +1 -0
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +2 -0
- package/dist/index.js +1854 -1398
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/session-agent-resolver.d.ts +19 -0
- package/dist/plugin-config.d.ts +1 -0
- package/dist/plugin-handlers/command-config-handler.d.ts +3 -0
- package/dist/shared/session-tools-store.d.ts +3 -0
- package/dist/tools/call-omo-agent/sync-executor.d.ts +10 -1
- package/dist/tools/delegate-task/constants.d.ts +1 -1
- package/dist/tools/slashcommand/command-discovery.d.ts +1 -1
- package/package.json +8 -8
- package/dist/cli/doctor/checks/auth.d.ts +0 -7
- package/dist/cli/doctor/checks/lsp.d.ts +0 -8
- package/dist/cli/doctor/checks/mcp-oauth.d.ts +0 -15
- package/dist/cli/doctor/checks/mcp.d.ts +0 -6
- package/dist/cli/doctor/checks/plugin.d.ts +0 -4
- package/dist/cli/doctor/checks/version.d.ts +0 -4
- package/dist/hooks/subagent-question-blocker/hook.d.ts +0 -2
- package/dist/hooks/subagent-question-blocker/index.d.ts +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -6782,9 +6782,10 @@ var init_model_requirements = __esm(() => {
|
|
|
6782
6782
|
CATEGORY_MODEL_REQUIREMENTS = {
|
|
6783
6783
|
"visual-engineering": {
|
|
6784
6784
|
fallbackChain: [
|
|
6785
|
-
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
|
6785
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
|
6786
|
+
{ providers: ["zai-coding-plan"], model: "glm-5" },
|
|
6786
6787
|
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
|
6787
|
-
{ providers: ["
|
|
6788
|
+
{ providers: ["kimi-for-coding"], model: "k2p5" }
|
|
6788
6789
|
]
|
|
6789
6790
|
},
|
|
6790
6791
|
ultrabrain: {
|
|
@@ -6833,10 +6834,9 @@ var init_model_requirements = __esm(() => {
|
|
|
6833
6834
|
},
|
|
6834
6835
|
writing: {
|
|
6835
6836
|
fallbackChain: [
|
|
6837
|
+
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
|
6836
6838
|
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
|
6837
|
-
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" }
|
|
6838
|
-
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
|
6839
|
-
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
|
|
6839
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" }
|
|
6840
6840
|
]
|
|
6841
6841
|
}
|
|
6842
6842
|
};
|
|
@@ -8877,7 +8877,7 @@ var {
|
|
|
8877
8877
|
// package.json
|
|
8878
8878
|
var package_default = {
|
|
8879
8879
|
name: "oh-my-opencode",
|
|
8880
|
-
version: "3.5.
|
|
8880
|
+
version: "3.5.5",
|
|
8881
8881
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
8882
8882
|
main: "dist/index.js",
|
|
8883
8883
|
types: "dist/index.d.ts",
|
|
@@ -8951,13 +8951,13 @@ var package_default = {
|
|
|
8951
8951
|
typescript: "^5.7.3"
|
|
8952
8952
|
},
|
|
8953
8953
|
optionalDependencies: {
|
|
8954
|
-
"oh-my-opencode-darwin-arm64": "3.5.
|
|
8955
|
-
"oh-my-opencode-darwin-x64": "3.5.
|
|
8956
|
-
"oh-my-opencode-linux-arm64": "3.5.
|
|
8957
|
-
"oh-my-opencode-linux-arm64-musl": "3.5.
|
|
8958
|
-
"oh-my-opencode-linux-x64": "3.5.
|
|
8959
|
-
"oh-my-opencode-linux-x64-musl": "3.5.
|
|
8960
|
-
"oh-my-opencode-windows-x64": "3.5.
|
|
8954
|
+
"oh-my-opencode-darwin-arm64": "3.5.5",
|
|
8955
|
+
"oh-my-opencode-darwin-x64": "3.5.5",
|
|
8956
|
+
"oh-my-opencode-linux-arm64": "3.5.5",
|
|
8957
|
+
"oh-my-opencode-linux-arm64-musl": "3.5.5",
|
|
8958
|
+
"oh-my-opencode-linux-x64": "3.5.5",
|
|
8959
|
+
"oh-my-opencode-linux-x64-musl": "3.5.5",
|
|
8960
|
+
"oh-my-opencode-windows-x64": "3.5.5"
|
|
8961
8961
|
},
|
|
8962
8962
|
trustedDependencies: [
|
|
8963
8963
|
"@ast-grep/cli",
|
|
@@ -22699,7 +22699,8 @@ var BackgroundTaskConfigSchema = exports_external.object({
|
|
|
22699
22699
|
defaultConcurrency: exports_external.number().min(1).optional(),
|
|
22700
22700
|
providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
22701
22701
|
modelConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
22702
|
-
staleTimeoutMs: exports_external.number().min(60000).optional()
|
|
22702
|
+
staleTimeoutMs: exports_external.number().min(60000).optional(),
|
|
22703
|
+
messageStalenessTimeoutMs: exports_external.number().min(60000).optional()
|
|
22703
22704
|
});
|
|
22704
22705
|
// src/config/schema/browser-automation.ts
|
|
22705
22706
|
var BrowserAutomationProviderSchema = exports_external.enum([
|
|
@@ -22825,7 +22826,6 @@ var HookNameSchema = exports_external.enum([
|
|
|
22825
22826
|
"directory-readme-injector",
|
|
22826
22827
|
"empty-task-response-detector",
|
|
22827
22828
|
"think-mode",
|
|
22828
|
-
"subagent-question-blocker",
|
|
22829
22829
|
"anthropic-context-window-limit-recovery",
|
|
22830
22830
|
"preemptive-compaction",
|
|
22831
22831
|
"rules-injector",
|
|
@@ -22898,11 +22898,11 @@ var SkillDefinitionSchema = exports_external.object({
|
|
|
22898
22898
|
var SkillEntrySchema = exports_external.union([exports_external.boolean(), SkillDefinitionSchema]);
|
|
22899
22899
|
var SkillsConfigSchema = exports_external.union([
|
|
22900
22900
|
exports_external.array(exports_external.string()),
|
|
22901
|
-
exports_external.
|
|
22901
|
+
exports_external.object({
|
|
22902
22902
|
sources: exports_external.array(SkillSourceSchema).optional(),
|
|
22903
22903
|
enable: exports_external.array(exports_external.string()).optional(),
|
|
22904
22904
|
disable: exports_external.array(exports_external.string()).optional()
|
|
22905
|
-
}).
|
|
22905
|
+
}).catchall(SkillEntrySchema)
|
|
22906
22906
|
]);
|
|
22907
22907
|
|
|
22908
22908
|
// src/config/schema/sisyphus.ts
|
|
@@ -22977,6 +22977,32 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
22977
22977
|
});
|
|
22978
22978
|
// src/plugin-config.ts
|
|
22979
22979
|
init_shared();
|
|
22980
|
+
function parseConfigPartially(rawConfig) {
|
|
22981
|
+
const fullResult = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
22982
|
+
if (fullResult.success) {
|
|
22983
|
+
return fullResult.data;
|
|
22984
|
+
}
|
|
22985
|
+
const partialConfig = {};
|
|
22986
|
+
const invalidSections = [];
|
|
22987
|
+
for (const key of Object.keys(rawConfig)) {
|
|
22988
|
+
const sectionResult = OhMyOpenCodeConfigSchema.safeParse({ [key]: rawConfig[key] });
|
|
22989
|
+
if (sectionResult.success) {
|
|
22990
|
+
const parsed = sectionResult.data;
|
|
22991
|
+
if (parsed[key] !== undefined) {
|
|
22992
|
+
partialConfig[key] = parsed[key];
|
|
22993
|
+
}
|
|
22994
|
+
} else {
|
|
22995
|
+
const sectionErrors = sectionResult.error.issues.filter((i2) => i2.path[0] === key).map((i2) => `${i2.path.join(".")}: ${i2.message}`).join(", ");
|
|
22996
|
+
if (sectionErrors) {
|
|
22997
|
+
invalidSections.push(`${key}: ${sectionErrors}`);
|
|
22998
|
+
}
|
|
22999
|
+
}
|
|
23000
|
+
}
|
|
23001
|
+
if (invalidSections.length > 0) {
|
|
23002
|
+
log("Partial config loaded \u2014 invalid sections skipped:", invalidSections);
|
|
23003
|
+
}
|
|
23004
|
+
return partialConfig;
|
|
23005
|
+
}
|
|
22980
23006
|
function loadConfigFromPath(configPath, ctx) {
|
|
22981
23007
|
try {
|
|
22982
23008
|
if (fs3.existsSync(configPath)) {
|
|
@@ -22984,17 +23010,22 @@ function loadConfigFromPath(configPath, ctx) {
|
|
|
22984
23010
|
const rawConfig = parseJsonc(content);
|
|
22985
23011
|
migrateConfigFile(configPath, rawConfig);
|
|
22986
23012
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
22987
|
-
if (
|
|
22988
|
-
|
|
22989
|
-
|
|
22990
|
-
|
|
22991
|
-
|
|
22992
|
-
|
|
22993
|
-
|
|
22994
|
-
|
|
23013
|
+
if (result.success) {
|
|
23014
|
+
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
|
|
23015
|
+
return result.data;
|
|
23016
|
+
}
|
|
23017
|
+
const errorMsg = result.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join(", ");
|
|
23018
|
+
log(`Config validation error in ${configPath}:`, result.error.issues);
|
|
23019
|
+
addConfigLoadError({
|
|
23020
|
+
path: configPath,
|
|
23021
|
+
error: `Partial config loaded \u2014 invalid sections skipped: ${errorMsg}`
|
|
23022
|
+
});
|
|
23023
|
+
const partialResult = parseConfigPartially(rawConfig);
|
|
23024
|
+
if (partialResult) {
|
|
23025
|
+
log(`Partial config loaded from ${configPath}`, { agents: partialResult.agents });
|
|
23026
|
+
return partialResult;
|
|
22995
23027
|
}
|
|
22996
|
-
|
|
22997
|
-
return result.data;
|
|
23028
|
+
return null;
|
|
22998
23029
|
}
|
|
22999
23030
|
} catch (err) {
|
|
23000
23031
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -24819,6 +24850,16 @@ All tasks completed.`));
|
|
|
24819
24850
|
|
|
24820
24851
|
// src/cli/run/runner.ts
|
|
24821
24852
|
var DEFAULT_TIMEOUT_MS = 600000;
|
|
24853
|
+
var EVENT_PROCESSOR_SHUTDOWN_TIMEOUT_MS = 2000;
|
|
24854
|
+
async function waitForEventProcessorShutdown(eventProcessor, timeoutMs = EVENT_PROCESSOR_SHUTDOWN_TIMEOUT_MS) {
|
|
24855
|
+
const completed = await Promise.race([
|
|
24856
|
+
eventProcessor.then(() => true),
|
|
24857
|
+
new Promise((resolve2) => setTimeout(() => resolve2(false), timeoutMs))
|
|
24858
|
+
]);
|
|
24859
|
+
if (!completed) {
|
|
24860
|
+
console.log(import_picocolors14.default.dim(`[run] Event stream did not close within ${timeoutMs}ms after abort; continuing shutdown.`));
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24822
24863
|
async function run(options) {
|
|
24823
24864
|
process.env.OPENCODE_CLI_RUN_MODE = "true";
|
|
24824
24865
|
const startTime = Date.now();
|
|
@@ -24882,7 +24923,7 @@ Sending prompt...`));
|
|
|
24882
24923
|
`));
|
|
24883
24924
|
const exitCode = await pollForCompletion(ctx, eventState, abortController);
|
|
24884
24925
|
abortController.abort();
|
|
24885
|
-
await eventProcessor;
|
|
24926
|
+
await waitForEventProcessorShutdown(eventProcessor);
|
|
24886
24927
|
cleanup();
|
|
24887
24928
|
const durationMs = Date.now() - startTime;
|
|
24888
24929
|
if (options.onComplete) {
|
|
@@ -25069,11 +25110,6 @@ async function getLocalVersion(options = {}) {
|
|
|
25069
25110
|
return 1;
|
|
25070
25111
|
}
|
|
25071
25112
|
}
|
|
25072
|
-
// src/cli/doctor/checks/opencode.ts
|
|
25073
|
-
import { existsSync as existsSync16 } from "fs";
|
|
25074
|
-
import { homedir as homedir5 } from "os";
|
|
25075
|
-
import { join as join12 } from "path";
|
|
25076
|
-
|
|
25077
25113
|
// src/cli/doctor/constants.ts
|
|
25078
25114
|
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
25079
25115
|
var SYMBOLS3 = {
|
|
@@ -25092,48 +25128,16 @@ var STATUS_COLORS = {
|
|
|
25092
25128
|
skip: import_picocolors16.default.dim
|
|
25093
25129
|
};
|
|
25094
25130
|
var CHECK_IDS = {
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
AUTH_ANTHROPIC: "auth-anthropic",
|
|
25100
|
-
AUTH_OPENAI: "auth-openai",
|
|
25101
|
-
AUTH_GOOGLE: "auth-google",
|
|
25102
|
-
DEP_AST_GREP_CLI: "dep-ast-grep-cli",
|
|
25103
|
-
DEP_AST_GREP_NAPI: "dep-ast-grep-napi",
|
|
25104
|
-
DEP_COMMENT_CHECKER: "dep-comment-checker",
|
|
25105
|
-
GH_CLI: "gh-cli",
|
|
25106
|
-
LSP_SERVERS: "lsp-servers",
|
|
25107
|
-
MCP_BUILTIN: "mcp-builtin",
|
|
25108
|
-
MCP_USER: "mcp-user",
|
|
25109
|
-
MCP_OAUTH_TOKENS: "mcp-oauth-tokens",
|
|
25110
|
-
VERSION_STATUS: "version-status"
|
|
25131
|
+
SYSTEM: "system",
|
|
25132
|
+
CONFIG: "config",
|
|
25133
|
+
TOOLS: "tools",
|
|
25134
|
+
MODELS: "models"
|
|
25111
25135
|
};
|
|
25112
25136
|
var CHECK_NAMES = {
|
|
25113
|
-
[CHECK_IDS.
|
|
25114
|
-
[CHECK_IDS.
|
|
25115
|
-
[CHECK_IDS.
|
|
25116
|
-
[CHECK_IDS.
|
|
25117
|
-
[CHECK_IDS.AUTH_ANTHROPIC]: "Anthropic (Claude) Auth",
|
|
25118
|
-
[CHECK_IDS.AUTH_OPENAI]: "OpenAI (ChatGPT) Auth",
|
|
25119
|
-
[CHECK_IDS.AUTH_GOOGLE]: "Google (Gemini) Auth",
|
|
25120
|
-
[CHECK_IDS.DEP_AST_GREP_CLI]: "AST-Grep CLI",
|
|
25121
|
-
[CHECK_IDS.DEP_AST_GREP_NAPI]: "AST-Grep NAPI",
|
|
25122
|
-
[CHECK_IDS.DEP_COMMENT_CHECKER]: "Comment Checker",
|
|
25123
|
-
[CHECK_IDS.GH_CLI]: "GitHub CLI",
|
|
25124
|
-
[CHECK_IDS.LSP_SERVERS]: "LSP Servers",
|
|
25125
|
-
[CHECK_IDS.MCP_BUILTIN]: "Built-in MCP Servers",
|
|
25126
|
-
[CHECK_IDS.MCP_USER]: "User MCP Configuration",
|
|
25127
|
-
[CHECK_IDS.MCP_OAUTH_TOKENS]: "MCP OAuth Tokens",
|
|
25128
|
-
[CHECK_IDS.VERSION_STATUS]: "Version Status"
|
|
25129
|
-
};
|
|
25130
|
-
var CATEGORY_NAMES = {
|
|
25131
|
-
installation: "Installation",
|
|
25132
|
-
configuration: "Configuration",
|
|
25133
|
-
authentication: "Authentication",
|
|
25134
|
-
dependencies: "Dependencies",
|
|
25135
|
-
tools: "Tools & Servers",
|
|
25136
|
-
updates: "Updates"
|
|
25137
|
+
[CHECK_IDS.SYSTEM]: "System",
|
|
25138
|
+
[CHECK_IDS.CONFIG]: "Configuration",
|
|
25139
|
+
[CHECK_IDS.TOOLS]: "Tools",
|
|
25140
|
+
[CHECK_IDS.MODELS]: "Models"
|
|
25137
25141
|
};
|
|
25138
25142
|
var EXIT_CODES = {
|
|
25139
25143
|
SUCCESS: 0,
|
|
@@ -25143,7 +25147,13 @@ var MIN_OPENCODE_VERSION = "1.0.150";
|
|
|
25143
25147
|
var PACKAGE_NAME4 = "oh-my-opencode";
|
|
25144
25148
|
var OPENCODE_BINARIES2 = ["opencode", "opencode-desktop"];
|
|
25145
25149
|
|
|
25146
|
-
// src/cli/doctor/checks/
|
|
25150
|
+
// src/cli/doctor/checks/system.ts
|
|
25151
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
|
|
25152
|
+
|
|
25153
|
+
// src/cli/doctor/checks/system-binary.ts
|
|
25154
|
+
import { existsSync as existsSync16 } from "fs";
|
|
25155
|
+
import { homedir as homedir5 } from "os";
|
|
25156
|
+
import { join as join12 } from "path";
|
|
25147
25157
|
function getDesktopAppPaths(platform) {
|
|
25148
25158
|
const home = homedir5();
|
|
25149
25159
|
switch (platform) {
|
|
@@ -25177,21 +25187,12 @@ function getDesktopAppPaths(platform) {
|
|
|
25177
25187
|
}
|
|
25178
25188
|
function buildVersionCommand(binaryPath, platform) {
|
|
25179
25189
|
if (platform === "win32" && binaryPath.toLowerCase().endsWith(".ps1")) {
|
|
25180
|
-
return [
|
|
25181
|
-
"powershell",
|
|
25182
|
-
"-NoProfile",
|
|
25183
|
-
"-ExecutionPolicy",
|
|
25184
|
-
"Bypass",
|
|
25185
|
-
"-File",
|
|
25186
|
-
binaryPath,
|
|
25187
|
-
"--version"
|
|
25188
|
-
];
|
|
25190
|
+
return ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", binaryPath, "--version"];
|
|
25189
25191
|
}
|
|
25190
25192
|
return [binaryPath, "--version"];
|
|
25191
25193
|
}
|
|
25192
25194
|
function findDesktopBinary(platform = process.platform, checkExists = existsSync16) {
|
|
25193
|
-
const
|
|
25194
|
-
for (const desktopPath of desktopPaths) {
|
|
25195
|
+
for (const desktopPath of getDesktopAppPaths(platform)) {
|
|
25195
25196
|
if (checkExists(desktopPath)) {
|
|
25196
25197
|
return { binary: "opencode", path: desktopPath };
|
|
25197
25198
|
}
|
|
@@ -25200,346 +25201,312 @@ function findDesktopBinary(platform = process.platform, checkExists = existsSync
|
|
|
25200
25201
|
}
|
|
25201
25202
|
async function findOpenCodeBinary() {
|
|
25202
25203
|
for (const binary2 of OPENCODE_BINARIES2) {
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
return { binary: binary2, path: path9 };
|
|
25207
|
-
}
|
|
25208
|
-
} catch {
|
|
25209
|
-
continue;
|
|
25204
|
+
const path9 = Bun.which(binary2);
|
|
25205
|
+
if (path9) {
|
|
25206
|
+
return { binary: binary2, path: path9 };
|
|
25210
25207
|
}
|
|
25211
25208
|
}
|
|
25212
|
-
|
|
25213
|
-
if (desktopResult) {
|
|
25214
|
-
return desktopResult;
|
|
25215
|
-
}
|
|
25216
|
-
return null;
|
|
25209
|
+
return findDesktopBinary();
|
|
25217
25210
|
}
|
|
25218
25211
|
async function getOpenCodeVersion2(binaryPath, platform = process.platform) {
|
|
25219
25212
|
try {
|
|
25220
25213
|
const command = buildVersionCommand(binaryPath, platform);
|
|
25221
|
-
const
|
|
25222
|
-
const output = await new Response(
|
|
25223
|
-
await
|
|
25224
|
-
if (
|
|
25225
|
-
return
|
|
25226
|
-
|
|
25214
|
+
const processResult = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
25215
|
+
const output = await new Response(processResult.stdout).text();
|
|
25216
|
+
await processResult.exited;
|
|
25217
|
+
if (processResult.exitCode !== 0)
|
|
25218
|
+
return null;
|
|
25219
|
+
return output.trim() || null;
|
|
25227
25220
|
} catch {
|
|
25228
25221
|
return null;
|
|
25229
25222
|
}
|
|
25230
|
-
return null;
|
|
25231
25223
|
}
|
|
25232
25224
|
function compareVersions(current, minimum) {
|
|
25233
|
-
const parseVersion = (v) =>
|
|
25234
|
-
|
|
25235
|
-
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
const m2 = min[i2] ?? 0;
|
|
25242
|
-
if (c > m2)
|
|
25225
|
+
const parseVersion = (version2) => version2.replace(/^v/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
25226
|
+
const currentParts = parseVersion(current);
|
|
25227
|
+
const minimumParts = parseVersion(minimum);
|
|
25228
|
+
const length = Math.max(currentParts.length, minimumParts.length);
|
|
25229
|
+
for (let index = 0;index < length; index++) {
|
|
25230
|
+
const currentPart = currentParts[index] ?? 0;
|
|
25231
|
+
const minimumPart = minimumParts[index] ?? 0;
|
|
25232
|
+
if (currentPart > minimumPart)
|
|
25243
25233
|
return true;
|
|
25244
|
-
if (
|
|
25234
|
+
if (currentPart < minimumPart)
|
|
25245
25235
|
return false;
|
|
25246
25236
|
}
|
|
25247
25237
|
return true;
|
|
25248
25238
|
}
|
|
25249
|
-
async function getOpenCodeInfo() {
|
|
25250
|
-
const binaryInfo = await findOpenCodeBinary();
|
|
25251
|
-
if (!binaryInfo) {
|
|
25252
|
-
return {
|
|
25253
|
-
installed: false,
|
|
25254
|
-
version: null,
|
|
25255
|
-
path: null,
|
|
25256
|
-
binary: null
|
|
25257
|
-
};
|
|
25258
|
-
}
|
|
25259
|
-
const version2 = await getOpenCodeVersion2(binaryInfo.path ?? binaryInfo.binary);
|
|
25260
|
-
return {
|
|
25261
|
-
installed: true,
|
|
25262
|
-
version: version2,
|
|
25263
|
-
path: binaryInfo.path,
|
|
25264
|
-
binary: binaryInfo.binary
|
|
25265
|
-
};
|
|
25266
|
-
}
|
|
25267
|
-
async function checkOpenCodeInstallation() {
|
|
25268
|
-
const info = await getOpenCodeInfo();
|
|
25269
|
-
if (!info.installed) {
|
|
25270
|
-
return {
|
|
25271
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25272
|
-
status: "fail",
|
|
25273
|
-
message: "OpenCode is not installed",
|
|
25274
|
-
details: [
|
|
25275
|
-
"Visit: https://opencode.ai/docs for installation instructions",
|
|
25276
|
-
"Run: npm install -g opencode"
|
|
25277
|
-
]
|
|
25278
|
-
};
|
|
25279
|
-
}
|
|
25280
|
-
if (info.version && !compareVersions(info.version, MIN_OPENCODE_VERSION)) {
|
|
25281
|
-
return {
|
|
25282
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25283
|
-
status: "warn",
|
|
25284
|
-
message: `Version ${info.version} is below minimum ${MIN_OPENCODE_VERSION}`,
|
|
25285
|
-
details: [
|
|
25286
|
-
`Current: ${info.version}`,
|
|
25287
|
-
`Required: >= ${MIN_OPENCODE_VERSION}`,
|
|
25288
|
-
"Run: npm update -g opencode"
|
|
25289
|
-
]
|
|
25290
|
-
};
|
|
25291
|
-
}
|
|
25292
|
-
return {
|
|
25293
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25294
|
-
status: "pass",
|
|
25295
|
-
message: info.version ?? "installed",
|
|
25296
|
-
details: info.path ? [`Path: ${info.path}`] : undefined
|
|
25297
|
-
};
|
|
25298
|
-
}
|
|
25299
|
-
function getOpenCodeCheckDefinition() {
|
|
25300
|
-
return {
|
|
25301
|
-
id: CHECK_IDS.OPENCODE_INSTALLATION,
|
|
25302
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25303
|
-
category: "installation",
|
|
25304
|
-
check: checkOpenCodeInstallation,
|
|
25305
|
-
critical: true
|
|
25306
|
-
};
|
|
25307
|
-
}
|
|
25308
25239
|
|
|
25309
|
-
// src/cli/doctor/checks/plugin.ts
|
|
25240
|
+
// src/cli/doctor/checks/system-plugin.ts
|
|
25310
25241
|
import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
|
|
25311
25242
|
init_shared();
|
|
25312
25243
|
function detectConfigPath() {
|
|
25313
25244
|
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
|
|
25314
|
-
if (existsSync17(paths.configJsonc))
|
|
25315
|
-
return
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
return { path: paths.configJson, format: "json" };
|
|
25319
|
-
}
|
|
25245
|
+
if (existsSync17(paths.configJsonc))
|
|
25246
|
+
return paths.configJsonc;
|
|
25247
|
+
if (existsSync17(paths.configJson))
|
|
25248
|
+
return paths.configJson;
|
|
25320
25249
|
return null;
|
|
25321
25250
|
}
|
|
25322
|
-
function
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25251
|
+
function parsePluginVersion(entry) {
|
|
25252
|
+
if (!entry.startsWith(`${PACKAGE_NAME4}@`))
|
|
25253
|
+
return null;
|
|
25254
|
+
const value = entry.slice(PACKAGE_NAME4.length + 1);
|
|
25255
|
+
if (!value || value === "latest")
|
|
25256
|
+
return null;
|
|
25257
|
+
return value;
|
|
25258
|
+
}
|
|
25259
|
+
function findPluginEntry2(entries) {
|
|
25260
|
+
for (const entry of entries) {
|
|
25261
|
+
if (entry === PACKAGE_NAME4 || entry.startsWith(`${PACKAGE_NAME4}@`)) {
|
|
25262
|
+
return { entry, isLocalDev: false };
|
|
25328
25263
|
}
|
|
25329
|
-
if (
|
|
25330
|
-
return { entry
|
|
25264
|
+
if (entry.startsWith("file://") && entry.includes(PACKAGE_NAME4)) {
|
|
25265
|
+
return { entry, isLocalDev: true };
|
|
25331
25266
|
}
|
|
25332
25267
|
}
|
|
25333
25268
|
return null;
|
|
25334
25269
|
}
|
|
25335
25270
|
function getPluginInfo() {
|
|
25336
|
-
const
|
|
25337
|
-
if (!
|
|
25271
|
+
const configPath = detectConfigPath();
|
|
25272
|
+
if (!configPath) {
|
|
25338
25273
|
return {
|
|
25339
25274
|
registered: false,
|
|
25340
25275
|
configPath: null,
|
|
25341
25276
|
entry: null,
|
|
25342
25277
|
isPinned: false,
|
|
25343
|
-
pinnedVersion: null
|
|
25278
|
+
pinnedVersion: null,
|
|
25279
|
+
isLocalDev: false
|
|
25344
25280
|
};
|
|
25345
25281
|
}
|
|
25346
25282
|
try {
|
|
25347
|
-
const content = readFileSync18(
|
|
25348
|
-
const
|
|
25349
|
-
const
|
|
25350
|
-
const pluginEntry = findPluginEntry2(plugins);
|
|
25283
|
+
const content = readFileSync18(configPath, "utf-8");
|
|
25284
|
+
const parsedConfig = parseJsonc(content);
|
|
25285
|
+
const pluginEntry = findPluginEntry2(parsedConfig.plugin ?? []);
|
|
25351
25286
|
if (!pluginEntry) {
|
|
25352
25287
|
return {
|
|
25353
25288
|
registered: false,
|
|
25354
|
-
configPath
|
|
25289
|
+
configPath,
|
|
25355
25290
|
entry: null,
|
|
25356
25291
|
isPinned: false,
|
|
25357
|
-
pinnedVersion: null
|
|
25292
|
+
pinnedVersion: null,
|
|
25293
|
+
isLocalDev: false
|
|
25358
25294
|
};
|
|
25359
25295
|
}
|
|
25296
|
+
const pinnedVersion = parsePluginVersion(pluginEntry.entry);
|
|
25360
25297
|
return {
|
|
25361
25298
|
registered: true,
|
|
25362
|
-
configPath
|
|
25299
|
+
configPath,
|
|
25363
25300
|
entry: pluginEntry.entry,
|
|
25364
|
-
isPinned:
|
|
25365
|
-
pinnedVersion
|
|
25301
|
+
isPinned: pinnedVersion !== null,
|
|
25302
|
+
pinnedVersion,
|
|
25303
|
+
isLocalDev: pluginEntry.isLocalDev
|
|
25366
25304
|
};
|
|
25367
25305
|
} catch {
|
|
25368
25306
|
return {
|
|
25369
25307
|
registered: false,
|
|
25370
|
-
configPath
|
|
25308
|
+
configPath,
|
|
25371
25309
|
entry: null,
|
|
25372
25310
|
isPinned: false,
|
|
25373
|
-
pinnedVersion: null
|
|
25311
|
+
pinnedVersion: null,
|
|
25312
|
+
isLocalDev: false
|
|
25374
25313
|
};
|
|
25375
25314
|
}
|
|
25376
25315
|
}
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
|
|
25316
|
+
|
|
25317
|
+
// src/cli/doctor/checks/system-loaded-version.ts
|
|
25318
|
+
init_checker();
|
|
25319
|
+
init_auto_update_checker();
|
|
25320
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
25321
|
+
import { homedir as homedir6 } from "os";
|
|
25322
|
+
import { join as join13 } from "path";
|
|
25323
|
+
init_shared();
|
|
25324
|
+
function getPlatformDefaultCacheDir(platform = process.platform) {
|
|
25325
|
+
if (platform === "darwin")
|
|
25326
|
+
return join13(homedir6(), "Library", "Caches");
|
|
25327
|
+
if (platform === "win32")
|
|
25328
|
+
return process.env.LOCALAPPDATA ?? join13(homedir6(), "AppData", "Local");
|
|
25329
|
+
return join13(homedir6(), ".cache");
|
|
25330
|
+
}
|
|
25331
|
+
function resolveOpenCodeCacheDir() {
|
|
25332
|
+
const xdgCacheHome = process.env.XDG_CACHE_HOME;
|
|
25333
|
+
if (xdgCacheHome)
|
|
25334
|
+
return join13(xdgCacheHome, "opencode");
|
|
25335
|
+
const fromShared = getOpenCodeCacheDir();
|
|
25336
|
+
const platformDefault = join13(getPlatformDefaultCacheDir(), "opencode");
|
|
25337
|
+
if (existsSync18(fromShared) || !existsSync18(platformDefault))
|
|
25338
|
+
return fromShared;
|
|
25339
|
+
return platformDefault;
|
|
25340
|
+
}
|
|
25341
|
+
function readPackageJson(filePath) {
|
|
25342
|
+
if (!existsSync18(filePath))
|
|
25343
|
+
return null;
|
|
25344
|
+
try {
|
|
25345
|
+
const content = readFileSync19(filePath, "utf-8");
|
|
25346
|
+
return parseJsonc(content);
|
|
25347
|
+
} catch {
|
|
25348
|
+
return null;
|
|
25401
25349
|
}
|
|
25402
|
-
const message = info.isPinned ? `Registered (pinned: ${info.pinnedVersion})` : "Registered";
|
|
25403
|
-
return {
|
|
25404
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
25405
|
-
status: "pass",
|
|
25406
|
-
message,
|
|
25407
|
-
details: [`Config: ${info.configPath}`]
|
|
25408
|
-
};
|
|
25409
25350
|
}
|
|
25410
|
-
function
|
|
25351
|
+
function normalizeVersion(value) {
|
|
25352
|
+
if (!value)
|
|
25353
|
+
return null;
|
|
25354
|
+
const match = value.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/);
|
|
25355
|
+
return match?.[0] ?? null;
|
|
25356
|
+
}
|
|
25357
|
+
function getLoadedPluginVersion() {
|
|
25358
|
+
const cacheDir = resolveOpenCodeCacheDir();
|
|
25359
|
+
const cachePackagePath = join13(cacheDir, "package.json");
|
|
25360
|
+
const installedPackagePath = join13(cacheDir, "node_modules", PACKAGE_NAME4, "package.json");
|
|
25361
|
+
const cachePackage = readPackageJson(cachePackagePath);
|
|
25362
|
+
const installedPackage = readPackageJson(installedPackagePath);
|
|
25363
|
+
const expectedVersion = normalizeVersion(cachePackage?.dependencies?.[PACKAGE_NAME4]);
|
|
25364
|
+
const loadedVersion = normalizeVersion(installedPackage?.version);
|
|
25411
25365
|
return {
|
|
25412
|
-
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
25366
|
+
cacheDir,
|
|
25367
|
+
cachePackagePath,
|
|
25368
|
+
installedPackagePath,
|
|
25369
|
+
expectedVersion,
|
|
25370
|
+
loadedVersion
|
|
25417
25371
|
};
|
|
25418
25372
|
}
|
|
25373
|
+
async function getLatestPluginVersion(currentVersion) {
|
|
25374
|
+
const channel = extractChannel(currentVersion);
|
|
25375
|
+
return getLatestVersion(channel);
|
|
25376
|
+
}
|
|
25419
25377
|
|
|
25420
|
-
// src/cli/doctor/checks/
|
|
25421
|
-
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
25422
|
-
import { join as join13 } from "path";
|
|
25378
|
+
// src/cli/doctor/checks/system.ts
|
|
25423
25379
|
init_shared();
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
|
|
25428
|
-
|
|
25429
|
-
if (projectDetected.format !== "none") {
|
|
25430
|
-
return { path: projectDetected.path, format: projectDetected.format };
|
|
25431
|
-
}
|
|
25432
|
-
const userDetected = detectConfigFile(USER_CONFIG_BASE);
|
|
25433
|
-
if (userDetected.format !== "none") {
|
|
25434
|
-
return { path: userDetected.path, format: userDetected.format };
|
|
25435
|
-
}
|
|
25436
|
-
return null;
|
|
25437
|
-
}
|
|
25438
|
-
function validateConfig(configPath) {
|
|
25380
|
+
function isConfigValid(configPath) {
|
|
25381
|
+
if (!configPath)
|
|
25382
|
+
return true;
|
|
25383
|
+
if (!existsSync19(configPath))
|
|
25384
|
+
return false;
|
|
25439
25385
|
try {
|
|
25440
|
-
|
|
25441
|
-
|
|
25442
|
-
|
|
25443
|
-
|
|
25444
|
-
const errors3 = result.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`);
|
|
25445
|
-
return { valid: false, errors: errors3 };
|
|
25446
|
-
}
|
|
25447
|
-
return { valid: true, errors: [] };
|
|
25448
|
-
} catch (err) {
|
|
25449
|
-
return {
|
|
25450
|
-
valid: false,
|
|
25451
|
-
errors: [err instanceof Error ? err.message : "Failed to parse config"]
|
|
25452
|
-
};
|
|
25386
|
+
parseJsonc(readFileSync20(configPath, "utf-8"));
|
|
25387
|
+
return true;
|
|
25388
|
+
} catch {
|
|
25389
|
+
return false;
|
|
25453
25390
|
}
|
|
25454
25391
|
}
|
|
25455
|
-
function
|
|
25456
|
-
|
|
25457
|
-
|
|
25458
|
-
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
|
|
25464
|
-
|
|
25392
|
+
function getResultStatus(issues) {
|
|
25393
|
+
if (issues.some((issue2) => issue2.severity === "error"))
|
|
25394
|
+
return "fail";
|
|
25395
|
+
if (issues.some((issue2) => issue2.severity === "warning"))
|
|
25396
|
+
return "warn";
|
|
25397
|
+
return "pass";
|
|
25398
|
+
}
|
|
25399
|
+
function buildMessage(status, issues) {
|
|
25400
|
+
if (status === "pass")
|
|
25401
|
+
return "System checks passed";
|
|
25402
|
+
if (status === "fail")
|
|
25403
|
+
return `${issues.length} system issue(s) detected`;
|
|
25404
|
+
return `${issues.length} system warning(s) detected`;
|
|
25405
|
+
}
|
|
25406
|
+
async function gatherSystemInfo() {
|
|
25407
|
+
const [binaryInfo, pluginInfo] = await Promise.all([findOpenCodeBinary(), Promise.resolve(getPluginInfo())]);
|
|
25408
|
+
const loadedInfo = getLoadedPluginVersion();
|
|
25409
|
+
const opencodeVersion = binaryInfo ? await getOpenCodeVersion2(binaryInfo.path) : null;
|
|
25410
|
+
const pluginVersion = pluginInfo.pinnedVersion ?? loadedInfo.expectedVersion;
|
|
25411
|
+
return {
|
|
25412
|
+
opencodeVersion,
|
|
25413
|
+
opencodePath: binaryInfo?.path ?? null,
|
|
25414
|
+
pluginVersion,
|
|
25415
|
+
loadedVersion: loadedInfo.loadedVersion,
|
|
25416
|
+
bunVersion: Bun.version,
|
|
25417
|
+
configPath: pluginInfo.configPath,
|
|
25418
|
+
configValid: isConfigValid(pluginInfo.configPath),
|
|
25419
|
+
isLocalDev: pluginInfo.isLocalDev
|
|
25420
|
+
};
|
|
25421
|
+
}
|
|
25422
|
+
async function checkSystem() {
|
|
25423
|
+
const [systemInfo, pluginInfo] = await Promise.all([gatherSystemInfo(), Promise.resolve(getPluginInfo())]);
|
|
25424
|
+
const loadedInfo = getLoadedPluginVersion();
|
|
25425
|
+
const latestVersion = await getLatestPluginVersion(systemInfo.loadedVersion);
|
|
25426
|
+
const issues = [];
|
|
25427
|
+
if (!systemInfo.opencodePath) {
|
|
25428
|
+
issues.push({
|
|
25429
|
+
title: "OpenCode binary not found",
|
|
25430
|
+
description: "Install OpenCode CLI or desktop and ensure the binary is available.",
|
|
25431
|
+
fix: "Install from https://opencode.ai/docs",
|
|
25432
|
+
severity: "error",
|
|
25433
|
+
affects: ["doctor", "run"]
|
|
25434
|
+
});
|
|
25465
25435
|
}
|
|
25466
|
-
if (!
|
|
25467
|
-
|
|
25468
|
-
|
|
25469
|
-
|
|
25470
|
-
|
|
25471
|
-
|
|
25472
|
-
|
|
25473
|
-
};
|
|
25436
|
+
if (systemInfo.opencodeVersion && !compareVersions(systemInfo.opencodeVersion, MIN_OPENCODE_VERSION)) {
|
|
25437
|
+
issues.push({
|
|
25438
|
+
title: "OpenCode version below minimum",
|
|
25439
|
+
description: `Detected ${systemInfo.opencodeVersion}; required >= ${MIN_OPENCODE_VERSION}.`,
|
|
25440
|
+
fix: "Update OpenCode to the latest stable release",
|
|
25441
|
+
severity: "warning",
|
|
25442
|
+
affects: ["tooling", "doctor"]
|
|
25443
|
+
});
|
|
25474
25444
|
}
|
|
25475
|
-
|
|
25476
|
-
|
|
25477
|
-
|
|
25478
|
-
|
|
25479
|
-
|
|
25480
|
-
|
|
25481
|
-
|
|
25482
|
-
|
|
25483
|
-
}
|
|
25484
|
-
async function checkConfigValidity() {
|
|
25485
|
-
const info = getConfigInfo();
|
|
25486
|
-
if (!info.exists) {
|
|
25487
|
-
return {
|
|
25488
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
25489
|
-
status: "pass",
|
|
25490
|
-
message: "Using default configuration",
|
|
25491
|
-
details: ["No custom config file found (optional)"]
|
|
25492
|
-
};
|
|
25445
|
+
if (!pluginInfo.registered) {
|
|
25446
|
+
issues.push({
|
|
25447
|
+
title: "oh-my-opencode is not registered",
|
|
25448
|
+
description: "Plugin entry is missing from OpenCode configuration.",
|
|
25449
|
+
fix: "Run: bunx oh-my-opencode install",
|
|
25450
|
+
severity: "error",
|
|
25451
|
+
affects: ["all agents"]
|
|
25452
|
+
});
|
|
25493
25453
|
}
|
|
25494
|
-
if (
|
|
25495
|
-
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
|
|
25499
|
-
|
|
25500
|
-
|
|
25501
|
-
|
|
25502
|
-
]
|
|
25503
|
-
};
|
|
25454
|
+
if (loadedInfo.expectedVersion && loadedInfo.loadedVersion && loadedInfo.expectedVersion !== loadedInfo.loadedVersion) {
|
|
25455
|
+
issues.push({
|
|
25456
|
+
title: "Loaded plugin version mismatch",
|
|
25457
|
+
description: `Cache expects ${loadedInfo.expectedVersion} but loaded ${loadedInfo.loadedVersion}.`,
|
|
25458
|
+
fix: "Reinstall plugin dependencies in OpenCode cache",
|
|
25459
|
+
severity: "warning",
|
|
25460
|
+
affects: ["plugin loading"]
|
|
25461
|
+
});
|
|
25504
25462
|
}
|
|
25463
|
+
if (systemInfo.loadedVersion && latestVersion && !compareVersions(systemInfo.loadedVersion, latestVersion)) {
|
|
25464
|
+
issues.push({
|
|
25465
|
+
title: "Loaded plugin is outdated",
|
|
25466
|
+
description: `Loaded ${systemInfo.loadedVersion}, latest ${latestVersion}.`,
|
|
25467
|
+
fix: "Update: cd ~/.config/opencode && bun update oh-my-opencode",
|
|
25468
|
+
severity: "warning",
|
|
25469
|
+
affects: ["plugin features"]
|
|
25470
|
+
});
|
|
25471
|
+
}
|
|
25472
|
+
const status = getResultStatus(issues);
|
|
25505
25473
|
return {
|
|
25506
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
25507
|
-
status
|
|
25508
|
-
message:
|
|
25509
|
-
details: [
|
|
25510
|
-
|
|
25511
|
-
}
|
|
25512
|
-
|
|
25513
|
-
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
category: "configuration",
|
|
25517
|
-
check: checkConfigValidity,
|
|
25518
|
-
critical: false
|
|
25474
|
+
name: CHECK_NAMES[CHECK_IDS.SYSTEM],
|
|
25475
|
+
status,
|
|
25476
|
+
message: buildMessage(status, issues),
|
|
25477
|
+
details: [
|
|
25478
|
+
systemInfo.opencodeVersion ? `OpenCode: ${systemInfo.opencodeVersion}` : "OpenCode: not detected",
|
|
25479
|
+
`Plugin expected: ${systemInfo.pluginVersion ?? "unknown"}`,
|
|
25480
|
+
`Plugin loaded: ${systemInfo.loadedVersion ?? "unknown"}`,
|
|
25481
|
+
`Bun: ${systemInfo.bunVersion ?? "unknown"}`
|
|
25482
|
+
],
|
|
25483
|
+
issues
|
|
25519
25484
|
};
|
|
25520
25485
|
}
|
|
25521
25486
|
|
|
25522
|
-
// src/cli/doctor/checks/
|
|
25523
|
-
|
|
25487
|
+
// src/cli/doctor/checks/config.ts
|
|
25488
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
25489
|
+
import { join as join17 } from "path";
|
|
25490
|
+
init_shared();
|
|
25524
25491
|
|
|
25525
25492
|
// src/cli/doctor/checks/model-resolution-cache.ts
|
|
25526
25493
|
init_shared();
|
|
25527
|
-
import { existsSync as
|
|
25528
|
-
import { homedir as
|
|
25494
|
+
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
|
|
25495
|
+
import { homedir as homedir7 } from "os";
|
|
25529
25496
|
import { join as join14 } from "path";
|
|
25530
25497
|
function getOpenCodeCacheDir2() {
|
|
25531
25498
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
25532
25499
|
if (xdgCache)
|
|
25533
25500
|
return join14(xdgCache, "opencode");
|
|
25534
|
-
return join14(
|
|
25501
|
+
return join14(homedir7(), ".cache", "opencode");
|
|
25535
25502
|
}
|
|
25536
25503
|
function loadAvailableModelsFromCache() {
|
|
25537
25504
|
const cacheFile = join14(getOpenCodeCacheDir2(), "models.json");
|
|
25538
|
-
if (!
|
|
25505
|
+
if (!existsSync20(cacheFile)) {
|
|
25539
25506
|
return { providers: [], modelCount: 0, cacheExists: false };
|
|
25540
25507
|
}
|
|
25541
25508
|
try {
|
|
25542
|
-
const content =
|
|
25509
|
+
const content = readFileSync21(cacheFile, "utf-8");
|
|
25543
25510
|
const data = parseJsonc(content);
|
|
25544
25511
|
const providers = Object.keys(data);
|
|
25545
25512
|
let modelCount = 0;
|
|
@@ -25555,27 +25522,30 @@ function loadAvailableModelsFromCache() {
|
|
|
25555
25522
|
}
|
|
25556
25523
|
}
|
|
25557
25524
|
|
|
25525
|
+
// src/cli/doctor/checks/model-resolution.ts
|
|
25526
|
+
init_model_requirements();
|
|
25527
|
+
|
|
25558
25528
|
// src/cli/doctor/checks/model-resolution-config.ts
|
|
25559
25529
|
init_shared();
|
|
25560
|
-
import { readFileSync as
|
|
25530
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
25561
25531
|
import { join as join15 } from "path";
|
|
25562
25532
|
var PACKAGE_NAME5 = "oh-my-opencode";
|
|
25563
|
-
var
|
|
25564
|
-
var
|
|
25533
|
+
var USER_CONFIG_BASE = join15(getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir, PACKAGE_NAME5);
|
|
25534
|
+
var PROJECT_CONFIG_BASE = join15(process.cwd(), ".opencode", PACKAGE_NAME5);
|
|
25565
25535
|
function loadOmoConfig() {
|
|
25566
|
-
const projectDetected = detectConfigFile(
|
|
25536
|
+
const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE);
|
|
25567
25537
|
if (projectDetected.format !== "none") {
|
|
25568
25538
|
try {
|
|
25569
|
-
const content =
|
|
25539
|
+
const content = readFileSync22(projectDetected.path, "utf-8");
|
|
25570
25540
|
return parseJsonc(content);
|
|
25571
25541
|
} catch {
|
|
25572
25542
|
return null;
|
|
25573
25543
|
}
|
|
25574
25544
|
}
|
|
25575
|
-
const userDetected = detectConfigFile(
|
|
25545
|
+
const userDetected = detectConfigFile(USER_CONFIG_BASE);
|
|
25576
25546
|
if (userDetected.format !== "none") {
|
|
25577
25547
|
try {
|
|
25578
|
-
const content =
|
|
25548
|
+
const content = readFileSync22(userDetected.path, "utf-8");
|
|
25579
25549
|
return parseJsonc(content);
|
|
25580
25550
|
} catch {
|
|
25581
25551
|
return null;
|
|
@@ -25584,31 +25554,6 @@ function loadOmoConfig() {
|
|
|
25584
25554
|
return null;
|
|
25585
25555
|
}
|
|
25586
25556
|
|
|
25587
|
-
// src/cli/doctor/checks/model-resolution-effective-model.ts
|
|
25588
|
-
function formatProviderChain(providers) {
|
|
25589
|
-
return providers.join(" \u2192 ");
|
|
25590
|
-
}
|
|
25591
|
-
function getEffectiveModel(requirement, userOverride) {
|
|
25592
|
-
if (userOverride) {
|
|
25593
|
-
return userOverride;
|
|
25594
|
-
}
|
|
25595
|
-
const firstEntry = requirement.fallbackChain[0];
|
|
25596
|
-
if (!firstEntry) {
|
|
25597
|
-
return "unknown";
|
|
25598
|
-
}
|
|
25599
|
-
return `${firstEntry.providers[0]}/${firstEntry.model}`;
|
|
25600
|
-
}
|
|
25601
|
-
function buildEffectiveResolution(requirement, userOverride) {
|
|
25602
|
-
if (userOverride) {
|
|
25603
|
-
return `User override: ${userOverride}`;
|
|
25604
|
-
}
|
|
25605
|
-
const firstEntry = requirement.fallbackChain[0];
|
|
25606
|
-
if (!firstEntry) {
|
|
25607
|
-
return "No fallback chain defined";
|
|
25608
|
-
}
|
|
25609
|
-
return `Provider fallback: ${formatProviderChain(firstEntry.providers)} \u2192 ${firstEntry.model}`;
|
|
25610
|
-
}
|
|
25611
|
-
|
|
25612
25557
|
// src/cli/doctor/checks/model-resolution-details.ts
|
|
25613
25558
|
init_shared();
|
|
25614
25559
|
import { join as join16 } from "path";
|
|
@@ -25684,6 +25629,31 @@ function buildModelResolutionDetails(options) {
|
|
|
25684
25629
|
return details;
|
|
25685
25630
|
}
|
|
25686
25631
|
|
|
25632
|
+
// src/cli/doctor/checks/model-resolution-effective-model.ts
|
|
25633
|
+
function formatProviderChain(providers) {
|
|
25634
|
+
return providers.join(" \u2192 ");
|
|
25635
|
+
}
|
|
25636
|
+
function getEffectiveModel(requirement, userOverride) {
|
|
25637
|
+
if (userOverride) {
|
|
25638
|
+
return userOverride;
|
|
25639
|
+
}
|
|
25640
|
+
const firstEntry = requirement.fallbackChain[0];
|
|
25641
|
+
if (!firstEntry) {
|
|
25642
|
+
return "unknown";
|
|
25643
|
+
}
|
|
25644
|
+
return `${firstEntry.providers[0]}/${firstEntry.model}`;
|
|
25645
|
+
}
|
|
25646
|
+
function buildEffectiveResolution(requirement, userOverride) {
|
|
25647
|
+
if (userOverride) {
|
|
25648
|
+
return `User override: ${userOverride}`;
|
|
25649
|
+
}
|
|
25650
|
+
const firstEntry = requirement.fallbackChain[0];
|
|
25651
|
+
if (!firstEntry) {
|
|
25652
|
+
return "No fallback chain defined";
|
|
25653
|
+
}
|
|
25654
|
+
return `Provider fallback: ${formatProviderChain(firstEntry.providers)} \u2192 ${firstEntry.model}`;
|
|
25655
|
+
}
|
|
25656
|
+
|
|
25687
25657
|
// src/cli/doctor/checks/model-resolution.ts
|
|
25688
25658
|
function getModelResolutionInfoWithOverrides(config2) {
|
|
25689
25659
|
const agents = Object.entries(AGENT_MODEL_REQUIREMENTS).map(([name, requirement]) => {
|
|
@@ -25712,134 +25682,154 @@ function getModelResolutionInfoWithOverrides(config2) {
|
|
|
25712
25682
|
});
|
|
25713
25683
|
return { agents, categories: categories2 };
|
|
25714
25684
|
}
|
|
25715
|
-
async function
|
|
25685
|
+
async function checkModels() {
|
|
25716
25686
|
const config2 = loadOmoConfig() ?? {};
|
|
25717
25687
|
const info = getModelResolutionInfoWithOverrides(config2);
|
|
25718
25688
|
const available = loadAvailableModelsFromCache();
|
|
25719
|
-
const
|
|
25720
|
-
|
|
25721
|
-
|
|
25722
|
-
|
|
25723
|
-
|
|
25724
|
-
|
|
25725
|
-
|
|
25726
|
-
|
|
25727
|
-
|
|
25728
|
-
|
|
25729
|
-
|
|
25730
|
-
details: buildModelResolutionDetails({ info, available, config: config2 })
|
|
25731
|
-
};
|
|
25732
|
-
}
|
|
25733
|
-
function getModelResolutionCheckDefinition() {
|
|
25689
|
+
const issues = [];
|
|
25690
|
+
if (!available.cacheExists) {
|
|
25691
|
+
issues.push({
|
|
25692
|
+
title: "Model cache not found",
|
|
25693
|
+
description: "OpenCode model cache is missing, so model availability cannot be validated.",
|
|
25694
|
+
fix: "Run: opencode models --refresh",
|
|
25695
|
+
severity: "warning",
|
|
25696
|
+
affects: ["model resolution"]
|
|
25697
|
+
});
|
|
25698
|
+
}
|
|
25699
|
+
const overrideCount = info.agents.filter((agent) => Boolean(agent.userOverride)).length + info.categories.filter((category) => Boolean(category.userOverride)).length;
|
|
25734
25700
|
return {
|
|
25735
|
-
|
|
25736
|
-
|
|
25737
|
-
|
|
25738
|
-
|
|
25739
|
-
|
|
25701
|
+
name: CHECK_NAMES[CHECK_IDS.MODELS],
|
|
25702
|
+
status: issues.length > 0 ? "warn" : "pass",
|
|
25703
|
+
message: `${info.agents.length} agents, ${info.categories.length} categories, ${overrideCount} override${overrideCount === 1 ? "" : "s"}`,
|
|
25704
|
+
details: buildModelResolutionDetails({ info, available, config: config2 }),
|
|
25705
|
+
issues
|
|
25740
25706
|
};
|
|
25741
25707
|
}
|
|
25742
25708
|
|
|
25743
|
-
// src/cli/doctor/checks/
|
|
25744
|
-
|
|
25745
|
-
|
|
25746
|
-
|
|
25747
|
-
|
|
25748
|
-
|
|
25749
|
-
|
|
25750
|
-
|
|
25751
|
-
|
|
25752
|
-
|
|
25753
|
-
|
|
25754
|
-
};
|
|
25755
|
-
function getOpenCodeConfig() {
|
|
25756
|
-
const configPath = existsSync20(OPENCODE_JSONC) ? OPENCODE_JSONC : OPENCODE_JSON;
|
|
25757
|
-
if (!existsSync20(configPath))
|
|
25758
|
-
return null;
|
|
25759
|
-
try {
|
|
25760
|
-
const content = readFileSync22(configPath, "utf-8");
|
|
25761
|
-
return parseJsonc(content);
|
|
25762
|
-
} catch {
|
|
25763
|
-
return null;
|
|
25764
|
-
}
|
|
25765
|
-
}
|
|
25766
|
-
function isPluginInstalled(plugins, pluginName) {
|
|
25767
|
-
if (pluginName === "builtin")
|
|
25768
|
-
return true;
|
|
25769
|
-
return plugins.some((p2) => p2 === pluginName || p2.startsWith(`${pluginName}@`));
|
|
25770
|
-
}
|
|
25771
|
-
function getAuthProviderInfo(providerId) {
|
|
25772
|
-
const config2 = getOpenCodeConfig();
|
|
25773
|
-
const plugins = config2?.plugin ?? [];
|
|
25774
|
-
const authConfig = AUTH_PLUGINS[providerId];
|
|
25775
|
-
const pluginInstalled = isPluginInstalled(plugins, authConfig.plugin);
|
|
25776
|
-
return {
|
|
25777
|
-
id: providerId,
|
|
25778
|
-
name: authConfig.name,
|
|
25779
|
-
pluginInstalled,
|
|
25780
|
-
configured: pluginInstalled
|
|
25781
|
-
};
|
|
25709
|
+
// src/cli/doctor/checks/config.ts
|
|
25710
|
+
var USER_CONFIG_BASE2 = join17(getOpenCodeConfigDir({ binary: "opencode" }), PACKAGE_NAME4);
|
|
25711
|
+
var PROJECT_CONFIG_BASE2 = join17(process.cwd(), ".opencode", PACKAGE_NAME4);
|
|
25712
|
+
function findConfigPath() {
|
|
25713
|
+
const projectConfig = detectConfigFile(PROJECT_CONFIG_BASE2);
|
|
25714
|
+
if (projectConfig.format !== "none")
|
|
25715
|
+
return projectConfig.path;
|
|
25716
|
+
const userConfig = detectConfigFile(USER_CONFIG_BASE2);
|
|
25717
|
+
if (userConfig.format !== "none")
|
|
25718
|
+
return userConfig.path;
|
|
25719
|
+
return null;
|
|
25782
25720
|
}
|
|
25783
|
-
|
|
25784
|
-
const
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
|
|
25721
|
+
function validateConfig() {
|
|
25722
|
+
const configPath = findConfigPath();
|
|
25723
|
+
if (!configPath) {
|
|
25724
|
+
return { exists: false, path: null, valid: true, config: null, errors: [] };
|
|
25725
|
+
}
|
|
25726
|
+
try {
|
|
25727
|
+
const content = readFileSync23(configPath, "utf-8");
|
|
25728
|
+
const rawConfig = parseJsonc(content);
|
|
25729
|
+
const schemaResult = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
25730
|
+
if (!schemaResult.success) {
|
|
25731
|
+
return {
|
|
25732
|
+
exists: true,
|
|
25733
|
+
path: configPath,
|
|
25734
|
+
valid: false,
|
|
25735
|
+
config: rawConfig,
|
|
25736
|
+
errors: schemaResult.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`)
|
|
25737
|
+
};
|
|
25738
|
+
}
|
|
25739
|
+
return { exists: true, path: configPath, valid: true, config: rawConfig, errors: [] };
|
|
25740
|
+
} catch (error45) {
|
|
25788
25741
|
return {
|
|
25789
|
-
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
|
|
25793
|
-
|
|
25794
|
-
"Run: bunx oh-my-opencode install"
|
|
25795
|
-
]
|
|
25742
|
+
exists: true,
|
|
25743
|
+
path: configPath,
|
|
25744
|
+
valid: false,
|
|
25745
|
+
config: null,
|
|
25746
|
+
errors: [error45 instanceof Error ? error45.message : "Failed to parse config"]
|
|
25796
25747
|
};
|
|
25797
25748
|
}
|
|
25798
|
-
return {
|
|
25799
|
-
name: checkName,
|
|
25800
|
-
status: "pass",
|
|
25801
|
-
message: "Auth plugin available",
|
|
25802
|
-
details: [
|
|
25803
|
-
providerId === "anthropic" ? "Run: opencode auth login (select Anthropic)" : `Plugin: ${AUTH_PLUGINS[providerId].plugin}`
|
|
25804
|
-
]
|
|
25805
|
-
};
|
|
25806
|
-
}
|
|
25807
|
-
async function checkAnthropicAuth() {
|
|
25808
|
-
return checkAuthProvider("anthropic");
|
|
25809
|
-
}
|
|
25810
|
-
async function checkOpenAIAuth() {
|
|
25811
|
-
return checkAuthProvider("openai");
|
|
25812
25749
|
}
|
|
25813
|
-
|
|
25814
|
-
|
|
25815
|
-
|
|
25816
|
-
|
|
25817
|
-
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
|
|
25823
|
-
|
|
25824
|
-
|
|
25825
|
-
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
25829
|
-
|
|
25830
|
-
|
|
25831
|
-
|
|
25832
|
-
|
|
25833
|
-
|
|
25834
|
-
|
|
25835
|
-
|
|
25836
|
-
|
|
25837
|
-
|
|
25750
|
+
function collectModelResolutionIssues(config2) {
|
|
25751
|
+
const issues = [];
|
|
25752
|
+
const availableModels = loadAvailableModelsFromCache();
|
|
25753
|
+
const resolution = getModelResolutionInfoWithOverrides(config2);
|
|
25754
|
+
const invalidAgentOverrides = resolution.agents.filter((agent) => agent.userOverride && !agent.userOverride.includes("/"));
|
|
25755
|
+
const invalidCategoryOverrides = resolution.categories.filter((category) => category.userOverride && !category.userOverride.includes("/"));
|
|
25756
|
+
for (const invalidAgent of invalidAgentOverrides) {
|
|
25757
|
+
issues.push({
|
|
25758
|
+
title: `Invalid agent override: ${invalidAgent.name}`,
|
|
25759
|
+
description: `Override '${invalidAgent.userOverride}' must be in provider/model format.`,
|
|
25760
|
+
severity: "warning",
|
|
25761
|
+
affects: [invalidAgent.name]
|
|
25762
|
+
});
|
|
25763
|
+
}
|
|
25764
|
+
for (const invalidCategory of invalidCategoryOverrides) {
|
|
25765
|
+
issues.push({
|
|
25766
|
+
title: `Invalid category override: ${invalidCategory.name}`,
|
|
25767
|
+
description: `Override '${invalidCategory.userOverride}' must be in provider/model format.`,
|
|
25768
|
+
severity: "warning",
|
|
25769
|
+
affects: [invalidCategory.name]
|
|
25770
|
+
});
|
|
25771
|
+
}
|
|
25772
|
+
if (availableModels.cacheExists) {
|
|
25773
|
+
const providerSet = new Set(availableModels.providers);
|
|
25774
|
+
const unknownProviders = [
|
|
25775
|
+
...resolution.agents.map((agent) => agent.userOverride),
|
|
25776
|
+
...resolution.categories.map((category) => category.userOverride)
|
|
25777
|
+
].filter((value) => Boolean(value)).map((value) => value.split("/")[0]).filter((provider) => provider.length > 0 && !providerSet.has(provider));
|
|
25778
|
+
if (unknownProviders.length > 0) {
|
|
25779
|
+
const uniqueProviders = [...new Set(unknownProviders)];
|
|
25780
|
+
issues.push({
|
|
25781
|
+
title: "Model override uses unavailable provider",
|
|
25782
|
+
description: `Provider(s) not found in OpenCode model cache: ${uniqueProviders.join(", ")}`,
|
|
25783
|
+
severity: "warning",
|
|
25784
|
+
affects: ["model resolution"]
|
|
25785
|
+
});
|
|
25838
25786
|
}
|
|
25839
|
-
|
|
25787
|
+
}
|
|
25788
|
+
return issues;
|
|
25789
|
+
}
|
|
25790
|
+
async function checkConfig() {
|
|
25791
|
+
const validation = validateConfig();
|
|
25792
|
+
const issues = [];
|
|
25793
|
+
if (!validation.exists) {
|
|
25794
|
+
return {
|
|
25795
|
+
name: CHECK_NAMES[CHECK_IDS.CONFIG],
|
|
25796
|
+
status: "pass",
|
|
25797
|
+
message: "No custom config found; defaults are used",
|
|
25798
|
+
details: undefined,
|
|
25799
|
+
issues
|
|
25800
|
+
};
|
|
25801
|
+
}
|
|
25802
|
+
if (!validation.valid) {
|
|
25803
|
+
issues.push(...validation.errors.map((error45) => ({
|
|
25804
|
+
title: "Invalid configuration",
|
|
25805
|
+
description: error45,
|
|
25806
|
+
severity: "error",
|
|
25807
|
+
affects: ["plugin startup"]
|
|
25808
|
+
})));
|
|
25809
|
+
return {
|
|
25810
|
+
name: CHECK_NAMES[CHECK_IDS.CONFIG],
|
|
25811
|
+
status: "fail",
|
|
25812
|
+
message: `Configuration invalid (${issues.length} issue${issues.length > 1 ? "s" : ""})`,
|
|
25813
|
+
details: validation.path ? [`Path: ${validation.path}`] : undefined,
|
|
25814
|
+
issues
|
|
25815
|
+
};
|
|
25816
|
+
}
|
|
25817
|
+
if (validation.config) {
|
|
25818
|
+
issues.push(...collectModelResolutionIssues(validation.config));
|
|
25819
|
+
}
|
|
25820
|
+
return {
|
|
25821
|
+
name: CHECK_NAMES[CHECK_IDS.CONFIG],
|
|
25822
|
+
status: issues.length > 0 ? "warn" : "pass",
|
|
25823
|
+
message: issues.length > 0 ? `${issues.length} configuration warning(s)` : "Configuration is valid",
|
|
25824
|
+
details: validation.path ? [`Path: ${validation.path}`] : undefined,
|
|
25825
|
+
issues
|
|
25826
|
+
};
|
|
25840
25827
|
}
|
|
25841
25828
|
|
|
25842
25829
|
// src/cli/doctor/checks/dependencies.ts
|
|
25830
|
+
import { existsSync as existsSync21 } from "fs";
|
|
25831
|
+
import { createRequire } from "module";
|
|
25832
|
+
import { dirname as dirname3, join as join18 } from "path";
|
|
25843
25833
|
async function checkBinaryExists(binary2) {
|
|
25844
25834
|
try {
|
|
25845
25835
|
const path9 = Bun.which(binary2);
|
|
@@ -25895,15 +25885,15 @@ async function checkAstGrepNapi() {
|
|
|
25895
25885
|
path: null
|
|
25896
25886
|
};
|
|
25897
25887
|
} catch {
|
|
25898
|
-
const { existsSync:
|
|
25899
|
-
const { join:
|
|
25900
|
-
const { homedir:
|
|
25888
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
25889
|
+
const { join: join19 } = await import("path");
|
|
25890
|
+
const { homedir: homedir8 } = await import("os");
|
|
25901
25891
|
const pathsToCheck = [
|
|
25902
|
-
|
|
25903
|
-
|
|
25892
|
+
join19(homedir8(), ".config", "opencode", "node_modules", "@ast-grep", "napi"),
|
|
25893
|
+
join19(process.cwd(), "node_modules", "@ast-grep", "napi")
|
|
25904
25894
|
];
|
|
25905
25895
|
for (const napiPath of pathsToCheck) {
|
|
25906
|
-
if (
|
|
25896
|
+
if (existsSync22(napiPath)) {
|
|
25907
25897
|
return {
|
|
25908
25898
|
name: "AST-Grep NAPI",
|
|
25909
25899
|
required: false,
|
|
@@ -25923,9 +25913,21 @@ async function checkAstGrepNapi() {
|
|
|
25923
25913
|
};
|
|
25924
25914
|
}
|
|
25925
25915
|
}
|
|
25916
|
+
function findCommentCheckerPackageBinary() {
|
|
25917
|
+
const binaryName = process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
25918
|
+
try {
|
|
25919
|
+
const require2 = createRequire(import.meta.url);
|
|
25920
|
+
const pkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
25921
|
+
const binaryPath = join18(dirname3(pkgPath), "bin", binaryName);
|
|
25922
|
+
if (existsSync21(binaryPath))
|
|
25923
|
+
return binaryPath;
|
|
25924
|
+
} catch {}
|
|
25925
|
+
return null;
|
|
25926
|
+
}
|
|
25926
25927
|
async function checkCommentChecker() {
|
|
25927
25928
|
const binaryCheck = await checkBinaryExists("comment-checker");
|
|
25928
|
-
|
|
25929
|
+
const resolvedPath = binaryCheck.exists ? binaryCheck.path : findCommentCheckerPackageBinary();
|
|
25930
|
+
if (!resolvedPath) {
|
|
25929
25931
|
return {
|
|
25930
25932
|
name: "Comment Checker",
|
|
25931
25933
|
required: false,
|
|
@@ -25935,112 +25937,59 @@ async function checkCommentChecker() {
|
|
|
25935
25937
|
installHint: "Hook will be disabled if not available"
|
|
25936
25938
|
};
|
|
25937
25939
|
}
|
|
25938
|
-
const version2 = await getBinaryVersion(
|
|
25940
|
+
const version2 = await getBinaryVersion(resolvedPath);
|
|
25939
25941
|
return {
|
|
25940
25942
|
name: "Comment Checker",
|
|
25941
25943
|
required: false,
|
|
25942
25944
|
installed: true,
|
|
25943
25945
|
version: version2,
|
|
25944
|
-
path:
|
|
25945
|
-
};
|
|
25946
|
-
}
|
|
25947
|
-
function dependencyToCheckResult(dep, checkName) {
|
|
25948
|
-
if (dep.installed) {
|
|
25949
|
-
return {
|
|
25950
|
-
name: checkName,
|
|
25951
|
-
status: "pass",
|
|
25952
|
-
message: dep.version ?? "installed",
|
|
25953
|
-
details: dep.path ? [`Path: ${dep.path}`] : undefined
|
|
25954
|
-
};
|
|
25955
|
-
}
|
|
25956
|
-
return {
|
|
25957
|
-
name: checkName,
|
|
25958
|
-
status: "warn",
|
|
25959
|
-
message: "Not installed (optional)",
|
|
25960
|
-
details: dep.installHint ? [dep.installHint] : undefined
|
|
25946
|
+
path: resolvedPath
|
|
25961
25947
|
};
|
|
25962
25948
|
}
|
|
25963
|
-
async function checkDependencyAstGrepCli() {
|
|
25964
|
-
const info = await checkAstGrepCli();
|
|
25965
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI]);
|
|
25966
|
-
}
|
|
25967
|
-
async function checkDependencyAstGrepNapi() {
|
|
25968
|
-
const info = await checkAstGrepNapi();
|
|
25969
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI]);
|
|
25970
|
-
}
|
|
25971
|
-
async function checkDependencyCommentChecker() {
|
|
25972
|
-
const info = await checkCommentChecker();
|
|
25973
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_COMMENT_CHECKER]);
|
|
25974
|
-
}
|
|
25975
|
-
function getDependencyCheckDefinitions() {
|
|
25976
|
-
return [
|
|
25977
|
-
{
|
|
25978
|
-
id: CHECK_IDS.DEP_AST_GREP_CLI,
|
|
25979
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI],
|
|
25980
|
-
category: "dependencies",
|
|
25981
|
-
check: checkDependencyAstGrepCli,
|
|
25982
|
-
critical: false
|
|
25983
|
-
},
|
|
25984
|
-
{
|
|
25985
|
-
id: CHECK_IDS.DEP_AST_GREP_NAPI,
|
|
25986
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI],
|
|
25987
|
-
category: "dependencies",
|
|
25988
|
-
check: checkDependencyAstGrepNapi,
|
|
25989
|
-
critical: false
|
|
25990
|
-
},
|
|
25991
|
-
{
|
|
25992
|
-
id: CHECK_IDS.DEP_COMMENT_CHECKER,
|
|
25993
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_COMMENT_CHECKER],
|
|
25994
|
-
category: "dependencies",
|
|
25995
|
-
check: checkDependencyCommentChecker,
|
|
25996
|
-
critical: false
|
|
25997
|
-
}
|
|
25998
|
-
];
|
|
25999
|
-
}
|
|
26000
25949
|
|
|
26001
|
-
// src/cli/doctor/checks/gh.ts
|
|
25950
|
+
// src/cli/doctor/checks/tools-gh.ts
|
|
26002
25951
|
async function checkBinaryExists2(binary2) {
|
|
26003
25952
|
try {
|
|
26004
|
-
const
|
|
26005
|
-
|
|
26006
|
-
|
|
26007
|
-
|
|
26008
|
-
|
|
26009
|
-
return { exists: true, path: output.trim() };
|
|
26010
|
-
}
|
|
26011
|
-
} catch {}
|
|
26012
|
-
return { exists: false, path: null };
|
|
25953
|
+
const binaryPath = Bun.which(binary2);
|
|
25954
|
+
return { exists: Boolean(binaryPath), path: binaryPath ?? null };
|
|
25955
|
+
} catch {
|
|
25956
|
+
return { exists: false, path: null };
|
|
25957
|
+
}
|
|
26013
25958
|
}
|
|
26014
25959
|
async function getGhVersion() {
|
|
26015
25960
|
try {
|
|
26016
|
-
const
|
|
26017
|
-
const output = await new Response(
|
|
26018
|
-
await
|
|
26019
|
-
if (
|
|
26020
|
-
|
|
26021
|
-
|
|
26022
|
-
|
|
26023
|
-
|
|
26024
|
-
} catch {
|
|
26025
|
-
|
|
25961
|
+
const processResult = Bun.spawn(["gh", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
25962
|
+
const output = await new Response(processResult.stdout).text();
|
|
25963
|
+
await processResult.exited;
|
|
25964
|
+
if (processResult.exitCode !== 0)
|
|
25965
|
+
return null;
|
|
25966
|
+
const matchedVersion = output.match(/gh version (\S+)/);
|
|
25967
|
+
return matchedVersion?.[1] ?? output.trim().split(`
|
|
25968
|
+
`)[0] ?? null;
|
|
25969
|
+
} catch {
|
|
25970
|
+
return null;
|
|
25971
|
+
}
|
|
26026
25972
|
}
|
|
26027
25973
|
async function getGhAuthStatus() {
|
|
26028
25974
|
try {
|
|
26029
|
-
const
|
|
25975
|
+
const processResult = Bun.spawn(["gh", "auth", "status"], {
|
|
26030
25976
|
stdout: "pipe",
|
|
26031
25977
|
stderr: "pipe",
|
|
26032
25978
|
env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" }
|
|
26033
25979
|
});
|
|
26034
|
-
const stdout = await new Response(
|
|
26035
|
-
const stderr = await new Response(
|
|
26036
|
-
await
|
|
25980
|
+
const stdout = await new Response(processResult.stdout).text();
|
|
25981
|
+
const stderr = await new Response(processResult.stderr).text();
|
|
25982
|
+
await processResult.exited;
|
|
26037
25983
|
const output = stderr || stdout;
|
|
26038
|
-
if (
|
|
25984
|
+
if (processResult.exitCode === 0) {
|
|
26039
25985
|
const usernameMatch = output.match(/Logged in to github\.com account (\S+)/);
|
|
26040
|
-
const username = usernameMatch?.[1]?.replace(/[()]/g, "") ?? null;
|
|
26041
25986
|
const scopesMatch = output.match(/Token scopes?:\s*(.+)/i);
|
|
26042
|
-
|
|
26043
|
-
|
|
25987
|
+
return {
|
|
25988
|
+
authenticated: true,
|
|
25989
|
+
username: usernameMatch?.[1]?.replace(/[()]/g, "") ?? null,
|
|
25990
|
+
scopes: scopesMatch?.[1]?.split(/,\s*/).map((scope) => scope.trim()).filter(Boolean) ?? [],
|
|
25991
|
+
error: null
|
|
25992
|
+
};
|
|
26044
25993
|
}
|
|
26045
25994
|
const errorMatch = output.match(/error[:\s]+(.+)/i);
|
|
26046
25995
|
return {
|
|
@@ -26049,18 +25998,18 @@ async function getGhAuthStatus() {
|
|
|
26049
25998
|
scopes: [],
|
|
26050
25999
|
error: errorMatch?.[1]?.trim() ?? "Not authenticated"
|
|
26051
26000
|
};
|
|
26052
|
-
} catch (
|
|
26001
|
+
} catch (error45) {
|
|
26053
26002
|
return {
|
|
26054
26003
|
authenticated: false,
|
|
26055
26004
|
username: null,
|
|
26056
26005
|
scopes: [],
|
|
26057
|
-
error:
|
|
26006
|
+
error: error45 instanceof Error ? error45.message : "Failed to check auth status"
|
|
26058
26007
|
};
|
|
26059
26008
|
}
|
|
26060
26009
|
}
|
|
26061
26010
|
async function getGhCliInfo() {
|
|
26062
|
-
const
|
|
26063
|
-
if (!
|
|
26011
|
+
const binaryStatus = await checkBinaryExists2("gh");
|
|
26012
|
+
if (!binaryStatus.exists) {
|
|
26064
26013
|
return {
|
|
26065
26014
|
installed: false,
|
|
26066
26015
|
version: null,
|
|
@@ -26075,76 +26024,27 @@ async function getGhCliInfo() {
|
|
|
26075
26024
|
return {
|
|
26076
26025
|
installed: true,
|
|
26077
26026
|
version: version2,
|
|
26078
|
-
path:
|
|
26027
|
+
path: binaryStatus.path,
|
|
26079
26028
|
authenticated: authStatus.authenticated,
|
|
26080
26029
|
username: authStatus.username,
|
|
26081
26030
|
scopes: authStatus.scopes,
|
|
26082
26031
|
error: authStatus.error
|
|
26083
26032
|
};
|
|
26084
26033
|
}
|
|
26085
|
-
async function checkGhCli() {
|
|
26086
|
-
const info = await getGhCliInfo();
|
|
26087
|
-
const name = CHECK_NAMES[CHECK_IDS.GH_CLI];
|
|
26088
|
-
if (!info.installed) {
|
|
26089
|
-
return {
|
|
26090
|
-
name,
|
|
26091
|
-
status: "warn",
|
|
26092
|
-
message: "Not installed (optional)",
|
|
26093
|
-
details: [
|
|
26094
|
-
"GitHub CLI is used by librarian agent and scripts",
|
|
26095
|
-
"Install: https://cli.github.com/"
|
|
26096
|
-
]
|
|
26097
|
-
};
|
|
26098
|
-
}
|
|
26099
|
-
if (!info.authenticated) {
|
|
26100
|
-
return {
|
|
26101
|
-
name,
|
|
26102
|
-
status: "warn",
|
|
26103
|
-
message: `${info.version ?? "installed"} - not authenticated`,
|
|
26104
|
-
details: [
|
|
26105
|
-
info.path ? `Path: ${info.path}` : null,
|
|
26106
|
-
"Authenticate: gh auth login",
|
|
26107
|
-
info.error ? `Error: ${info.error}` : null
|
|
26108
|
-
].filter((d3) => d3 !== null)
|
|
26109
|
-
};
|
|
26110
|
-
}
|
|
26111
|
-
const details = [];
|
|
26112
|
-
if (info.path)
|
|
26113
|
-
details.push(`Path: ${info.path}`);
|
|
26114
|
-
if (info.username)
|
|
26115
|
-
details.push(`Account: ${info.username}`);
|
|
26116
|
-
if (info.scopes.length > 0)
|
|
26117
|
-
details.push(`Scopes: ${info.scopes.join(", ")}`);
|
|
26118
|
-
return {
|
|
26119
|
-
name,
|
|
26120
|
-
status: "pass",
|
|
26121
|
-
message: `${info.version ?? "installed"} - authenticated as ${info.username ?? "unknown"}`,
|
|
26122
|
-
details: details.length > 0 ? details : undefined
|
|
26123
|
-
};
|
|
26124
|
-
}
|
|
26125
|
-
function getGhCliCheckDefinition() {
|
|
26126
|
-
return {
|
|
26127
|
-
id: CHECK_IDS.GH_CLI,
|
|
26128
|
-
name: CHECK_NAMES[CHECK_IDS.GH_CLI],
|
|
26129
|
-
category: "tools",
|
|
26130
|
-
check: checkGhCli,
|
|
26131
|
-
critical: false
|
|
26132
|
-
};
|
|
26133
|
-
}
|
|
26134
26034
|
// src/tools/lsp/server-config-loader.ts
|
|
26135
26035
|
init_shared();
|
|
26136
26036
|
init_jsonc_parser();
|
|
26137
26037
|
|
|
26138
26038
|
// src/tools/lsp/server-installation.ts
|
|
26139
26039
|
init_shared();
|
|
26140
|
-
import { existsSync as
|
|
26141
|
-
import { join as
|
|
26040
|
+
import { existsSync as existsSync22 } from "fs";
|
|
26041
|
+
import { join as join19 } from "path";
|
|
26142
26042
|
function isServerInstalled(command) {
|
|
26143
26043
|
if (command.length === 0)
|
|
26144
26044
|
return false;
|
|
26145
26045
|
const cmd = command[0];
|
|
26146
26046
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
26147
|
-
if (
|
|
26047
|
+
if (existsSync22(cmd))
|
|
26148
26048
|
return true;
|
|
26149
26049
|
}
|
|
26150
26050
|
const isWindows = process.platform === "win32";
|
|
@@ -26166,23 +26066,23 @@ function isServerInstalled(command) {
|
|
|
26166
26066
|
const paths = pathEnv.split(pathSeparator);
|
|
26167
26067
|
for (const p2 of paths) {
|
|
26168
26068
|
for (const suffix of exts) {
|
|
26169
|
-
if (
|
|
26069
|
+
if (existsSync22(join19(p2, cmd + suffix))) {
|
|
26170
26070
|
return true;
|
|
26171
26071
|
}
|
|
26172
26072
|
}
|
|
26173
26073
|
}
|
|
26174
26074
|
const cwd = process.cwd();
|
|
26175
26075
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
26176
|
-
const dataDir =
|
|
26076
|
+
const dataDir = join19(getDataDir(), "opencode");
|
|
26177
26077
|
const additionalBases = [
|
|
26178
|
-
|
|
26179
|
-
|
|
26180
|
-
|
|
26181
|
-
|
|
26078
|
+
join19(cwd, "node_modules", ".bin"),
|
|
26079
|
+
join19(configDir, "bin"),
|
|
26080
|
+
join19(configDir, "node_modules", ".bin"),
|
|
26081
|
+
join19(dataDir, "bin")
|
|
26182
26082
|
];
|
|
26183
26083
|
for (const base of additionalBases) {
|
|
26184
26084
|
for (const suffix of exts) {
|
|
26185
|
-
if (
|
|
26085
|
+
if (existsSync22(join19(base, cmd + suffix))) {
|
|
26186
26086
|
return true;
|
|
26187
26087
|
}
|
|
26188
26088
|
}
|
|
@@ -26192,180 +26092,454 @@ function isServerInstalled(command) {
|
|
|
26192
26092
|
}
|
|
26193
26093
|
return false;
|
|
26194
26094
|
}
|
|
26195
|
-
// src/cli/doctor/checks/lsp.ts
|
|
26095
|
+
// src/cli/doctor/checks/tools-lsp.ts
|
|
26196
26096
|
var DEFAULT_LSP_SERVERS = [
|
|
26197
26097
|
{ id: "typescript-language-server", binary: "typescript-language-server", extensions: [".ts", ".tsx", ".js", ".jsx"] },
|
|
26198
26098
|
{ id: "pyright", binary: "pyright-langserver", extensions: [".py"] },
|
|
26199
26099
|
{ id: "rust-analyzer", binary: "rust-analyzer", extensions: [".rs"] },
|
|
26200
26100
|
{ id: "gopls", binary: "gopls", extensions: [".go"] }
|
|
26201
26101
|
];
|
|
26202
|
-
|
|
26203
|
-
|
|
26204
|
-
|
|
26205
|
-
|
|
26206
|
-
|
|
26207
|
-
|
|
26208
|
-
|
|
26209
|
-
extensions: server2.extensions,
|
|
26210
|
-
source: "builtin"
|
|
26211
|
-
});
|
|
26212
|
-
}
|
|
26213
|
-
return servers;
|
|
26102
|
+
function getLspServersInfo() {
|
|
26103
|
+
return DEFAULT_LSP_SERVERS.map((server2) => ({
|
|
26104
|
+
id: server2.id,
|
|
26105
|
+
installed: isServerInstalled([server2.binary]),
|
|
26106
|
+
extensions: server2.extensions,
|
|
26107
|
+
source: "builtin"
|
|
26108
|
+
}));
|
|
26214
26109
|
}
|
|
26215
26110
|
function getLspServerStats(servers) {
|
|
26216
|
-
const installed = servers.filter((s) => s.installed).length;
|
|
26217
|
-
return { installed, total: servers.length };
|
|
26218
|
-
}
|
|
26219
|
-
async function checkLspServers() {
|
|
26220
|
-
const servers = await getLspServersInfo();
|
|
26221
|
-
const stats = getLspServerStats(servers);
|
|
26222
|
-
const installedServers = servers.filter((s) => s.installed);
|
|
26223
|
-
const missingServers = servers.filter((s) => !s.installed);
|
|
26224
|
-
if (stats.installed === 0) {
|
|
26225
|
-
return {
|
|
26226
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
26227
|
-
status: "warn",
|
|
26228
|
-
message: "No LSP servers detected",
|
|
26229
|
-
details: [
|
|
26230
|
-
"LSP tools will have limited functionality",
|
|
26231
|
-
...missingServers.map((s) => `Missing: ${s.id}`)
|
|
26232
|
-
]
|
|
26233
|
-
};
|
|
26234
|
-
}
|
|
26235
|
-
const details = [
|
|
26236
|
-
...installedServers.map((s) => `Installed: ${s.id}`),
|
|
26237
|
-
...missingServers.map((s) => `Not found: ${s.id} (optional)`)
|
|
26238
|
-
];
|
|
26239
|
-
return {
|
|
26240
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
26241
|
-
status: "pass",
|
|
26242
|
-
message: `${stats.installed}/${stats.total} servers available`,
|
|
26243
|
-
details
|
|
26244
|
-
};
|
|
26245
|
-
}
|
|
26246
|
-
function getLspCheckDefinition() {
|
|
26247
26111
|
return {
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
category: "tools",
|
|
26251
|
-
check: checkLspServers,
|
|
26252
|
-
critical: false
|
|
26112
|
+
installed: servers.filter((server2) => server2.installed).length,
|
|
26113
|
+
total: servers.length
|
|
26253
26114
|
};
|
|
26254
26115
|
}
|
|
26255
26116
|
|
|
26256
|
-
// src/cli/doctor/checks/mcp.ts
|
|
26257
|
-
import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
|
|
26258
|
-
import { homedir as homedir7 } from "os";
|
|
26259
|
-
import { join as join19 } from "path";
|
|
26117
|
+
// src/cli/doctor/checks/tools-mcp.ts
|
|
26260
26118
|
init_shared();
|
|
26119
|
+
import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
|
|
26120
|
+
import { homedir as homedir8 } from "os";
|
|
26121
|
+
import { join as join20 } from "path";
|
|
26261
26122
|
var BUILTIN_MCP_SERVERS = ["context7", "grep_app"];
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26123
|
+
function getMcpConfigPaths() {
|
|
26124
|
+
return [
|
|
26125
|
+
join20(homedir8(), ".claude", ".mcp.json"),
|
|
26126
|
+
join20(process.cwd(), ".mcp.json"),
|
|
26127
|
+
join20(process.cwd(), ".claude", ".mcp.json")
|
|
26128
|
+
];
|
|
26129
|
+
}
|
|
26267
26130
|
function loadUserMcpConfig() {
|
|
26268
26131
|
const servers = {};
|
|
26269
|
-
for (const configPath of
|
|
26270
|
-
if (!
|
|
26132
|
+
for (const configPath of getMcpConfigPaths()) {
|
|
26133
|
+
if (!existsSync23(configPath))
|
|
26271
26134
|
continue;
|
|
26272
26135
|
try {
|
|
26273
|
-
const content =
|
|
26136
|
+
const content = readFileSync24(configPath, "utf-8");
|
|
26274
26137
|
const config2 = parseJsonc(content);
|
|
26275
26138
|
if (config2.mcpServers) {
|
|
26276
26139
|
Object.assign(servers, config2.mcpServers);
|
|
26277
26140
|
}
|
|
26278
|
-
} catch {
|
|
26141
|
+
} catch {
|
|
26142
|
+
continue;
|
|
26143
|
+
}
|
|
26279
26144
|
}
|
|
26280
26145
|
return servers;
|
|
26281
26146
|
}
|
|
26282
26147
|
function getBuiltinMcpInfo() {
|
|
26283
|
-
return BUILTIN_MCP_SERVERS.map((
|
|
26284
|
-
id,
|
|
26148
|
+
return BUILTIN_MCP_SERVERS.map((serverId) => ({
|
|
26149
|
+
id: serverId,
|
|
26285
26150
|
type: "builtin",
|
|
26286
26151
|
enabled: true,
|
|
26287
26152
|
valid: true
|
|
26288
26153
|
}));
|
|
26289
26154
|
}
|
|
26290
26155
|
function getUserMcpInfo() {
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
servers.push({
|
|
26296
|
-
id,
|
|
26156
|
+
return Object.entries(loadUserMcpConfig()).map(([serverId, value]) => {
|
|
26157
|
+
const valid = typeof value === "object" && value !== null;
|
|
26158
|
+
return {
|
|
26159
|
+
id: serverId,
|
|
26297
26160
|
type: "user",
|
|
26298
26161
|
enabled: true,
|
|
26299
|
-
valid
|
|
26300
|
-
error:
|
|
26301
|
-
}
|
|
26302
|
-
}
|
|
26303
|
-
return servers;
|
|
26162
|
+
valid,
|
|
26163
|
+
error: valid ? undefined : "Invalid configuration format"
|
|
26164
|
+
};
|
|
26165
|
+
});
|
|
26304
26166
|
}
|
|
26305
|
-
|
|
26306
|
-
|
|
26167
|
+
|
|
26168
|
+
// src/cli/doctor/checks/tools.ts
|
|
26169
|
+
async function gatherToolsSummary() {
|
|
26170
|
+
const [astGrepCliInfo, astGrepNapiInfo, commentCheckerInfo, ghInfo] = await Promise.all([
|
|
26171
|
+
checkAstGrepCli(),
|
|
26172
|
+
checkAstGrepNapi(),
|
|
26173
|
+
checkCommentChecker(),
|
|
26174
|
+
getGhCliInfo()
|
|
26175
|
+
]);
|
|
26176
|
+
const lspServers = getLspServersInfo();
|
|
26177
|
+
const lspStats = getLspServerStats(lspServers);
|
|
26178
|
+
const builtinMcp = getBuiltinMcpInfo();
|
|
26179
|
+
const userMcp = getUserMcpInfo();
|
|
26307
26180
|
return {
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
26315
|
-
|
|
26316
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26181
|
+
lspInstalled: lspStats.installed,
|
|
26182
|
+
lspTotal: lspStats.total,
|
|
26183
|
+
astGrepCli: astGrepCliInfo.installed,
|
|
26184
|
+
astGrepNapi: astGrepNapiInfo.installed,
|
|
26185
|
+
commentChecker: commentCheckerInfo.installed,
|
|
26186
|
+
ghCli: {
|
|
26187
|
+
installed: ghInfo.installed,
|
|
26188
|
+
authenticated: ghInfo.authenticated,
|
|
26189
|
+
username: ghInfo.username
|
|
26190
|
+
},
|
|
26191
|
+
mcpBuiltin: builtinMcp.map((server2) => server2.id),
|
|
26192
|
+
mcpUser: userMcp.map((server2) => server2.id)
|
|
26193
|
+
};
|
|
26194
|
+
}
|
|
26195
|
+
function buildToolIssues(summary) {
|
|
26196
|
+
const issues = [];
|
|
26197
|
+
if (!summary.astGrepCli && !summary.astGrepNapi) {
|
|
26198
|
+
issues.push({
|
|
26199
|
+
title: "AST-Grep unavailable",
|
|
26200
|
+
description: "Neither AST-Grep CLI nor NAPI backend is available.",
|
|
26201
|
+
fix: "Install @ast-grep/cli globally or add @ast-grep/napi",
|
|
26202
|
+
severity: "warning",
|
|
26203
|
+
affects: ["ast_grep_search", "ast_grep_replace"]
|
|
26204
|
+
});
|
|
26323
26205
|
}
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
|
|
26330
|
-
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
|
|
26334
|
-
|
|
26206
|
+
if (!summary.commentChecker) {
|
|
26207
|
+
issues.push({
|
|
26208
|
+
title: "Comment checker unavailable",
|
|
26209
|
+
description: "Comment checker binary is not installed.",
|
|
26210
|
+
fix: "Install @code-yeongyu/comment-checker",
|
|
26211
|
+
severity: "warning",
|
|
26212
|
+
affects: ["comment-checker hook"]
|
|
26213
|
+
});
|
|
26214
|
+
}
|
|
26215
|
+
if (summary.lspInstalled === 0) {
|
|
26216
|
+
issues.push({
|
|
26217
|
+
title: "No LSP servers detected",
|
|
26218
|
+
description: "LSP-dependent tools will be limited until at least one server is installed.",
|
|
26219
|
+
severity: "warning",
|
|
26220
|
+
affects: ["lsp diagnostics", "rename", "references"]
|
|
26221
|
+
});
|
|
26222
|
+
}
|
|
26223
|
+
if (!summary.ghCli.installed) {
|
|
26224
|
+
issues.push({
|
|
26225
|
+
title: "GitHub CLI missing",
|
|
26226
|
+
description: "gh CLI is not installed.",
|
|
26227
|
+
fix: "Install from https://cli.github.com/",
|
|
26228
|
+
severity: "warning",
|
|
26229
|
+
affects: ["GitHub automation"]
|
|
26230
|
+
});
|
|
26231
|
+
} else if (!summary.ghCli.authenticated) {
|
|
26232
|
+
issues.push({
|
|
26233
|
+
title: "GitHub CLI not authenticated",
|
|
26234
|
+
description: "gh CLI is installed but not logged in.",
|
|
26235
|
+
fix: "Run: gh auth login",
|
|
26236
|
+
severity: "warning",
|
|
26237
|
+
affects: ["GitHub automation"]
|
|
26238
|
+
});
|
|
26239
|
+
}
|
|
26240
|
+
return issues;
|
|
26241
|
+
}
|
|
26242
|
+
async function checkTools() {
|
|
26243
|
+
const summary = await gatherToolsSummary();
|
|
26244
|
+
const userMcpServers = getUserMcpInfo();
|
|
26245
|
+
const invalidUserMcpServers = userMcpServers.filter((server2) => !server2.valid);
|
|
26246
|
+
const issues = buildToolIssues(summary);
|
|
26247
|
+
if (invalidUserMcpServers.length > 0) {
|
|
26248
|
+
issues.push({
|
|
26249
|
+
title: "Invalid MCP server configuration",
|
|
26250
|
+
description: `${invalidUserMcpServers.length} user MCP server(s) have invalid config format.`,
|
|
26251
|
+
severity: "warning",
|
|
26252
|
+
affects: ["custom MCP tools"]
|
|
26253
|
+
});
|
|
26335
26254
|
}
|
|
26336
26255
|
return {
|
|
26337
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26338
|
-
status: "pass",
|
|
26339
|
-
message: `${
|
|
26340
|
-
details:
|
|
26256
|
+
name: CHECK_NAMES[CHECK_IDS.TOOLS],
|
|
26257
|
+
status: issues.length === 0 ? "pass" : "warn",
|
|
26258
|
+
message: issues.length === 0 ? "All tools checks passed" : `${issues.length} tools issue(s) detected`,
|
|
26259
|
+
details: [
|
|
26260
|
+
`AST-Grep: cli=${summary.astGrepCli ? "yes" : "no"}, napi=${summary.astGrepNapi ? "yes" : "no"}`,
|
|
26261
|
+
`Comment checker: ${summary.commentChecker ? "yes" : "no"}`,
|
|
26262
|
+
`LSP: ${summary.lspInstalled}/${summary.lspTotal}`,
|
|
26263
|
+
`GH CLI: ${summary.ghCli.installed ? "installed" : "missing"}${summary.ghCli.authenticated ? " (authenticated)" : ""}`,
|
|
26264
|
+
`MCP: builtin=${summary.mcpBuiltin.length}, user=${summary.mcpUser.length}`
|
|
26265
|
+
],
|
|
26266
|
+
issues
|
|
26341
26267
|
};
|
|
26342
26268
|
}
|
|
26343
|
-
|
|
26269
|
+
// src/cli/doctor/checks/index.ts
|
|
26270
|
+
function getAllCheckDefinitions() {
|
|
26344
26271
|
return [
|
|
26345
26272
|
{
|
|
26346
|
-
id: CHECK_IDS.
|
|
26347
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26348
|
-
|
|
26349
|
-
|
|
26350
|
-
|
|
26273
|
+
id: CHECK_IDS.SYSTEM,
|
|
26274
|
+
name: CHECK_NAMES[CHECK_IDS.SYSTEM],
|
|
26275
|
+
check: checkSystem,
|
|
26276
|
+
critical: true
|
|
26277
|
+
},
|
|
26278
|
+
{
|
|
26279
|
+
id: CHECK_IDS.CONFIG,
|
|
26280
|
+
name: CHECK_NAMES[CHECK_IDS.CONFIG],
|
|
26281
|
+
check: checkConfig
|
|
26282
|
+
},
|
|
26283
|
+
{
|
|
26284
|
+
id: CHECK_IDS.TOOLS,
|
|
26285
|
+
name: CHECK_NAMES[CHECK_IDS.TOOLS],
|
|
26286
|
+
check: checkTools
|
|
26351
26287
|
},
|
|
26352
26288
|
{
|
|
26353
|
-
id: CHECK_IDS.
|
|
26354
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26355
|
-
|
|
26356
|
-
check: checkUserMcpServers,
|
|
26357
|
-
critical: false
|
|
26289
|
+
id: CHECK_IDS.MODELS,
|
|
26290
|
+
name: CHECK_NAMES[CHECK_IDS.MODELS],
|
|
26291
|
+
check: checkModels
|
|
26358
26292
|
}
|
|
26359
26293
|
];
|
|
26360
26294
|
}
|
|
26361
26295
|
|
|
26296
|
+
// src/cli/doctor/format-default.ts
|
|
26297
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
26298
|
+
|
|
26299
|
+
// src/cli/doctor/format-shared.ts
|
|
26300
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
26301
|
+
function formatStatusSymbol(status) {
|
|
26302
|
+
const colorFn = STATUS_COLORS[status];
|
|
26303
|
+
switch (status) {
|
|
26304
|
+
case "pass":
|
|
26305
|
+
return colorFn(SYMBOLS3.check);
|
|
26306
|
+
case "fail":
|
|
26307
|
+
return colorFn(SYMBOLS3.cross);
|
|
26308
|
+
case "warn":
|
|
26309
|
+
return colorFn(SYMBOLS3.warn);
|
|
26310
|
+
case "skip":
|
|
26311
|
+
return colorFn(SYMBOLS3.skip);
|
|
26312
|
+
}
|
|
26313
|
+
}
|
|
26314
|
+
function formatStatusMark(available) {
|
|
26315
|
+
return available ? import_picocolors17.default.green(SYMBOLS3.check) : import_picocolors17.default.red(SYMBOLS3.cross);
|
|
26316
|
+
}
|
|
26317
|
+
function formatHeader() {
|
|
26318
|
+
return `
|
|
26319
|
+
${import_picocolors17.default.bgMagenta(import_picocolors17.default.white(" oMoMoMoMo Doctor "))}
|
|
26320
|
+
`;
|
|
26321
|
+
}
|
|
26322
|
+
function formatIssue(issue2, index) {
|
|
26323
|
+
const lines = [];
|
|
26324
|
+
const severityColor = issue2.severity === "error" ? import_picocolors17.default.red : import_picocolors17.default.yellow;
|
|
26325
|
+
lines.push(`${index}. ${severityColor(issue2.title)}`);
|
|
26326
|
+
lines.push(` ${import_picocolors17.default.dim(issue2.description)}`);
|
|
26327
|
+
if (issue2.fix) {
|
|
26328
|
+
lines.push(` ${import_picocolors17.default.cyan("Fix:")} ${import_picocolors17.default.dim(issue2.fix)}`);
|
|
26329
|
+
}
|
|
26330
|
+
if (issue2.affects && issue2.affects.length > 0) {
|
|
26331
|
+
lines.push(` ${import_picocolors17.default.cyan("Affects:")} ${import_picocolors17.default.dim(issue2.affects.join(", "))}`);
|
|
26332
|
+
}
|
|
26333
|
+
return lines.join(`
|
|
26334
|
+
`);
|
|
26335
|
+
}
|
|
26336
|
+
|
|
26337
|
+
// src/cli/doctor/format-default.ts
|
|
26338
|
+
function formatDefault(result) {
|
|
26339
|
+
const lines = [];
|
|
26340
|
+
lines.push(formatHeader());
|
|
26341
|
+
const allIssues = result.results.flatMap((r2) => r2.issues);
|
|
26342
|
+
if (allIssues.length === 0) {
|
|
26343
|
+
const opencodeVer = result.systemInfo.opencodeVersion ?? "unknown";
|
|
26344
|
+
const pluginVer = result.systemInfo.pluginVersion ?? "unknown";
|
|
26345
|
+
lines.push(` ${import_picocolors18.default.green(SYMBOLS3.check)} ${import_picocolors18.default.green(`System OK (opencode ${opencodeVer} \xB7 oh-my-opencode ${pluginVer})`)}`);
|
|
26346
|
+
} else {
|
|
26347
|
+
const issueCount = allIssues.filter((i2) => i2.severity === "error").length;
|
|
26348
|
+
const warnCount = allIssues.filter((i2) => i2.severity === "warning").length;
|
|
26349
|
+
const totalStr = `${issueCount + warnCount} ${issueCount + warnCount === 1 ? "issue" : "issues"}`;
|
|
26350
|
+
lines.push(` ${import_picocolors18.default.yellow(SYMBOLS3.warn)} ${totalStr} found:
|
|
26351
|
+
`);
|
|
26352
|
+
allIssues.forEach((issue2, index) => {
|
|
26353
|
+
lines.push(formatIssue(issue2, index + 1));
|
|
26354
|
+
lines.push("");
|
|
26355
|
+
});
|
|
26356
|
+
}
|
|
26357
|
+
return lines.join(`
|
|
26358
|
+
`);
|
|
26359
|
+
}
|
|
26360
|
+
|
|
26361
|
+
// src/cli/doctor/format-status.ts
|
|
26362
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
26363
|
+
function formatStatus(result) {
|
|
26364
|
+
const lines = [];
|
|
26365
|
+
lines.push(formatHeader());
|
|
26366
|
+
const { systemInfo, tools } = result;
|
|
26367
|
+
const padding = " ";
|
|
26368
|
+
const opencodeVer = systemInfo.opencodeVersion ?? "unknown";
|
|
26369
|
+
const pluginVer = systemInfo.pluginVersion ?? "unknown";
|
|
26370
|
+
const bunVer = systemInfo.bunVersion ?? "unknown";
|
|
26371
|
+
lines.push(` ${padding}System ${opencodeVer} \xB7 ${pluginVer} \xB7 Bun ${bunVer}`);
|
|
26372
|
+
const configPath = systemInfo.configPath ?? "unknown";
|
|
26373
|
+
const configStatus = systemInfo.configValid ? import_picocolors19.default.green("(valid)") : import_picocolors19.default.red("(invalid)");
|
|
26374
|
+
lines.push(` ${padding}Config ${configPath} ${configStatus}`);
|
|
26375
|
+
const lspText = `LSP ${tools.lspInstalled}/${tools.lspTotal}`;
|
|
26376
|
+
const astGrepMark = formatStatusMark(tools.astGrepCli);
|
|
26377
|
+
const ghMark = formatStatusMark(tools.ghCli.installed && tools.ghCli.authenticated);
|
|
26378
|
+
const ghUser = tools.ghCli.username ?? "";
|
|
26379
|
+
lines.push(` ${padding}Tools ${lspText} \xB7 AST-Grep ${astGrepMark} \xB7 gh ${ghMark}${ghUser ? ` (${ghUser})` : ""}`);
|
|
26380
|
+
const builtinCount = tools.mcpBuiltin.length;
|
|
26381
|
+
const userCount = tools.mcpUser.length;
|
|
26382
|
+
const builtinText = builtinCount > 0 ? tools.mcpBuiltin.join(" \xB7 ") : "none";
|
|
26383
|
+
const userText = userCount > 0 ? `+ ${userCount} user` : "";
|
|
26384
|
+
lines.push(` ${padding}MCPs ${builtinText} ${userText}`);
|
|
26385
|
+
return lines.join(`
|
|
26386
|
+
`);
|
|
26387
|
+
}
|
|
26388
|
+
|
|
26389
|
+
// src/cli/doctor/format-verbose.ts
|
|
26390
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
26391
|
+
function formatVerbose(result) {
|
|
26392
|
+
const lines = [];
|
|
26393
|
+
lines.push(formatHeader());
|
|
26394
|
+
const { systemInfo, tools, results, summary } = result;
|
|
26395
|
+
lines.push(`${import_picocolors20.default.bold("System Information")}`);
|
|
26396
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26397
|
+
lines.push(` ${formatStatusSymbol("pass")} opencode ${systemInfo.opencodeVersion ?? "unknown"}`);
|
|
26398
|
+
lines.push(` ${formatStatusSymbol("pass")} oh-my-opencode ${systemInfo.pluginVersion ?? "unknown"}`);
|
|
26399
|
+
if (systemInfo.loadedVersion) {
|
|
26400
|
+
lines.push(` ${formatStatusSymbol("pass")} loaded ${systemInfo.loadedVersion}`);
|
|
26401
|
+
}
|
|
26402
|
+
if (systemInfo.bunVersion) {
|
|
26403
|
+
lines.push(` ${formatStatusSymbol("pass")} bun ${systemInfo.bunVersion}`);
|
|
26404
|
+
}
|
|
26405
|
+
lines.push(` ${formatStatusSymbol("pass")} path ${systemInfo.opencodePath ?? "unknown"}`);
|
|
26406
|
+
if (systemInfo.isLocalDev) {
|
|
26407
|
+
lines.push(` ${import_picocolors20.default.yellow("*")} ${import_picocolors20.default.dim("(local development mode)")}`);
|
|
26408
|
+
}
|
|
26409
|
+
lines.push("");
|
|
26410
|
+
lines.push(`${import_picocolors20.default.bold("Configuration")}`);
|
|
26411
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26412
|
+
const configStatus = systemInfo.configValid ? import_picocolors20.default.green("valid") : import_picocolors20.default.red("invalid");
|
|
26413
|
+
lines.push(` ${formatStatusSymbol(systemInfo.configValid ? "pass" : "fail")} ${systemInfo.configPath ?? "unknown"} (${configStatus})`);
|
|
26414
|
+
lines.push("");
|
|
26415
|
+
lines.push(`${import_picocolors20.default.bold("Tools")}`);
|
|
26416
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26417
|
+
lines.push(` ${formatStatusSymbol("pass")} LSP ${tools.lspInstalled}/${tools.lspTotal} installed`);
|
|
26418
|
+
lines.push(` ${formatStatusSymbol(tools.astGrepCli ? "pass" : "fail")} ast-grep CLI ${tools.astGrepCli ? "installed" : "not found"}`);
|
|
26419
|
+
lines.push(` ${formatStatusSymbol(tools.astGrepNapi ? "pass" : "fail")} ast-grep napi ${tools.astGrepNapi ? "installed" : "not found"}`);
|
|
26420
|
+
lines.push(` ${formatStatusSymbol(tools.commentChecker ? "pass" : "fail")} comment-checker ${tools.commentChecker ? "installed" : "not found"}`);
|
|
26421
|
+
lines.push(` ${formatStatusSymbol(tools.ghCli.installed && tools.ghCli.authenticated ? "pass" : "fail")} gh CLI ${tools.ghCli.installed ? "installed" : "not found"}${tools.ghCli.authenticated && tools.ghCli.username ? ` (${tools.ghCli.username})` : ""}`);
|
|
26422
|
+
lines.push("");
|
|
26423
|
+
lines.push(`${import_picocolors20.default.bold("MCPs")}`);
|
|
26424
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26425
|
+
if (tools.mcpBuiltin.length === 0) {
|
|
26426
|
+
lines.push(` ${import_picocolors20.default.dim("No built-in MCPs")}`);
|
|
26427
|
+
} else {
|
|
26428
|
+
for (const mcp of tools.mcpBuiltin) {
|
|
26429
|
+
lines.push(` ${formatStatusSymbol("pass")} ${mcp}`);
|
|
26430
|
+
}
|
|
26431
|
+
}
|
|
26432
|
+
if (tools.mcpUser.length > 0) {
|
|
26433
|
+
lines.push(` ${import_picocolors20.default.cyan("+")} ${tools.mcpUser.length} user MCP(s):`);
|
|
26434
|
+
for (const mcp of tools.mcpUser) {
|
|
26435
|
+
lines.push(` ${formatStatusSymbol("pass")} ${mcp}`);
|
|
26436
|
+
}
|
|
26437
|
+
}
|
|
26438
|
+
lines.push("");
|
|
26439
|
+
const allIssues = results.flatMap((r2) => r2.issues);
|
|
26440
|
+
if (allIssues.length > 0) {
|
|
26441
|
+
lines.push(`${import_picocolors20.default.bold("Issues")}`);
|
|
26442
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26443
|
+
allIssues.forEach((issue2, index) => {
|
|
26444
|
+
lines.push(formatIssue(issue2, index + 1));
|
|
26445
|
+
lines.push("");
|
|
26446
|
+
});
|
|
26447
|
+
}
|
|
26448
|
+
lines.push(`${import_picocolors20.default.bold("Summary")}`);
|
|
26449
|
+
lines.push(`${import_picocolors20.default.dim("\u2500".repeat(40))}`);
|
|
26450
|
+
const passText = summary.passed > 0 ? import_picocolors20.default.green(`${summary.passed} passed`) : `${summary.passed} passed`;
|
|
26451
|
+
const failText = summary.failed > 0 ? import_picocolors20.default.red(`${summary.failed} failed`) : `${summary.failed} failed`;
|
|
26452
|
+
const warnText = summary.warnings > 0 ? import_picocolors20.default.yellow(`${summary.warnings} warnings`) : `${summary.warnings} warnings`;
|
|
26453
|
+
lines.push(` ${passText}, ${failText}, ${warnText}`);
|
|
26454
|
+
lines.push(` ${import_picocolors20.default.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
|
|
26455
|
+
return lines.join(`
|
|
26456
|
+
`);
|
|
26457
|
+
}
|
|
26458
|
+
|
|
26459
|
+
// src/cli/doctor/formatter.ts
|
|
26460
|
+
function formatDoctorOutput(result, mode) {
|
|
26461
|
+
switch (mode) {
|
|
26462
|
+
case "default":
|
|
26463
|
+
return formatDefault(result);
|
|
26464
|
+
case "status":
|
|
26465
|
+
return formatStatus(result);
|
|
26466
|
+
case "verbose":
|
|
26467
|
+
return formatVerbose(result);
|
|
26468
|
+
}
|
|
26469
|
+
}
|
|
26470
|
+
function formatJsonOutput2(result) {
|
|
26471
|
+
return JSON.stringify(result, null, 2);
|
|
26472
|
+
}
|
|
26473
|
+
|
|
26474
|
+
// src/cli/doctor/runner.ts
|
|
26475
|
+
async function runCheck(check2) {
|
|
26476
|
+
const start = performance.now();
|
|
26477
|
+
try {
|
|
26478
|
+
const result = await check2.check();
|
|
26479
|
+
result.duration = Math.round(performance.now() - start);
|
|
26480
|
+
return result;
|
|
26481
|
+
} catch (err) {
|
|
26482
|
+
return {
|
|
26483
|
+
name: check2.name,
|
|
26484
|
+
status: "fail",
|
|
26485
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
26486
|
+
issues: [{ title: check2.name, description: String(err), severity: "error" }],
|
|
26487
|
+
duration: Math.round(performance.now() - start)
|
|
26488
|
+
};
|
|
26489
|
+
}
|
|
26490
|
+
}
|
|
26491
|
+
function calculateSummary(results, duration3) {
|
|
26492
|
+
return {
|
|
26493
|
+
total: results.length,
|
|
26494
|
+
passed: results.filter((r2) => r2.status === "pass").length,
|
|
26495
|
+
failed: results.filter((r2) => r2.status === "fail").length,
|
|
26496
|
+
warnings: results.filter((r2) => r2.status === "warn").length,
|
|
26497
|
+
skipped: results.filter((r2) => r2.status === "skip").length,
|
|
26498
|
+
duration: Math.round(duration3)
|
|
26499
|
+
};
|
|
26500
|
+
}
|
|
26501
|
+
function determineExitCode(results) {
|
|
26502
|
+
return results.some((r2) => r2.status === "fail") ? EXIT_CODES.FAILURE : EXIT_CODES.SUCCESS;
|
|
26503
|
+
}
|
|
26504
|
+
async function runDoctor(options) {
|
|
26505
|
+
const start = performance.now();
|
|
26506
|
+
const allChecks = getAllCheckDefinitions();
|
|
26507
|
+
const [results, systemInfo, tools] = await Promise.all([
|
|
26508
|
+
Promise.all(allChecks.map(runCheck)),
|
|
26509
|
+
gatherSystemInfo(),
|
|
26510
|
+
gatherToolsSummary()
|
|
26511
|
+
]);
|
|
26512
|
+
const duration3 = performance.now() - start;
|
|
26513
|
+
const summary = calculateSummary(results, duration3);
|
|
26514
|
+
const exitCode = determineExitCode(results);
|
|
26515
|
+
const doctorResult = {
|
|
26516
|
+
results,
|
|
26517
|
+
systemInfo,
|
|
26518
|
+
tools,
|
|
26519
|
+
summary,
|
|
26520
|
+
exitCode
|
|
26521
|
+
};
|
|
26522
|
+
if (options.json) {
|
|
26523
|
+
console.log(formatJsonOutput2(doctorResult));
|
|
26524
|
+
} else {
|
|
26525
|
+
console.log(formatDoctorOutput(doctorResult, options.mode));
|
|
26526
|
+
}
|
|
26527
|
+
return doctorResult;
|
|
26528
|
+
}
|
|
26529
|
+
|
|
26530
|
+
// src/cli/doctor/index.ts
|
|
26531
|
+
async function doctor(options = { mode: "default" }) {
|
|
26532
|
+
const result = await runDoctor(options);
|
|
26533
|
+
return result.exitCode;
|
|
26534
|
+
}
|
|
26535
|
+
|
|
26362
26536
|
// src/features/mcp-oauth/storage.ts
|
|
26363
26537
|
init_shared();
|
|
26364
|
-
import { chmodSync, existsSync as
|
|
26365
|
-
import { dirname as
|
|
26538
|
+
import { chmodSync, existsSync as existsSync24, mkdirSync as mkdirSync3, readFileSync as readFileSync25, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
26539
|
+
import { dirname as dirname4, join as join21 } from "path";
|
|
26366
26540
|
var STORAGE_FILE_NAME = "mcp-oauth.json";
|
|
26367
26541
|
function getMcpOauthStoragePath() {
|
|
26368
|
-
return
|
|
26542
|
+
return join21(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
|
|
26369
26543
|
}
|
|
26370
26544
|
function normalizeHost(serverHost) {
|
|
26371
26545
|
let host = serverHost.trim();
|
|
@@ -26402,11 +26576,11 @@ function buildKey(serverHost, resource) {
|
|
|
26402
26576
|
}
|
|
26403
26577
|
function readStore() {
|
|
26404
26578
|
const filePath = getMcpOauthStoragePath();
|
|
26405
|
-
if (!
|
|
26579
|
+
if (!existsSync24(filePath)) {
|
|
26406
26580
|
return null;
|
|
26407
26581
|
}
|
|
26408
26582
|
try {
|
|
26409
|
-
const content =
|
|
26583
|
+
const content = readFileSync25(filePath, "utf-8");
|
|
26410
26584
|
return JSON.parse(content);
|
|
26411
26585
|
} catch {
|
|
26412
26586
|
return null;
|
|
@@ -26415,8 +26589,8 @@ function readStore() {
|
|
|
26415
26589
|
function writeStore(store) {
|
|
26416
26590
|
const filePath = getMcpOauthStoragePath();
|
|
26417
26591
|
try {
|
|
26418
|
-
const dir =
|
|
26419
|
-
if (!
|
|
26592
|
+
const dir = dirname4(filePath);
|
|
26593
|
+
if (!existsSync24(dir)) {
|
|
26420
26594
|
mkdirSync3(dir, { recursive: true });
|
|
26421
26595
|
}
|
|
26422
26596
|
writeFileSync9(filePath, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
|
|
@@ -26451,7 +26625,7 @@ function deleteToken(serverHost, resource) {
|
|
|
26451
26625
|
if (Object.keys(store).length === 0) {
|
|
26452
26626
|
try {
|
|
26453
26627
|
const filePath = getMcpOauthStoragePath();
|
|
26454
|
-
if (
|
|
26628
|
+
if (existsSync24(filePath)) {
|
|
26455
26629
|
unlinkSync(filePath);
|
|
26456
26630
|
}
|
|
26457
26631
|
return true;
|
|
@@ -26479,368 +26653,6 @@ function listAllTokens() {
|
|
|
26479
26653
|
return readStore() ?? {};
|
|
26480
26654
|
}
|
|
26481
26655
|
|
|
26482
|
-
// src/cli/doctor/checks/mcp-oauth.ts
|
|
26483
|
-
import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
|
|
26484
|
-
function readTokenStore() {
|
|
26485
|
-
const filePath = getMcpOauthStoragePath();
|
|
26486
|
-
if (!existsSync24(filePath)) {
|
|
26487
|
-
return null;
|
|
26488
|
-
}
|
|
26489
|
-
try {
|
|
26490
|
-
const content = readFileSync25(filePath, "utf-8");
|
|
26491
|
-
return JSON.parse(content);
|
|
26492
|
-
} catch {
|
|
26493
|
-
return null;
|
|
26494
|
-
}
|
|
26495
|
-
}
|
|
26496
|
-
async function checkMcpOAuthTokens() {
|
|
26497
|
-
const store = readTokenStore();
|
|
26498
|
-
if (!store || Object.keys(store).length === 0) {
|
|
26499
|
-
return {
|
|
26500
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26501
|
-
status: "skip",
|
|
26502
|
-
message: "No OAuth tokens configured",
|
|
26503
|
-
details: ["Optional: Configure OAuth tokens for MCP servers"]
|
|
26504
|
-
};
|
|
26505
|
-
}
|
|
26506
|
-
const now = Math.floor(Date.now() / 1000);
|
|
26507
|
-
const tokens = Object.entries(store);
|
|
26508
|
-
const expiredTokens = tokens.filter(([, token]) => token.expiresAt && token.expiresAt < now);
|
|
26509
|
-
if (expiredTokens.length > 0) {
|
|
26510
|
-
return {
|
|
26511
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26512
|
-
status: "warn",
|
|
26513
|
-
message: `${expiredTokens.length} of ${tokens.length} token(s) expired`,
|
|
26514
|
-
details: [
|
|
26515
|
-
...tokens.filter(([, token]) => !token.expiresAt || token.expiresAt >= now).map(([key]) => `Valid: ${key}`),
|
|
26516
|
-
...expiredTokens.map(([key]) => `Expired: ${key}`)
|
|
26517
|
-
]
|
|
26518
|
-
};
|
|
26519
|
-
}
|
|
26520
|
-
return {
|
|
26521
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26522
|
-
status: "pass",
|
|
26523
|
-
message: `${tokens.length} OAuth token(s) valid`,
|
|
26524
|
-
details: tokens.map(([key]) => `Configured: ${key}`)
|
|
26525
|
-
};
|
|
26526
|
-
}
|
|
26527
|
-
function getMcpOAuthCheckDefinition() {
|
|
26528
|
-
return {
|
|
26529
|
-
id: CHECK_IDS.MCP_OAUTH_TOKENS,
|
|
26530
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26531
|
-
category: "tools",
|
|
26532
|
-
check: checkMcpOAuthTokens,
|
|
26533
|
-
critical: false
|
|
26534
|
-
};
|
|
26535
|
-
}
|
|
26536
|
-
|
|
26537
|
-
// src/cli/doctor/checks/version.ts
|
|
26538
|
-
init_checker();
|
|
26539
|
-
function compareVersions2(current, latest) {
|
|
26540
|
-
const parseVersion = (v) => {
|
|
26541
|
-
const cleaned = v.replace(/^v/, "").split("-")[0];
|
|
26542
|
-
return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
|
|
26543
|
-
};
|
|
26544
|
-
const curr = parseVersion(current);
|
|
26545
|
-
const lat = parseVersion(latest);
|
|
26546
|
-
for (let i2 = 0;i2 < Math.max(curr.length, lat.length); i2++) {
|
|
26547
|
-
const c = curr[i2] ?? 0;
|
|
26548
|
-
const l2 = lat[i2] ?? 0;
|
|
26549
|
-
if (c < l2)
|
|
26550
|
-
return false;
|
|
26551
|
-
if (c > l2)
|
|
26552
|
-
return true;
|
|
26553
|
-
}
|
|
26554
|
-
return true;
|
|
26555
|
-
}
|
|
26556
|
-
async function getVersionInfo() {
|
|
26557
|
-
const cwd = process.cwd();
|
|
26558
|
-
if (isLocalDevMode(cwd)) {
|
|
26559
|
-
return {
|
|
26560
|
-
currentVersion: "local-dev",
|
|
26561
|
-
latestVersion: null,
|
|
26562
|
-
isUpToDate: true,
|
|
26563
|
-
isLocalDev: true,
|
|
26564
|
-
isPinned: false
|
|
26565
|
-
};
|
|
26566
|
-
}
|
|
26567
|
-
const pluginInfo = findPluginEntry(cwd);
|
|
26568
|
-
if (pluginInfo?.isPinned) {
|
|
26569
|
-
return {
|
|
26570
|
-
currentVersion: pluginInfo.pinnedVersion,
|
|
26571
|
-
latestVersion: null,
|
|
26572
|
-
isUpToDate: true,
|
|
26573
|
-
isLocalDev: false,
|
|
26574
|
-
isPinned: true
|
|
26575
|
-
};
|
|
26576
|
-
}
|
|
26577
|
-
const currentVersion = getCachedVersion();
|
|
26578
|
-
const { extractChannel: extractChannel2 } = await Promise.resolve().then(() => (init_auto_update_checker(), exports_auto_update_checker));
|
|
26579
|
-
const channel = extractChannel2(pluginInfo?.pinnedVersion ?? currentVersion);
|
|
26580
|
-
const latestVersion = await getLatestVersion(channel);
|
|
26581
|
-
const isUpToDate = !currentVersion || !latestVersion || compareVersions2(currentVersion, latestVersion);
|
|
26582
|
-
return {
|
|
26583
|
-
currentVersion,
|
|
26584
|
-
latestVersion,
|
|
26585
|
-
isUpToDate,
|
|
26586
|
-
isLocalDev: false,
|
|
26587
|
-
isPinned: false
|
|
26588
|
-
};
|
|
26589
|
-
}
|
|
26590
|
-
async function checkVersionStatus() {
|
|
26591
|
-
const info = await getVersionInfo();
|
|
26592
|
-
if (info.isLocalDev) {
|
|
26593
|
-
return {
|
|
26594
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26595
|
-
status: "pass",
|
|
26596
|
-
message: "Running in local development mode",
|
|
26597
|
-
details: ["Using file:// protocol from config"]
|
|
26598
|
-
};
|
|
26599
|
-
}
|
|
26600
|
-
if (info.isPinned) {
|
|
26601
|
-
return {
|
|
26602
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26603
|
-
status: "pass",
|
|
26604
|
-
message: `Pinned to version ${info.currentVersion}`,
|
|
26605
|
-
details: ["Update check skipped for pinned versions"]
|
|
26606
|
-
};
|
|
26607
|
-
}
|
|
26608
|
-
if (!info.currentVersion) {
|
|
26609
|
-
return {
|
|
26610
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26611
|
-
status: "warn",
|
|
26612
|
-
message: "Unable to determine current version",
|
|
26613
|
-
details: ["Run: bunx oh-my-opencode get-local-version"]
|
|
26614
|
-
};
|
|
26615
|
-
}
|
|
26616
|
-
if (!info.latestVersion) {
|
|
26617
|
-
return {
|
|
26618
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26619
|
-
status: "warn",
|
|
26620
|
-
message: `Current: ${info.currentVersion}`,
|
|
26621
|
-
details: ["Unable to check for updates (network error)"]
|
|
26622
|
-
};
|
|
26623
|
-
}
|
|
26624
|
-
if (!info.isUpToDate) {
|
|
26625
|
-
return {
|
|
26626
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26627
|
-
status: "warn",
|
|
26628
|
-
message: `Update available: ${info.currentVersion} -> ${info.latestVersion}`,
|
|
26629
|
-
details: ["Run: cd ~/.config/opencode && bun update oh-my-opencode"]
|
|
26630
|
-
};
|
|
26631
|
-
}
|
|
26632
|
-
return {
|
|
26633
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26634
|
-
status: "pass",
|
|
26635
|
-
message: `Up to date (${info.currentVersion})`,
|
|
26636
|
-
details: info.latestVersion ? [`Latest: ${info.latestVersion}`] : undefined
|
|
26637
|
-
};
|
|
26638
|
-
}
|
|
26639
|
-
function getVersionCheckDefinition() {
|
|
26640
|
-
return {
|
|
26641
|
-
id: CHECK_IDS.VERSION_STATUS,
|
|
26642
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26643
|
-
category: "updates",
|
|
26644
|
-
check: checkVersionStatus,
|
|
26645
|
-
critical: false
|
|
26646
|
-
};
|
|
26647
|
-
}
|
|
26648
|
-
|
|
26649
|
-
// src/cli/doctor/checks/index.ts
|
|
26650
|
-
function getAllCheckDefinitions() {
|
|
26651
|
-
return [
|
|
26652
|
-
getOpenCodeCheckDefinition(),
|
|
26653
|
-
getPluginCheckDefinition(),
|
|
26654
|
-
getConfigCheckDefinition(),
|
|
26655
|
-
getModelResolutionCheckDefinition(),
|
|
26656
|
-
...getAuthCheckDefinitions(),
|
|
26657
|
-
...getDependencyCheckDefinitions(),
|
|
26658
|
-
getGhCliCheckDefinition(),
|
|
26659
|
-
getLspCheckDefinition(),
|
|
26660
|
-
...getMcpCheckDefinitions(),
|
|
26661
|
-
getMcpOAuthCheckDefinition(),
|
|
26662
|
-
getVersionCheckDefinition()
|
|
26663
|
-
];
|
|
26664
|
-
}
|
|
26665
|
-
|
|
26666
|
-
// src/cli/doctor/formatter.ts
|
|
26667
|
-
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
26668
|
-
function formatStatusSymbol(status) {
|
|
26669
|
-
switch (status) {
|
|
26670
|
-
case "pass":
|
|
26671
|
-
return SYMBOLS3.check;
|
|
26672
|
-
case "fail":
|
|
26673
|
-
return SYMBOLS3.cross;
|
|
26674
|
-
case "warn":
|
|
26675
|
-
return SYMBOLS3.warn;
|
|
26676
|
-
case "skip":
|
|
26677
|
-
return SYMBOLS3.skip;
|
|
26678
|
-
}
|
|
26679
|
-
}
|
|
26680
|
-
function formatCheckResult(result, verbose) {
|
|
26681
|
-
const symbol2 = formatStatusSymbol(result.status);
|
|
26682
|
-
const colorFn = STATUS_COLORS[result.status];
|
|
26683
|
-
const name = colorFn(result.name);
|
|
26684
|
-
const message = import_picocolors17.default.dim(result.message);
|
|
26685
|
-
let line = ` ${symbol2} ${name}`;
|
|
26686
|
-
if (result.message) {
|
|
26687
|
-
line += ` ${SYMBOLS3.arrow} ${message}`;
|
|
26688
|
-
}
|
|
26689
|
-
if (verbose && result.details && result.details.length > 0) {
|
|
26690
|
-
const detailLines = result.details.map((d3) => ` ${SYMBOLS3.bullet} ${import_picocolors17.default.dim(d3)}`).join(`
|
|
26691
|
-
`);
|
|
26692
|
-
line += `
|
|
26693
|
-
` + detailLines;
|
|
26694
|
-
}
|
|
26695
|
-
return line;
|
|
26696
|
-
}
|
|
26697
|
-
function formatCategoryHeader(category) {
|
|
26698
|
-
const name = CATEGORY_NAMES[category] || category;
|
|
26699
|
-
return `
|
|
26700
|
-
${import_picocolors17.default.bold(import_picocolors17.default.white(name))}
|
|
26701
|
-
${import_picocolors17.default.dim("\u2500".repeat(40))}`;
|
|
26702
|
-
}
|
|
26703
|
-
function formatSummary(summary) {
|
|
26704
|
-
const lines = [];
|
|
26705
|
-
lines.push(import_picocolors17.default.bold(import_picocolors17.default.white("Summary")));
|
|
26706
|
-
lines.push(import_picocolors17.default.dim("\u2500".repeat(40)));
|
|
26707
|
-
lines.push("");
|
|
26708
|
-
const passText = summary.passed > 0 ? import_picocolors17.default.green(`${summary.passed} passed`) : import_picocolors17.default.dim("0 passed");
|
|
26709
|
-
const failText = summary.failed > 0 ? import_picocolors17.default.red(`${summary.failed} failed`) : import_picocolors17.default.dim("0 failed");
|
|
26710
|
-
const warnText = summary.warnings > 0 ? import_picocolors17.default.yellow(`${summary.warnings} warnings`) : import_picocolors17.default.dim("0 warnings");
|
|
26711
|
-
const skipText = summary.skipped > 0 ? import_picocolors17.default.dim(`${summary.skipped} skipped`) : "";
|
|
26712
|
-
const parts = [passText, failText, warnText];
|
|
26713
|
-
if (skipText)
|
|
26714
|
-
parts.push(skipText);
|
|
26715
|
-
lines.push(` ${parts.join(", ")}`);
|
|
26716
|
-
lines.push(` ${import_picocolors17.default.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
|
|
26717
|
-
return lines.join(`
|
|
26718
|
-
`);
|
|
26719
|
-
}
|
|
26720
|
-
function formatHeader() {
|
|
26721
|
-
return `
|
|
26722
|
-
${import_picocolors17.default.bgMagenta(import_picocolors17.default.white(" oMoMoMoMo... Doctor "))}
|
|
26723
|
-
`;
|
|
26724
|
-
}
|
|
26725
|
-
function formatFooter(summary) {
|
|
26726
|
-
if (summary.failed > 0) {
|
|
26727
|
-
return `
|
|
26728
|
-
${SYMBOLS3.cross} ${import_picocolors17.default.red("Issues detected. Please review the errors above.")}
|
|
26729
|
-
`;
|
|
26730
|
-
}
|
|
26731
|
-
if (summary.warnings > 0) {
|
|
26732
|
-
return `
|
|
26733
|
-
${SYMBOLS3.warn} ${import_picocolors17.default.yellow("All systems operational with warnings.")}
|
|
26734
|
-
`;
|
|
26735
|
-
}
|
|
26736
|
-
return `
|
|
26737
|
-
${SYMBOLS3.check} ${import_picocolors17.default.green("All systems operational!")}
|
|
26738
|
-
`;
|
|
26739
|
-
}
|
|
26740
|
-
function formatJsonOutput2(result) {
|
|
26741
|
-
return JSON.stringify(result, null, 2);
|
|
26742
|
-
}
|
|
26743
|
-
|
|
26744
|
-
// src/cli/doctor/runner.ts
|
|
26745
|
-
async function runCheck(check2) {
|
|
26746
|
-
const start = performance.now();
|
|
26747
|
-
try {
|
|
26748
|
-
const result = await check2.check();
|
|
26749
|
-
result.duration = Math.round(performance.now() - start);
|
|
26750
|
-
return result;
|
|
26751
|
-
} catch (err) {
|
|
26752
|
-
return {
|
|
26753
|
-
name: check2.name,
|
|
26754
|
-
status: "fail",
|
|
26755
|
-
message: err instanceof Error ? err.message : "Unknown error",
|
|
26756
|
-
duration: Math.round(performance.now() - start)
|
|
26757
|
-
};
|
|
26758
|
-
}
|
|
26759
|
-
}
|
|
26760
|
-
function calculateSummary(results, duration3) {
|
|
26761
|
-
return {
|
|
26762
|
-
total: results.length,
|
|
26763
|
-
passed: results.filter((r2) => r2.status === "pass").length,
|
|
26764
|
-
failed: results.filter((r2) => r2.status === "fail").length,
|
|
26765
|
-
warnings: results.filter((r2) => r2.status === "warn").length,
|
|
26766
|
-
skipped: results.filter((r2) => r2.status === "skip").length,
|
|
26767
|
-
duration: Math.round(duration3)
|
|
26768
|
-
};
|
|
26769
|
-
}
|
|
26770
|
-
function determineExitCode(results) {
|
|
26771
|
-
const hasFailures = results.some((r2) => r2.status === "fail");
|
|
26772
|
-
return hasFailures ? EXIT_CODES.FAILURE : EXIT_CODES.SUCCESS;
|
|
26773
|
-
}
|
|
26774
|
-
function filterChecksByCategory(checks3, category) {
|
|
26775
|
-
if (!category)
|
|
26776
|
-
return checks3;
|
|
26777
|
-
return checks3.filter((c) => c.category === category);
|
|
26778
|
-
}
|
|
26779
|
-
function groupChecksByCategory(checks3) {
|
|
26780
|
-
const groups = new Map;
|
|
26781
|
-
for (const check2 of checks3) {
|
|
26782
|
-
const existing = groups.get(check2.category) ?? [];
|
|
26783
|
-
existing.push(check2);
|
|
26784
|
-
groups.set(check2.category, existing);
|
|
26785
|
-
}
|
|
26786
|
-
return groups;
|
|
26787
|
-
}
|
|
26788
|
-
var CATEGORY_ORDER = [
|
|
26789
|
-
"installation",
|
|
26790
|
-
"configuration",
|
|
26791
|
-
"authentication",
|
|
26792
|
-
"dependencies",
|
|
26793
|
-
"tools",
|
|
26794
|
-
"updates"
|
|
26795
|
-
];
|
|
26796
|
-
async function runDoctor(options) {
|
|
26797
|
-
const start = performance.now();
|
|
26798
|
-
const allChecks = getAllCheckDefinitions();
|
|
26799
|
-
const filteredChecks = filterChecksByCategory(allChecks, options.category);
|
|
26800
|
-
const groupedChecks = groupChecksByCategory(filteredChecks);
|
|
26801
|
-
const results = [];
|
|
26802
|
-
if (!options.json) {
|
|
26803
|
-
console.log(formatHeader());
|
|
26804
|
-
}
|
|
26805
|
-
for (const category of CATEGORY_ORDER) {
|
|
26806
|
-
const checks3 = groupedChecks.get(category);
|
|
26807
|
-
if (!checks3 || checks3.length === 0)
|
|
26808
|
-
continue;
|
|
26809
|
-
if (!options.json) {
|
|
26810
|
-
console.log(formatCategoryHeader(category));
|
|
26811
|
-
}
|
|
26812
|
-
for (const check2 of checks3) {
|
|
26813
|
-
const result = await runCheck(check2);
|
|
26814
|
-
results.push(result);
|
|
26815
|
-
if (!options.json) {
|
|
26816
|
-
console.log(formatCheckResult(result, options.verbose ?? false));
|
|
26817
|
-
}
|
|
26818
|
-
}
|
|
26819
|
-
}
|
|
26820
|
-
const duration3 = performance.now() - start;
|
|
26821
|
-
const summary = calculateSummary(results, duration3);
|
|
26822
|
-
const exitCode = determineExitCode(results);
|
|
26823
|
-
const doctorResult = {
|
|
26824
|
-
results,
|
|
26825
|
-
summary,
|
|
26826
|
-
exitCode
|
|
26827
|
-
};
|
|
26828
|
-
if (options.json) {
|
|
26829
|
-
console.log(formatJsonOutput2(doctorResult));
|
|
26830
|
-
} else {
|
|
26831
|
-
console.log("");
|
|
26832
|
-
console.log(formatSummary(summary));
|
|
26833
|
-
console.log(formatFooter(summary));
|
|
26834
|
-
}
|
|
26835
|
-
return doctorResult;
|
|
26836
|
-
}
|
|
26837
|
-
|
|
26838
|
-
// src/cli/doctor/index.ts
|
|
26839
|
-
async function doctor(options = {}) {
|
|
26840
|
-
const result = await runDoctor(options);
|
|
26841
|
-
return result.exitCode;
|
|
26842
|
-
}
|
|
26843
|
-
|
|
26844
26656
|
// src/features/mcp-oauth/discovery.ts
|
|
26845
26657
|
var discoveryCache = new Map;
|
|
26846
26658
|
var pendingDiscovery = new Map;
|
|
@@ -27344,7 +27156,7 @@ async function status(serverName) {
|
|
|
27344
27156
|
|
|
27345
27157
|
// src/cli/mcp-oauth/index.ts
|
|
27346
27158
|
function createMcpOAuthCommand() {
|
|
27347
|
-
const
|
|
27159
|
+
const mcp = new Command("mcp").description("MCP server management");
|
|
27348
27160
|
const oauth = new Command("oauth").description("OAuth token management for MCP servers");
|
|
27349
27161
|
oauth.command("login <server-name>").description("Authenticate with an MCP server using OAuth").option("--server-url <url>", "OAuth server URL (required if not in config)").option("--client-id <id>", "OAuth client ID (optional, uses DCR if not provided)").option("--scopes <scopes...>", "OAuth scopes to request").action(async (serverName, options) => {
|
|
27350
27162
|
const exitCode = await login(serverName, options);
|
|
@@ -27358,8 +27170,8 @@ function createMcpOAuthCommand() {
|
|
|
27358
27170
|
const exitCode = await status(serverName);
|
|
27359
27171
|
process.exit(exitCode);
|
|
27360
27172
|
});
|
|
27361
|
-
|
|
27362
|
-
return
|
|
27173
|
+
mcp.addCommand(oauth);
|
|
27174
|
+
return mcp;
|
|
27363
27175
|
}
|
|
27364
27176
|
|
|
27365
27177
|
// src/cli/cli-program.ts
|
|
@@ -27456,25 +27268,17 @@ This command shows:
|
|
|
27456
27268
|
const exitCode = await getLocalVersion(versionOptions);
|
|
27457
27269
|
process.exit(exitCode);
|
|
27458
27270
|
});
|
|
27459
|
-
program2.command("doctor").description("Check oh-my-opencode installation health and diagnose issues").option("--
|
|
27271
|
+
program2.command("doctor").description("Check oh-my-opencode installation health and diagnose issues").option("--status", "Show compact system dashboard").option("--verbose", "Show detailed diagnostic information").option("--json", "Output results in JSON format").addHelpText("after", `
|
|
27460
27272
|
Examples:
|
|
27461
|
-
$ bunx oh-my-opencode doctor
|
|
27462
|
-
$ bunx oh-my-opencode doctor --
|
|
27463
|
-
$ bunx oh-my-opencode doctor --
|
|
27464
|
-
$ bunx oh-my-opencode doctor --
|
|
27465
|
-
|
|
27466
|
-
Categories:
|
|
27467
|
-
installation Check OpenCode and plugin installation
|
|
27468
|
-
configuration Validate configuration files
|
|
27469
|
-
authentication Check auth provider status
|
|
27470
|
-
dependencies Check external dependencies
|
|
27471
|
-
tools Check LSP and MCP servers
|
|
27472
|
-
updates Check for version updates
|
|
27273
|
+
$ bunx oh-my-opencode doctor # Show problems only
|
|
27274
|
+
$ bunx oh-my-opencode doctor --status # Compact dashboard
|
|
27275
|
+
$ bunx oh-my-opencode doctor --verbose # Deep diagnostics
|
|
27276
|
+
$ bunx oh-my-opencode doctor --json # JSON output
|
|
27473
27277
|
`).action(async (options) => {
|
|
27278
|
+
const mode = options.status ? "status" : options.verbose ? "verbose" : "default";
|
|
27474
27279
|
const doctorOptions = {
|
|
27475
|
-
|
|
27476
|
-
json: options.json ?? false
|
|
27477
|
-
category: options.category
|
|
27280
|
+
mode,
|
|
27281
|
+
json: options.json ?? false
|
|
27478
27282
|
};
|
|
27479
27283
|
const exitCode = await doctor(doctorOptions);
|
|
27480
27284
|
process.exit(exitCode);
|