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.
Files changed (81) hide show
  1. package/README.md +3 -3
  2. package/dist/agents/builtin-agents/agent-overrides.d.ts +2 -2
  3. package/dist/agents/builtin-agents/atlas-agent.d.ts +1 -0
  4. package/dist/agents/builtin-agents/resolve-file-uri.d.ts +1 -0
  5. package/dist/cli/doctor/checks/config.d.ts +2 -8
  6. package/dist/cli/doctor/checks/dependencies.d.ts +1 -5
  7. package/dist/cli/doctor/checks/index.d.ts +4 -16
  8. package/dist/cli/doctor/checks/model-resolution.d.ts +4 -4
  9. package/dist/cli/doctor/checks/{opencode.d.ts → system-binary.d.ts} +6 -12
  10. package/dist/cli/doctor/checks/system-loaded-version.d.ts +9 -0
  11. package/dist/cli/doctor/checks/system-plugin.d.ts +15 -0
  12. package/dist/cli/doctor/checks/system.d.ts +3 -0
  13. package/dist/cli/doctor/checks/{gh.d.ts → tools-gh.d.ts} +0 -3
  14. package/dist/cli/doctor/checks/tools-lsp.d.ts +6 -0
  15. package/dist/cli/doctor/checks/tools-mcp.d.ts +3 -0
  16. package/dist/cli/doctor/checks/tools.d.ts +3 -0
  17. package/dist/cli/doctor/constants.d.ts +4 -17
  18. package/dist/cli/doctor/format-default.d.ts +2 -0
  19. package/dist/cli/doctor/format-shared.d.ts +6 -0
  20. package/dist/cli/doctor/format-status.d.ts +2 -0
  21. package/dist/cli/doctor/format-verbose.d.ts +2 -0
  22. package/dist/cli/doctor/formatter.d.ts +2 -11
  23. package/dist/cli/doctor/index.d.ts +1 -1
  24. package/dist/cli/doctor/runner.d.ts +1 -3
  25. package/dist/cli/doctor/types.d.ts +39 -6
  26. package/dist/cli/index.js +959 -1138
  27. package/dist/cli/run/poll-for-completion.d.ts +1 -0
  28. package/dist/cli/run/runner.d.ts +1 -0
  29. package/dist/config/schema/background-task.d.ts +1 -0
  30. package/dist/config/schema/hooks.d.ts +0 -1
  31. package/dist/config/schema/oh-my-opencode-config.d.ts +11 -11
  32. package/dist/config/schema/skills.d.ts +10 -10
  33. package/dist/create-hooks.d.ts +0 -1
  34. package/dist/features/background-agent/background-event-handler.d.ts +1 -0
  35. package/dist/features/background-agent/constants.d.ts +1 -0
  36. package/dist/features/background-agent/index.d.ts +1 -0
  37. package/dist/features/background-agent/manager.d.ts +4 -0
  38. package/dist/features/background-agent/poll-running-tasks.d.ts +3 -1
  39. package/dist/features/background-agent/session-idle-event-handler.d.ts +10 -0
  40. package/dist/features/background-agent/session-task-cleanup.d.ts +10 -0
  41. package/dist/features/background-agent/stale-task-pruner.d.ts +7 -1
  42. package/dist/features/background-agent/task-history.d.ts +18 -0
  43. package/dist/features/background-agent/task-poller.d.ts +4 -0
  44. package/dist/features/background-agent/types.d.ts +4 -0
  45. package/dist/features/claude-code-agent-loader/loader.d.ts +1 -1
  46. package/dist/features/claude-code-command-loader/loader.d.ts +3 -3
  47. package/dist/features/opencode-skill-loader/config-source-discovery.d.ts +7 -0
  48. package/dist/features/opencode-skill-loader/index.d.ts +1 -0
  49. package/dist/features/opencode-skill-loader/loader.d.ts +8 -5
  50. package/dist/features/opencode-skill-loader/merger.d.ts +1 -1
  51. package/dist/features/opencode-skill-loader/skill-resolution-options.d.ts +2 -0
  52. package/dist/features/tmux-subagent/grid-planning.d.ts +1 -1
  53. package/dist/features/tmux-subagent/pane-split-availability.d.ts +3 -3
  54. package/dist/features/tmux-subagent/spawn-action-decider.d.ts +1 -1
  55. package/dist/hooks/claude-code-hooks/transcript.d.ts +8 -13
  56. package/dist/hooks/compaction-context-injector/hook.d.ts +2 -1
  57. package/dist/hooks/index.d.ts +0 -1
  58. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +2 -5
  59. package/dist/hooks/prometheus-md-only/agent-matcher.d.ts +1 -0
  60. package/dist/hooks/think-mode/switcher.d.ts +1 -2
  61. package/dist/hooks/todo-continuation-enforcer/constants.d.ts +1 -0
  62. package/dist/hooks/todo-continuation-enforcer/session-state.d.ts +1 -0
  63. package/dist/hooks/todo-continuation-enforcer/types.d.ts +2 -0
  64. package/dist/index.js +2043 -1425
  65. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  66. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  67. package/dist/plugin/session-agent-resolver.d.ts +19 -0
  68. package/dist/plugin-config.d.ts +1 -0
  69. package/dist/plugin-handlers/command-config-handler.d.ts +3 -0
  70. package/dist/shared/session-tools-store.d.ts +3 -0
  71. package/dist/tools/call-omo-agent/sync-executor.d.ts +10 -1
  72. package/dist/tools/slashcommand/command-discovery.d.ts +1 -1
  73. package/package.json +8 -8
  74. package/dist/cli/doctor/checks/auth.d.ts +0 -7
  75. package/dist/cli/doctor/checks/lsp.d.ts +0 -8
  76. package/dist/cli/doctor/checks/mcp-oauth.d.ts +0 -15
  77. package/dist/cli/doctor/checks/mcp.d.ts +0 -6
  78. package/dist/cli/doctor/checks/plugin.d.ts +0 -4
  79. package/dist/cli/doctor/checks/version.d.ts +0 -4
  80. package/dist/hooks/subagent-question-blocker/hook.d.ts +0 -2
  81. 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.2",
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.2",
8948
- "oh-my-opencode-darwin-x64": "3.5.2",
8949
- "oh-my-opencode-linux-arm64": "3.5.2",
8950
- "oh-my-opencode-linux-arm64-musl": "3.5.2",
8951
- "oh-my-opencode-linux-x64": "3.5.2",
8952
- "oh-my-opencode-linux-x64-musl": "3.5.2",
8953
- "oh-my-opencode-windows-x64": "3.5.2"
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.record(exports_external.string(), SkillEntrySchema).and(exports_external.object({
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
- }).partial())
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 (!result.success) {
22981
- const errorMsg = result.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join(", ");
22982
- log(`Config validation error in ${configPath}:`, result.error.issues);
22983
- addConfigLoadError({
22984
- path: configPath,
22985
- error: `Validation error: ${errorMsg}`
22986
- });
22987
- return null;
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
- log(`Config loaded from ${configPath}`, { agents: result.data.agents });
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
- OPENCODE_INSTALLATION: "opencode-installation",
25079
- PLUGIN_REGISTRATION: "plugin-registration",
25080
- CONFIG_VALIDATION: "config-validation",
25081
- MODEL_RESOLUTION: "model-resolution",
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.OPENCODE_INSTALLATION]: "OpenCode Installation",
25097
- [CHECK_IDS.PLUGIN_REGISTRATION]: "Plugin Registration",
25098
- [CHECK_IDS.CONFIG_VALIDATION]: "Configuration Validity",
25099
- [CHECK_IDS.MODEL_RESOLUTION]: "Model Resolution",
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/opencode.ts
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 desktopPaths = getDesktopAppPaths(platform);
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
- try {
25187
- const path9 = Bun.which(binary2);
25188
- if (path9) {
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
- const desktopResult = findDesktopBinary();
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 proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
25205
- const output = await new Response(proc.stdout).text();
25206
- await proc.exited;
25207
- if (proc.exitCode === 0) {
25208
- return output.trim();
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
- const cleaned = v.replace(/^v/, "").split("-")[0];
25218
- return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
25219
- };
25220
- const curr = parseVersion(current);
25221
- const min = parseVersion(minimum);
25222
- for (let i2 = 0;i2 < Math.max(curr.length, min.length); i2++) {
25223
- const c = curr[i2] ?? 0;
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 (c < m2)
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 { path: paths.configJsonc, format: "jsonc" };
25299
- }
25300
- if (existsSync17(paths.configJson)) {
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 findPluginEntry2(plugins) {
25306
- for (const plugin of plugins) {
25307
- if (plugin === PACKAGE_NAME4 || plugin.startsWith(`${PACKAGE_NAME4}@`)) {
25308
- const isPinned = plugin.includes("@");
25309
- const version2 = isPinned ? plugin.split("@")[1] : null;
25310
- return { entry: plugin, isPinned, version: version2 };
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 (plugin.startsWith("file://") && plugin.includes(PACKAGE_NAME4)) {
25313
- return { entry: plugin, isPinned: false, version: "local-dev" };
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 configInfo = detectConfigPath();
25320
- if (!configInfo) {
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(configInfo.path, "utf-8");
25331
- const config2 = parseJsonc(content);
25332
- const plugins = config2.plugin ?? [];
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: configInfo.path,
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: configInfo.path,
25299
+ configPath,
25346
25300
  entry: pluginEntry.entry,
25347
- isPinned: pluginEntry.isPinned,
25348
- pinnedVersion: pluginEntry.version
25301
+ isPinned: pinnedVersion !== null,
25302
+ pinnedVersion,
25303
+ isLocalDev: pluginEntry.isLocalDev
25349
25304
  };
25350
25305
  } catch {
25351
25306
  return {
25352
25307
  registered: false,
25353
- configPath: configInfo.path,
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
- async function checkPluginRegistration() {
25361
- const info = getPluginInfo();
25362
- if (!info.configPath) {
25363
- const expectedPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
25364
- return {
25365
- name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
25366
- status: "fail",
25367
- message: "OpenCode config file not found",
25368
- details: [
25369
- "Run: bunx oh-my-opencode install",
25370
- `Expected: ${expectedPaths.configJson} or ${expectedPaths.configJsonc}`
25371
- ]
25372
- };
25373
- }
25374
- if (!info.registered) {
25375
- return {
25376
- name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
25377
- status: "fail",
25378
- message: "Plugin not registered in config",
25379
- details: [
25380
- "Run: bunx oh-my-opencode install",
25381
- `Config: ${info.configPath}`
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 getPluginCheckDefinition() {
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
- id: CHECK_IDS.PLUGIN_REGISTRATION,
25396
- name: CHECK_NAMES[CHECK_IDS.PLUGIN_REGISTRATION],
25397
- category: "installation",
25398
- check: checkPluginRegistration,
25399
- critical: true
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/config.ts
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
- var USER_CONFIG_DIR2 = getOpenCodeConfigDir({ binary: "opencode" });
25408
- var USER_CONFIG_BASE = join13(USER_CONFIG_DIR2, `${PACKAGE_NAME4}`);
25409
- var PROJECT_CONFIG_BASE = join13(process.cwd(), ".opencode", PACKAGE_NAME4);
25410
- function findConfigPath() {
25411
- const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE);
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
- const content = readFileSync19(configPath, "utf-8");
25424
- const rawConfig = parseJsonc(content);
25425
- const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
25426
- if (!result.success) {
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 getConfigInfo() {
25439
- const configPath = findConfigPath();
25440
- if (!configPath) {
25441
- return {
25442
- exists: false,
25443
- path: null,
25444
- format: null,
25445
- valid: true,
25446
- errors: []
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 (!existsSync18(configPath.path)) {
25450
- return {
25451
- exists: false,
25452
- path: configPath.path,
25453
- format: configPath.format,
25454
- valid: true,
25455
- errors: []
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
- const validation = validateConfig(configPath.path);
25459
- return {
25460
- exists: true,
25461
- path: configPath.path,
25462
- format: configPath.format,
25463
- valid: validation.valid,
25464
- errors: validation.errors
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 (!info.valid) {
25478
- return {
25479
- name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
25480
- status: "fail",
25481
- message: "Configuration has validation errors",
25482
- details: [
25483
- `Path: ${info.path}`,
25484
- ...info.errors.map((e2) => `Error: ${e2}`)
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.CONFIG_VALIDATION],
25490
- status: "pass",
25491
- message: `Valid ${info.format?.toUpperCase()} config`,
25492
- details: [`Path: ${info.path}`]
25493
- };
25494
- }
25495
- function getConfigCheckDefinition() {
25496
- return {
25497
- id: CHECK_IDS.CONFIG_VALIDATION,
25498
- name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
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/model-resolution.ts
25506
- init_model_requirements();
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 existsSync19, readFileSync as readFileSync20 } from "fs";
25511
- import { homedir as homedir6 } from "os";
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(homedir6(), ".cache", "opencode");
25501
+ return join14(homedir7(), ".cache", "opencode");
25518
25502
  }
25519
25503
  function loadAvailableModelsFromCache() {
25520
25504
  const cacheFile = join14(getOpenCodeCacheDir2(), "models.json");
25521
- if (!existsSync19(cacheFile)) {
25505
+ if (!existsSync20(cacheFile)) {
25522
25506
  return { providers: [], modelCount: 0, cacheExists: false };
25523
25507
  }
25524
25508
  try {
25525
- const content = readFileSync20(cacheFile, "utf-8");
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 readFileSync21 } from "fs";
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 USER_CONFIG_BASE2 = join15(getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir, PACKAGE_NAME5);
25547
- var PROJECT_CONFIG_BASE2 = join15(process.cwd(), ".opencode", PACKAGE_NAME5);
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(PROJECT_CONFIG_BASE2);
25536
+ const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE);
25550
25537
  if (projectDetected.format !== "none") {
25551
25538
  try {
25552
- const content = readFileSync21(projectDetected.path, "utf-8");
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(USER_CONFIG_BASE2);
25545
+ const userDetected = detectConfigFile(USER_CONFIG_BASE);
25559
25546
  if (userDetected.format !== "none") {
25560
25547
  try {
25561
- const content = readFileSync21(userDetected.path, "utf-8");
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 checkModelResolution() {
25685
+ async function checkModels() {
25699
25686
  const config2 = loadOmoConfig() ?? {};
25700
25687
  const info = getModelResolutionInfoWithOverrides(config2);
25701
25688
  const available = loadAvailableModelsFromCache();
25702
- const agentCount = info.agents.length;
25703
- const categoryCount = info.categories.length;
25704
- const agentOverrides = info.agents.filter((a) => a.userOverride).length;
25705
- const categoryOverrides = info.categories.filter((c) => c.userOverride).length;
25706
- const totalOverrides = agentOverrides + categoryOverrides;
25707
- const overrideNote = totalOverrides > 0 ? ` (${totalOverrides} override${totalOverrides > 1 ? "s" : ""})` : "";
25708
- const cacheNote = available.cacheExists ? `, ${available.modelCount} available` : ", cache not found";
25709
- return {
25710
- name: CHECK_NAMES[CHECK_IDS.MODEL_RESOLUTION],
25711
- status: available.cacheExists ? "pass" : "warn",
25712
- message: `${agentCount} agents, ${categoryCount} categories${overrideNote}${cacheNote}`,
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
- id: CHECK_IDS.MODEL_RESOLUTION,
25719
- name: CHECK_NAMES[CHECK_IDS.MODEL_RESOLUTION],
25720
- category: "configuration",
25721
- check: checkModelResolution,
25722
- critical: false
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/auth.ts
25727
- import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
25728
- import { join as join17 } from "path";
25729
- init_shared();
25730
- var OPENCODE_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" });
25731
- var OPENCODE_JSON = join17(OPENCODE_CONFIG_DIR, "opencode.json");
25732
- var OPENCODE_JSONC = join17(OPENCODE_CONFIG_DIR, "opencode.jsonc");
25733
- var AUTH_PLUGINS = {
25734
- anthropic: { plugin: "builtin", name: "Anthropic (Claude)" },
25735
- openai: { plugin: "opencode-openai-codex-auth", name: "OpenAI (ChatGPT)" },
25736
- google: { plugin: "opencode-antigravity-auth", name: "Google (Gemini)" }
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
- async function checkAuthProvider(providerId) {
25767
- const info = getAuthProviderInfo(providerId);
25768
- const checkId = `auth-${providerId}`;
25769
- const checkName = CHECK_NAMES[checkId] || info.name;
25770
- if (!info.pluginInstalled) {
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
- name: checkName,
25773
- status: "skip",
25774
- message: "Auth plugin not installed",
25775
- details: [
25776
- `Plugin: ${AUTH_PLUGINS[providerId].plugin}`,
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 getAuthCheckDefinitions() {
25800
- return [
25801
- {
25802
- id: CHECK_IDS.AUTH_ANTHROPIC,
25803
- name: CHECK_NAMES[CHECK_IDS.AUTH_ANTHROPIC],
25804
- category: "authentication",
25805
- check: checkAnthropicAuth,
25806
- critical: false
25807
- },
25808
- {
25809
- id: CHECK_IDS.AUTH_OPENAI,
25810
- name: CHECK_NAMES[CHECK_IDS.AUTH_OPENAI],
25811
- category: "authentication",
25812
- check: checkOpenAIAuth,
25813
- critical: false
25814
- },
25815
- {
25816
- id: CHECK_IDS.AUTH_GOOGLE,
25817
- name: CHECK_NAMES[CHECK_IDS.AUTH_GOOGLE],
25818
- category: "authentication",
25819
- check: checkGoogleAuth,
25820
- critical: false
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: existsSync21 } = await import("fs");
25882
- const { join: join18 } = await import("path");
25883
- const { homedir: homedir7 } = await import("os");
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
- join18(homedir7(), ".config", "opencode", "node_modules", "@ast-grep", "napi"),
25886
- join18(process.cwd(), "node_modules", "@ast-grep", "napi")
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 (existsSync21(napiPath)) {
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
- if (!binaryCheck.exists) {
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("comment-checker");
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: binaryCheck.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 whichCmd = process.platform === "win32" ? "where" : "which";
25988
- const proc = Bun.spawn([whichCmd, binary2], { stdout: "pipe", stderr: "pipe" });
25989
- const output = await new Response(proc.stdout).text();
25990
- await proc.exited;
25991
- if (proc.exitCode === 0) {
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 proc = Bun.spawn(["gh", "--version"], { stdout: "pipe", stderr: "pipe" });
26000
- const output = await new Response(proc.stdout).text();
26001
- await proc.exited;
26002
- if (proc.exitCode === 0) {
26003
- const match = output.match(/gh version (\S+)/);
26004
- return match?.[1] ?? output.trim().split(`
26005
- `)[0];
26006
- }
26007
- } catch {}
26008
- return null;
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 proc = Bun.spawn(["gh", "auth", "status"], {
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(proc.stdout).text();
26018
- const stderr = await new Response(proc.stderr).text();
26019
- await proc.exited;
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 (proc.exitCode === 0) {
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
- const scopes = scopesMatch?.[1] ? scopesMatch[1].split(/,\s*/).map((s) => s.replace(/['"]/g, "").trim()).filter(Boolean) : [];
26026
- return { authenticated: true, username, scopes, error: null };
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 (err) {
26001
+ } catch (error45) {
26036
26002
  return {
26037
26003
  authenticated: false,
26038
26004
  username: null,
26039
26005
  scopes: [],
26040
- error: err instanceof Error ? err.message : "Failed to check auth status"
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 binaryCheck = await checkBinaryExists2("gh");
26046
- if (!binaryCheck.exists) {
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: binaryCheck.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 existsSync21 } from "fs";
26124
- import { join as join18 } from "path";
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 (existsSync21(cmd))
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 (existsSync21(join18(p2, cmd + suffix))) {
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 = join18(getDataDir(), "opencode");
26076
+ const dataDir = join19(getDataDir(), "opencode");
26160
26077
  const additionalBases = [
26161
- join18(cwd, "node_modules", ".bin"),
26162
- join18(configDir, "bin"),
26163
- join18(configDir, "node_modules", ".bin"),
26164
- join18(dataDir, "bin")
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 (existsSync21(join18(base, cmd + suffix))) {
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
- async function getLspServersInfo() {
26186
- const servers = [];
26187
- for (const server2 of DEFAULT_LSP_SERVERS) {
26188
- const installed = isServerInstalled([server2.binary]);
26189
- servers.push({
26190
- id: server2.id,
26191
- installed,
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
- id: CHECK_IDS.LSP_SERVERS,
26232
- name: CHECK_NAMES[CHECK_IDS.LSP_SERVERS],
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
- var MCP_CONFIG_PATHS = [
26246
- join19(homedir7(), ".claude", ".mcp.json"),
26247
- join19(process.cwd(), ".mcp.json"),
26248
- join19(process.cwd(), ".claude", ".mcp.json")
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 MCP_CONFIG_PATHS) {
26253
- if (!existsSync22(configPath))
26132
+ for (const configPath of getMcpConfigPaths()) {
26133
+ if (!existsSync23(configPath))
26254
26134
  continue;
26255
26135
  try {
26256
- const content = readFileSync23(configPath, "utf-8");
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((id) => ({
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
- const userServers = loadUserMcpConfig();
26275
- const servers = [];
26276
- for (const [id, config2] of Object.entries(userServers)) {
26277
- const isValid = typeof config2 === "object" && config2 !== null;
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: isValid,
26283
- error: isValid ? undefined : "Invalid configuration format"
26284
- });
26285
- }
26286
- return servers;
26162
+ valid,
26163
+ error: valid ? undefined : "Invalid configuration format"
26164
+ };
26165
+ });
26287
26166
  }
26288
- async function checkBuiltinMcpServers() {
26289
- const servers = getBuiltinMcpInfo();
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
- name: CHECK_NAMES[CHECK_IDS.MCP_BUILTIN],
26292
- status: "pass",
26293
- message: `${servers.length} built-in servers enabled`,
26294
- details: servers.map((s) => `Enabled: ${s.id}`)
26295
- };
26296
- }
26297
- async function checkUserMcpServers() {
26298
- const servers = getUserMcpInfo();
26299
- if (servers.length === 0) {
26300
- return {
26301
- name: CHECK_NAMES[CHECK_IDS.MCP_USER],
26302
- status: "skip",
26303
- message: "No user MCP configuration found",
26304
- details: ["Optional: Add .mcp.json for custom MCP servers"]
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
- const invalidServers = servers.filter((s) => !s.valid);
26308
- if (invalidServers.length > 0) {
26309
- return {
26310
- name: CHECK_NAMES[CHECK_IDS.MCP_USER],
26311
- status: "warn",
26312
- message: `${invalidServers.length} server(s) have configuration issues`,
26313
- details: [
26314
- ...servers.filter((s) => s.valid).map((s) => `Valid: ${s.id}`),
26315
- ...invalidServers.map((s) => `Invalid: ${s.id} - ${s.error}`)
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.MCP_USER],
26321
- status: "pass",
26322
- message: `${servers.length} user server(s) configured`,
26323
- details: servers.map((s) => `Configured: ${s.id}`)
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
- function getMcpCheckDefinitions() {
26269
+ // src/cli/doctor/checks/index.ts
26270
+ function getAllCheckDefinitions() {
26327
26271
  return [
26328
26272
  {
26329
- id: CHECK_IDS.MCP_BUILTIN,
26330
- name: CHECK_NAMES[CHECK_IDS.MCP_BUILTIN],
26331
- category: "tools",
26332
- check: checkBuiltinMcpServers,
26333
- critical: false
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.MCP_USER,
26337
- name: CHECK_NAMES[CHECK_IDS.MCP_USER],
26338
- category: "tools",
26339
- check: checkUserMcpServers,
26340
- critical: false
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 existsSync23, mkdirSync as mkdirSync3, readFileSync as readFileSync24, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
26348
- import { dirname as dirname3, join as join20 } from "path";
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 join20(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
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 (!existsSync23(filePath)) {
26579
+ if (!existsSync24(filePath)) {
26389
26580
  return null;
26390
26581
  }
26391
26582
  try {
26392
- const content = readFileSync24(filePath, "utf-8");
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 = dirname3(filePath);
26402
- if (!existsSync23(dir)) {
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 (existsSync23(filePath)) {
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 mcp2 = new Command("mcp").description("MCP server management");
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
- mcp2.addCommand(oauth);
27345
- return mcp2;
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("--verbose", "Show detailed diagnostic information").option("--json", "Output results in JSON format").option("--category <category>", "Run only specific category").addHelpText("after", `
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 --verbose
27446
- $ bunx oh-my-opencode doctor --json
27447
- $ bunx oh-my-opencode doctor --category authentication
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
- verbose: options.verbose ?? false,
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);