opencode-repos 0.1.0 → 0.3.0

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 (477) hide show
  1. package/AGENTS.md +180 -0
  2. package/README.md +103 -3
  3. package/TODO.md +3 -0
  4. package/bun.lock +0 -85
  5. package/index.ts +1585 -153
  6. package/oh-my-opencode/.github/FUNDING.yml +15 -0
  7. package/oh-my-opencode/.github/ISSUE_TEMPLATE/bug_report.yml +129 -0
  8. package/oh-my-opencode/.github/ISSUE_TEMPLATE/config.yml +8 -0
  9. package/oh-my-opencode/.github/ISSUE_TEMPLATE/feature_request.yml +100 -0
  10. package/oh-my-opencode/.github/ISSUE_TEMPLATE/general.yml +83 -0
  11. package/oh-my-opencode/.github/assets/google.jpg +0 -0
  12. package/oh-my-opencode/.github/assets/hero.jpg +0 -0
  13. package/oh-my-opencode/.github/assets/indent.jpg +0 -0
  14. package/oh-my-opencode/.github/assets/microsoft.jpg +0 -0
  15. package/oh-my-opencode/.github/assets/omo.png +0 -0
  16. package/oh-my-opencode/.github/assets/orchestrator-atlas.png +0 -0
  17. package/oh-my-opencode/.github/assets/sisyphus.png +0 -0
  18. package/oh-my-opencode/.github/assets/sisyphuslabs.png +0 -0
  19. package/oh-my-opencode/.github/pull_request_template.md +34 -0
  20. package/oh-my-opencode/.github/workflows/ci.yml +138 -0
  21. package/oh-my-opencode/.github/workflows/cla.yml +41 -0
  22. package/oh-my-opencode/.github/workflows/lint-workflows.yml +22 -0
  23. package/oh-my-opencode/.github/workflows/publish.yml +165 -0
  24. package/oh-my-opencode/.github/workflows/sisyphus-agent.yml +500 -0
  25. package/oh-my-opencode/.opencode/background-tasks.json +27 -0
  26. package/oh-my-opencode/.opencode/command/get-unpublished-changes.md +84 -0
  27. package/oh-my-opencode/.opencode/command/omomomo.md +37 -0
  28. package/oh-my-opencode/.opencode/command/publish.md +257 -0
  29. package/oh-my-opencode/AGENTS.md +179 -0
  30. package/oh-my-opencode/CLA.md +58 -0
  31. package/oh-my-opencode/CONTRIBUTING.md +268 -0
  32. package/oh-my-opencode/LICENSE.md +82 -0
  33. package/oh-my-opencode/README.ja.md +370 -0
  34. package/oh-my-opencode/README.md +376 -0
  35. package/oh-my-opencode/README.zh-cn.md +380 -0
  36. package/oh-my-opencode/assets/oh-my-opencode.schema.json +2171 -0
  37. package/oh-my-opencode/bin/oh-my-opencode.js +80 -0
  38. package/oh-my-opencode/bin/platform.js +38 -0
  39. package/oh-my-opencode/bin/platform.test.ts +148 -0
  40. package/oh-my-opencode/bun.lock +314 -0
  41. package/oh-my-opencode/bunfig.toml +2 -0
  42. package/oh-my-opencode/docs/category-skill-guide.md +200 -0
  43. package/oh-my-opencode/docs/cli-guide.md +272 -0
  44. package/oh-my-opencode/docs/configurations.md +654 -0
  45. package/oh-my-opencode/docs/features.md +550 -0
  46. package/oh-my-opencode/docs/guide/installation.md +288 -0
  47. package/oh-my-opencode/docs/guide/overview.md +97 -0
  48. package/oh-my-opencode/docs/guide/understanding-orchestration-system.md +445 -0
  49. package/oh-my-opencode/docs/orchestration-guide.md +152 -0
  50. package/oh-my-opencode/docs/ultrawork-manifesto.md +197 -0
  51. package/oh-my-opencode/package.json +89 -0
  52. package/oh-my-opencode/packages/darwin-arm64/bin/.gitkeep +0 -0
  53. package/oh-my-opencode/packages/darwin-arm64/package.json +22 -0
  54. package/oh-my-opencode/packages/darwin-x64/bin/.gitkeep +0 -0
  55. package/oh-my-opencode/packages/darwin-x64/package.json +22 -0
  56. package/oh-my-opencode/packages/linux-arm64/bin/.gitkeep +0 -0
  57. package/oh-my-opencode/packages/linux-arm64/package.json +25 -0
  58. package/oh-my-opencode/packages/linux-arm64-musl/bin/.gitkeep +0 -0
  59. package/oh-my-opencode/packages/linux-arm64-musl/package.json +25 -0
  60. package/oh-my-opencode/packages/linux-x64/bin/.gitkeep +0 -0
  61. package/oh-my-opencode/packages/linux-x64/package.json +25 -0
  62. package/oh-my-opencode/packages/linux-x64-musl/bin/.gitkeep +0 -0
  63. package/oh-my-opencode/packages/linux-x64-musl/package.json +25 -0
  64. package/oh-my-opencode/packages/windows-x64/bin/.gitkeep +0 -0
  65. package/oh-my-opencode/packages/windows-x64/package.json +22 -0
  66. package/oh-my-opencode/postinstall.mjs +43 -0
  67. package/oh-my-opencode/script/build-binaries.ts +103 -0
  68. package/oh-my-opencode/script/build-schema.ts +28 -0
  69. package/oh-my-opencode/script/generate-changelog.ts +92 -0
  70. package/oh-my-opencode/script/publish.ts +344 -0
  71. package/oh-my-opencode/signatures/cla.json +676 -0
  72. package/oh-my-opencode/src/agents/AGENTS.md +67 -0
  73. package/oh-my-opencode/src/agents/atlas.ts +1383 -0
  74. package/oh-my-opencode/src/agents/dynamic-agent-prompt-builder.ts +400 -0
  75. package/oh-my-opencode/src/agents/explore.ts +122 -0
  76. package/oh-my-opencode/src/agents/index.ts +13 -0
  77. package/oh-my-opencode/src/agents/librarian.ts +326 -0
  78. package/oh-my-opencode/src/agents/metis.ts +315 -0
  79. package/oh-my-opencode/src/agents/momus.test.ts +57 -0
  80. package/oh-my-opencode/src/agents/momus.ts +444 -0
  81. package/oh-my-opencode/src/agents/multimodal-looker.ts +56 -0
  82. package/oh-my-opencode/src/agents/oracle.ts +122 -0
  83. package/oh-my-opencode/src/agents/prometheus-prompt.test.ts +22 -0
  84. package/oh-my-opencode/src/agents/prometheus-prompt.ts +1196 -0
  85. package/oh-my-opencode/src/agents/sisyphus-junior.test.ts +232 -0
  86. package/oh-my-opencode/src/agents/sisyphus-junior.ts +134 -0
  87. package/oh-my-opencode/src/agents/sisyphus.ts +633 -0
  88. package/oh-my-opencode/src/agents/types.ts +80 -0
  89. package/oh-my-opencode/src/agents/utils.test.ts +311 -0
  90. package/oh-my-opencode/src/agents/utils.ts +240 -0
  91. package/oh-my-opencode/src/cli/AGENTS.md +91 -0
  92. package/oh-my-opencode/src/cli/config-manager.test.ts +364 -0
  93. package/oh-my-opencode/src/cli/config-manager.ts +641 -0
  94. package/oh-my-opencode/src/cli/doctor/checks/auth.test.ts +114 -0
  95. package/oh-my-opencode/src/cli/doctor/checks/auth.ts +115 -0
  96. package/oh-my-opencode/src/cli/doctor/checks/config.test.ts +103 -0
  97. package/oh-my-opencode/src/cli/doctor/checks/config.ts +123 -0
  98. package/oh-my-opencode/src/cli/doctor/checks/dependencies.test.ts +152 -0
  99. package/oh-my-opencode/src/cli/doctor/checks/dependencies.ts +163 -0
  100. package/oh-my-opencode/src/cli/doctor/checks/gh.test.ts +151 -0
  101. package/oh-my-opencode/src/cli/doctor/checks/gh.ts +171 -0
  102. package/oh-my-opencode/src/cli/doctor/checks/index.ts +34 -0
  103. package/oh-my-opencode/src/cli/doctor/checks/lsp.test.ts +134 -0
  104. package/oh-my-opencode/src/cli/doctor/checks/lsp.ts +77 -0
  105. package/oh-my-opencode/src/cli/doctor/checks/mcp.test.ts +115 -0
  106. package/oh-my-opencode/src/cli/doctor/checks/mcp.ts +128 -0
  107. package/oh-my-opencode/src/cli/doctor/checks/opencode.test.ts +227 -0
  108. package/oh-my-opencode/src/cli/doctor/checks/opencode.ts +178 -0
  109. package/oh-my-opencode/src/cli/doctor/checks/plugin.test.ts +109 -0
  110. package/oh-my-opencode/src/cli/doctor/checks/plugin.ts +124 -0
  111. package/oh-my-opencode/src/cli/doctor/checks/version.test.ts +148 -0
  112. package/oh-my-opencode/src/cli/doctor/checks/version.ts +135 -0
  113. package/oh-my-opencode/src/cli/doctor/constants.ts +72 -0
  114. package/oh-my-opencode/src/cli/doctor/formatter.test.ts +218 -0
  115. package/oh-my-opencode/src/cli/doctor/formatter.ts +140 -0
  116. package/oh-my-opencode/src/cli/doctor/index.ts +11 -0
  117. package/oh-my-opencode/src/cli/doctor/runner.test.ts +153 -0
  118. package/oh-my-opencode/src/cli/doctor/runner.ts +132 -0
  119. package/oh-my-opencode/src/cli/doctor/types.ts +113 -0
  120. package/oh-my-opencode/src/cli/get-local-version/formatter.ts +66 -0
  121. package/oh-my-opencode/src/cli/get-local-version/index.ts +106 -0
  122. package/oh-my-opencode/src/cli/get-local-version/types.ts +14 -0
  123. package/oh-my-opencode/src/cli/index.ts +153 -0
  124. package/oh-my-opencode/src/cli/install.ts +523 -0
  125. package/oh-my-opencode/src/cli/model-fallback.ts +246 -0
  126. package/oh-my-opencode/src/cli/run/completion.test.ts +170 -0
  127. package/oh-my-opencode/src/cli/run/completion.ts +79 -0
  128. package/oh-my-opencode/src/cli/run/events.test.ts +155 -0
  129. package/oh-my-opencode/src/cli/run/events.ts +325 -0
  130. package/oh-my-opencode/src/cli/run/index.ts +2 -0
  131. package/oh-my-opencode/src/cli/run/runner.ts +159 -0
  132. package/oh-my-opencode/src/cli/run/types.ts +76 -0
  133. package/oh-my-opencode/src/cli/types.ts +40 -0
  134. package/oh-my-opencode/src/config/index.ts +26 -0
  135. package/oh-my-opencode/src/config/schema.test.ts +444 -0
  136. package/oh-my-opencode/src/config/schema.ts +339 -0
  137. package/oh-my-opencode/src/features/AGENTS.md +77 -0
  138. package/oh-my-opencode/src/features/background-agent/concurrency.test.ts +418 -0
  139. package/oh-my-opencode/src/features/background-agent/concurrency.ts +137 -0
  140. package/oh-my-opencode/src/features/background-agent/index.ts +3 -0
  141. package/oh-my-opencode/src/features/background-agent/manager.test.ts +1928 -0
  142. package/oh-my-opencode/src/features/background-agent/manager.ts +1335 -0
  143. package/oh-my-opencode/src/features/background-agent/types.ts +66 -0
  144. package/oh-my-opencode/src/features/boulder-state/constants.ts +13 -0
  145. package/oh-my-opencode/src/features/boulder-state/index.ts +3 -0
  146. package/oh-my-opencode/src/features/boulder-state/storage.test.ts +250 -0
  147. package/oh-my-opencode/src/features/boulder-state/storage.ts +150 -0
  148. package/oh-my-opencode/src/features/boulder-state/types.ts +26 -0
  149. package/oh-my-opencode/src/features/builtin-commands/commands.ts +89 -0
  150. package/oh-my-opencode/src/features/builtin-commands/index.ts +2 -0
  151. package/oh-my-opencode/src/features/builtin-commands/templates/init-deep.ts +300 -0
  152. package/oh-my-opencode/src/features/builtin-commands/templates/ralph-loop.ts +38 -0
  153. package/oh-my-opencode/src/features/builtin-commands/templates/refactor.ts +619 -0
  154. package/oh-my-opencode/src/features/builtin-commands/templates/start-work.ts +72 -0
  155. package/oh-my-opencode/src/features/builtin-commands/types.ts +9 -0
  156. package/oh-my-opencode/src/features/builtin-skills/frontend-ui-ux/SKILL.md +78 -0
  157. package/oh-my-opencode/src/features/builtin-skills/git-master/SKILL.md +1105 -0
  158. package/oh-my-opencode/src/features/builtin-skills/index.ts +2 -0
  159. package/oh-my-opencode/src/features/builtin-skills/skills.ts +1203 -0
  160. package/oh-my-opencode/src/features/builtin-skills/types.ts +16 -0
  161. package/oh-my-opencode/src/features/claude-code-agent-loader/index.ts +2 -0
  162. package/oh-my-opencode/src/features/claude-code-agent-loader/loader.ts +90 -0
  163. package/oh-my-opencode/src/features/claude-code-agent-loader/types.ts +17 -0
  164. package/oh-my-opencode/src/features/claude-code-command-loader/index.ts +2 -0
  165. package/oh-my-opencode/src/features/claude-code-command-loader/loader.ts +144 -0
  166. package/oh-my-opencode/src/features/claude-code-command-loader/types.ts +46 -0
  167. package/oh-my-opencode/src/features/claude-code-mcp-loader/env-expander.ts +27 -0
  168. package/oh-my-opencode/src/features/claude-code-mcp-loader/index.ts +11 -0
  169. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.test.ts +162 -0
  170. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.ts +113 -0
  171. package/oh-my-opencode/src/features/claude-code-mcp-loader/transformer.ts +53 -0
  172. package/oh-my-opencode/src/features/claude-code-mcp-loader/types.ts +42 -0
  173. package/oh-my-opencode/src/features/claude-code-plugin-loader/index.ts +3 -0
  174. package/oh-my-opencode/src/features/claude-code-plugin-loader/loader.ts +486 -0
  175. package/oh-my-opencode/src/features/claude-code-plugin-loader/types.ts +210 -0
  176. package/oh-my-opencode/src/features/claude-code-session-state/index.ts +1 -0
  177. package/oh-my-opencode/src/features/claude-code-session-state/state.test.ts +126 -0
  178. package/oh-my-opencode/src/features/claude-code-session-state/state.ts +37 -0
  179. package/oh-my-opencode/src/features/context-injector/collector.test.ts +330 -0
  180. package/oh-my-opencode/src/features/context-injector/collector.ts +85 -0
  181. package/oh-my-opencode/src/features/context-injector/index.ts +14 -0
  182. package/oh-my-opencode/src/features/context-injector/injector.test.ts +122 -0
  183. package/oh-my-opencode/src/features/context-injector/injector.ts +167 -0
  184. package/oh-my-opencode/src/features/context-injector/types.ts +91 -0
  185. package/oh-my-opencode/src/features/hook-message-injector/constants.ts +6 -0
  186. package/oh-my-opencode/src/features/hook-message-injector/index.ts +4 -0
  187. package/oh-my-opencode/src/features/hook-message-injector/injector.ts +195 -0
  188. package/oh-my-opencode/src/features/hook-message-injector/types.ts +47 -0
  189. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.test.ts +448 -0
  190. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.ts +180 -0
  191. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.test.ts +210 -0
  192. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.ts +62 -0
  193. package/oh-my-opencode/src/features/opencode-skill-loader/discover-worker.ts +59 -0
  194. package/oh-my-opencode/src/features/opencode-skill-loader/index.ts +4 -0
  195. package/oh-my-opencode/src/features/opencode-skill-loader/loader.test.ts +273 -0
  196. package/oh-my-opencode/src/features/opencode-skill-loader/loader.ts +259 -0
  197. package/oh-my-opencode/src/features/opencode-skill-loader/merger.ts +267 -0
  198. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.test.ts +267 -0
  199. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.ts +206 -0
  200. package/oh-my-opencode/src/features/opencode-skill-loader/types.ts +38 -0
  201. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.test.ts +201 -0
  202. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.ts +27 -0
  203. package/oh-my-opencode/src/features/skill-mcp-manager/index.ts +2 -0
  204. package/oh-my-opencode/src/features/skill-mcp-manager/manager.test.ts +611 -0
  205. package/oh-my-opencode/src/features/skill-mcp-manager/manager.ts +520 -0
  206. package/oh-my-opencode/src/features/skill-mcp-manager/types.ts +14 -0
  207. package/oh-my-opencode/src/features/task-toast-manager/index.ts +2 -0
  208. package/oh-my-opencode/src/features/task-toast-manager/manager.test.ts +249 -0
  209. package/oh-my-opencode/src/features/task-toast-manager/manager.ts +215 -0
  210. package/oh-my-opencode/src/features/task-toast-manager/types.ts +24 -0
  211. package/oh-my-opencode/src/hooks/AGENTS.md +73 -0
  212. package/oh-my-opencode/src/hooks/agent-usage-reminder/constants.ts +54 -0
  213. package/oh-my-opencode/src/hooks/agent-usage-reminder/index.ts +109 -0
  214. package/oh-my-opencode/src/hooks/agent-usage-reminder/storage.ts +42 -0
  215. package/oh-my-opencode/src/hooks/agent-usage-reminder/types.ts +6 -0
  216. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +307 -0
  217. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.ts +485 -0
  218. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/index.ts +151 -0
  219. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/parser.ts +201 -0
  220. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts +33 -0
  221. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts +184 -0
  222. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts +44 -0
  223. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +77 -0
  224. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.ts +250 -0
  225. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/types.ts +42 -0
  226. package/oh-my-opencode/src/hooks/atlas/index.test.ts +953 -0
  227. package/oh-my-opencode/src/hooks/atlas/index.ts +771 -0
  228. package/oh-my-opencode/src/hooks/auto-slash-command/constants.ts +12 -0
  229. package/oh-my-opencode/src/hooks/auto-slash-command/detector.test.ts +296 -0
  230. package/oh-my-opencode/src/hooks/auto-slash-command/detector.ts +65 -0
  231. package/oh-my-opencode/src/hooks/auto-slash-command/executor.ts +205 -0
  232. package/oh-my-opencode/src/hooks/auto-slash-command/index.test.ts +254 -0
  233. package/oh-my-opencode/src/hooks/auto-slash-command/index.ts +89 -0
  234. package/oh-my-opencode/src/hooks/auto-slash-command/types.ts +23 -0
  235. package/oh-my-opencode/src/hooks/auto-update-checker/cache.ts +93 -0
  236. package/oh-my-opencode/src/hooks/auto-update-checker/checker.test.ts +24 -0
  237. package/oh-my-opencode/src/hooks/auto-update-checker/checker.ts +284 -0
  238. package/oh-my-opencode/src/hooks/auto-update-checker/constants.ts +64 -0
  239. package/oh-my-opencode/src/hooks/auto-update-checker/index.test.ts +254 -0
  240. package/oh-my-opencode/src/hooks/auto-update-checker/index.ts +260 -0
  241. package/oh-my-opencode/src/hooks/auto-update-checker/types.ts +29 -0
  242. package/oh-my-opencode/src/hooks/background-compaction/index.ts +87 -0
  243. package/oh-my-opencode/src/hooks/background-notification/index.ts +28 -0
  244. package/oh-my-opencode/src/hooks/background-notification/types.ts +5 -0
  245. package/oh-my-opencode/src/hooks/claude-code-hooks/AGENTS.md +70 -0
  246. package/oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts +107 -0
  247. package/oh-my-opencode/src/hooks/claude-code-hooks/config.ts +103 -0
  248. package/oh-my-opencode/src/hooks/claude-code-hooks/index.ts +401 -0
  249. package/oh-my-opencode/src/hooks/claude-code-hooks/plugin-config.ts +12 -0
  250. package/oh-my-opencode/src/hooks/claude-code-hooks/post-tool-use.ts +199 -0
  251. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-compact.ts +109 -0
  252. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-tool-use.ts +172 -0
  253. package/oh-my-opencode/src/hooks/claude-code-hooks/stop.ts +118 -0
  254. package/oh-my-opencode/src/hooks/claude-code-hooks/todo.ts +76 -0
  255. package/oh-my-opencode/src/hooks/claude-code-hooks/tool-input-cache.ts +47 -0
  256. package/oh-my-opencode/src/hooks/claude-code-hooks/transcript.ts +252 -0
  257. package/oh-my-opencode/src/hooks/claude-code-hooks/types.ts +204 -0
  258. package/oh-my-opencode/src/hooks/claude-code-hooks/user-prompt-submit.ts +117 -0
  259. package/oh-my-opencode/src/hooks/comment-checker/cli.test.ts +68 -0
  260. package/oh-my-opencode/src/hooks/comment-checker/cli.ts +221 -0
  261. package/oh-my-opencode/src/hooks/comment-checker/downloader.ts +196 -0
  262. package/oh-my-opencode/src/hooks/comment-checker/index.ts +171 -0
  263. package/oh-my-opencode/src/hooks/comment-checker/types.ts +33 -0
  264. package/oh-my-opencode/src/hooks/compaction-context-injector/index.ts +61 -0
  265. package/oh-my-opencode/src/hooks/context-window-monitor.ts +99 -0
  266. package/oh-my-opencode/src/hooks/delegate-task-retry/index.test.ts +119 -0
  267. package/oh-my-opencode/src/hooks/delegate-task-retry/index.ts +136 -0
  268. package/oh-my-opencode/src/hooks/directory-agents-injector/constants.ts +9 -0
  269. package/oh-my-opencode/src/hooks/directory-agents-injector/index.ts +182 -0
  270. package/oh-my-opencode/src/hooks/directory-agents-injector/storage.ts +48 -0
  271. package/oh-my-opencode/src/hooks/directory-agents-injector/types.ts +5 -0
  272. package/oh-my-opencode/src/hooks/directory-readme-injector/constants.ts +9 -0
  273. package/oh-my-opencode/src/hooks/directory-readme-injector/index.ts +177 -0
  274. package/oh-my-opencode/src/hooks/directory-readme-injector/storage.ts +48 -0
  275. package/oh-my-opencode/src/hooks/directory-readme-injector/types.ts +5 -0
  276. package/oh-my-opencode/src/hooks/edit-error-recovery/index.test.ts +126 -0
  277. package/oh-my-opencode/src/hooks/edit-error-recovery/index.ts +57 -0
  278. package/oh-my-opencode/src/hooks/empty-task-response-detector.ts +27 -0
  279. package/oh-my-opencode/src/hooks/index.ts +32 -0
  280. package/oh-my-opencode/src/hooks/interactive-bash-session/constants.ts +15 -0
  281. package/oh-my-opencode/src/hooks/interactive-bash-session/index.ts +262 -0
  282. package/oh-my-opencode/src/hooks/interactive-bash-session/storage.ts +59 -0
  283. package/oh-my-opencode/src/hooks/interactive-bash-session/types.ts +11 -0
  284. package/oh-my-opencode/src/hooks/keyword-detector/constants.ts +300 -0
  285. package/oh-my-opencode/src/hooks/keyword-detector/detector.ts +52 -0
  286. package/oh-my-opencode/src/hooks/keyword-detector/index.test.ts +529 -0
  287. package/oh-my-opencode/src/hooks/keyword-detector/index.ts +100 -0
  288. package/oh-my-opencode/src/hooks/keyword-detector/types.ts +4 -0
  289. package/oh-my-opencode/src/hooks/non-interactive-env/constants.ts +70 -0
  290. package/oh-my-opencode/src/hooks/non-interactive-env/detector.ts +19 -0
  291. package/oh-my-opencode/src/hooks/non-interactive-env/index.test.ts +323 -0
  292. package/oh-my-opencode/src/hooks/non-interactive-env/index.ts +63 -0
  293. package/oh-my-opencode/src/hooks/non-interactive-env/types.ts +3 -0
  294. package/oh-my-opencode/src/hooks/prometheus-md-only/constants.ts +32 -0
  295. package/oh-my-opencode/src/hooks/prometheus-md-only/index.test.ts +488 -0
  296. package/oh-my-opencode/src/hooks/prometheus-md-only/index.ts +136 -0
  297. package/oh-my-opencode/src/hooks/ralph-loop/constants.ts +5 -0
  298. package/oh-my-opencode/src/hooks/ralph-loop/index.test.ts +835 -0
  299. package/oh-my-opencode/src/hooks/ralph-loop/index.ts +417 -0
  300. package/oh-my-opencode/src/hooks/ralph-loop/storage.ts +115 -0
  301. package/oh-my-opencode/src/hooks/ralph-loop/types.ts +19 -0
  302. package/oh-my-opencode/src/hooks/rules-injector/constants.ts +30 -0
  303. package/oh-my-opencode/src/hooks/rules-injector/finder.test.ts +381 -0
  304. package/oh-my-opencode/src/hooks/rules-injector/finder.ts +263 -0
  305. package/oh-my-opencode/src/hooks/rules-injector/index.ts +223 -0
  306. package/oh-my-opencode/src/hooks/rules-injector/matcher.ts +63 -0
  307. package/oh-my-opencode/src/hooks/rules-injector/parser.test.ts +226 -0
  308. package/oh-my-opencode/src/hooks/rules-injector/parser.ts +211 -0
  309. package/oh-my-opencode/src/hooks/rules-injector/storage.ts +59 -0
  310. package/oh-my-opencode/src/hooks/rules-injector/types.ts +57 -0
  311. package/oh-my-opencode/src/hooks/session-notification-utils.ts +140 -0
  312. package/oh-my-opencode/src/hooks/session-notification.test.ts +361 -0
  313. package/oh-my-opencode/src/hooks/session-notification.ts +330 -0
  314. package/oh-my-opencode/src/hooks/session-recovery/constants.ts +10 -0
  315. package/oh-my-opencode/src/hooks/session-recovery/index.test.ts +223 -0
  316. package/oh-my-opencode/src/hooks/session-recovery/index.ts +435 -0
  317. package/oh-my-opencode/src/hooks/session-recovery/storage.ts +390 -0
  318. package/oh-my-opencode/src/hooks/session-recovery/types.ts +98 -0
  319. package/oh-my-opencode/src/hooks/start-work/index.test.ts +402 -0
  320. package/oh-my-opencode/src/hooks/start-work/index.ts +242 -0
  321. package/oh-my-opencode/src/hooks/task-resume-info/index.ts +36 -0
  322. package/oh-my-opencode/src/hooks/think-mode/detector.ts +57 -0
  323. package/oh-my-opencode/src/hooks/think-mode/index.test.ts +353 -0
  324. package/oh-my-opencode/src/hooks/think-mode/index.ts +89 -0
  325. package/oh-my-opencode/src/hooks/think-mode/switcher.test.ts +461 -0
  326. package/oh-my-opencode/src/hooks/think-mode/switcher.ts +222 -0
  327. package/oh-my-opencode/src/hooks/think-mode/types.ts +21 -0
  328. package/oh-my-opencode/src/hooks/thinking-block-validator/index.ts +171 -0
  329. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.test.ts +876 -0
  330. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.ts +480 -0
  331. package/oh-my-opencode/src/hooks/tool-output-truncator.test.ts +168 -0
  332. package/oh-my-opencode/src/hooks/tool-output-truncator.ts +61 -0
  333. package/oh-my-opencode/src/index.ts +589 -0
  334. package/oh-my-opencode/src/mcp/AGENTS.md +70 -0
  335. package/oh-my-opencode/src/mcp/context7.ts +6 -0
  336. package/oh-my-opencode/src/mcp/grep-app.ts +6 -0
  337. package/oh-my-opencode/src/mcp/index.test.ts +86 -0
  338. package/oh-my-opencode/src/mcp/index.ts +32 -0
  339. package/oh-my-opencode/src/mcp/types.ts +9 -0
  340. package/oh-my-opencode/src/mcp/websearch.ts +10 -0
  341. package/oh-my-opencode/src/plugin-config.test.ts +119 -0
  342. package/oh-my-opencode/src/plugin-config.ts +135 -0
  343. package/oh-my-opencode/src/plugin-handlers/config-handler.test.ts +103 -0
  344. package/oh-my-opencode/src/plugin-handlers/config-handler.ts +399 -0
  345. package/oh-my-opencode/src/plugin-handlers/index.ts +1 -0
  346. package/oh-my-opencode/src/plugin-state.ts +30 -0
  347. package/oh-my-opencode/src/shared/AGENTS.md +63 -0
  348. package/oh-my-opencode/src/shared/agent-tool-restrictions.ts +44 -0
  349. package/oh-my-opencode/src/shared/agent-variant.test.ts +83 -0
  350. package/oh-my-opencode/src/shared/agent-variant.ts +40 -0
  351. package/oh-my-opencode/src/shared/claude-config-dir.test.ts +60 -0
  352. package/oh-my-opencode/src/shared/claude-config-dir.ts +11 -0
  353. package/oh-my-opencode/src/shared/command-executor.ts +225 -0
  354. package/oh-my-opencode/src/shared/config-errors.ts +18 -0
  355. package/oh-my-opencode/src/shared/config-path.ts +47 -0
  356. package/oh-my-opencode/src/shared/data-path.ts +22 -0
  357. package/oh-my-opencode/src/shared/deep-merge.test.ts +336 -0
  358. package/oh-my-opencode/src/shared/deep-merge.ts +53 -0
  359. package/oh-my-opencode/src/shared/dynamic-truncator.ts +193 -0
  360. package/oh-my-opencode/src/shared/external-plugin-detector.test.ts +133 -0
  361. package/oh-my-opencode/src/shared/external-plugin-detector.ts +132 -0
  362. package/oh-my-opencode/src/shared/file-reference-resolver.ts +85 -0
  363. package/oh-my-opencode/src/shared/file-utils.ts +40 -0
  364. package/oh-my-opencode/src/shared/first-message-variant.test.ts +32 -0
  365. package/oh-my-opencode/src/shared/first-message-variant.ts +28 -0
  366. package/oh-my-opencode/src/shared/frontmatter.test.ts +262 -0
  367. package/oh-my-opencode/src/shared/frontmatter.ts +31 -0
  368. package/oh-my-opencode/src/shared/hook-disabled.ts +22 -0
  369. package/oh-my-opencode/src/shared/index.ts +29 -0
  370. package/oh-my-opencode/src/shared/jsonc-parser.test.ts +266 -0
  371. package/oh-my-opencode/src/shared/jsonc-parser.ts +66 -0
  372. package/oh-my-opencode/src/shared/logger.ts +20 -0
  373. package/oh-my-opencode/src/shared/migration.test.ts +602 -0
  374. package/oh-my-opencode/src/shared/migration.ts +191 -0
  375. package/oh-my-opencode/src/shared/model-resolver.test.ts +101 -0
  376. package/oh-my-opencode/src/shared/model-resolver.ts +35 -0
  377. package/oh-my-opencode/src/shared/model-sanitizer.ts +12 -0
  378. package/oh-my-opencode/src/shared/opencode-config-dir.test.ts +318 -0
  379. package/oh-my-opencode/src/shared/opencode-config-dir.ts +142 -0
  380. package/oh-my-opencode/src/shared/opencode-version.test.ts +223 -0
  381. package/oh-my-opencode/src/shared/opencode-version.ts +72 -0
  382. package/oh-my-opencode/src/shared/pattern-matcher.ts +29 -0
  383. package/oh-my-opencode/src/shared/permission-compat.test.ts +134 -0
  384. package/oh-my-opencode/src/shared/permission-compat.ts +77 -0
  385. package/oh-my-opencode/src/shared/session-cursor.test.ts +66 -0
  386. package/oh-my-opencode/src/shared/session-cursor.ts +85 -0
  387. package/oh-my-opencode/src/shared/shell-env.test.ts +278 -0
  388. package/oh-my-opencode/src/shared/shell-env.ts +111 -0
  389. package/oh-my-opencode/src/shared/snake-case.ts +49 -0
  390. package/oh-my-opencode/src/shared/system-directive.ts +40 -0
  391. package/oh-my-opencode/src/shared/tool-name.ts +26 -0
  392. package/oh-my-opencode/src/shared/zip-extractor.ts +83 -0
  393. package/oh-my-opencode/src/tools/AGENTS.md +74 -0
  394. package/oh-my-opencode/src/tools/ast-grep/cli.ts +230 -0
  395. package/oh-my-opencode/src/tools/ast-grep/constants.ts +261 -0
  396. package/oh-my-opencode/src/tools/ast-grep/downloader.ts +128 -0
  397. package/oh-my-opencode/src/tools/ast-grep/index.ts +13 -0
  398. package/oh-my-opencode/src/tools/ast-grep/tools.ts +112 -0
  399. package/oh-my-opencode/src/tools/ast-grep/types.ts +61 -0
  400. package/oh-my-opencode/src/tools/ast-grep/utils.ts +102 -0
  401. package/oh-my-opencode/src/tools/background-task/constants.ts +7 -0
  402. package/oh-my-opencode/src/tools/background-task/index.ts +7 -0
  403. package/oh-my-opencode/src/tools/background-task/tools.ts +479 -0
  404. package/oh-my-opencode/src/tools/background-task/types.ts +16 -0
  405. package/oh-my-opencode/src/tools/call-omo-agent/constants.ts +7 -0
  406. package/oh-my-opencode/src/tools/call-omo-agent/index.ts +3 -0
  407. package/oh-my-opencode/src/tools/call-omo-agent/tools.ts +338 -0
  408. package/oh-my-opencode/src/tools/call-omo-agent/types.ts +27 -0
  409. package/oh-my-opencode/src/tools/delegate-task/constants.ts +205 -0
  410. package/oh-my-opencode/src/tools/delegate-task/index.ts +3 -0
  411. package/oh-my-opencode/src/tools/delegate-task/tools.test.ts +1575 -0
  412. package/oh-my-opencode/src/tools/delegate-task/tools.ts +885 -0
  413. package/oh-my-opencode/src/tools/delegate-task/types.ts +9 -0
  414. package/oh-my-opencode/src/tools/glob/cli.test.ts +158 -0
  415. package/oh-my-opencode/src/tools/glob/cli.ts +191 -0
  416. package/oh-my-opencode/src/tools/glob/constants.ts +12 -0
  417. package/oh-my-opencode/src/tools/glob/index.ts +3 -0
  418. package/oh-my-opencode/src/tools/glob/tools.ts +41 -0
  419. package/oh-my-opencode/src/tools/glob/types.ts +22 -0
  420. package/oh-my-opencode/src/tools/glob/utils.ts +26 -0
  421. package/oh-my-opencode/src/tools/grep/cli.ts +229 -0
  422. package/oh-my-opencode/src/tools/grep/constants.ts +127 -0
  423. package/oh-my-opencode/src/tools/grep/downloader.test.ts +103 -0
  424. package/oh-my-opencode/src/tools/grep/downloader.ts +145 -0
  425. package/oh-my-opencode/src/tools/grep/index.ts +3 -0
  426. package/oh-my-opencode/src/tools/grep/tools.ts +40 -0
  427. package/oh-my-opencode/src/tools/grep/types.ts +39 -0
  428. package/oh-my-opencode/src/tools/grep/utils.ts +53 -0
  429. package/oh-my-opencode/src/tools/index.ts +72 -0
  430. package/oh-my-opencode/src/tools/interactive-bash/constants.ts +18 -0
  431. package/oh-my-opencode/src/tools/interactive-bash/index.ts +4 -0
  432. package/oh-my-opencode/src/tools/interactive-bash/tools.ts +126 -0
  433. package/oh-my-opencode/src/tools/interactive-bash/utils.ts +71 -0
  434. package/oh-my-opencode/src/tools/look-at/constants.ts +3 -0
  435. package/oh-my-opencode/src/tools/look-at/index.ts +3 -0
  436. package/oh-my-opencode/src/tools/look-at/tools.test.ts +73 -0
  437. package/oh-my-opencode/src/tools/look-at/tools.ts +173 -0
  438. package/oh-my-opencode/src/tools/look-at/types.ts +4 -0
  439. package/oh-my-opencode/src/tools/lsp/client.ts +596 -0
  440. package/oh-my-opencode/src/tools/lsp/config.test.ts +130 -0
  441. package/oh-my-opencode/src/tools/lsp/config.ts +285 -0
  442. package/oh-my-opencode/src/tools/lsp/constants.ts +390 -0
  443. package/oh-my-opencode/src/tools/lsp/index.ts +7 -0
  444. package/oh-my-opencode/src/tools/lsp/tools.ts +261 -0
  445. package/oh-my-opencode/src/tools/lsp/types.ts +124 -0
  446. package/oh-my-opencode/src/tools/lsp/utils.ts +406 -0
  447. package/oh-my-opencode/src/tools/session-manager/constants.ts +97 -0
  448. package/oh-my-opencode/src/tools/session-manager/index.ts +3 -0
  449. package/oh-my-opencode/src/tools/session-manager/storage.test.ts +315 -0
  450. package/oh-my-opencode/src/tools/session-manager/storage.ts +238 -0
  451. package/oh-my-opencode/src/tools/session-manager/tools.test.ts +124 -0
  452. package/oh-my-opencode/src/tools/session-manager/tools.ts +146 -0
  453. package/oh-my-opencode/src/tools/session-manager/types.ts +99 -0
  454. package/oh-my-opencode/src/tools/session-manager/utils.test.ts +160 -0
  455. package/oh-my-opencode/src/tools/session-manager/utils.ts +199 -0
  456. package/oh-my-opencode/src/tools/skill/constants.ts +8 -0
  457. package/oh-my-opencode/src/tools/skill/index.ts +3 -0
  458. package/oh-my-opencode/src/tools/skill/tools.test.ts +239 -0
  459. package/oh-my-opencode/src/tools/skill/tools.ts +200 -0
  460. package/oh-my-opencode/src/tools/skill/types.ts +31 -0
  461. package/oh-my-opencode/src/tools/skill-mcp/constants.ts +3 -0
  462. package/oh-my-opencode/src/tools/skill-mcp/index.ts +3 -0
  463. package/oh-my-opencode/src/tools/skill-mcp/tools.test.ts +215 -0
  464. package/oh-my-opencode/src/tools/skill-mcp/tools.ts +172 -0
  465. package/oh-my-opencode/src/tools/skill-mcp/types.ts +8 -0
  466. package/oh-my-opencode/src/tools/slashcommand/index.ts +2 -0
  467. package/oh-my-opencode/src/tools/slashcommand/tools.ts +252 -0
  468. package/oh-my-opencode/src/tools/slashcommand/types.ts +28 -0
  469. package/oh-my-opencode/test-setup.ts +6 -0
  470. package/oh-my-opencode/tsconfig.json +20 -0
  471. package/package.json +1 -4
  472. package/src/__tests__/git.test.ts +7 -2
  473. package/src/__tests__/manifest.test.ts +5 -5
  474. package/src/agents/repo-explorer.ts +2 -1
  475. package/src/git.ts +18 -3
  476. package/src/manifest.ts +22 -15
  477. package/src/scanner.ts +41 -32
