oh-my-opencode 3.5.3 → 3.5.5

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