@openchambery/web 2.0.0-beta.3 → 2.0.0-beta.30

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 (292) hide show
  1. package/README.md +14 -0
  2. package/dist/assets/{AgentAvatar-B_JQoreg.js → AgentAvatar-O2x_1cFU.js} +1 -1
  3. package/dist/assets/AssistantView-pBywAsqy.js +3 -0
  4. package/dist/assets/{DiagramView-BNnda8qO.js → DiagramView-B984aE-R.js} +1 -1
  5. package/dist/assets/{FadeInOnReveal-VYQTSAkr.js → FadeInOnReveal-CMBd5BTd.js} +1 -1
  6. package/dist/assets/Icon-BN87CyS4.js +1 -0
  7. package/dist/assets/MarkdownRenderer-DHiNU8n3.js +2 -0
  8. package/dist/assets/{MarkdownRendererImpl-DwrHFGUp.js → MarkdownRendererImpl-xJHMGHju.js} +3 -3
  9. package/dist/assets/MarkstreamRendererImpl-BZflns6x.js +3 -0
  10. package/dist/assets/{MobileDetailNavigation-BXr6AGLT.js → MobileDetailNavigation-DUvxsvrB.js} +1 -1
  11. package/dist/assets/{MobileShareBridge-D7OE9OP_.js → MobileShareBridge-DWo-J3Vc.js} +3 -3
  12. package/dist/assets/MobileSurface-B-Y6U5xm.js +2 -0
  13. package/dist/assets/{MultiRunWindow-DF3iIFCd.js → MultiRunWindow-rYiVSNKK.js} +1 -1
  14. package/dist/assets/{OnboardingScreen-BjHmlwlw.js → OnboardingScreen-BLjZIBOs.js} +1 -1
  15. package/dist/assets/{OverlayScrollbar-DLtWO_Gp.js → OverlayScrollbar-CoZ4wtau.js} +1 -1
  16. package/dist/assets/SettingsView-pjD0LrHE.js +25 -0
  17. package/dist/assets/{SettingsWindow-BI5swspb.js → SettingsWindow-CFOQRwjA.js} +1 -1
  18. package/dist/assets/TerminalView-Dw5jaYkc.js +8 -0
  19. package/dist/assets/{ToolOutputDialog-BEWhBcRd.js → ToolOutputDialog-B4v-ona2.js} +1 -1
  20. package/dist/assets/appThemeRegistry-BqDCGocy.js +1 -0
  21. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-sG8YQsVs.js} +1 -1
  22. package/dist/assets/{button-DKuz3L8l.js → button-Ck8ipzBm.js} +1 -1
  23. package/dist/assets/collapsible-nU99prkD.js +1 -0
  24. package/dist/assets/context-menu-DMzZN1oi.js +1 -0
  25. package/dist/assets/{desktopHosts-CoDkOnyS.js → desktopHosts-DuftEj8B.js} +1 -1
  26. package/dist/assets/desktopRelayRestore-g378BdZf.js +1 -0
  27. package/dist/assets/{diagram-D7T8HQgj.js → diagram-CvO4P8ds.js} +1 -1
  28. package/dist/assets/dialog-ByGUkS3c.js +1 -0
  29. package/dist/assets/dropdown-menu.styles-ayLWk2gf.js +1 -0
  30. package/dist/assets/es-DCjl24dV.js +15 -0
  31. package/dist/assets/fileMentionTouchSelection-Cgkxwzqb.js +2 -0
  32. package/dist/assets/{fileQueries-JrpcsOCs.js → fileQueries-DuF89nFO.js} +1 -1
  33. package/dist/assets/fr-DfJ5j0t5.js +13 -0
  34. package/dist/assets/{gitApi-CM42qUed.js → gitApi-Dwqkc2nZ.js} +3 -3
  35. package/dist/assets/{imageSource-CIl0FL10.js → imageSource-BpUsiwFd.js} +1 -1
  36. package/dist/assets/{input-CzQy8UhV.js → input-CXhFmHcq.js} +1 -1
  37. package/dist/assets/insertionBoundaries-BhWfAOPY.js +1 -0
  38. package/dist/assets/ja-B_9_w2Xf.js +15 -0
  39. package/dist/assets/ko-DlGbo3kV.js +15 -0
  40. package/dist/assets/main-CGMinhgQ.js +169 -0
  41. package/dist/assets/main-pMpcLJha.js +2 -0
  42. package/dist/assets/{markdownHeightCache-Cj1I5NDw.js → markdownHeightCache-g_UsWzbA.js} +1 -1
  43. package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
  44. package/dist/assets/{mcpQueries-D5mQymgM.js → mcpQueries-DfrW3VHF.js} +1 -1
  45. package/dist/assets/miniChat-YOt30ECc.js +2 -0
  46. package/dist/assets/mobile-BK1bBCYD.js +2 -0
  47. package/dist/assets/multirun-Dua24A69.js +3 -0
  48. package/dist/assets/persistence-Dnj1QOdX.js +3 -0
  49. package/dist/assets/pl-DjUsS8Ei.js +15 -0
  50. package/dist/assets/platform-X3Y3GyM5.js +6 -0
  51. package/dist/assets/projectMeta-DXdoPmbz.js +1 -0
  52. package/dist/assets/pt-BR-B8sV7qo1.js +15 -0
  53. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-D2XooXKZ.js} +1 -1
  54. package/dist/assets/renderElectronMiniChatApp-BOuEz_oT.js +2 -0
  55. package/dist/assets/renderMobileApp-C8Alpv-B.js +28 -0
  56. package/dist/assets/runtime-fetch-CiqQd__Y.js +1 -0
  57. package/dist/assets/runtime-switch-BJM2k_uH.js +1 -0
  58. package/dist/assets/runtimeConfig-DX_NP2Zm.js +2 -0
  59. package/dist/assets/runtimeEndpointReset-BSQU3e5U.js +148 -0
  60. package/dist/assets/runtimeEndpointReset-DsuP5FPT.css +1 -0
  61. package/dist/assets/sessionChangeSummary-8AIBkExf.js +2 -0
  62. package/dist/assets/{sessionLookup-myHe5ltu.js → sessionLookup-Bdwd1Fit.js} +1 -1
  63. package/dist/assets/{sortable-tabs-strip-BQtmHx9r.js → sortable-tabs-strip-T-dk_L69.js} +1 -1
  64. package/dist/assets/src-OzbnJsph.css +2 -0
  65. package/dist/assets/store-Cuktref-.js +15 -0
  66. package/dist/assets/{terminalApi-BxcdiduX.js → terminalApi-C_4joKUG.js} +1 -1
  67. package/dist/assets/textarea-biAqiqnJ.js +1 -0
  68. package/dist/assets/{tooltip-Cx5gTbdY.js → tooltip-BKL7J5hx.js} +1 -1
  69. package/dist/assets/uk-Bh7HXyLp.js +15 -0
  70. package/dist/assets/{url-CDHnjlsm.js → url-DNRMq_0R.js} +1 -1
  71. package/dist/assets/{useAppFontEffects-CNeo0aZs.js → useAppFontEffects-bafOEpgK.js} +57 -52
  72. package/dist/assets/{useAssistantUIStore-asNG6K7n.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
  73. package/dist/assets/useDesktopWindowControlsLayout-CkCVOkxa.js +1 -0
  74. package/dist/assets/{useEffectiveDirectory-B4LvR8Gt.js → useEffectiveDirectory-B7htpJfU.js} +1 -1
  75. package/dist/assets/{useFontPreferences-CUnQ4TVm.js → useFontPreferences-CMrbTA0r.js} +1 -1
  76. package/dist/assets/useMobileNavigationStore-D2tnhKYV.js +415 -0
  77. package/dist/assets/{useRuntimeAPIs-DwLtO0c_.js → useRuntimeAPIs-DSLXJybW.js} +1 -1
  78. package/dist/assets/useSessionAutoCleanup-BC5OOH3M.js +2 -0
  79. package/dist/assets/{useThemeSystem-BlNUWH47.js → useThemeSystem-DMzWVJaW.js} +1 -1
  80. package/dist/assets/useUIStore-Dfx6TKvi.js +1 -0
  81. package/dist/assets/{useWorkerHighlightedLines-CJZbJDW3.js → useWorkerHighlightedLines-BNQUAMEB.js} +1 -1
  82. package/dist/assets/{vendor-base-ui-CDjyofeD.js → vendor-base-ui-CnHfUmDN.js} +1 -1
  83. package/dist/assets/{vendor-cmdk-BFH0UpXX.js → vendor-cmdk-CeAGfrLo.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-accessibility-BrR3AKvM.js → vendor-dnd-kit-accessibility-BY5LhhUB.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-core-CNfdvk6H.js → vendor-dnd-kit-core-CvWxUPdb.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-sortable-DQUaTeGj.js → vendor-dnd-kit-sortable-hHdP9lLb.js} +1 -1
  87. package/dist/assets/{vendor-framer-motion-4dogsUk3.js → vendor-framer-motion-DFy_BSVd.js} +1 -1
  88. package/dist/assets/{vendor-legendapp-list-BU7MdPZR.js → vendor-legendapp-list-WE32lwNk.js} +1 -1
  89. package/dist/assets/{vendor-markstream-react-qHC2FykU.js → vendor-markstream-react-DX55XLGO.js} +3 -3
  90. package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
  91. package/dist/assets/{vendor-pierre-diffs-CUfqC003.js → vendor-pierre-diffs-DX7XTPG3.js} +1 -1
  92. package/dist/assets/{vendor-react-C94A0AHl.js → vendor-react-DLPElpoQ.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-FVlWqKVE.js → vendor-react-drawio-ECtEwvbc.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-C2A3EjUi.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DNqHmUQs.js → vendor-sonner-DVzioF3d.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-CwbKsmlv.js → vendor-stream-diffs-DWy3xSmp.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D0iFK57C.js → vendor-tanstack-react-query-D7pys_c9.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-B2kV6cnT.js → vendor-tanstack-react-virtual-tlDgH6PG.js} +1 -1
  99. package/dist/assets/{vendor-zustand-BqLKf6ZY.js → vendor-zustand-DzRqcymP.js} +2 -2
  100. package/dist/assets/zh-CN-Uzia0gk4.js +15 -0
  101. package/dist/assets/zh-TW-CiI7DLdB.js +15 -0
  102. package/dist/index.html +15 -15
  103. package/dist/mini-chat.html +15 -15
  104. package/dist/mobile.html +15 -15
  105. package/package.json +3 -2
  106. package/server/index.js +46 -27
  107. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  108. package/server/lib/browser-provider/contract.js +312 -0
  109. package/server/lib/browser-provider/host.js +421 -0
  110. package/server/lib/browser-provider/host.test.js +304 -0
  111. package/server/lib/browser-provider/routes.js +122 -0
  112. package/server/lib/browser-provider/routes.test.js +120 -0
  113. package/server/lib/browser-provider/selection-store.js +43 -0
  114. package/server/lib/browser-provider/selection-store.test.js +35 -0
  115. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  116. package/server/lib/event-stream/reasoning-projection.js +16 -1
  117. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  118. package/server/lib/git/DOCUMENTATION.md +1 -0
  119. package/server/lib/git/routes.js +7 -4
  120. package/server/lib/git/routes.test.js +34 -0
  121. package/server/lib/git/service.js +3 -1
  122. package/server/lib/llm/DOCUMENTATION.md +6 -4
  123. package/server/lib/llm/generate.js +8 -9
  124. package/server/lib/llm/generate.test.js +33 -6
  125. package/server/lib/message-queue/service.js +1 -1
  126. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  127. package/server/lib/notifications/runtime.js +128 -31
  128. package/server/lib/notifications/runtime.test.js +103 -0
  129. package/server/lib/notifications/template-runtime.js +130 -38
  130. package/server/lib/notifications/template-runtime.test.js +96 -0
  131. package/server/lib/opencode/DOCUMENTATION.md +36 -19
  132. package/server/lib/opencode/agents.js +13 -22
  133. package/server/lib/opencode/auth-state-runtime.js +3 -3
  134. package/server/lib/opencode/auth-state-runtime.test.js +59 -0
  135. package/server/lib/opencode/auth.d.ts +13 -0
  136. package/server/lib/opencode/auth.js +74 -64
  137. package/server/lib/opencode/auth.test.js +57 -0
  138. package/server/lib/opencode/commands.js +1 -1
  139. package/server/lib/opencode/config-entities-layering.test.js +72 -0
  140. package/server/lib/opencode/config-entity-routes.js +16 -124
  141. package/server/lib/opencode/config-entity-routes.test.js +48 -11
  142. package/server/lib/opencode/config-files.d.ts +17 -0
  143. package/server/lib/opencode/config-files.js +187 -0
  144. package/server/lib/opencode/config-files.test.js +201 -0
  145. package/server/lib/opencode/core-routes.js +0 -1
  146. package/server/lib/opencode/core-routes.test.js +0 -15
  147. package/server/lib/opencode/credential-boundary.d.ts +1 -0
  148. package/server/lib/opencode/credential-boundary.js +11 -0
  149. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  150. package/server/lib/opencode/ensure-cli.js +4 -1
  151. package/server/lib/opencode/env-config.js +4 -1
  152. package/server/lib/opencode/env-config.test.js +28 -0
  153. package/server/lib/opencode/env-runtime.test.js +18 -1
  154. package/server/lib/opencode/feature-routes-runtime.js +19 -7
  155. package/server/lib/opencode/feature-routes-runtime.test.js +7 -0
  156. package/server/lib/opencode/hmr-state-runtime.js +4 -4
  157. package/server/lib/opencode/lifecycle.js +1 -49
  158. package/server/lib/opencode/lifecycle.test.js +30 -4
  159. package/server/lib/opencode/location-release.d.ts +8 -0
  160. package/server/lib/opencode/location-release.js +32 -0
  161. package/server/lib/opencode/location-release.test.js +43 -0
  162. package/server/lib/opencode/mcp.js +1 -1
  163. package/server/lib/opencode/opencode2-pin.js +2 -1
  164. package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
  165. package/server/lib/opencode/plugin-routes.test.js +4 -2
  166. package/server/lib/opencode/plugins.js +12 -60
  167. package/server/lib/opencode/plugins.test.js +5 -3
  168. package/server/lib/opencode/providers.js +6 -3
  169. package/server/lib/opencode/proxy.js +13 -1
  170. package/server/lib/opencode/proxy.test.js +31 -3
  171. package/server/lib/opencode/remote-upgrade.integration.test.js +66 -0
  172. package/server/lib/opencode/routes.js +40 -8
  173. package/server/lib/opencode/routes.test.js +51 -10
  174. package/server/lib/opencode/runtime-contract.js +3 -2
  175. package/server/lib/opencode/runtime-contract.test.js +12 -12
  176. package/server/lib/opencode/shared.js +6 -175
  177. package/server/lib/opencode/update-discovery.js +39 -0
  178. package/server/lib/opencode/update-discovery.test.js +39 -0
  179. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  180. package/server/lib/opencode/upgrade-screen.js +292 -0
  181. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  182. package/server/lib/opencode/version-alignment.test.js +40 -0
  183. package/server/lib/question-auto-delegate/DOCUMENTATION.md +21 -6
  184. package/server/lib/question-auto-delegate/core.d.ts +5 -1
  185. package/server/lib/question-auto-delegate/core.js +21 -13
  186. package/server/lib/question-auto-delegate/form-io.d.ts +6 -0
  187. package/server/lib/question-auto-delegate/form-io.js +39 -0
  188. package/server/lib/question-auto-delegate/runtime.js +4 -24
  189. package/server/lib/question-auto-delegate/runtime.test.js +138 -0
  190. package/server/lib/quota/DOCUMENTATION.md +9 -0
  191. package/server/lib/quota/provider-credentials.test.js +29 -0
  192. package/server/lib/quota/providers/claude.js +4 -4
  193. package/server/lib/quota/providers/codex.js +4 -4
  194. package/server/lib/quota/providers/codex.test.js +1 -1
  195. package/server/lib/quota/providers/copilot.js +5 -5
  196. package/server/lib/quota/providers/google/auth.js +3 -3
  197. package/server/lib/quota/providers/google/index.js +2 -2
  198. package/server/lib/quota/providers/index.js +3 -7
  199. package/server/lib/quota/providers/index.test.js +2 -2
  200. package/server/lib/quota/providers/kimi.js +4 -4
  201. package/server/lib/quota/providers/minimax-shared.js +4 -4
  202. package/server/lib/quota/providers/nanogpt.js +4 -4
  203. package/server/lib/quota/providers/openai.js +4 -4
  204. package/server/lib/quota/providers/openrouter.js +4 -4
  205. package/server/lib/quota/providers/wafer.js +4 -4
  206. package/server/lib/quota/providers/zai.js +4 -4
  207. package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
  208. package/server/lib/quota/routes.js +1 -1
  209. package/server/lib/relay/DOCUMENTATION.md +3 -1
  210. package/server/lib/relay/service.js +1 -0
  211. package/server/lib/relay/service.test.js +13 -0
  212. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -1
  213. package/server/lib/scheduled-tasks/runtime.js +12 -4
  214. package/server/lib/scheduled-tasks/runtime.test.js +81 -6
  215. package/server/lib/session-goal/DOCUMENTATION.md +11 -0
  216. package/server/lib/session-goal/runtime.js +4 -4
  217. package/server/lib/session-goal/runtime.test.js +32 -3
  218. package/server/lib/session-index/DOCUMENTATION.md +7 -0
  219. package/server/lib/session-index/event-ingest.js +13 -0
  220. package/server/lib/session-index/event-ingest.test.js +7 -0
  221. package/server/lib/session-index/service.js +45 -0
  222. package/server/lib/session-index/service.test.js +47 -0
  223. package/server/lib/session-metadata/DOCUMENTATION.md +33 -17
  224. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  225. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  226. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  227. package/server/lib/session-metadata/routes.js +5 -1
  228. package/server/lib/session-metadata/session-archive.js +1 -1
  229. package/server/lib/session-metadata/session-metadata-store.d.ts +2 -0
  230. package/server/lib/session-metadata/session-metadata-store.js +63 -16
  231. package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
  232. package/server/lib/session-metadata/session-projection.d.ts +1 -0
  233. package/server/lib/session-metadata/session-projection.js +49 -2
  234. package/server/lib/session-metadata/session-projection.test.js +35 -0
  235. package/server/lib/session-title/DOCUMENTATION.md +52 -14
  236. package/server/lib/session-title/native-runtime.test.js +59 -5
  237. package/server/lib/session-title/runtime.js +65 -19
  238. package/server/lib/session-title/session-access.js +108 -3
  239. package/server/lib/session-title/session-access.test.js +125 -7
  240. package/server/lib/tts/DOCUMENTATION.md +7 -5
  241. package/server/lib/tts/credential-source.test.js +23 -0
  242. package/server/lib/tts/routes.js +2 -2
  243. package/server/lib/tts/service.js +16 -37
  244. package/dist/assets/AssistantView-ClQAfGOT.js +0 -3
  245. package/dist/assets/Icon-BiEQEcih.js +0 -1
  246. package/dist/assets/MarkdownRenderer-CJHSsWin.js +0 -2
  247. package/dist/assets/MarkstreamRendererImpl-CSnbQMu3.js +0 -3
  248. package/dist/assets/MobileSurface-D2jUkX9l.js +0 -2
  249. package/dist/assets/SettingsView-BRxELjow.js +0 -25
  250. package/dist/assets/TerminalView-BU3x8-fr.js +0 -8
  251. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +0 -1
  252. package/dist/assets/collapsible-DXbDynN4.js +0 -3
  253. package/dist/assets/context-menu-CqolsLCm.js +0 -1
  254. package/dist/assets/desktopRelayRestore-DDlVKyOE.js +0 -1
  255. package/dist/assets/dialog-DQYl3W_o.js +0 -1
  256. package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +0 -1
  257. package/dist/assets/es-CN3E_zXj.js +0 -15
  258. package/dist/assets/fileMentionTouchSelection-BZ6ovBYH.js +0 -4
  259. package/dist/assets/fr-9zZvQZD2.js +0 -13
  260. package/dist/assets/insertionBoundaries-CtkZ9720.js +0 -1
  261. package/dist/assets/ja-Dnjk_82N.js +0 -15
  262. package/dist/assets/ko-BxCEE99C.js +0 -15
  263. package/dist/assets/main-BILgBI3B.js +0 -169
  264. package/dist/assets/main-BeveiW6F.js +0 -2
  265. package/dist/assets/markdownTheme-CKac3wKC.js +0 -1
  266. package/dist/assets/miniChat-BgrzM3u7.js +0 -2
  267. package/dist/assets/mobile-DtYoQE8C.js +0 -2
  268. package/dist/assets/multirun-jy8dEI96.js +0 -4
  269. package/dist/assets/persistence-C9LQpiOS.js +0 -3
  270. package/dist/assets/pl-CNNsWnPM.js +0 -15
  271. package/dist/assets/platform-BQghHRfp.js +0 -6
  272. package/dist/assets/pt-BR-BZB2DJMp.js +0 -15
  273. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +0 -2
  274. package/dist/assets/renderMobileApp-u3c4GtFC.js +0 -28
  275. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  276. package/dist/assets/runtime-switch-LvWDU-SL.js +0 -1
  277. package/dist/assets/runtimeConfig-BIGBRGQI.js +0 -2
  278. package/dist/assets/runtimeEndpointReset-D2savkuw.js +0 -147
  279. package/dist/assets/sessionChangeSummary-eIbpSfrO.js +0 -2
  280. package/dist/assets/src-CLN7Z-zx.css +0 -2
  281. package/dist/assets/store-DLCiZaA6.js +0 -15
  282. package/dist/assets/textarea-CakKvqFw.js +0 -1
  283. package/dist/assets/uk-rwQrxeYM.js +0 -15
  284. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +0 -1
  285. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +0 -414
  286. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +0 -2
  287. package/dist/assets/useUIStore-CQYKJnrg.js +0 -1
  288. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
  289. package/dist/assets/zh-CN-BvmBDnkD.js +0 -15
  290. package/dist/assets/zh-TW-9wcHTX0y.js +0 -15
  291. package/server/lib/opencode/snippets.js +0 -242
  292. package/server/lib/opencode/snippets.test.js +0 -97
@@ -1,242 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
- import yaml from 'yaml';
5
-
6
- const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
7
- const GLOBAL_SNIPPET_DIR = path.join(OPENCODE_CONFIG_DIR, 'snippet');
8
- const GLOBAL_SNIPPET_DIR_ALT = path.join(OPENCODE_CONFIG_DIR, 'snippets');
9
- const SNIPPET_EXTENSION = '.md';
10
- const SNIPPET_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/i;
11
- const HASHTAG_PATTERN = /#([a-z0-9_-]+)/gi;
12
- const MAX_EXPANSION_COUNT = 15;
13
-
14
- function getProjectSnippetDirs(workingDirectory) {
15
- if (!workingDirectory) return [];
16
- return [
17
- path.join(workingDirectory, '.opencode', 'snippets'),
18
- path.join(workingDirectory, '.opencode', 'snippet'),
19
- ];
20
- }
21
-
22
- function getGlobalSnippetDirs() {
23
- return [GLOBAL_SNIPPET_DIR_ALT, GLOBAL_SNIPPET_DIR];
24
- }
25
-
26
- function getLoadDirs(workingDirectory) {
27
- return [
28
- ...getGlobalSnippetDirs().map((dir) => ({ dir, source: 'global' })),
29
- ...getProjectSnippetDirs(workingDirectory).map((dir) => ({ dir, source: 'project' })),
30
- ];
31
- }
32
-
33
- function assertValidSnippetName(name) {
34
- if (typeof name !== 'string' || !SNIPPET_NAME_PATTERN.test(name)) {
35
- throw new Error('Snippet name must use letters, numbers, dashes, or underscores');
36
- }
37
- }
38
-
39
- function parseMarkdownFile(filePath) {
40
- const content = fs.readFileSync(filePath, 'utf8');
41
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
42
- if (!match) {
43
- return { frontmatter: {}, body: content.trim() };
44
- }
45
- return {
46
- frontmatter: yaml.parse(match[1]) || {},
47
- body: match[2].trim(),
48
- };
49
- }
50
-
51
- function normalizeAliases(frontmatter) {
52
- const raw = frontmatter.aliases ?? frontmatter.alias;
53
- if (!raw) return [];
54
- const aliases = Array.isArray(raw) ? raw : [raw];
55
- return aliases.map((alias) => String(alias).trim()).filter(Boolean);
56
- }
57
-
58
- function writeMarkdownFile(filePath, { content, aliases = [], description }) {
59
- const frontmatter = {};
60
- const normalizedAliases = aliases.map((alias) => String(alias).trim()).filter(Boolean);
61
- if (normalizedAliases.length > 0) frontmatter.aliases = normalizedAliases;
62
- if (description?.trim()) frontmatter.description = description.trim();
63
-
64
- const body = content ?? '';
65
- const output = Object.keys(frontmatter).length > 0
66
- ? `---\n${yaml.stringify(frontmatter)}---\n${body ? `\n${body}` : ''}`
67
- : body;
68
-
69
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
70
- fs.writeFileSync(filePath, output, 'utf8');
71
- }
72
-
73
- function loadSnippetFile(dir, filename, source) {
74
- const name = path.basename(filename, SNIPPET_EXTENSION);
75
- if (!SNIPPET_NAME_PATTERN.test(name)) return null;
76
- const filePath = path.join(dir, filename);
77
- const { frontmatter, body } = parseMarkdownFile(filePath);
78
- return {
79
- name,
80
- content: body,
81
- aliases: normalizeAliases(frontmatter),
82
- description: typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
83
- filePath,
84
- source,
85
- };
86
- }
87
-
88
- function removeSnippetAliases(registry, snippet, canonicalNames) {
89
- for (const alias of snippet.aliases) {
90
- const aliasKey = alias.toLowerCase();
91
- if (canonicalNames.has(aliasKey)) continue;
92
- if (registry.get(aliasKey) === snippet) registry.delete(aliasKey);
93
- }
94
- }
95
-
96
- function registerSnippet(registry, snippet, canonicalNames) {
97
- const key = snippet.name.toLowerCase();
98
- const existing = registry.get(key);
99
- if (existing?.name.toLowerCase() === key) {
100
- removeSnippetAliases(registry, existing, canonicalNames);
101
- }
102
- registry.set(key, snippet);
103
- canonicalNames.add(key);
104
- for (const alias of snippet.aliases) {
105
- const aliasKey = alias.toLowerCase();
106
- if (SNIPPET_NAME_PATTERN.test(alias) && !canonicalNames.has(aliasKey)) registry.set(aliasKey, snippet);
107
- }
108
- }
109
-
110
- function loadSnippetRegistry(workingDirectory) {
111
- const registry = new Map();
112
- const canonicalNames = new Set();
113
- for (const { dir, source } of getLoadDirs(workingDirectory)) {
114
- if (!fs.existsSync(dir)) continue;
115
- for (const filename of fs.readdirSync(dir)) {
116
- if (!filename.endsWith(SNIPPET_EXTENSION)) continue;
117
- try {
118
- const snippet = loadSnippetFile(dir, filename, source);
119
- if (snippet) registerSnippet(registry, snippet, canonicalNames);
120
- } catch (error) {
121
- console.warn(`[Snippets] Failed to load ${path.join(dir, filename)}:`, error);
122
- }
123
- }
124
- }
125
- return registry;
126
- }
127
-
128
- function listUniqueSnippets(registry) {
129
- const seen = new Set();
130
- const snippets = [];
131
- for (const snippet of registry.values()) {
132
- const key = `${snippet.source}:${snippet.filePath}`;
133
- if (seen.has(key)) continue;
134
- seen.add(key);
135
- snippets.push(snippet);
136
- }
137
- return snippets.sort((a, b) => a.name.localeCompare(b.name));
138
- }
139
-
140
- function getWritableSnippetDir(scope, workingDirectory) {
141
- if (scope === 'project') {
142
- if (!workingDirectory) throw new Error('Project directory is required for project snippets');
143
- const preferred = path.join(workingDirectory, '.opencode', 'snippet');
144
- const alternate = path.join(workingDirectory, '.opencode', 'snippets');
145
- return fs.existsSync(alternate) && !fs.existsSync(preferred) ? alternate : preferred;
146
- }
147
- return fs.existsSync(GLOBAL_SNIPPET_DIR_ALT) && !fs.existsSync(GLOBAL_SNIPPET_DIR)
148
- ? GLOBAL_SNIPPET_DIR_ALT
149
- : GLOBAL_SNIPPET_DIR;
150
- }
151
-
152
- function findSnippetByName(name, workingDirectory) {
153
- assertValidSnippetName(name);
154
- const registry = loadSnippetRegistry(workingDirectory);
155
- return registry.get(name.toLowerCase()) ?? null;
156
- }
157
-
158
- function parseSnippetBlocks(content) {
159
- const blocks = { prepend: [], append: [] };
160
- let inline = content;
161
- for (const type of ['prepend', 'append']) {
162
- const regex = new RegExp(`<${type}>([\\s\\S]*?)(?:<\\/${type}>|$)`, 'gi');
163
- inline = inline.replace(regex, (_match, value) => {
164
- const normalized = String(value).trim();
165
- if (normalized) blocks[type].push(normalized);
166
- return '';
167
- });
168
- }
169
- inline = inline.replace(/<inject>[\s\S]*?(?:<\/inject>|$)/gi, '').trim();
170
- return { inline, prepend: blocks.prepend, append: blocks.append };
171
- }
172
-
173
- function expandText(text, registry, expansionCounts, collector) {
174
- let expanded = text;
175
- let changed = true;
176
-
177
- while (changed) {
178
- const previous = expanded;
179
- let loopDetected = false;
180
- HASHTAG_PATTERN.lastIndex = 0;
181
-
182
- expanded = expanded.replace(HASHTAG_PATTERN, (match, name, offset, input) => {
183
- if (name.toLowerCase() === 'skill' && input[offset + match.length] === '(') return match;
184
- const snippet = registry.get(name.toLowerCase());
185
- if (!snippet) return match;
186
-
187
- const key = snippet.name.toLowerCase();
188
- const count = (expansionCounts.get(key) || 0) + 1;
189
- if (count > MAX_EXPANSION_COUNT) {
190
- loopDetected = true;
191
- return match;
192
- }
193
- expansionCounts.set(key, count);
194
-
195
- const parsed = parseSnippetBlocks(snippet.content);
196
- for (const block of parsed.prepend) collector.prepend.push(expandText(block, registry, expansionCounts, collector));
197
- for (const block of parsed.append) collector.append.push(expandText(block, registry, expansionCounts, collector));
198
- return expandText(parsed.inline, registry, expansionCounts, collector);
199
- });
200
-
201
- changed = expanded !== previous && !loopDetected;
202
- }
203
-
204
- return expanded;
205
- }
206
-
207
- export function listSnippets(workingDirectory) {
208
- return listUniqueSnippets(loadSnippetRegistry(workingDirectory));
209
- }
210
-
211
- export function getSnippet(name, workingDirectory) {
212
- return findSnippetByName(name, workingDirectory);
213
- }
214
-
215
- export function createSnippet(name, config, workingDirectory, scope = 'global') {
216
- assertValidSnippetName(name);
217
- const dir = getWritableSnippetDir(scope, workingDirectory);
218
- const filePath = path.join(dir, `${name}${SNIPPET_EXTENSION}`);
219
- if (fs.existsSync(filePath)) throw new Error(`Snippet "${name}" already exists`);
220
- writeMarkdownFile(filePath, config || {});
221
- return getSnippet(name, workingDirectory);
222
- }
223
-
224
- export function updateSnippet(name, updates, workingDirectory) {
225
- const existing = findSnippetByName(name, workingDirectory);
226
- if (!existing) throw new Error(`Snippet "${name}" not found`);
227
- writeMarkdownFile(existing.filePath, { ...existing, ...(updates || {}) });
228
- return getSnippet(name, workingDirectory);
229
- }
230
-
231
- export function deleteSnippet(name, workingDirectory) {
232
- const existing = findSnippetByName(name, workingDirectory);
233
- if (!existing) throw new Error(`Snippet "${name}" not found`);
234
- fs.unlinkSync(existing.filePath);
235
- }
236
-
237
- export function expandSnippets(text, workingDirectory) {
238
- const registry = loadSnippetRegistry(workingDirectory);
239
- const collector = { prepend: [], append: [] };
240
- const expanded = expandText(text || '', registry, new Map(), collector).trim();
241
- return [...collector.prepend, expanded, ...collector.append].filter(Boolean).join('\n\n');
242
- }
@@ -1,97 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import {
6
- createSnippet,
7
- deleteSnippet,
8
- expandSnippets,
9
- getSnippet,
10
- listSnippets,
11
- updateSnippet,
12
- } from './snippets.js';
13
-
14
- let projectDir;
15
-
16
- function writeSnippet(relativePath, content) {
17
- const filePath = path.join(projectDir, relativePath);
18
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
- fs.writeFileSync(filePath, content, 'utf8');
20
- }
21
-
22
- describe('snippets', () => {
23
- beforeEach(() => {
24
- projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-snippets-'));
25
- });
26
-
27
- afterEach(() => {
28
- fs.rmSync(projectDir, { recursive: true, force: true });
29
- });
30
-
31
- test('loads project snippets with aliases and description', () => {
32
- writeSnippet('.opencode/snippet/review.md', '---\naliases: [rev]\ndescription: Review helper\n---\nReview carefully.');
33
-
34
- expect(listSnippets(projectDir)).toContainEqual(
35
- expect.objectContaining({ name: 'review', aliases: ['rev'], description: 'Review helper', source: 'project' }),
36
- );
37
- expect(getSnippet('rev', projectDir)).toEqual(expect.objectContaining({ name: 'review' }));
38
- });
39
-
40
- test('snippet directory wins over snippets directory', () => {
41
- writeSnippet('.opencode/snippets/same.md', 'Old');
42
- writeSnippet('.opencode/snippet/same.md', 'New');
43
-
44
- expect(getSnippet('same', projectDir)?.content).toBe('New');
45
- });
46
-
47
- test('canonical snippet names take precedence over colliding aliases', () => {
48
- writeSnippet('.opencode/snippets/review.md', 'Review by name');
49
- writeSnippet('.opencode/snippet/helper.md', '---\naliases: [review, help]\n---\nReview by alias');
50
-
51
- expect(getSnippet('review', projectDir)).toEqual(expect.objectContaining({ name: 'review', content: 'Review by name' }));
52
- expect(getSnippet('help', projectDir)).toEqual(expect.objectContaining({ name: 'helper', content: 'Review by alias' }));
53
- expect(expandSnippets('Use #review and #help', projectDir)).toBe('Use Review by name and Review by alias');
54
- });
55
-
56
- test('canonical snippet names override earlier same-directory aliases', () => {
57
- writeSnippet('.opencode/snippet/alias-first.md', '---\naliases: [review, assist]\n---\nReview by alias');
58
- writeSnippet('.opencode/snippet/review.md', 'Review by name');
59
-
60
- const snippetDir = path.join(projectDir, '.opencode', 'snippet');
61
- const originalReaddirSync = fs.readdirSync;
62
- fs.readdirSync = function readdirSync(dir, ...args) {
63
- if (dir === snippetDir) return ['alias-first.md', 'review.md'];
64
- return originalReaddirSync.call(fs, dir, ...args);
65
- };
66
-
67
- try {
68
- expect(getSnippet('review', projectDir)).toEqual(expect.objectContaining({ name: 'review', content: 'Review by name' }));
69
- expect(getSnippet('assist', projectDir)).toEqual(expect.objectContaining({ name: 'alias-first', content: 'Review by alias' }));
70
- expect(expandSnippets('Use #review and #assist', projectDir)).toBe('Use Review by name and Review by alias');
71
- } finally {
72
- fs.readdirSync = originalReaddirSync;
73
- }
74
- });
75
-
76
- test('creates updates and deletes snippets', () => {
77
- expect(createSnippet('custom-one', { content: 'Body', aliases: ['co'] }, projectDir, 'project')).toEqual(
78
- expect.objectContaining({ name: 'custom-one', content: 'Body', aliases: ['co'] }),
79
- );
80
- expect(updateSnippet('custom-one', { content: 'Updated' }, projectDir)).toEqual(
81
- expect.objectContaining({ name: 'custom-one', content: 'Updated', aliases: ['co'] }),
82
- );
83
- deleteSnippet('custom-one', projectDir);
84
- expect(getSnippet('custom-one', projectDir)).toBeNull();
85
- });
86
-
87
- test('expands snippets recursively with prepend and append blocks', () => {
88
- writeSnippet('.opencode/snippet/base.md', 'Base text');
89
- writeSnippet('.opencode/snippet/review.md', '<prepend>Before</prepend>Review #base<append>After</append>');
90
-
91
- expect(expandSnippets('Please #review', projectDir)).toBe('Before\n\nPlease Review Base text\n\nAfter');
92
- });
93
-
94
- test('rejects invalid snippet names', () => {
95
- expect(() => createSnippet('../bad', { content: '' }, projectDir, 'project')).toThrow('Snippet name');
96
- });
97
- });