@@ -0,0 +1,611 @@
1
+ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"
2
+ import { SkillMcpManager } from "./manager"
3
+ import type { SkillMcpClientInfo, SkillMcpServerContext } from "./types"
4
+ import type { ClaudeCodeMcpServer } from "../claude-code-mcp-loader/types"
5
+
6
+
7
+
8
+ // Mock the MCP SDK transports to avoid network calls
9
+ const mockHttpConnect = mock(() => Promise.reject(new Error("Mocked HTTP connection failure")))
10
+ const mockHttpClose = mock(() => Promise.resolve())
11
+ let lastTransportInstance: { url?: URL; options?: { requestInit?: RequestInit } } = {}
12
+
13
+ mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
14
+ StreamableHTTPClientTransport: class MockStreamableHTTPClientTransport {
15
+ constructor(public url: URL, public options?: { requestInit?: RequestInit }) {
16
+ lastTransportInstance = { url, options }
17
+ }
18
+ async start() {
19
+ await mockHttpConnect()
20
+ }
21
+ async close() {
22
+ await mockHttpClose()
23
+ }
24
+ },
25
+ }))
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ describe("SkillMcpManager", () => {
41
+ let manager: SkillMcpManager
42
+
43
+ beforeEach(() => {
44
+ manager = new SkillMcpManager()
45
+ mockHttpConnect.mockClear()
46
+ mockHttpClose.mockClear()
47
+ })
48
+
49
+ afterEach(async () => {
50
+ await manager.disconnectAll()
51
+ })
52
+
53
+ describe("getOrCreateClient", () => {
54
+ describe("configuration validation", () => {
55
+ it("throws error when neither url nor command is provided", async () => {
56
+ // #given
57
+ const info: SkillMcpClientInfo = {
58
+ serverName: "test-server",
59
+ skillName: "test-skill",
60
+ sessionID: "session-1",
61
+ }
62
+ const config: ClaudeCodeMcpServer = {}
63
+
64
+ // #when / #then
65
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
66
+ /no valid connection configuration/
67
+ )
68
+ })
69
+
70
+ it("includes both HTTP and stdio examples in error message", async () => {
71
+ // #given
72
+ const info: SkillMcpClientInfo = {
73
+ serverName: "my-mcp",
74
+ skillName: "data-skill",
75
+ sessionID: "session-1",
76
+ }
77
+ const config: ClaudeCodeMcpServer = {}
78
+
79
+ // #when / #then
80
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
81
+ /HTTP[\s\S]*Stdio/
82
+ )
83
+ })
84
+
85
+ it("includes server and skill names in error message", async () => {
86
+ // #given
87
+ const info: SkillMcpClientInfo = {
88
+ serverName: "custom-server",
89
+ skillName: "custom-skill",
90
+ sessionID: "session-1",
91
+ }
92
+ const config: ClaudeCodeMcpServer = {}
93
+
94
+ // #when / #then
95
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
96
+ /custom-server[\s\S]*custom-skill/
97
+ )
98
+ })
99
+ })
100
+
101
+ describe("connection type detection", () => {
102
+ it("detects HTTP connection from explicit type='http'", async () => {
103
+ // #given
104
+ const info: SkillMcpClientInfo = {
105
+ serverName: "http-server",
106
+ skillName: "test-skill",
107
+ sessionID: "session-1",
108
+ }
109
+ const config: ClaudeCodeMcpServer = {
110
+ type: "http",
111
+ url: "https://example.com/mcp",
112
+ }
113
+
114
+ // #when / #then - should fail at connection, not config validation
115
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
116
+ /Failed to connect/
117
+ )
118
+ })
119
+
120
+ it("detects HTTP connection from explicit type='sse'", async () => {
121
+ // #given
122
+ const info: SkillMcpClientInfo = {
123
+ serverName: "sse-server",
124
+ skillName: "test-skill",
125
+ sessionID: "session-1",
126
+ }
127
+ const config: ClaudeCodeMcpServer = {
128
+ type: "sse",
129
+ url: "https://example.com/mcp",
130
+ }
131
+
132
+ // #when / #then - should fail at connection, not config validation
133
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
134
+ /Failed to connect/
135
+ )
136
+ })
137
+
138
+ it("detects HTTP connection from url field when type is not specified", async () => {
139
+ // #given
140
+ const info: SkillMcpClientInfo = {
141
+ serverName: "inferred-http",
142
+ skillName: "test-skill",
143
+ sessionID: "session-1",
144
+ }
145
+ const config: ClaudeCodeMcpServer = {
146
+ url: "https://example.com/mcp",
147
+ }
148
+
149
+ // #when / #then - should fail at connection, not config validation
150
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
151
+ /Failed to connect[\s\S]*URL/
152
+ )
153
+ })
154
+
155
+ it("detects stdio connection from explicit type='stdio'", async () => {
156
+ // #given
157
+ const info: SkillMcpClientInfo = {
158
+ serverName: "stdio-server",
159
+ skillName: "test-skill",
160
+ sessionID: "session-1",
161
+ }
162
+ const config: ClaudeCodeMcpServer = {
163
+ type: "stdio",
164
+ command: "node",
165
+ args: ["-e", "process.exit(0)"],
166
+ }
167
+
168
+ // #when / #then - should fail at connection, not config validation
169
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
170
+ /Failed to connect[\s\S]*Command/
171
+ )
172
+ })
173
+
174
+ it("detects stdio connection from command field when type is not specified", async () => {
175
+ // #given
176
+ const info: SkillMcpClientInfo = {
177
+ serverName: "inferred-stdio",
178
+ skillName: "test-skill",
179
+ sessionID: "session-1",
180
+ }
181
+ const config: ClaudeCodeMcpServer = {
182
+ command: "node",
183
+ args: ["-e", "process.exit(0)"],
184
+ }
185
+
186
+ // #when / #then - should fail at connection, not config validation
187
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
188
+ /Failed to connect[\s\S]*Command/
189
+ )
190
+ })
191
+
192
+ it("prefers explicit type over inferred type", async () => {
193
+ // #given - has both url and command, but type is explicitly stdio
194
+ const info: SkillMcpClientInfo = {
195
+ serverName: "mixed-config",
196
+ skillName: "test-skill",
197
+ sessionID: "session-1",
198
+ }
199
+ const config: ClaudeCodeMcpServer = {
200
+ type: "stdio",
201
+ url: "https://example.com/mcp", // should be ignored
202
+ command: "node",
203
+ args: ["-e", "process.exit(0)"],
204
+ }
205
+
206
+ // #when / #then - should use stdio (show Command in error, not URL)
207
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
208
+ /Command: node/
209
+ )
210
+ })
211
+ })
212
+
213
+ describe("HTTP connection", () => {
214
+ it("throws error for invalid URL", async () => {
215
+ // #given
216
+ const info: SkillMcpClientInfo = {
217
+ serverName: "bad-url-server",
218
+ skillName: "test-skill",
219
+ sessionID: "session-1",
220
+ }
221
+ const config: ClaudeCodeMcpServer = {
222
+ type: "http",
223
+ url: "not-a-valid-url",
224
+ }
225
+
226
+ // #when / #then
227
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
228
+ /invalid URL/
229
+ )
230
+ })
231
+
232
+ it("includes URL in HTTP connection error", async () => {
233
+ // #given
234
+ const info: SkillMcpClientInfo = {
235
+ serverName: "http-error-server",
236
+ skillName: "test-skill",
237
+ sessionID: "session-1",
238
+ }
239
+ const config: ClaudeCodeMcpServer = {
240
+ url: "https://nonexistent.example.com/mcp",
241
+ }
242
+
243
+ // #when / #then
244
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
245
+ /https:\/\/nonexistent\.example\.com\/mcp/
246
+ )
247
+ })
248
+
249
+ it("includes helpful hints for HTTP connection failures", async () => {
250
+ // #given
251
+ const info: SkillMcpClientInfo = {
252
+ serverName: "hint-server",
253
+ skillName: "test-skill",
254
+ sessionID: "session-1",
255
+ }
256
+ const config: ClaudeCodeMcpServer = {
257
+ url: "https://nonexistent.example.com/mcp",
258
+ }
259
+
260
+ // #when / #then
261
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
262
+ /Hints[\s\S]*Verify the URL[\s\S]*authentication headers[\s\S]*MCP over HTTP/
263
+ )
264
+ })
265
+
266
+ it("calls mocked transport connect for HTTP connections", async () => {
267
+ // #given
268
+ const info: SkillMcpClientInfo = {
269
+ serverName: "mock-test-server",
270
+ skillName: "test-skill",
271
+ sessionID: "session-1",
272
+ }
273
+ const config: ClaudeCodeMcpServer = {
274
+ url: "https://example.com/mcp",
275
+ }
276
+
277
+ // #when
278
+ try {
279
+ await manager.getOrCreateClient(info, config)
280
+ } catch {
281
+ // Expected to fail
282
+ }
283
+
284
+ // #then - verify mock was called (transport was instantiated)
285
+ // The connection attempt happens through the Client.connect() which
286
+ // internally calls transport.start()
287
+ expect(mockHttpConnect).toHaveBeenCalled()
288
+ })
289
+ })
290
+
291
+ describe("stdio connection (backward compatibility)", () => {
292
+ it("throws error when command is missing for stdio type", async () => {
293
+ // #given
294
+ const info: SkillMcpClientInfo = {
295
+ serverName: "missing-command",
296
+ skillName: "test-skill",
297
+ sessionID: "session-1",
298
+ }
299
+ const config: ClaudeCodeMcpServer = {
300
+ type: "stdio",
301
+ // command is missing
302
+ }
303
+
304
+ // #when / #then
305
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
306
+ /missing 'command' field/
307
+ )
308
+ })
309
+
310
+ it("includes command in stdio connection error", async () => {
311
+ // #given
312
+ const info: SkillMcpClientInfo = {
313
+ serverName: "test-server",
314
+ skillName: "test-skill",
315
+ sessionID: "session-1",
316
+ }
317
+ const config: ClaudeCodeMcpServer = {
318
+ command: "nonexistent-command-xyz",
319
+ args: ["--foo"],
320
+ }
321
+
322
+ // #when / #then
323
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
324
+ /nonexistent-command-xyz --foo/
325
+ )
326
+ })
327
+
328
+ it("includes helpful hints for stdio connection failures", async () => {
329
+ // #given
330
+ const info: SkillMcpClientInfo = {
331
+ serverName: "test-server",
332
+ skillName: "test-skill",
333
+ sessionID: "session-1",
334
+ }
335
+ const config: ClaudeCodeMcpServer = {
336
+ command: "nonexistent-command",
337
+ }
338
+
339
+ // #when / #then
340
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
341
+ /Hints[\s\S]*PATH[\s\S]*package exists/
342
+ )
343
+ })
344
+ })
345
+ })
346
+
347
+ describe("disconnectSession", () => {
348
+ it("removes all clients for a specific session", async () => {
349
+ // #given
350
+ const session1Info: SkillMcpClientInfo = {
351
+ serverName: "server1",
352
+ skillName: "skill1",
353
+ sessionID: "session-1",
354
+ }
355
+ const session2Info: SkillMcpClientInfo = {
356
+ serverName: "server1",
357
+ skillName: "skill1",
358
+ sessionID: "session-2",
359
+ }
360
+
361
+ // #when
362
+ await manager.disconnectSession("session-1")
363
+
364
+ // #then
365
+ expect(manager.isConnected(session1Info)).toBe(false)
366
+ expect(manager.isConnected(session2Info)).toBe(false)
367
+ })
368
+
369
+ it("does not throw when session has no clients", async () => {
370
+ // #given / #when / #then
371
+ await expect(manager.disconnectSession("nonexistent")).resolves.toBeUndefined()
372
+ })
373
+ })
374
+
375
+ describe("disconnectAll", () => {
376
+ it("clears all clients", async () => {
377
+ // #given - no actual clients connected (would require real MCP server)
378
+
379
+ // #when
380
+ await manager.disconnectAll()
381
+
382
+ // #then
383
+ expect(manager.getConnectedServers()).toEqual([])
384
+ })
385
+ })
386
+
387
+ describe("isConnected", () => {
388
+ it("returns false for unconnected server", () => {
389
+ // #given
390
+ const info: SkillMcpClientInfo = {
391
+ serverName: "unknown",
392
+ skillName: "test",
393
+ sessionID: "session-1",
394
+ }
395
+
396
+ // #when / #then
397
+ expect(manager.isConnected(info)).toBe(false)
398
+ })
399
+ })
400
+
401
+ describe("getConnectedServers", () => {
402
+ it("returns empty array when no servers connected", () => {
403
+ // #given / #when / #then
404
+ expect(manager.getConnectedServers()).toEqual([])
405
+ })
406
+ })
407
+
408
+ describe("environment variable handling", () => {
409
+ it("always inherits process.env even when config.env is undefined", async () => {
410
+ // #given
411
+ const info: SkillMcpClientInfo = {
412
+ serverName: "test-server",
413
+ skillName: "test-skill",
414
+ sessionID: "session-1",
415
+ }
416
+ const configWithoutEnv: ClaudeCodeMcpServer = {
417
+ command: "node",
418
+ args: ["-e", "process.exit(0)"],
419
+ }
420
+
421
+ // #when - attempt connection (will fail but exercises env merging code path)
422
+ // #then - should not throw "undefined" related errors for env
423
+ try {
424
+ await manager.getOrCreateClient(info, configWithoutEnv)
425
+ } catch (error) {
426
+ const message = error instanceof Error ? error.message : String(error)
427
+ expect(message).not.toContain("env")
428
+ expect(message).not.toContain("undefined")
429
+ }
430
+ })
431
+
432
+ it("overlays config.env on top of inherited process.env", async () => {
433
+ // #given
434
+ const info: SkillMcpClientInfo = {
435
+ serverName: "test-server",
436
+ skillName: "test-skill",
437
+ sessionID: "session-2",
438
+ }
439
+ const configWithEnv: ClaudeCodeMcpServer = {
440
+ command: "node",
441
+ args: ["-e", "process.exit(0)"],
442
+ env: {
443
+ CUSTOM_VAR: "custom_value",
444
+ },
445
+ }
446
+
447
+ // #when - attempt connection
448
+ // #then - should not throw, env merging should work
449
+ try {
450
+ await manager.getOrCreateClient(info, configWithEnv)
451
+ } catch (error) {
452
+ const message = error instanceof Error ? error.message : String(error)
453
+ expect(message).toContain("Failed to connect")
454
+ }
455
+ })
456
+ })
457
+
458
+ describe("HTTP headers handling", () => {
459
+ it("accepts configuration with headers", async () => {
460
+ // #given
461
+ const info: SkillMcpClientInfo = {
462
+ serverName: "auth-server",
463
+ skillName: "test-skill",
464
+ sessionID: "session-1",
465
+ }
466
+ const config: ClaudeCodeMcpServer = {
467
+ url: "https://example.com/mcp",
468
+ headers: {
469
+ Authorization: "Bearer test-token",
470
+ "X-Custom-Header": "custom-value",
471
+ },
472
+ }
473
+
474
+ // #when / #then - should fail at connection, not config validation
475
+ // Headers are passed through to the transport
476
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
477
+ /Failed to connect/
478
+ )
479
+
480
+ // Verify headers were forwarded to transport
481
+ expect(lastTransportInstance.options?.requestInit?.headers).toEqual({
482
+ Authorization: "Bearer test-token",
483
+ "X-Custom-Header": "custom-value",
484
+ })
485
+ })
486
+
487
+ it("works without headers (optional)", async () => {
488
+ // #given
489
+ const info: SkillMcpClientInfo = {
490
+ serverName: "no-auth-server",
491
+ skillName: "test-skill",
492
+ sessionID: "session-1",
493
+ }
494
+ const config: ClaudeCodeMcpServer = {
495
+ url: "https://example.com/mcp",
496
+ // no headers
497
+ }
498
+
499
+ // #when / #then - should fail at connection, not config validation
500
+ await expect(manager.getOrCreateClient(info, config)).rejects.toThrow(
501
+ /Failed to connect/
502
+ )
503
+ })
504
+ })
505
+
506
+ describe("operation retry logic", () => {
507
+ it("should retry operation when 'Not connected' error occurs", async () => {
508
+ // #given
509
+ const info: SkillMcpClientInfo = {
510
+ serverName: "retry-server",
511
+ skillName: "retry-skill",
512
+ sessionID: "session-retry-1",
513
+ }
514
+ const context: SkillMcpServerContext = {
515
+ config: {
516
+ url: "https://example.com/mcp",
517
+ },
518
+ skillName: "retry-skill",
519
+ }
520
+
521
+ // Mock client that fails first time with "Not connected", then succeeds
522
+ let callCount = 0
523
+ const mockClient = {
524
+ callTool: mock(async () => {
525
+ callCount++
526
+ if (callCount === 1) {
527
+ throw new Error("Not connected")
528
+ }
529
+ return { content: [{ type: "text", text: "success" }] }
530
+ }),
531
+ close: mock(() => Promise.resolve()),
532
+ }
533
+
534
+ // Spy on getOrCreateClientWithRetry to inject mock client
535
+ const getOrCreateSpy = spyOn(manager as any, "getOrCreateClientWithRetry")
536
+ getOrCreateSpy.mockResolvedValue(mockClient)
537
+
538
+ // #when
539
+ const result = await manager.callTool(info, context, "test-tool", {})
540
+
541
+ // #then
542
+ expect(callCount).toBe(2) // First call fails, second succeeds
543
+ expect(result).toEqual([{ type: "text", text: "success" }])
544
+ expect(getOrCreateSpy).toHaveBeenCalledTimes(2) // Called twice due to retry
545
+ })
546
+
547
+ it("should fail after 3 retry attempts", async () => {
548
+ // #given
549
+ const info: SkillMcpClientInfo = {
550
+ serverName: "fail-server",
551
+ skillName: "fail-skill",
552
+ sessionID: "session-fail-1",
553
+ }
554
+ const context: SkillMcpServerContext = {
555
+ config: {
556
+ url: "https://example.com/mcp",
557
+ },
558
+ skillName: "fail-skill",
559
+ }
560
+
561
+ // Mock client that always fails with "Not connected"
562
+ const mockClient = {
563
+ callTool: mock(async () => {
564
+ throw new Error("Not connected")
565
+ }),
566
+ close: mock(() => Promise.resolve()),
567
+ }
568
+
569
+ const getOrCreateSpy = spyOn(manager as any, "getOrCreateClientWithRetry")
570
+ getOrCreateSpy.mockResolvedValue(mockClient)
571
+
572
+ // #when / #then
573
+ await expect(manager.callTool(info, context, "test-tool", {})).rejects.toThrow(
574
+ /Failed after 3 reconnection attempts/
575
+ )
576
+ expect(getOrCreateSpy).toHaveBeenCalledTimes(3) // Initial + 2 retries
577
+ })
578
+
579
+ it("should not retry on non-connection errors", async () => {
580
+ // #given
581
+ const info: SkillMcpClientInfo = {
582
+ serverName: "error-server",
583
+ skillName: "error-skill",
584
+ sessionID: "session-error-1",
585
+ }
586
+ const context: SkillMcpServerContext = {
587
+ config: {
588
+ url: "https://example.com/mcp",
589
+ },
590
+ skillName: "error-skill",
591
+ }
592
+
593
+ // Mock client that fails with non-connection error
594
+ const mockClient = {
595
+ callTool: mock(async () => {
596
+ throw new Error("Tool not found")
597
+ }),
598
+ close: mock(() => Promise.resolve()),
599
+ }
600
+
601
+ const getOrCreateSpy = spyOn(manager as any, "getOrCreateClientWithRetry")
602
+ getOrCreateSpy.mockResolvedValue(mockClient)
603
+
604
+ // #when / #then
605
+ await expect(manager.callTool(info, context, "test-tool", {})).rejects.toThrow(
606
+ "Tool not found"
607
+ )
608
+ expect(getOrCreateSpy).toHaveBeenCalledTimes(1) // No retry
609
+ })
610
+ })
611
+ })