oh-my-opencode 3.5.2 → 3.5.4
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 +959 -1138
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- 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/background-event-handler.d.ts +1 -0
- 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 +4 -0
- package/dist/features/background-agent/poll-running-tasks.d.ts +3 -1
- package/dist/features/background-agent/session-idle-event-handler.d.ts +10 -0
- package/dist/features/background-agent/session-task-cleanup.d.ts +10 -0
- package/dist/features/background-agent/stale-task-pruner.d.ts +7 -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/prometheus-md-only/agent-matcher.d.ts +1 -0
- 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 +2043 -1425
- 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/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
|
@@ -7052,6 +7052,12 @@ var init_tmux = __esm(() => {
|
|
|
7052
7052
|
var init_model_suggestion_retry = __esm(() => {
|
|
7053
7053
|
init_logger();
|
|
7054
7054
|
});
|
|
7055
|
+
|
|
7056
|
+
// src/shared/opencode-server-auth.ts
|
|
7057
|
+
var init_opencode_server_auth = __esm(() => {
|
|
7058
|
+
init_logger();
|
|
7059
|
+
});
|
|
7060
|
+
|
|
7055
7061
|
// src/shared/port-utils.ts
|
|
7056
7062
|
async function isPortAvailable(port, hostname = "127.0.0.1") {
|
|
7057
7063
|
try {
|
|
@@ -7128,6 +7134,7 @@ var init_shared = __esm(() => {
|
|
|
7128
7134
|
init_session_utils();
|
|
7129
7135
|
init_tmux();
|
|
7130
7136
|
init_model_suggestion_retry();
|
|
7137
|
+
init_opencode_server_auth();
|
|
7131
7138
|
init_port_utils();
|
|
7132
7139
|
init_git_worktree();
|
|
7133
7140
|
init_safe_create_hook();
|
|
@@ -8870,7 +8877,7 @@ var {
|
|
|
8870
8877
|
// package.json
|
|
8871
8878
|
var package_default = {
|
|
8872
8879
|
name: "oh-my-opencode",
|
|
8873
|
-
version: "3.5.
|
|
8880
|
+
version: "3.5.4",
|
|
8874
8881
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
8875
8882
|
main: "dist/index.js",
|
|
8876
8883
|
types: "dist/index.d.ts",
|
|
@@ -8944,13 +8951,13 @@ var package_default = {
|
|
|
8944
8951
|
typescript: "^5.7.3"
|
|
8945
8952
|
},
|
|
8946
8953
|
optionalDependencies: {
|
|
8947
|
-
"oh-my-opencode-darwin-arm64": "3.5.
|
|
8948
|
-
"oh-my-opencode-darwin-x64": "3.5.
|
|
8949
|
-
"oh-my-opencode-linux-arm64": "3.5.
|
|
8950
|
-
"oh-my-opencode-linux-arm64-musl": "3.5.
|
|
8951
|
-
"oh-my-opencode-linux-x64": "3.5.
|
|
8952
|
-
"oh-my-opencode-linux-x64-musl": "3.5.
|
|
8953
|
-
"oh-my-opencode-windows-x64": "3.5.
|
|
8954
|
+
"oh-my-opencode-darwin-arm64": "3.5.4",
|
|
8955
|
+
"oh-my-opencode-darwin-x64": "3.5.4",
|
|
8956
|
+
"oh-my-opencode-linux-arm64": "3.5.4",
|
|
8957
|
+
"oh-my-opencode-linux-arm64-musl": "3.5.4",
|
|
8958
|
+
"oh-my-opencode-linux-x64": "3.5.4",
|
|
8959
|
+
"oh-my-opencode-linux-x64-musl": "3.5.4",
|
|
8960
|
+
"oh-my-opencode-windows-x64": "3.5.4"
|
|
8954
8961
|
},
|
|
8955
8962
|
trustedDependencies: [
|
|
8956
8963
|
"@ast-grep/cli",
|
|
@@ -22692,7 +22699,8 @@ var BackgroundTaskConfigSchema = exports_external.object({
|
|
|
22692
22699
|
defaultConcurrency: exports_external.number().min(1).optional(),
|
|
22693
22700
|
providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
22694
22701
|
modelConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
22695
|
-
staleTimeoutMs: exports_external.number().min(60000).optional()
|
|
22702
|
+
staleTimeoutMs: exports_external.number().min(60000).optional(),
|
|
22703
|
+
messageStalenessTimeoutMs: exports_external.number().min(60000).optional()
|
|
22696
22704
|
});
|
|
22697
22705
|
// src/config/schema/browser-automation.ts
|
|
22698
22706
|
var BrowserAutomationProviderSchema = exports_external.enum([
|
|
@@ -22818,7 +22826,6 @@ var HookNameSchema = exports_external.enum([
|
|
|
22818
22826
|
"directory-readme-injector",
|
|
22819
22827
|
"empty-task-response-detector",
|
|
22820
22828
|
"think-mode",
|
|
22821
|
-
"subagent-question-blocker",
|
|
22822
22829
|
"anthropic-context-window-limit-recovery",
|
|
22823
22830
|
"preemptive-compaction",
|
|
22824
22831
|
"rules-injector",
|
|
@@ -22891,11 +22898,11 @@ var SkillDefinitionSchema = exports_external.object({
|
|
|
22891
22898
|
var SkillEntrySchema = exports_external.union([exports_external.boolean(), SkillDefinitionSchema]);
|
|
22892
22899
|
var SkillsConfigSchema = exports_external.union([
|
|
22893
22900
|
exports_external.array(exports_external.string()),
|
|
22894
|
-
exports_external.
|
|
22901
|
+
exports_external.object({
|
|
22895
22902
|
sources: exports_external.array(SkillSourceSchema).optional(),
|
|
22896
22903
|
enable: exports_external.array(exports_external.string()).optional(),
|
|
22897
22904
|
disable: exports_external.array(exports_external.string()).optional()
|
|
22898
|
-
}).
|
|
22905
|
+
}).catchall(SkillEntrySchema)
|
|
22899
22906
|
]);
|
|
22900
22907
|
|
|
22901
22908
|
// src/config/schema/sisyphus.ts
|
|
@@ -22970,6 +22977,32 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
22970
22977
|
});
|
|
22971
22978
|
// src/plugin-config.ts
|
|
22972
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
|
+
}
|
|
22973
23006
|
function loadConfigFromPath(configPath, ctx) {
|
|
22974
23007
|
try {
|
|
22975
23008
|
if (fs3.existsSync(configPath)) {
|
|
@@ -22977,17 +23010,22 @@ function loadConfigFromPath(configPath, ctx) {
|
|
|
22977
23010
|
const rawConfig = parseJsonc(content);
|
|
22978
23011
|
migrateConfigFile(configPath, rawConfig);
|
|
22979
23012
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
22980
|
-
if (
|
|
22981
|
-
|
|
22982
|
-
|
|
22983
|
-
|
|
22984
|
-
|
|
22985
|
-
|
|
22986
|
-
|
|
22987
|
-
|
|
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;
|
|
22988
23027
|
}
|
|
22989
|
-
|
|
22990
|
-
return result.data;
|
|
23028
|
+
return null;
|
|
22991
23029
|
}
|
|
22992
23030
|
} catch (err) {
|
|
22993
23031
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -24752,11 +24790,14 @@ async function areAllDescendantsIdle(ctx, sessionID, allStatuses) {
|
|
|
24752
24790
|
var DEFAULT_POLL_INTERVAL_MS = 500;
|
|
24753
24791
|
var DEFAULT_REQUIRED_CONSECUTIVE = 3;
|
|
24754
24792
|
var ERROR_GRACE_CYCLES = 3;
|
|
24793
|
+
var MIN_STABILIZATION_MS = 1e4;
|
|
24755
24794
|
async function pollForCompletion(ctx, eventState, abortController, options = {}) {
|
|
24756
24795
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
24757
24796
|
const requiredConsecutive = options.requiredConsecutive ?? DEFAULT_REQUIRED_CONSECUTIVE;
|
|
24797
|
+
const minStabilizationMs = options.minStabilizationMs ?? MIN_STABILIZATION_MS;
|
|
24758
24798
|
let consecutiveCompleteChecks = 0;
|
|
24759
24799
|
let errorCycleCount = 0;
|
|
24800
|
+
let firstWorkTimestamp = null;
|
|
24760
24801
|
while (!abortController.signal.aborted) {
|
|
24761
24802
|
await new Promise((resolve2) => setTimeout(resolve2, pollIntervalMs));
|
|
24762
24803
|
if (eventState.mainSessionError) {
|
|
@@ -24784,6 +24825,13 @@ Session ended with error: ${eventState.lastError}`));
|
|
|
24784
24825
|
consecutiveCompleteChecks = 0;
|
|
24785
24826
|
continue;
|
|
24786
24827
|
}
|
|
24828
|
+
if (firstWorkTimestamp === null) {
|
|
24829
|
+
firstWorkTimestamp = Date.now();
|
|
24830
|
+
}
|
|
24831
|
+
if (Date.now() - firstWorkTimestamp < minStabilizationMs) {
|
|
24832
|
+
consecutiveCompleteChecks = 0;
|
|
24833
|
+
continue;
|
|
24834
|
+
}
|
|
24787
24835
|
const shouldExit = await checkCompletionConditions(ctx);
|
|
24788
24836
|
if (shouldExit) {
|
|
24789
24837
|
consecutiveCompleteChecks++;
|
|
@@ -24802,6 +24850,16 @@ All tasks completed.`));
|
|
|
24802
24850
|
|
|
24803
24851
|
// src/cli/run/runner.ts
|
|
24804
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
|
+
}
|
|
24805
24863
|
async function run(options) {
|
|
24806
24864
|
process.env.OPENCODE_CLI_RUN_MODE = "true";
|
|
24807
24865
|
const startTime = Date.now();
|
|
@@ -24848,7 +24906,7 @@ Interrupted. Shutting down...`));
|
|
|
24848
24906
|
});
|
|
24849
24907
|
console.log(import_picocolors14.default.dim(`Session: ${sessionID}`));
|
|
24850
24908
|
const ctx = { client: client3, sessionID, directory, abortController };
|
|
24851
|
-
const events = await client3.event.subscribe();
|
|
24909
|
+
const events = await client3.event.subscribe({ query: { directory } });
|
|
24852
24910
|
const eventState = createEventState();
|
|
24853
24911
|
const eventProcessor = processEvents(ctx, events.stream, eventState).catch(() => {});
|
|
24854
24912
|
console.log(import_picocolors14.default.dim(`
|
|
@@ -24865,7 +24923,7 @@ Sending prompt...`));
|
|
|
24865
24923
|
`));
|
|
24866
24924
|
const exitCode = await pollForCompletion(ctx, eventState, abortController);
|
|
24867
24925
|
abortController.abort();
|
|
24868
|
-
await eventProcessor;
|
|
24926
|
+
await waitForEventProcessorShutdown(eventProcessor);
|
|
24869
24927
|
cleanup();
|
|
24870
24928
|
const durationMs = Date.now() - startTime;
|
|
24871
24929
|
if (options.onComplete) {
|
|
@@ -25052,11 +25110,6 @@ async function getLocalVersion(options = {}) {
|
|
|
25052
25110
|
return 1;
|
|
25053
25111
|
}
|
|
25054
25112
|
}
|
|
25055
|
-
// src/cli/doctor/checks/opencode.ts
|
|
25056
|
-
import { existsSync as existsSync16 } from "fs";
|
|
25057
|
-
import { homedir as homedir5 } from "os";
|
|
25058
|
-
import { join as join12 } from "path";
|
|
25059
|
-
|
|
25060
25113
|
// src/cli/doctor/constants.ts
|
|
25061
25114
|
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
25062
25115
|
var SYMBOLS3 = {
|
|
@@ -25075,48 +25128,16 @@ var STATUS_COLORS = {
|
|
|
25075
25128
|
skip: import_picocolors16.default.dim
|
|
25076
25129
|
};
|
|
25077
25130
|
var CHECK_IDS = {
|
|
25078
|
-
|
|
25079
|
-
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
AUTH_ANTHROPIC: "auth-anthropic",
|
|
25083
|
-
AUTH_OPENAI: "auth-openai",
|
|
25084
|
-
AUTH_GOOGLE: "auth-google",
|
|
25085
|
-
DEP_AST_GREP_CLI: "dep-ast-grep-cli",
|
|
25086
|
-
DEP_AST_GREP_NAPI: "dep-ast-grep-napi",
|
|
25087
|
-
DEP_COMMENT_CHECKER: "dep-comment-checker",
|
|
25088
|
-
GH_CLI: "gh-cli",
|
|
25089
|
-
LSP_SERVERS: "lsp-servers",
|
|
25090
|
-
MCP_BUILTIN: "mcp-builtin",
|
|
25091
|
-
MCP_USER: "mcp-user",
|
|
25092
|
-
MCP_OAUTH_TOKENS: "mcp-oauth-tokens",
|
|
25093
|
-
VERSION_STATUS: "version-status"
|
|
25131
|
+
SYSTEM: "system",
|
|
25132
|
+
CONFIG: "config",
|
|
25133
|
+
TOOLS: "tools",
|
|
25134
|
+
MODELS: "models"
|
|
25094
25135
|
};
|
|
25095
25136
|
var CHECK_NAMES = {
|
|
25096
|
-
[CHECK_IDS.
|
|
25097
|
-
[CHECK_IDS.
|
|
25098
|
-
[CHECK_IDS.
|
|
25099
|
-
[CHECK_IDS.
|
|
25100
|
-
[CHECK_IDS.AUTH_ANTHROPIC]: "Anthropic (Claude) Auth",
|
|
25101
|
-
[CHECK_IDS.AUTH_OPENAI]: "OpenAI (ChatGPT) Auth",
|
|
25102
|
-
[CHECK_IDS.AUTH_GOOGLE]: "Google (Gemini) Auth",
|
|
25103
|
-
[CHECK_IDS.DEP_AST_GREP_CLI]: "AST-Grep CLI",
|
|
25104
|
-
[CHECK_IDS.DEP_AST_GREP_NAPI]: "AST-Grep NAPI",
|
|
25105
|
-
[CHECK_IDS.DEP_COMMENT_CHECKER]: "Comment Checker",
|
|
25106
|
-
[CHECK_IDS.GH_CLI]: "GitHub CLI",
|
|
25107
|
-
[CHECK_IDS.LSP_SERVERS]: "LSP Servers",
|
|
25108
|
-
[CHECK_IDS.MCP_BUILTIN]: "Built-in MCP Servers",
|
|
25109
|
-
[CHECK_IDS.MCP_USER]: "User MCP Configuration",
|
|
25110
|
-
[CHECK_IDS.MCP_OAUTH_TOKENS]: "MCP OAuth Tokens",
|
|
25111
|
-
[CHECK_IDS.VERSION_STATUS]: "Version Status"
|
|
25112
|
-
};
|
|
25113
|
-
var CATEGORY_NAMES = {
|
|
25114
|
-
installation: "Installation",
|
|
25115
|
-
configuration: "Configuration",
|
|
25116
|
-
authentication: "Authentication",
|
|
25117
|
-
dependencies: "Dependencies",
|
|
25118
|
-
tools: "Tools & Servers",
|
|
25119
|
-
updates: "Updates"
|
|
25137
|
+
[CHECK_IDS.SYSTEM]: "System",
|
|
25138
|
+
[CHECK_IDS.CONFIG]: "Configuration",
|
|
25139
|
+
[CHECK_IDS.TOOLS]: "Tools",
|
|
25140
|
+
[CHECK_IDS.MODELS]: "Models"
|
|
25120
25141
|
};
|
|
25121
25142
|
var EXIT_CODES = {
|
|
25122
25143
|
SUCCESS: 0,
|
|
@@ -25126,7 +25147,13 @@ var MIN_OPENCODE_VERSION = "1.0.150";
|
|
|
25126
25147
|
var PACKAGE_NAME4 = "oh-my-opencode";
|
|
25127
25148
|
var OPENCODE_BINARIES2 = ["opencode", "opencode-desktop"];
|
|
25128
25149
|
|
|
25129
|
-
// 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";
|
|
25130
25157
|
function getDesktopAppPaths(platform) {
|
|
25131
25158
|
const home = homedir5();
|
|
25132
25159
|
switch (platform) {
|
|
@@ -25160,21 +25187,12 @@ function getDesktopAppPaths(platform) {
|
|
|
25160
25187
|
}
|
|
25161
25188
|
function buildVersionCommand(binaryPath, platform) {
|
|
25162
25189
|
if (platform === "win32" && binaryPath.toLowerCase().endsWith(".ps1")) {
|
|
25163
|
-
return [
|
|
25164
|
-
"powershell",
|
|
25165
|
-
"-NoProfile",
|
|
25166
|
-
"-ExecutionPolicy",
|
|
25167
|
-
"Bypass",
|
|
25168
|
-
"-File",
|
|
25169
|
-
binaryPath,
|
|
25170
|
-
"--version"
|
|
25171
|
-
];
|
|
25190
|
+
return ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", binaryPath, "--version"];
|
|
25172
25191
|
}
|
|
25173
25192
|
return [binaryPath, "--version"];
|
|
25174
25193
|
}
|
|
25175
25194
|
function findDesktopBinary(platform = process.platform, checkExists = existsSync16) {
|
|
25176
|
-
const
|
|
25177
|
-
for (const desktopPath of desktopPaths) {
|
|
25195
|
+
for (const desktopPath of getDesktopAppPaths(platform)) {
|
|
25178
25196
|
if (checkExists(desktopPath)) {
|
|
25179
25197
|
return { binary: "opencode", path: desktopPath };
|
|
25180
25198
|
}
|
|
@@ -25183,346 +25201,312 @@ function findDesktopBinary(platform = process.platform, checkExists = existsSync
|
|
|
25183
25201
|
}
|
|
25184
25202
|
async function findOpenCodeBinary() {
|
|
25185
25203
|
for (const binary2 of OPENCODE_BINARIES2) {
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
return { binary: binary2, path: path9 };
|
|
25190
|
-
}
|
|
25191
|
-
} catch {
|
|
25192
|
-
continue;
|
|
25204
|
+
const path9 = Bun.which(binary2);
|
|
25205
|
+
if (path9) {
|
|
25206
|
+
return { binary: binary2, path: path9 };
|
|
25193
25207
|
}
|
|
25194
25208
|
}
|
|
25195
|
-
|
|
25196
|
-
if (desktopResult) {
|
|
25197
|
-
return desktopResult;
|
|
25198
|
-
}
|
|
25199
|
-
return null;
|
|
25209
|
+
return findDesktopBinary();
|
|
25200
25210
|
}
|
|
25201
25211
|
async function getOpenCodeVersion2(binaryPath, platform = process.platform) {
|
|
25202
25212
|
try {
|
|
25203
25213
|
const command = buildVersionCommand(binaryPath, platform);
|
|
25204
|
-
const
|
|
25205
|
-
const output = await new Response(
|
|
25206
|
-
await
|
|
25207
|
-
if (
|
|
25208
|
-
return
|
|
25209
|
-
|
|
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;
|
|
25210
25220
|
} catch {
|
|
25211
25221
|
return null;
|
|
25212
25222
|
}
|
|
25213
|
-
return null;
|
|
25214
25223
|
}
|
|
25215
25224
|
function compareVersions(current, minimum) {
|
|
25216
|
-
const parseVersion = (v) =>
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
const m2 = min[i2] ?? 0;
|
|
25225
|
-
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)
|
|
25226
25233
|
return true;
|
|
25227
|
-
if (
|
|
25234
|
+
if (currentPart < minimumPart)
|
|
25228
25235
|
return false;
|
|
25229
25236
|
}
|
|
25230
25237
|
return true;
|
|
25231
25238
|
}
|
|
25232
|
-
async function getOpenCodeInfo() {
|
|
25233
|
-
const binaryInfo = await findOpenCodeBinary();
|
|
25234
|
-
if (!binaryInfo) {
|
|
25235
|
-
return {
|
|
25236
|
-
installed: false,
|
|
25237
|
-
version: null,
|
|
25238
|
-
path: null,
|
|
25239
|
-
binary: null
|
|
25240
|
-
};
|
|
25241
|
-
}
|
|
25242
|
-
const version2 = await getOpenCodeVersion2(binaryInfo.path ?? binaryInfo.binary);
|
|
25243
|
-
return {
|
|
25244
|
-
installed: true,
|
|
25245
|
-
version: version2,
|
|
25246
|
-
path: binaryInfo.path,
|
|
25247
|
-
binary: binaryInfo.binary
|
|
25248
|
-
};
|
|
25249
|
-
}
|
|
25250
|
-
async function checkOpenCodeInstallation() {
|
|
25251
|
-
const info = await getOpenCodeInfo();
|
|
25252
|
-
if (!info.installed) {
|
|
25253
|
-
return {
|
|
25254
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25255
|
-
status: "fail",
|
|
25256
|
-
message: "OpenCode is not installed",
|
|
25257
|
-
details: [
|
|
25258
|
-
"Visit: https://opencode.ai/docs for installation instructions",
|
|
25259
|
-
"Run: npm install -g opencode"
|
|
25260
|
-
]
|
|
25261
|
-
};
|
|
25262
|
-
}
|
|
25263
|
-
if (info.version && !compareVersions(info.version, MIN_OPENCODE_VERSION)) {
|
|
25264
|
-
return {
|
|
25265
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25266
|
-
status: "warn",
|
|
25267
|
-
message: `Version ${info.version} is below minimum ${MIN_OPENCODE_VERSION}`,
|
|
25268
|
-
details: [
|
|
25269
|
-
`Current: ${info.version}`,
|
|
25270
|
-
`Required: >= ${MIN_OPENCODE_VERSION}`,
|
|
25271
|
-
"Run: npm update -g opencode"
|
|
25272
|
-
]
|
|
25273
|
-
};
|
|
25274
|
-
}
|
|
25275
|
-
return {
|
|
25276
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25277
|
-
status: "pass",
|
|
25278
|
-
message: info.version ?? "installed",
|
|
25279
|
-
details: info.path ? [`Path: ${info.path}`] : undefined
|
|
25280
|
-
};
|
|
25281
|
-
}
|
|
25282
|
-
function getOpenCodeCheckDefinition() {
|
|
25283
|
-
return {
|
|
25284
|
-
id: CHECK_IDS.OPENCODE_INSTALLATION,
|
|
25285
|
-
name: CHECK_NAMES[CHECK_IDS.OPENCODE_INSTALLATION],
|
|
25286
|
-
category: "installation",
|
|
25287
|
-
check: checkOpenCodeInstallation,
|
|
25288
|
-
critical: true
|
|
25289
|
-
};
|
|
25290
|
-
}
|
|
25291
25239
|
|
|
25292
|
-
// src/cli/doctor/checks/plugin.ts
|
|
25240
|
+
// src/cli/doctor/checks/system-plugin.ts
|
|
25293
25241
|
import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
|
|
25294
25242
|
init_shared();
|
|
25295
25243
|
function detectConfigPath() {
|
|
25296
25244
|
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
|
|
25297
|
-
if (existsSync17(paths.configJsonc))
|
|
25298
|
-
return
|
|
25299
|
-
|
|
25300
|
-
|
|
25301
|
-
return { path: paths.configJson, format: "json" };
|
|
25302
|
-
}
|
|
25245
|
+
if (existsSync17(paths.configJsonc))
|
|
25246
|
+
return paths.configJsonc;
|
|
25247
|
+
if (existsSync17(paths.configJson))
|
|
25248
|
+
return paths.configJson;
|
|
25303
25249
|
return null;
|
|
25304
25250
|
}
|
|
25305
|
-
function
|
|
25306
|
-
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
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 };
|
|
25311
25263
|
}
|
|
25312
|
-
if (
|
|
25313
|
-
return { entry
|
|
25264
|
+
if (entry.startsWith("file://") && entry.includes(PACKAGE_NAME4)) {
|
|
25265
|
+
return { entry, isLocalDev: true };
|
|
25314
25266
|
}
|
|
25315
25267
|
}
|
|
25316
25268
|
return null;
|
|
25317
25269
|
}
|
|
25318
25270
|
function getPluginInfo() {
|
|
25319
|
-
const
|
|
25320
|
-
if (!
|
|
25271
|
+
const configPath = detectConfigPath();
|
|
25272
|
+
if (!configPath) {
|
|
25321
25273
|
return {
|
|
25322
25274
|
registered: false,
|
|
25323
25275
|
configPath: null,
|
|
25324
25276
|
entry: null,
|
|
25325
25277
|
isPinned: false,
|
|
25326
|
-
pinnedVersion: null
|
|
25278
|
+
pinnedVersion: null,
|
|
25279
|
+
isLocalDev: false
|
|
25327
25280
|
};
|
|
25328
25281
|
}
|
|
25329
25282
|
try {
|
|
25330
|
-
const content = readFileSync18(
|
|
25331
|
-
const
|
|
25332
|
-
const
|
|
25333
|
-
const pluginEntry = findPluginEntry2(plugins);
|
|
25283
|
+
const content = readFileSync18(configPath, "utf-8");
|
|
25284
|
+
const parsedConfig = parseJsonc(content);
|
|
25285
|
+
const pluginEntry = findPluginEntry2(parsedConfig.plugin ?? []);
|
|
25334
25286
|
if (!pluginEntry) {
|
|
25335
25287
|
return {
|
|
25336
25288
|
registered: false,
|
|
25337
|
-
configPath
|
|
25289
|
+
configPath,
|
|
25338
25290
|
entry: null,
|
|
25339
25291
|
isPinned: false,
|
|
25340
|
-
pinnedVersion: null
|
|
25292
|
+
pinnedVersion: null,
|
|
25293
|
+
isLocalDev: false
|
|
25341
25294
|
};
|
|
25342
25295
|
}
|
|
25296
|
+
const pinnedVersion = parsePluginVersion(pluginEntry.entry);
|
|
25343
25297
|
return {
|
|
25344
25298
|
registered: true,
|
|
25345
|
-
configPath
|
|
25299
|
+
configPath,
|
|
25346
25300
|
entry: pluginEntry.entry,
|
|
25347
|
-
isPinned:
|
|
25348
|
-
pinnedVersion
|
|
25301
|
+
isPinned: pinnedVersion !== null,
|
|
25302
|
+
pinnedVersion,
|
|
25303
|
+
isLocalDev: pluginEntry.isLocalDev
|
|
25349
25304
|
};
|
|
25350
25305
|
} catch {
|
|
25351
25306
|
return {
|
|
25352
25307
|
registered: false,
|
|
25353
|
-
configPath
|
|
25308
|
+
configPath,
|
|
25354
25309
|
entry: null,
|
|
25355
25310
|
isPinned: false,
|
|
25356
|
-
pinnedVersion: null
|
|
25311
|
+
pinnedVersion: null,
|
|
25312
|
+
isLocalDev: false
|
|
25357
25313
|
};
|
|
25358
25314
|
}
|
|
25359
25315
|
}
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
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;
|
|
25384
25349
|
}
|
|
25385
|
-
const message = info.isPinned ? `Registered (pinned: ${info.pinnedVersion})` : "Registered";
|
|
25386
|
-
return {
|
|
25387
|
-
name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
|
|
25388
|
-
status: "pass",
|
|
25389
|
-
message,
|
|
25390
|
-
details: [`Config: ${info.configPath}`]
|
|
25391
|
-
};
|
|
25392
25350
|
}
|
|
25393
|
-
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);
|
|
25394
25365
|
return {
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25366
|
+
cacheDir,
|
|
25367
|
+
cachePackagePath,
|
|
25368
|
+
installedPackagePath,
|
|
25369
|
+
expectedVersion,
|
|
25370
|
+
loadedVersion
|
|
25400
25371
|
};
|
|
25401
25372
|
}
|
|
25373
|
+
async function getLatestPluginVersion(currentVersion) {
|
|
25374
|
+
const channel = extractChannel(currentVersion);
|
|
25375
|
+
return getLatestVersion(channel);
|
|
25376
|
+
}
|
|
25402
25377
|
|
|
25403
|
-
// src/cli/doctor/checks/
|
|
25404
|
-
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
25405
|
-
import { join as join13 } from "path";
|
|
25378
|
+
// src/cli/doctor/checks/system.ts
|
|
25406
25379
|
init_shared();
|
|
25407
|
-
|
|
25408
|
-
|
|
25409
|
-
|
|
25410
|
-
|
|
25411
|
-
|
|
25412
|
-
if (projectDetected.format !== "none") {
|
|
25413
|
-
return { path: projectDetected.path, format: projectDetected.format };
|
|
25414
|
-
}
|
|
25415
|
-
const userDetected = detectConfigFile(USER_CONFIG_BASE);
|
|
25416
|
-
if (userDetected.format !== "none") {
|
|
25417
|
-
return { path: userDetected.path, format: userDetected.format };
|
|
25418
|
-
}
|
|
25419
|
-
return null;
|
|
25420
|
-
}
|
|
25421
|
-
function validateConfig(configPath) {
|
|
25380
|
+
function isConfigValid(configPath) {
|
|
25381
|
+
if (!configPath)
|
|
25382
|
+
return true;
|
|
25383
|
+
if (!existsSync19(configPath))
|
|
25384
|
+
return false;
|
|
25422
25385
|
try {
|
|
25423
|
-
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
const errors3 = result.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`);
|
|
25428
|
-
return { valid: false, errors: errors3 };
|
|
25429
|
-
}
|
|
25430
|
-
return { valid: true, errors: [] };
|
|
25431
|
-
} catch (err) {
|
|
25432
|
-
return {
|
|
25433
|
-
valid: false,
|
|
25434
|
-
errors: [err instanceof Error ? err.message : "Failed to parse config"]
|
|
25435
|
-
};
|
|
25386
|
+
parseJsonc(readFileSync20(configPath, "utf-8"));
|
|
25387
|
+
return true;
|
|
25388
|
+
} catch {
|
|
25389
|
+
return false;
|
|
25436
25390
|
}
|
|
25437
25391
|
}
|
|
25438
|
-
function
|
|
25439
|
-
|
|
25440
|
-
|
|
25441
|
-
|
|
25442
|
-
|
|
25443
|
-
|
|
25444
|
-
|
|
25445
|
-
|
|
25446
|
-
|
|
25447
|
-
|
|
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
|
+
});
|
|
25448
25435
|
}
|
|
25449
|
-
if (!
|
|
25450
|
-
|
|
25451
|
-
|
|
25452
|
-
|
|
25453
|
-
|
|
25454
|
-
|
|
25455
|
-
|
|
25456
|
-
};
|
|
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
|
+
});
|
|
25457
25444
|
}
|
|
25458
|
-
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
|
|
25464
|
-
|
|
25465
|
-
|
|
25466
|
-
}
|
|
25467
|
-
async function checkConfigValidity() {
|
|
25468
|
-
const info = getConfigInfo();
|
|
25469
|
-
if (!info.exists) {
|
|
25470
|
-
return {
|
|
25471
|
-
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
25472
|
-
status: "pass",
|
|
25473
|
-
message: "Using default configuration",
|
|
25474
|
-
details: ["No custom config file found (optional)"]
|
|
25475
|
-
};
|
|
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
|
+
});
|
|
25476
25453
|
}
|
|
25477
|
-
if (
|
|
25478
|
-
|
|
25479
|
-
|
|
25480
|
-
|
|
25481
|
-
|
|
25482
|
-
|
|
25483
|
-
|
|
25484
|
-
|
|
25485
|
-
]
|
|
25486
|
-
};
|
|
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
|
+
});
|
|
25487
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);
|
|
25488
25473
|
return {
|
|
25489
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
25490
|
-
status
|
|
25491
|
-
message:
|
|
25492
|
-
details: [
|
|
25493
|
-
|
|
25494
|
-
}
|
|
25495
|
-
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
|
|
25499
|
-
category: "configuration",
|
|
25500
|
-
check: checkConfigValidity,
|
|
25501
|
-
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
|
|
25502
25484
|
};
|
|
25503
25485
|
}
|
|
25504
25486
|
|
|
25505
|
-
// src/cli/doctor/checks/
|
|
25506
|
-
|
|
25487
|
+
// src/cli/doctor/checks/config.ts
|
|
25488
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
25489
|
+
import { join as join17 } from "path";
|
|
25490
|
+
init_shared();
|
|
25507
25491
|
|
|
25508
25492
|
// src/cli/doctor/checks/model-resolution-cache.ts
|
|
25509
25493
|
init_shared();
|
|
25510
|
-
import { existsSync as
|
|
25511
|
-
import { homedir as
|
|
25494
|
+
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
|
|
25495
|
+
import { homedir as homedir7 } from "os";
|
|
25512
25496
|
import { join as join14 } from "path";
|
|
25513
25497
|
function getOpenCodeCacheDir2() {
|
|
25514
25498
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
25515
25499
|
if (xdgCache)
|
|
25516
25500
|
return join14(xdgCache, "opencode");
|
|
25517
|
-
return join14(
|
|
25501
|
+
return join14(homedir7(), ".cache", "opencode");
|
|
25518
25502
|
}
|
|
25519
25503
|
function loadAvailableModelsFromCache() {
|
|
25520
25504
|
const cacheFile = join14(getOpenCodeCacheDir2(), "models.json");
|
|
25521
|
-
if (!
|
|
25505
|
+
if (!existsSync20(cacheFile)) {
|
|
25522
25506
|
return { providers: [], modelCount: 0, cacheExists: false };
|
|
25523
25507
|
}
|
|
25524
25508
|
try {
|
|
25525
|
-
const content =
|
|
25509
|
+
const content = readFileSync21(cacheFile, "utf-8");
|
|
25526
25510
|
const data = parseJsonc(content);
|
|
25527
25511
|
const providers = Object.keys(data);
|
|
25528
25512
|
let modelCount = 0;
|
|
@@ -25538,27 +25522,30 @@ function loadAvailableModelsFromCache() {
|
|
|
25538
25522
|
}
|
|
25539
25523
|
}
|
|
25540
25524
|
|
|
25525
|
+
// src/cli/doctor/checks/model-resolution.ts
|
|
25526
|
+
init_model_requirements();
|
|
25527
|
+
|
|
25541
25528
|
// src/cli/doctor/checks/model-resolution-config.ts
|
|
25542
25529
|
init_shared();
|
|
25543
|
-
import { readFileSync as
|
|
25530
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
25544
25531
|
import { join as join15 } from "path";
|
|
25545
25532
|
var PACKAGE_NAME5 = "oh-my-opencode";
|
|
25546
|
-
var
|
|
25547
|
-
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);
|
|
25548
25535
|
function loadOmoConfig() {
|
|
25549
|
-
const projectDetected = detectConfigFile(
|
|
25536
|
+
const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE);
|
|
25550
25537
|
if (projectDetected.format !== "none") {
|
|
25551
25538
|
try {
|
|
25552
|
-
const content =
|
|
25539
|
+
const content = readFileSync22(projectDetected.path, "utf-8");
|
|
25553
25540
|
return parseJsonc(content);
|
|
25554
25541
|
} catch {
|
|
25555
25542
|
return null;
|
|
25556
25543
|
}
|
|
25557
25544
|
}
|
|
25558
|
-
const userDetected = detectConfigFile(
|
|
25545
|
+
const userDetected = detectConfigFile(USER_CONFIG_BASE);
|
|
25559
25546
|
if (userDetected.format !== "none") {
|
|
25560
25547
|
try {
|
|
25561
|
-
const content =
|
|
25548
|
+
const content = readFileSync22(userDetected.path, "utf-8");
|
|
25562
25549
|
return parseJsonc(content);
|
|
25563
25550
|
} catch {
|
|
25564
25551
|
return null;
|
|
@@ -25567,31 +25554,6 @@ function loadOmoConfig() {
|
|
|
25567
25554
|
return null;
|
|
25568
25555
|
}
|
|
25569
25556
|
|
|
25570
|
-
// src/cli/doctor/checks/model-resolution-effective-model.ts
|
|
25571
|
-
function formatProviderChain(providers) {
|
|
25572
|
-
return providers.join(" \u2192 ");
|
|
25573
|
-
}
|
|
25574
|
-
function getEffectiveModel(requirement, userOverride) {
|
|
25575
|
-
if (userOverride) {
|
|
25576
|
-
return userOverride;
|
|
25577
|
-
}
|
|
25578
|
-
const firstEntry = requirement.fallbackChain[0];
|
|
25579
|
-
if (!firstEntry) {
|
|
25580
|
-
return "unknown";
|
|
25581
|
-
}
|
|
25582
|
-
return `${firstEntry.providers[0]}/${firstEntry.model}`;
|
|
25583
|
-
}
|
|
25584
|
-
function buildEffectiveResolution(requirement, userOverride) {
|
|
25585
|
-
if (userOverride) {
|
|
25586
|
-
return `User override: ${userOverride}`;
|
|
25587
|
-
}
|
|
25588
|
-
const firstEntry = requirement.fallbackChain[0];
|
|
25589
|
-
if (!firstEntry) {
|
|
25590
|
-
return "No fallback chain defined";
|
|
25591
|
-
}
|
|
25592
|
-
return `Provider fallback: ${formatProviderChain(firstEntry.providers)} \u2192 ${firstEntry.model}`;
|
|
25593
|
-
}
|
|
25594
|
-
|
|
25595
25557
|
// src/cli/doctor/checks/model-resolution-details.ts
|
|
25596
25558
|
init_shared();
|
|
25597
25559
|
import { join as join16 } from "path";
|
|
@@ -25667,6 +25629,31 @@ function buildModelResolutionDetails(options) {
|
|
|
25667
25629
|
return details;
|
|
25668
25630
|
}
|
|
25669
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
|
+
|
|
25670
25657
|
// src/cli/doctor/checks/model-resolution.ts
|
|
25671
25658
|
function getModelResolutionInfoWithOverrides(config2) {
|
|
25672
25659
|
const agents = Object.entries(AGENT_MODEL_REQUIREMENTS).map(([name, requirement]) => {
|
|
@@ -25695,134 +25682,154 @@ function getModelResolutionInfoWithOverrides(config2) {
|
|
|
25695
25682
|
});
|
|
25696
25683
|
return { agents, categories: categories2 };
|
|
25697
25684
|
}
|
|
25698
|
-
async function
|
|
25685
|
+
async function checkModels() {
|
|
25699
25686
|
const config2 = loadOmoConfig() ?? {};
|
|
25700
25687
|
const info = getModelResolutionInfoWithOverrides(config2);
|
|
25701
25688
|
const available = loadAvailableModelsFromCache();
|
|
25702
|
-
const
|
|
25703
|
-
|
|
25704
|
-
|
|
25705
|
-
|
|
25706
|
-
|
|
25707
|
-
|
|
25708
|
-
|
|
25709
|
-
|
|
25710
|
-
|
|
25711
|
-
|
|
25712
|
-
|
|
25713
|
-
details: buildModelResolutionDetails({ info, available, config: config2 })
|
|
25714
|
-
};
|
|
25715
|
-
}
|
|
25716
|
-
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;
|
|
25717
25700
|
return {
|
|
25718
|
-
|
|
25719
|
-
|
|
25720
|
-
|
|
25721
|
-
|
|
25722
|
-
|
|
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
|
|
25723
25706
|
};
|
|
25724
25707
|
}
|
|
25725
25708
|
|
|
25726
|
-
// src/cli/doctor/checks/
|
|
25727
|
-
|
|
25728
|
-
|
|
25729
|
-
|
|
25730
|
-
|
|
25731
|
-
|
|
25732
|
-
|
|
25733
|
-
|
|
25734
|
-
|
|
25735
|
-
|
|
25736
|
-
|
|
25737
|
-
};
|
|
25738
|
-
function getOpenCodeConfig() {
|
|
25739
|
-
const configPath = existsSync20(OPENCODE_JSONC) ? OPENCODE_JSONC : OPENCODE_JSON;
|
|
25740
|
-
if (!existsSync20(configPath))
|
|
25741
|
-
return null;
|
|
25742
|
-
try {
|
|
25743
|
-
const content = readFileSync22(configPath, "utf-8");
|
|
25744
|
-
return parseJsonc(content);
|
|
25745
|
-
} catch {
|
|
25746
|
-
return null;
|
|
25747
|
-
}
|
|
25748
|
-
}
|
|
25749
|
-
function isPluginInstalled(plugins, pluginName) {
|
|
25750
|
-
if (pluginName === "builtin")
|
|
25751
|
-
return true;
|
|
25752
|
-
return plugins.some((p2) => p2 === pluginName || p2.startsWith(`${pluginName}@`));
|
|
25753
|
-
}
|
|
25754
|
-
function getAuthProviderInfo(providerId) {
|
|
25755
|
-
const config2 = getOpenCodeConfig();
|
|
25756
|
-
const plugins = config2?.plugin ?? [];
|
|
25757
|
-
const authConfig = AUTH_PLUGINS[providerId];
|
|
25758
|
-
const pluginInstalled = isPluginInstalled(plugins, authConfig.plugin);
|
|
25759
|
-
return {
|
|
25760
|
-
id: providerId,
|
|
25761
|
-
name: authConfig.name,
|
|
25762
|
-
pluginInstalled,
|
|
25763
|
-
configured: pluginInstalled
|
|
25764
|
-
};
|
|
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;
|
|
25765
25720
|
}
|
|
25766
|
-
|
|
25767
|
-
const
|
|
25768
|
-
|
|
25769
|
-
|
|
25770
|
-
|
|
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) {
|
|
25771
25741
|
return {
|
|
25772
|
-
|
|
25773
|
-
|
|
25774
|
-
|
|
25775
|
-
|
|
25776
|
-
|
|
25777
|
-
"Run: bunx oh-my-opencode install"
|
|
25778
|
-
]
|
|
25742
|
+
exists: true,
|
|
25743
|
+
path: configPath,
|
|
25744
|
+
valid: false,
|
|
25745
|
+
config: null,
|
|
25746
|
+
errors: [error45 instanceof Error ? error45.message : "Failed to parse config"]
|
|
25779
25747
|
};
|
|
25780
25748
|
}
|
|
25781
|
-
return {
|
|
25782
|
-
name: checkName,
|
|
25783
|
-
status: "pass",
|
|
25784
|
-
message: "Auth plugin available",
|
|
25785
|
-
details: [
|
|
25786
|
-
providerId === "anthropic" ? "Run: opencode auth login (select Anthropic)" : `Plugin: ${AUTH_PLUGINS[providerId].plugin}`
|
|
25787
|
-
]
|
|
25788
|
-
};
|
|
25789
|
-
}
|
|
25790
|
-
async function checkAnthropicAuth() {
|
|
25791
|
-
return checkAuthProvider("anthropic");
|
|
25792
|
-
}
|
|
25793
|
-
async function checkOpenAIAuth() {
|
|
25794
|
-
return checkAuthProvider("openai");
|
|
25795
|
-
}
|
|
25796
|
-
async function checkGoogleAuth() {
|
|
25797
|
-
return checkAuthProvider("google");
|
|
25798
25749
|
}
|
|
25799
|
-
function
|
|
25800
|
-
|
|
25801
|
-
|
|
25802
|
-
|
|
25803
|
-
|
|
25804
|
-
|
|
25805
|
-
|
|
25806
|
-
|
|
25807
|
-
|
|
25808
|
-
|
|
25809
|
-
|
|
25810
|
-
|
|
25811
|
-
|
|
25812
|
-
|
|
25813
|
-
|
|
25814
|
-
|
|
25815
|
-
|
|
25816
|
-
|
|
25817
|
-
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
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
|
+
});
|
|
25821
25786
|
}
|
|
25822
|
-
|
|
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
|
+
};
|
|
25823
25827
|
}
|
|
25824
25828
|
|
|
25825
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";
|
|
25826
25833
|
async function checkBinaryExists(binary2) {
|
|
25827
25834
|
try {
|
|
25828
25835
|
const path9 = Bun.which(binary2);
|
|
@@ -25878,15 +25885,15 @@ async function checkAstGrepNapi() {
|
|
|
25878
25885
|
path: null
|
|
25879
25886
|
};
|
|
25880
25887
|
} catch {
|
|
25881
|
-
const { existsSync:
|
|
25882
|
-
const { join:
|
|
25883
|
-
const { homedir:
|
|
25888
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
25889
|
+
const { join: join19 } = await import("path");
|
|
25890
|
+
const { homedir: homedir8 } = await import("os");
|
|
25884
25891
|
const pathsToCheck = [
|
|
25885
|
-
|
|
25886
|
-
|
|
25892
|
+
join19(homedir8(), ".config", "opencode", "node_modules", "@ast-grep", "napi"),
|
|
25893
|
+
join19(process.cwd(), "node_modules", "@ast-grep", "napi")
|
|
25887
25894
|
];
|
|
25888
25895
|
for (const napiPath of pathsToCheck) {
|
|
25889
|
-
if (
|
|
25896
|
+
if (existsSync22(napiPath)) {
|
|
25890
25897
|
return {
|
|
25891
25898
|
name: "AST-Grep NAPI",
|
|
25892
25899
|
required: false,
|
|
@@ -25906,9 +25913,21 @@ async function checkAstGrepNapi() {
|
|
|
25906
25913
|
};
|
|
25907
25914
|
}
|
|
25908
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
|
+
}
|
|
25909
25927
|
async function checkCommentChecker() {
|
|
25910
25928
|
const binaryCheck = await checkBinaryExists("comment-checker");
|
|
25911
|
-
|
|
25929
|
+
const resolvedPath = binaryCheck.exists ? binaryCheck.path : findCommentCheckerPackageBinary();
|
|
25930
|
+
if (!resolvedPath) {
|
|
25912
25931
|
return {
|
|
25913
25932
|
name: "Comment Checker",
|
|
25914
25933
|
required: false,
|
|
@@ -25918,112 +25937,59 @@ async function checkCommentChecker() {
|
|
|
25918
25937
|
installHint: "Hook will be disabled if not available"
|
|
25919
25938
|
};
|
|
25920
25939
|
}
|
|
25921
|
-
const version2 = await getBinaryVersion(
|
|
25940
|
+
const version2 = await getBinaryVersion(resolvedPath);
|
|
25922
25941
|
return {
|
|
25923
25942
|
name: "Comment Checker",
|
|
25924
25943
|
required: false,
|
|
25925
25944
|
installed: true,
|
|
25926
25945
|
version: version2,
|
|
25927
|
-
path:
|
|
25928
|
-
};
|
|
25929
|
-
}
|
|
25930
|
-
function dependencyToCheckResult(dep, checkName) {
|
|
25931
|
-
if (dep.installed) {
|
|
25932
|
-
return {
|
|
25933
|
-
name: checkName,
|
|
25934
|
-
status: "pass",
|
|
25935
|
-
message: dep.version ?? "installed",
|
|
25936
|
-
details: dep.path ? [`Path: ${dep.path}`] : undefined
|
|
25937
|
-
};
|
|
25938
|
-
}
|
|
25939
|
-
return {
|
|
25940
|
-
name: checkName,
|
|
25941
|
-
status: "warn",
|
|
25942
|
-
message: "Not installed (optional)",
|
|
25943
|
-
details: dep.installHint ? [dep.installHint] : undefined
|
|
25946
|
+
path: resolvedPath
|
|
25944
25947
|
};
|
|
25945
25948
|
}
|
|
25946
|
-
async function checkDependencyAstGrepCli() {
|
|
25947
|
-
const info = await checkAstGrepCli();
|
|
25948
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI]);
|
|
25949
|
-
}
|
|
25950
|
-
async function checkDependencyAstGrepNapi() {
|
|
25951
|
-
const info = await checkAstGrepNapi();
|
|
25952
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI]);
|
|
25953
|
-
}
|
|
25954
|
-
async function checkDependencyCommentChecker() {
|
|
25955
|
-
const info = await checkCommentChecker();
|
|
25956
|
-
return dependencyToCheckResult(info, CHECK_NAMES[CHECK_IDS.DEP_COMMENT_CHECKER]);
|
|
25957
|
-
}
|
|
25958
|
-
function getDependencyCheckDefinitions() {
|
|
25959
|
-
return [
|
|
25960
|
-
{
|
|
25961
|
-
id: CHECK_IDS.DEP_AST_GREP_CLI,
|
|
25962
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_CLI],
|
|
25963
|
-
category: "dependencies",
|
|
25964
|
-
check: checkDependencyAstGrepCli,
|
|
25965
|
-
critical: false
|
|
25966
|
-
},
|
|
25967
|
-
{
|
|
25968
|
-
id: CHECK_IDS.DEP_AST_GREP_NAPI,
|
|
25969
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_AST_GREP_NAPI],
|
|
25970
|
-
category: "dependencies",
|
|
25971
|
-
check: checkDependencyAstGrepNapi,
|
|
25972
|
-
critical: false
|
|
25973
|
-
},
|
|
25974
|
-
{
|
|
25975
|
-
id: CHECK_IDS.DEP_COMMENT_CHECKER,
|
|
25976
|
-
name: CHECK_NAMES[CHECK_IDS.DEP_COMMENT_CHECKER],
|
|
25977
|
-
category: "dependencies",
|
|
25978
|
-
check: checkDependencyCommentChecker,
|
|
25979
|
-
critical: false
|
|
25980
|
-
}
|
|
25981
|
-
];
|
|
25982
|
-
}
|
|
25983
25949
|
|
|
25984
|
-
// src/cli/doctor/checks/gh.ts
|
|
25950
|
+
// src/cli/doctor/checks/tools-gh.ts
|
|
25985
25951
|
async function checkBinaryExists2(binary2) {
|
|
25986
25952
|
try {
|
|
25987
|
-
const
|
|
25988
|
-
|
|
25989
|
-
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
return { exists: true, path: output.trim() };
|
|
25993
|
-
}
|
|
25994
|
-
} catch {}
|
|
25995
|
-
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
|
+
}
|
|
25996
25958
|
}
|
|
25997
25959
|
async function getGhVersion() {
|
|
25998
25960
|
try {
|
|
25999
|
-
const
|
|
26000
|
-
const output = await new Response(
|
|
26001
|
-
await
|
|
26002
|
-
if (
|
|
26003
|
-
|
|
26004
|
-
|
|
26005
|
-
|
|
26006
|
-
|
|
26007
|
-
} catch {
|
|
26008
|
-
|
|
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
|
+
}
|
|
26009
25972
|
}
|
|
26010
25973
|
async function getGhAuthStatus() {
|
|
26011
25974
|
try {
|
|
26012
|
-
const
|
|
25975
|
+
const processResult = Bun.spawn(["gh", "auth", "status"], {
|
|
26013
25976
|
stdout: "pipe",
|
|
26014
25977
|
stderr: "pipe",
|
|
26015
25978
|
env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" }
|
|
26016
25979
|
});
|
|
26017
|
-
const stdout = await new Response(
|
|
26018
|
-
const stderr = await new Response(
|
|
26019
|
-
await
|
|
25980
|
+
const stdout = await new Response(processResult.stdout).text();
|
|
25981
|
+
const stderr = await new Response(processResult.stderr).text();
|
|
25982
|
+
await processResult.exited;
|
|
26020
25983
|
const output = stderr || stdout;
|
|
26021
|
-
if (
|
|
25984
|
+
if (processResult.exitCode === 0) {
|
|
26022
25985
|
const usernameMatch = output.match(/Logged in to github\.com account (\S+)/);
|
|
26023
|
-
const username = usernameMatch?.[1]?.replace(/[()]/g, "") ?? null;
|
|
26024
25986
|
const scopesMatch = output.match(/Token scopes?:\s*(.+)/i);
|
|
26025
|
-
|
|
26026
|
-
|
|
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
|
+
};
|
|
26027
25993
|
}
|
|
26028
25994
|
const errorMatch = output.match(/error[:\s]+(.+)/i);
|
|
26029
25995
|
return {
|
|
@@ -26032,18 +25998,18 @@ async function getGhAuthStatus() {
|
|
|
26032
25998
|
scopes: [],
|
|
26033
25999
|
error: errorMatch?.[1]?.trim() ?? "Not authenticated"
|
|
26034
26000
|
};
|
|
26035
|
-
} catch (
|
|
26001
|
+
} catch (error45) {
|
|
26036
26002
|
return {
|
|
26037
26003
|
authenticated: false,
|
|
26038
26004
|
username: null,
|
|
26039
26005
|
scopes: [],
|
|
26040
|
-
error:
|
|
26006
|
+
error: error45 instanceof Error ? error45.message : "Failed to check auth status"
|
|
26041
26007
|
};
|
|
26042
26008
|
}
|
|
26043
26009
|
}
|
|
26044
26010
|
async function getGhCliInfo() {
|
|
26045
|
-
const
|
|
26046
|
-
if (!
|
|
26011
|
+
const binaryStatus = await checkBinaryExists2("gh");
|
|
26012
|
+
if (!binaryStatus.exists) {
|
|
26047
26013
|
return {
|
|
26048
26014
|
installed: false,
|
|
26049
26015
|
version: null,
|
|
@@ -26058,76 +26024,27 @@ async function getGhCliInfo() {
|
|
|
26058
26024
|
return {
|
|
26059
26025
|
installed: true,
|
|
26060
26026
|
version: version2,
|
|
26061
|
-
path:
|
|
26027
|
+
path: binaryStatus.path,
|
|
26062
26028
|
authenticated: authStatus.authenticated,
|
|
26063
26029
|
username: authStatus.username,
|
|
26064
26030
|
scopes: authStatus.scopes,
|
|
26065
26031
|
error: authStatus.error
|
|
26066
26032
|
};
|
|
26067
26033
|
}
|
|
26068
|
-
async function checkGhCli() {
|
|
26069
|
-
const info = await getGhCliInfo();
|
|
26070
|
-
const name = CHECK_NAMES[CHECK_IDS.GH_CLI];
|
|
26071
|
-
if (!info.installed) {
|
|
26072
|
-
return {
|
|
26073
|
-
name,
|
|
26074
|
-
status: "warn",
|
|
26075
|
-
message: "Not installed (optional)",
|
|
26076
|
-
details: [
|
|
26077
|
-
"GitHub CLI is used by librarian agent and scripts",
|
|
26078
|
-
"Install: https://cli.github.com/"
|
|
26079
|
-
]
|
|
26080
|
-
};
|
|
26081
|
-
}
|
|
26082
|
-
if (!info.authenticated) {
|
|
26083
|
-
return {
|
|
26084
|
-
name,
|
|
26085
|
-
status: "warn",
|
|
26086
|
-
message: `${info.version ?? "installed"} - not authenticated`,
|
|
26087
|
-
details: [
|
|
26088
|
-
info.path ? `Path: ${info.path}` : null,
|
|
26089
|
-
"Authenticate: gh auth login",
|
|
26090
|
-
info.error ? `Error: ${info.error}` : null
|
|
26091
|
-
].filter((d3) => d3 !== null)
|
|
26092
|
-
};
|
|
26093
|
-
}
|
|
26094
|
-
const details = [];
|
|
26095
|
-
if (info.path)
|
|
26096
|
-
details.push(`Path: ${info.path}`);
|
|
26097
|
-
if (info.username)
|
|
26098
|
-
details.push(`Account: ${info.username}`);
|
|
26099
|
-
if (info.scopes.length > 0)
|
|
26100
|
-
details.push(`Scopes: ${info.scopes.join(", ")}`);
|
|
26101
|
-
return {
|
|
26102
|
-
name,
|
|
26103
|
-
status: "pass",
|
|
26104
|
-
message: `${info.version ?? "installed"} - authenticated as ${info.username ?? "unknown"}`,
|
|
26105
|
-
details: details.length > 0 ? details : undefined
|
|
26106
|
-
};
|
|
26107
|
-
}
|
|
26108
|
-
function getGhCliCheckDefinition() {
|
|
26109
|
-
return {
|
|
26110
|
-
id: CHECK_IDS.GH_CLI,
|
|
26111
|
-
name: CHECK_NAMES[CHECK_IDS.GH_CLI],
|
|
26112
|
-
category: "tools",
|
|
26113
|
-
check: checkGhCli,
|
|
26114
|
-
critical: false
|
|
26115
|
-
};
|
|
26116
|
-
}
|
|
26117
26034
|
// src/tools/lsp/server-config-loader.ts
|
|
26118
26035
|
init_shared();
|
|
26119
26036
|
init_jsonc_parser();
|
|
26120
26037
|
|
|
26121
26038
|
// src/tools/lsp/server-installation.ts
|
|
26122
26039
|
init_shared();
|
|
26123
|
-
import { existsSync as
|
|
26124
|
-
import { join as
|
|
26040
|
+
import { existsSync as existsSync22 } from "fs";
|
|
26041
|
+
import { join as join19 } from "path";
|
|
26125
26042
|
function isServerInstalled(command) {
|
|
26126
26043
|
if (command.length === 0)
|
|
26127
26044
|
return false;
|
|
26128
26045
|
const cmd = command[0];
|
|
26129
26046
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
26130
|
-
if (
|
|
26047
|
+
if (existsSync22(cmd))
|
|
26131
26048
|
return true;
|
|
26132
26049
|
}
|
|
26133
26050
|
const isWindows = process.platform === "win32";
|
|
@@ -26149,23 +26066,23 @@ function isServerInstalled(command) {
|
|
|
26149
26066
|
const paths = pathEnv.split(pathSeparator);
|
|
26150
26067
|
for (const p2 of paths) {
|
|
26151
26068
|
for (const suffix of exts) {
|
|
26152
|
-
if (
|
|
26069
|
+
if (existsSync22(join19(p2, cmd + suffix))) {
|
|
26153
26070
|
return true;
|
|
26154
26071
|
}
|
|
26155
26072
|
}
|
|
26156
26073
|
}
|
|
26157
26074
|
const cwd = process.cwd();
|
|
26158
26075
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
26159
|
-
const dataDir =
|
|
26076
|
+
const dataDir = join19(getDataDir(), "opencode");
|
|
26160
26077
|
const additionalBases = [
|
|
26161
|
-
|
|
26162
|
-
|
|
26163
|
-
|
|
26164
|
-
|
|
26078
|
+
join19(cwd, "node_modules", ".bin"),
|
|
26079
|
+
join19(configDir, "bin"),
|
|
26080
|
+
join19(configDir, "node_modules", ".bin"),
|
|
26081
|
+
join19(dataDir, "bin")
|
|
26165
26082
|
];
|
|
26166
26083
|
for (const base of additionalBases) {
|
|
26167
26084
|
for (const suffix of exts) {
|
|
26168
|
-
if (
|
|
26085
|
+
if (existsSync22(join19(base, cmd + suffix))) {
|
|
26169
26086
|
return true;
|
|
26170
26087
|
}
|
|
26171
26088
|
}
|
|
@@ -26175,180 +26092,454 @@ function isServerInstalled(command) {
|
|
|
26175
26092
|
}
|
|
26176
26093
|
return false;
|
|
26177
26094
|
}
|
|
26178
|
-
// src/cli/doctor/checks/lsp.ts
|
|
26095
|
+
// src/cli/doctor/checks/tools-lsp.ts
|
|
26179
26096
|
var DEFAULT_LSP_SERVERS = [
|
|
26180
26097
|
{ id: "typescript-language-server", binary: "typescript-language-server", extensions: [".ts", ".tsx", ".js", ".jsx"] },
|
|
26181
26098
|
{ id: "pyright", binary: "pyright-langserver", extensions: [".py"] },
|
|
26182
26099
|
{ id: "rust-analyzer", binary: "rust-analyzer", extensions: [".rs"] },
|
|
26183
26100
|
{ id: "gopls", binary: "gopls", extensions: [".go"] }
|
|
26184
26101
|
];
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
|
|
26192
|
-
extensions: server2.extensions,
|
|
26193
|
-
source: "builtin"
|
|
26194
|
-
});
|
|
26195
|
-
}
|
|
26196
|
-
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
|
+
}));
|
|
26197
26109
|
}
|
|
26198
26110
|
function getLspServerStats(servers) {
|
|
26199
|
-
const installed = servers.filter((s) => s.installed).length;
|
|
26200
|
-
return { installed, total: servers.length };
|
|
26201
|
-
}
|
|
26202
|
-
async function checkLspServers() {
|
|
26203
|
-
const servers = await getLspServersInfo();
|
|
26204
|
-
const stats = getLspServerStats(servers);
|
|
26205
|
-
const installedServers = servers.filter((s) => s.installed);
|
|
26206
|
-
const missingServers = servers.filter((s) => !s.installed);
|
|
26207
|
-
if (stats.installed === 0) {
|
|
26208
|
-
return {
|
|
26209
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
26210
|
-
status: "warn",
|
|
26211
|
-
message: "No LSP servers detected",
|
|
26212
|
-
details: [
|
|
26213
|
-
"LSP tools will have limited functionality",
|
|
26214
|
-
...missingServers.map((s) => `Missing: ${s.id}`)
|
|
26215
|
-
]
|
|
26216
|
-
};
|
|
26217
|
-
}
|
|
26218
|
-
const details = [
|
|
26219
|
-
...installedServers.map((s) => `Installed: ${s.id}`),
|
|
26220
|
-
...missingServers.map((s) => `Not found: ${s.id} (optional)`)
|
|
26221
|
-
];
|
|
26222
|
-
return {
|
|
26223
|
-
name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
|
|
26224
|
-
status: "pass",
|
|
26225
|
-
message: `${stats.installed}/${stats.total} servers available`,
|
|
26226
|
-
details
|
|
26227
|
-
};
|
|
26228
|
-
}
|
|
26229
|
-
function getLspCheckDefinition() {
|
|
26230
26111
|
return {
|
|
26231
|
-
|
|
26232
|
-
|
|
26233
|
-
category: "tools",
|
|
26234
|
-
check: checkLspServers,
|
|
26235
|
-
critical: false
|
|
26112
|
+
installed: servers.filter((server2) => server2.installed).length,
|
|
26113
|
+
total: servers.length
|
|
26236
26114
|
};
|
|
26237
26115
|
}
|
|
26238
26116
|
|
|
26239
|
-
// src/cli/doctor/checks/mcp.ts
|
|
26240
|
-
import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
|
|
26241
|
-
import { homedir as homedir7 } from "os";
|
|
26242
|
-
import { join as join19 } from "path";
|
|
26117
|
+
// src/cli/doctor/checks/tools-mcp.ts
|
|
26243
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";
|
|
26244
26122
|
var BUILTIN_MCP_SERVERS = ["context7", "grep_app"];
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
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
|
+
}
|
|
26250
26130
|
function loadUserMcpConfig() {
|
|
26251
26131
|
const servers = {};
|
|
26252
|
-
for (const configPath of
|
|
26253
|
-
if (!
|
|
26132
|
+
for (const configPath of getMcpConfigPaths()) {
|
|
26133
|
+
if (!existsSync23(configPath))
|
|
26254
26134
|
continue;
|
|
26255
26135
|
try {
|
|
26256
|
-
const content =
|
|
26136
|
+
const content = readFileSync24(configPath, "utf-8");
|
|
26257
26137
|
const config2 = parseJsonc(content);
|
|
26258
26138
|
if (config2.mcpServers) {
|
|
26259
26139
|
Object.assign(servers, config2.mcpServers);
|
|
26260
26140
|
}
|
|
26261
|
-
} catch {
|
|
26141
|
+
} catch {
|
|
26142
|
+
continue;
|
|
26143
|
+
}
|
|
26262
26144
|
}
|
|
26263
26145
|
return servers;
|
|
26264
26146
|
}
|
|
26265
26147
|
function getBuiltinMcpInfo() {
|
|
26266
|
-
return BUILTIN_MCP_SERVERS.map((
|
|
26267
|
-
id,
|
|
26148
|
+
return BUILTIN_MCP_SERVERS.map((serverId) => ({
|
|
26149
|
+
id: serverId,
|
|
26268
26150
|
type: "builtin",
|
|
26269
26151
|
enabled: true,
|
|
26270
26152
|
valid: true
|
|
26271
26153
|
}));
|
|
26272
26154
|
}
|
|
26273
26155
|
function getUserMcpInfo() {
|
|
26274
|
-
|
|
26275
|
-
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
servers.push({
|
|
26279
|
-
id,
|
|
26156
|
+
return Object.entries(loadUserMcpConfig()).map(([serverId, value]) => {
|
|
26157
|
+
const valid = typeof value === "object" && value !== null;
|
|
26158
|
+
return {
|
|
26159
|
+
id: serverId,
|
|
26280
26160
|
type: "user",
|
|
26281
26161
|
enabled: true,
|
|
26282
|
-
valid
|
|
26283
|
-
error:
|
|
26284
|
-
}
|
|
26285
|
-
}
|
|
26286
|
-
return servers;
|
|
26162
|
+
valid,
|
|
26163
|
+
error: valid ? undefined : "Invalid configuration format"
|
|
26164
|
+
};
|
|
26165
|
+
});
|
|
26287
26166
|
}
|
|
26288
|
-
|
|
26289
|
-
|
|
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();
|
|
26290
26180
|
return {
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26299
|
-
|
|
26300
|
-
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
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
|
+
});
|
|
26306
26205
|
}
|
|
26307
|
-
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
26315
|
-
|
|
26316
|
-
|
|
26317
|
-
|
|
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
|
+
});
|
|
26318
26254
|
}
|
|
26319
26255
|
return {
|
|
26320
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26321
|
-
status: "pass",
|
|
26322
|
-
message: `${
|
|
26323
|
-
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
|
|
26324
26267
|
};
|
|
26325
26268
|
}
|
|
26326
|
-
|
|
26269
|
+
// src/cli/doctor/checks/index.ts
|
|
26270
|
+
function getAllCheckDefinitions() {
|
|
26327
26271
|
return [
|
|
26328
26272
|
{
|
|
26329
|
-
id: CHECK_IDS.
|
|
26330
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
|
|
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
|
|
26334
26282
|
},
|
|
26335
26283
|
{
|
|
26336
|
-
id: CHECK_IDS.
|
|
26337
|
-
name: CHECK_NAMES[CHECK_IDS.
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26284
|
+
id: CHECK_IDS.TOOLS,
|
|
26285
|
+
name: CHECK_NAMES[CHECK_IDS.TOOLS],
|
|
26286
|
+
check: checkTools
|
|
26287
|
+
},
|
|
26288
|
+
{
|
|
26289
|
+
id: CHECK_IDS.MODELS,
|
|
26290
|
+
name: CHECK_NAMES[CHECK_IDS.MODELS],
|
|
26291
|
+
check: checkModels
|
|
26341
26292
|
}
|
|
26342
26293
|
];
|
|
26343
26294
|
}
|
|
26344
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
|
+
|
|
26345
26536
|
// src/features/mcp-oauth/storage.ts
|
|
26346
26537
|
init_shared();
|
|
26347
|
-
import { chmodSync, existsSync as
|
|
26348
|
-
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";
|
|
26349
26540
|
var STORAGE_FILE_NAME = "mcp-oauth.json";
|
|
26350
26541
|
function getMcpOauthStoragePath() {
|
|
26351
|
-
return
|
|
26542
|
+
return join21(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
|
|
26352
26543
|
}
|
|
26353
26544
|
function normalizeHost(serverHost) {
|
|
26354
26545
|
let host = serverHost.trim();
|
|
@@ -26385,11 +26576,11 @@ function buildKey(serverHost, resource) {
|
|
|
26385
26576
|
}
|
|
26386
26577
|
function readStore() {
|
|
26387
26578
|
const filePath = getMcpOauthStoragePath();
|
|
26388
|
-
if (!
|
|
26579
|
+
if (!existsSync24(filePath)) {
|
|
26389
26580
|
return null;
|
|
26390
26581
|
}
|
|
26391
26582
|
try {
|
|
26392
|
-
const content =
|
|
26583
|
+
const content = readFileSync25(filePath, "utf-8");
|
|
26393
26584
|
return JSON.parse(content);
|
|
26394
26585
|
} catch {
|
|
26395
26586
|
return null;
|
|
@@ -26398,8 +26589,8 @@ function readStore() {
|
|
|
26398
26589
|
function writeStore(store) {
|
|
26399
26590
|
const filePath = getMcpOauthStoragePath();
|
|
26400
26591
|
try {
|
|
26401
|
-
const dir =
|
|
26402
|
-
if (!
|
|
26592
|
+
const dir = dirname4(filePath);
|
|
26593
|
+
if (!existsSync24(dir)) {
|
|
26403
26594
|
mkdirSync3(dir, { recursive: true });
|
|
26404
26595
|
}
|
|
26405
26596
|
writeFileSync9(filePath, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
|
|
@@ -26434,7 +26625,7 @@ function deleteToken(serverHost, resource) {
|
|
|
26434
26625
|
if (Object.keys(store).length === 0) {
|
|
26435
26626
|
try {
|
|
26436
26627
|
const filePath = getMcpOauthStoragePath();
|
|
26437
|
-
if (
|
|
26628
|
+
if (existsSync24(filePath)) {
|
|
26438
26629
|
unlinkSync(filePath);
|
|
26439
26630
|
}
|
|
26440
26631
|
return true;
|
|
@@ -26462,368 +26653,6 @@ function listAllTokens() {
|
|
|
26462
26653
|
return readStore() ?? {};
|
|
26463
26654
|
}
|
|
26464
26655
|
|
|
26465
|
-
// src/cli/doctor/checks/mcp-oauth.ts
|
|
26466
|
-
import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
|
|
26467
|
-
function readTokenStore() {
|
|
26468
|
-
const filePath = getMcpOauthStoragePath();
|
|
26469
|
-
if (!existsSync24(filePath)) {
|
|
26470
|
-
return null;
|
|
26471
|
-
}
|
|
26472
|
-
try {
|
|
26473
|
-
const content = readFileSync25(filePath, "utf-8");
|
|
26474
|
-
return JSON.parse(content);
|
|
26475
|
-
} catch {
|
|
26476
|
-
return null;
|
|
26477
|
-
}
|
|
26478
|
-
}
|
|
26479
|
-
async function checkMcpOAuthTokens() {
|
|
26480
|
-
const store = readTokenStore();
|
|
26481
|
-
if (!store || Object.keys(store).length === 0) {
|
|
26482
|
-
return {
|
|
26483
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26484
|
-
status: "skip",
|
|
26485
|
-
message: "No OAuth tokens configured",
|
|
26486
|
-
details: ["Optional: Configure OAuth tokens for MCP servers"]
|
|
26487
|
-
};
|
|
26488
|
-
}
|
|
26489
|
-
const now = Math.floor(Date.now() / 1000);
|
|
26490
|
-
const tokens = Object.entries(store);
|
|
26491
|
-
const expiredTokens = tokens.filter(([, token]) => token.expiresAt && token.expiresAt < now);
|
|
26492
|
-
if (expiredTokens.length > 0) {
|
|
26493
|
-
return {
|
|
26494
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26495
|
-
status: "warn",
|
|
26496
|
-
message: `${expiredTokens.length} of ${tokens.length} token(s) expired`,
|
|
26497
|
-
details: [
|
|
26498
|
-
...tokens.filter(([, token]) => !token.expiresAt || token.expiresAt >= now).map(([key]) => `Valid: ${key}`),
|
|
26499
|
-
...expiredTokens.map(([key]) => `Expired: ${key}`)
|
|
26500
|
-
]
|
|
26501
|
-
};
|
|
26502
|
-
}
|
|
26503
|
-
return {
|
|
26504
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26505
|
-
status: "pass",
|
|
26506
|
-
message: `${tokens.length} OAuth token(s) valid`,
|
|
26507
|
-
details: tokens.map(([key]) => `Configured: ${key}`)
|
|
26508
|
-
};
|
|
26509
|
-
}
|
|
26510
|
-
function getMcpOAuthCheckDefinition() {
|
|
26511
|
-
return {
|
|
26512
|
-
id: CHECK_IDS.MCP_OAUTH_TOKENS,
|
|
26513
|
-
name: CHECK_NAMES[CHECK_IDS.MCP_OAUTH_TOKENS],
|
|
26514
|
-
category: "tools",
|
|
26515
|
-
check: checkMcpOAuthTokens,
|
|
26516
|
-
critical: false
|
|
26517
|
-
};
|
|
26518
|
-
}
|
|
26519
|
-
|
|
26520
|
-
// src/cli/doctor/checks/version.ts
|
|
26521
|
-
init_checker();
|
|
26522
|
-
function compareVersions2(current, latest) {
|
|
26523
|
-
const parseVersion = (v) => {
|
|
26524
|
-
const cleaned = v.replace(/^v/, "").split("-")[0];
|
|
26525
|
-
return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
|
|
26526
|
-
};
|
|
26527
|
-
const curr = parseVersion(current);
|
|
26528
|
-
const lat = parseVersion(latest);
|
|
26529
|
-
for (let i2 = 0;i2 < Math.max(curr.length, lat.length); i2++) {
|
|
26530
|
-
const c = curr[i2] ?? 0;
|
|
26531
|
-
const l2 = lat[i2] ?? 0;
|
|
26532
|
-
if (c < l2)
|
|
26533
|
-
return false;
|
|
26534
|
-
if (c > l2)
|
|
26535
|
-
return true;
|
|
26536
|
-
}
|
|
26537
|
-
return true;
|
|
26538
|
-
}
|
|
26539
|
-
async function getVersionInfo() {
|
|
26540
|
-
const cwd = process.cwd();
|
|
26541
|
-
if (isLocalDevMode(cwd)) {
|
|
26542
|
-
return {
|
|
26543
|
-
currentVersion: "local-dev",
|
|
26544
|
-
latestVersion: null,
|
|
26545
|
-
isUpToDate: true,
|
|
26546
|
-
isLocalDev: true,
|
|
26547
|
-
isPinned: false
|
|
26548
|
-
};
|
|
26549
|
-
}
|
|
26550
|
-
const pluginInfo = findPluginEntry(cwd);
|
|
26551
|
-
if (pluginInfo?.isPinned) {
|
|
26552
|
-
return {
|
|
26553
|
-
currentVersion: pluginInfo.pinnedVersion,
|
|
26554
|
-
latestVersion: null,
|
|
26555
|
-
isUpToDate: true,
|
|
26556
|
-
isLocalDev: false,
|
|
26557
|
-
isPinned: true
|
|
26558
|
-
};
|
|
26559
|
-
}
|
|
26560
|
-
const currentVersion = getCachedVersion();
|
|
26561
|
-
const { extractChannel: extractChannel2 } = await Promise.resolve().then(() => (init_auto_update_checker(), exports_auto_update_checker));
|
|
26562
|
-
const channel = extractChannel2(pluginInfo?.pinnedVersion ?? currentVersion);
|
|
26563
|
-
const latestVersion = await getLatestVersion(channel);
|
|
26564
|
-
const isUpToDate = !currentVersion || !latestVersion || compareVersions2(currentVersion, latestVersion);
|
|
26565
|
-
return {
|
|
26566
|
-
currentVersion,
|
|
26567
|
-
latestVersion,
|
|
26568
|
-
isUpToDate,
|
|
26569
|
-
isLocalDev: false,
|
|
26570
|
-
isPinned: false
|
|
26571
|
-
};
|
|
26572
|
-
}
|
|
26573
|
-
async function checkVersionStatus() {
|
|
26574
|
-
const info = await getVersionInfo();
|
|
26575
|
-
if (info.isLocalDev) {
|
|
26576
|
-
return {
|
|
26577
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26578
|
-
status: "pass",
|
|
26579
|
-
message: "Running in local development mode",
|
|
26580
|
-
details: ["Using file:// protocol from config"]
|
|
26581
|
-
};
|
|
26582
|
-
}
|
|
26583
|
-
if (info.isPinned) {
|
|
26584
|
-
return {
|
|
26585
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26586
|
-
status: "pass",
|
|
26587
|
-
message: `Pinned to version ${info.currentVersion}`,
|
|
26588
|
-
details: ["Update check skipped for pinned versions"]
|
|
26589
|
-
};
|
|
26590
|
-
}
|
|
26591
|
-
if (!info.currentVersion) {
|
|
26592
|
-
return {
|
|
26593
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26594
|
-
status: "warn",
|
|
26595
|
-
message: "Unable to determine current version",
|
|
26596
|
-
details: ["Run: bunx oh-my-opencode get-local-version"]
|
|
26597
|
-
};
|
|
26598
|
-
}
|
|
26599
|
-
if (!info.latestVersion) {
|
|
26600
|
-
return {
|
|
26601
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26602
|
-
status: "warn",
|
|
26603
|
-
message: `Current: ${info.currentVersion}`,
|
|
26604
|
-
details: ["Unable to check for updates (network error)"]
|
|
26605
|
-
};
|
|
26606
|
-
}
|
|
26607
|
-
if (!info.isUpToDate) {
|
|
26608
|
-
return {
|
|
26609
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26610
|
-
status: "warn",
|
|
26611
|
-
message: `Update available: ${info.currentVersion} -> ${info.latestVersion}`,
|
|
26612
|
-
details: ["Run: cd ~/.config/opencode && bun update oh-my-opencode"]
|
|
26613
|
-
};
|
|
26614
|
-
}
|
|
26615
|
-
return {
|
|
26616
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26617
|
-
status: "pass",
|
|
26618
|
-
message: `Up to date (${info.currentVersion})`,
|
|
26619
|
-
details: info.latestVersion ? [`Latest: ${info.latestVersion}`] : undefined
|
|
26620
|
-
};
|
|
26621
|
-
}
|
|
26622
|
-
function getVersionCheckDefinition() {
|
|
26623
|
-
return {
|
|
26624
|
-
id: CHECK_IDS.VERSION_STATUS,
|
|
26625
|
-
name: CHECK_NAMES[CHECK_IDS.VERSION_STATUS],
|
|
26626
|
-
category: "updates",
|
|
26627
|
-
check: checkVersionStatus,
|
|
26628
|
-
critical: false
|
|
26629
|
-
};
|
|
26630
|
-
}
|
|
26631
|
-
|
|
26632
|
-
// src/cli/doctor/checks/index.ts
|
|
26633
|
-
function getAllCheckDefinitions() {
|
|
26634
|
-
return [
|
|
26635
|
-
getOpenCodeCheckDefinition(),
|
|
26636
|
-
getPluginCheckDefinition(),
|
|
26637
|
-
getConfigCheckDefinition(),
|
|
26638
|
-
getModelResolutionCheckDefinition(),
|
|
26639
|
-
...getAuthCheckDefinitions(),
|
|
26640
|
-
...getDependencyCheckDefinitions(),
|
|
26641
|
-
getGhCliCheckDefinition(),
|
|
26642
|
-
getLspCheckDefinition(),
|
|
26643
|
-
...getMcpCheckDefinitions(),
|
|
26644
|
-
getMcpOAuthCheckDefinition(),
|
|
26645
|
-
getVersionCheckDefinition()
|
|
26646
|
-
];
|
|
26647
|
-
}
|
|
26648
|
-
|
|
26649
|
-
// src/cli/doctor/formatter.ts
|
|
26650
|
-
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
26651
|
-
function formatStatusSymbol(status) {
|
|
26652
|
-
switch (status) {
|
|
26653
|
-
case "pass":
|
|
26654
|
-
return SYMBOLS3.check;
|
|
26655
|
-
case "fail":
|
|
26656
|
-
return SYMBOLS3.cross;
|
|
26657
|
-
case "warn":
|
|
26658
|
-
return SYMBOLS3.warn;
|
|
26659
|
-
case "skip":
|
|
26660
|
-
return SYMBOLS3.skip;
|
|
26661
|
-
}
|
|
26662
|
-
}
|
|
26663
|
-
function formatCheckResult(result, verbose) {
|
|
26664
|
-
const symbol2 = formatStatusSymbol(result.status);
|
|
26665
|
-
const colorFn = STATUS_COLORS[result.status];
|
|
26666
|
-
const name = colorFn(result.name);
|
|
26667
|
-
const message = import_picocolors17.default.dim(result.message);
|
|
26668
|
-
let line = ` ${symbol2} ${name}`;
|
|
26669
|
-
if (result.message) {
|
|
26670
|
-
line += ` ${SYMBOLS3.arrow} ${message}`;
|
|
26671
|
-
}
|
|
26672
|
-
if (verbose && result.details && result.details.length > 0) {
|
|
26673
|
-
const detailLines = result.details.map((d3) => ` ${SYMBOLS3.bullet} ${import_picocolors17.default.dim(d3)}`).join(`
|
|
26674
|
-
`);
|
|
26675
|
-
line += `
|
|
26676
|
-
` + detailLines;
|
|
26677
|
-
}
|
|
26678
|
-
return line;
|
|
26679
|
-
}
|
|
26680
|
-
function formatCategoryHeader(category) {
|
|
26681
|
-
const name = CATEGORY_NAMES[category] || category;
|
|
26682
|
-
return `
|
|
26683
|
-
${import_picocolors17.default.bold(import_picocolors17.default.white(name))}
|
|
26684
|
-
${import_picocolors17.default.dim("\u2500".repeat(40))}`;
|
|
26685
|
-
}
|
|
26686
|
-
function formatSummary(summary) {
|
|
26687
|
-
const lines = [];
|
|
26688
|
-
lines.push(import_picocolors17.default.bold(import_picocolors17.default.white("Summary")));
|
|
26689
|
-
lines.push(import_picocolors17.default.dim("\u2500".repeat(40)));
|
|
26690
|
-
lines.push("");
|
|
26691
|
-
const passText = summary.passed > 0 ? import_picocolors17.default.green(`${summary.passed} passed`) : import_picocolors17.default.dim("0 passed");
|
|
26692
|
-
const failText = summary.failed > 0 ? import_picocolors17.default.red(`${summary.failed} failed`) : import_picocolors17.default.dim("0 failed");
|
|
26693
|
-
const warnText = summary.warnings > 0 ? import_picocolors17.default.yellow(`${summary.warnings} warnings`) : import_picocolors17.default.dim("0 warnings");
|
|
26694
|
-
const skipText = summary.skipped > 0 ? import_picocolors17.default.dim(`${summary.skipped} skipped`) : "";
|
|
26695
|
-
const parts = [passText, failText, warnText];
|
|
26696
|
-
if (skipText)
|
|
26697
|
-
parts.push(skipText);
|
|
26698
|
-
lines.push(` ${parts.join(", ")}`);
|
|
26699
|
-
lines.push(` ${import_picocolors17.default.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
|
|
26700
|
-
return lines.join(`
|
|
26701
|
-
`);
|
|
26702
|
-
}
|
|
26703
|
-
function formatHeader() {
|
|
26704
|
-
return `
|
|
26705
|
-
${import_picocolors17.default.bgMagenta(import_picocolors17.default.white(" oMoMoMoMo... Doctor "))}
|
|
26706
|
-
`;
|
|
26707
|
-
}
|
|
26708
|
-
function formatFooter(summary) {
|
|
26709
|
-
if (summary.failed > 0) {
|
|
26710
|
-
return `
|
|
26711
|
-
${SYMBOLS3.cross} ${import_picocolors17.default.red("Issues detected. Please review the errors above.")}
|
|
26712
|
-
`;
|
|
26713
|
-
}
|
|
26714
|
-
if (summary.warnings > 0) {
|
|
26715
|
-
return `
|
|
26716
|
-
${SYMBOLS3.warn} ${import_picocolors17.default.yellow("All systems operational with warnings.")}
|
|
26717
|
-
`;
|
|
26718
|
-
}
|
|
26719
|
-
return `
|
|
26720
|
-
${SYMBOLS3.check} ${import_picocolors17.default.green("All systems operational!")}
|
|
26721
|
-
`;
|
|
26722
|
-
}
|
|
26723
|
-
function formatJsonOutput2(result) {
|
|
26724
|
-
return JSON.stringify(result, null, 2);
|
|
26725
|
-
}
|
|
26726
|
-
|
|
26727
|
-
// src/cli/doctor/runner.ts
|
|
26728
|
-
async function runCheck(check2) {
|
|
26729
|
-
const start = performance.now();
|
|
26730
|
-
try {
|
|
26731
|
-
const result = await check2.check();
|
|
26732
|
-
result.duration = Math.round(performance.now() - start);
|
|
26733
|
-
return result;
|
|
26734
|
-
} catch (err) {
|
|
26735
|
-
return {
|
|
26736
|
-
name: check2.name,
|
|
26737
|
-
status: "fail",
|
|
26738
|
-
message: err instanceof Error ? err.message : "Unknown error",
|
|
26739
|
-
duration: Math.round(performance.now() - start)
|
|
26740
|
-
};
|
|
26741
|
-
}
|
|
26742
|
-
}
|
|
26743
|
-
function calculateSummary(results, duration3) {
|
|
26744
|
-
return {
|
|
26745
|
-
total: results.length,
|
|
26746
|
-
passed: results.filter((r2) => r2.status === "pass").length,
|
|
26747
|
-
failed: results.filter((r2) => r2.status === "fail").length,
|
|
26748
|
-
warnings: results.filter((r2) => r2.status === "warn").length,
|
|
26749
|
-
skipped: results.filter((r2) => r2.status === "skip").length,
|
|
26750
|
-
duration: Math.round(duration3)
|
|
26751
|
-
};
|
|
26752
|
-
}
|
|
26753
|
-
function determineExitCode(results) {
|
|
26754
|
-
const hasFailures = results.some((r2) => r2.status === "fail");
|
|
26755
|
-
return hasFailures ? EXIT_CODES.FAILURE : EXIT_CODES.SUCCESS;
|
|
26756
|
-
}
|
|
26757
|
-
function filterChecksByCategory(checks3, category) {
|
|
26758
|
-
if (!category)
|
|
26759
|
-
return checks3;
|
|
26760
|
-
return checks3.filter((c) => c.category === category);
|
|
26761
|
-
}
|
|
26762
|
-
function groupChecksByCategory(checks3) {
|
|
26763
|
-
const groups = new Map;
|
|
26764
|
-
for (const check2 of checks3) {
|
|
26765
|
-
const existing = groups.get(check2.category) ?? [];
|
|
26766
|
-
existing.push(check2);
|
|
26767
|
-
groups.set(check2.category, existing);
|
|
26768
|
-
}
|
|
26769
|
-
return groups;
|
|
26770
|
-
}
|
|
26771
|
-
var CATEGORY_ORDER = [
|
|
26772
|
-
"installation",
|
|
26773
|
-
"configuration",
|
|
26774
|
-
"authentication",
|
|
26775
|
-
"dependencies",
|
|
26776
|
-
"tools",
|
|
26777
|
-
"updates"
|
|
26778
|
-
];
|
|
26779
|
-
async function runDoctor(options) {
|
|
26780
|
-
const start = performance.now();
|
|
26781
|
-
const allChecks = getAllCheckDefinitions();
|
|
26782
|
-
const filteredChecks = filterChecksByCategory(allChecks, options.category);
|
|
26783
|
-
const groupedChecks = groupChecksByCategory(filteredChecks);
|
|
26784
|
-
const results = [];
|
|
26785
|
-
if (!options.json) {
|
|
26786
|
-
console.log(formatHeader());
|
|
26787
|
-
}
|
|
26788
|
-
for (const category of CATEGORY_ORDER) {
|
|
26789
|
-
const checks3 = groupedChecks.get(category);
|
|
26790
|
-
if (!checks3 || checks3.length === 0)
|
|
26791
|
-
continue;
|
|
26792
|
-
if (!options.json) {
|
|
26793
|
-
console.log(formatCategoryHeader(category));
|
|
26794
|
-
}
|
|
26795
|
-
for (const check2 of checks3) {
|
|
26796
|
-
const result = await runCheck(check2);
|
|
26797
|
-
results.push(result);
|
|
26798
|
-
if (!options.json) {
|
|
26799
|
-
console.log(formatCheckResult(result, options.verbose ?? false));
|
|
26800
|
-
}
|
|
26801
|
-
}
|
|
26802
|
-
}
|
|
26803
|
-
const duration3 = performance.now() - start;
|
|
26804
|
-
const summary = calculateSummary(results, duration3);
|
|
26805
|
-
const exitCode = determineExitCode(results);
|
|
26806
|
-
const doctorResult = {
|
|
26807
|
-
results,
|
|
26808
|
-
summary,
|
|
26809
|
-
exitCode
|
|
26810
|
-
};
|
|
26811
|
-
if (options.json) {
|
|
26812
|
-
console.log(formatJsonOutput2(doctorResult));
|
|
26813
|
-
} else {
|
|
26814
|
-
console.log("");
|
|
26815
|
-
console.log(formatSummary(summary));
|
|
26816
|
-
console.log(formatFooter(summary));
|
|
26817
|
-
}
|
|
26818
|
-
return doctorResult;
|
|
26819
|
-
}
|
|
26820
|
-
|
|
26821
|
-
// src/cli/doctor/index.ts
|
|
26822
|
-
async function doctor(options = {}) {
|
|
26823
|
-
const result = await runDoctor(options);
|
|
26824
|
-
return result.exitCode;
|
|
26825
|
-
}
|
|
26826
|
-
|
|
26827
26656
|
// src/features/mcp-oauth/discovery.ts
|
|
26828
26657
|
var discoveryCache = new Map;
|
|
26829
26658
|
var pendingDiscovery = new Map;
|
|
@@ -27327,7 +27156,7 @@ async function status(serverName) {
|
|
|
27327
27156
|
|
|
27328
27157
|
// src/cli/mcp-oauth/index.ts
|
|
27329
27158
|
function createMcpOAuthCommand() {
|
|
27330
|
-
const
|
|
27159
|
+
const mcp = new Command("mcp").description("MCP server management");
|
|
27331
27160
|
const oauth = new Command("oauth").description("OAuth token management for MCP servers");
|
|
27332
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) => {
|
|
27333
27162
|
const exitCode = await login(serverName, options);
|
|
@@ -27341,8 +27170,8 @@ function createMcpOAuthCommand() {
|
|
|
27341
27170
|
const exitCode = await status(serverName);
|
|
27342
27171
|
process.exit(exitCode);
|
|
27343
27172
|
});
|
|
27344
|
-
|
|
27345
|
-
return
|
|
27173
|
+
mcp.addCommand(oauth);
|
|
27174
|
+
return mcp;
|
|
27346
27175
|
}
|
|
27347
27176
|
|
|
27348
27177
|
// src/cli/cli-program.ts
|
|
@@ -27439,25 +27268,17 @@ This command shows:
|
|
|
27439
27268
|
const exitCode = await getLocalVersion(versionOptions);
|
|
27440
27269
|
process.exit(exitCode);
|
|
27441
27270
|
});
|
|
27442
|
-
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", `
|
|
27443
27272
|
Examples:
|
|
27444
|
-
$ bunx oh-my-opencode doctor
|
|
27445
|
-
$ bunx oh-my-opencode doctor --
|
|
27446
|
-
$ bunx oh-my-opencode doctor --
|
|
27447
|
-
$ bunx oh-my-opencode doctor --
|
|
27448
|
-
|
|
27449
|
-
Categories:
|
|
27450
|
-
installation Check OpenCode and plugin installation
|
|
27451
|
-
configuration Validate configuration files
|
|
27452
|
-
authentication Check auth provider status
|
|
27453
|
-
dependencies Check external dependencies
|
|
27454
|
-
tools Check LSP and MCP servers
|
|
27455
|
-
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
|
|
27456
27277
|
`).action(async (options) => {
|
|
27278
|
+
const mode = options.status ? "status" : options.verbose ? "verbose" : "default";
|
|
27457
27279
|
const doctorOptions = {
|
|
27458
|
-
|
|
27459
|
-
json: options.json ?? false
|
|
27460
|
-
category: options.category
|
|
27280
|
+
mode,
|
|
27281
|
+
json: options.json ?? false
|
|
27461
27282
|
};
|
|
27462
27283
|
const exitCode = await doctor(doctorOptions);
|
|
27463
27284
|
process.exit(exitCode);
|