@thevinci/web 1.0.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 (337) hide show
  1. package/README.md +197 -0
  2. package/bin/cli-entry.js +55 -0
  3. package/bin/cli-output.js +145 -0
  4. package/bin/cli.js +4887 -0
  5. package/bin/cli.test.js +64 -0
  6. package/dist/apple-touch-icon-120x120.png +0 -0
  7. package/dist/apple-touch-icon-152x152.png +0 -0
  8. package/dist/apple-touch-icon-167x167.png +0 -0
  9. package/dist/apple-touch-icon-180x180.png +0 -0
  10. package/dist/apple-touch-icon.png +0 -0
  11. package/dist/apple-touch-icon.svg +528 -0
  12. package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
  13. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  14. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  15. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  19. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  20. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  21. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  25. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  26. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  31. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  32. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  33. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  34. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  35. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  37. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  38. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  39. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  40. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  41. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  42. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  43. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  44. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  45. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  52. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  53. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  54. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  55. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  56. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  57. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  58. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  59. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  60. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  61. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  62. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  63. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  64. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  65. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  66. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  67. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  68. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  69. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  70. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  71. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  72. package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
  73. package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
  74. package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
  75. package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
  76. package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
  77. package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
  78. package/dist/assets/es-4o9ciP61.js +15 -0
  79. package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  80. package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  81. package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  82. package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  83. package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  84. package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  85. package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  86. package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  87. package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  88. package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  89. package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  90. package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  91. package/dist/assets/index-DLTDToSP.css +1 -0
  92. package/dist/assets/index-DgiFEKGN.js +1 -0
  93. package/dist/assets/ko-B20imCHE.js +15 -0
  94. package/dist/assets/main-BV3KOtdA.css +1 -0
  95. package/dist/assets/main-CDKJj0sH.js +226 -0
  96. package/dist/assets/main-LC-PSNVM.js +2 -0
  97. package/dist/assets/miniChat-CQUiG_cr.js +2 -0
  98. package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
  99. package/dist/assets/pl-DQJ7LSzj.js +15 -0
  100. package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
  101. package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
  102. package/dist/assets/uk-BNFxOlO4.js +15 -0
  103. package/dist/assets/vendor--DBfsbEis.css +1 -0
  104. package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
  105. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  106. package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
  107. package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
  108. package/dist/assets/worker-CbT6TVo7.js +155 -0
  109. package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
  110. package/dist/favicon-16.png +0 -0
  111. package/dist/favicon-32.png +0 -0
  112. package/dist/favicon.png +0 -0
  113. package/dist/favicon.svg +528 -0
  114. package/dist/index.html +607 -0
  115. package/dist/logo-dark-192x192.png +0 -0
  116. package/dist/logo-dark-512x512.png +0 -0
  117. package/dist/logo-dark-512x512.svg +528 -0
  118. package/dist/logo-light-192x192.png +0 -0
  119. package/dist/logo-light-512x512.png +0 -0
  120. package/dist/logo-light-512x512.svg +528 -0
  121. package/dist/mini-chat.html +16 -0
  122. package/dist/pwa-192.png +0 -0
  123. package/dist/pwa-512.png +0 -0
  124. package/dist/pwa-maskable-192.png +0 -0
  125. package/dist/pwa-maskable-512.png +0 -0
  126. package/dist/site.webmanifest +21 -0
  127. package/dist/sw.js +1 -0
  128. package/package.json +118 -0
  129. package/public/apple-touch-icon-120x120.png +0 -0
  130. package/public/apple-touch-icon-152x152.png +0 -0
  131. package/public/apple-touch-icon-167x167.png +0 -0
  132. package/public/apple-touch-icon-180x180.png +0 -0
  133. package/public/apple-touch-icon.png +0 -0
  134. package/public/apple-touch-icon.svg +528 -0
  135. package/public/favicon-16.png +0 -0
  136. package/public/favicon-32.png +0 -0
  137. package/public/favicon.png +0 -0
  138. package/public/favicon.svg +528 -0
  139. package/public/logo-dark-192x192.png +0 -0
  140. package/public/logo-dark-512x512.png +0 -0
  141. package/public/logo-dark-512x512.svg +528 -0
  142. package/public/logo-light-192x192.png +0 -0
  143. package/public/logo-light-512x512.png +0 -0
  144. package/public/logo-light-512x512.svg +528 -0
  145. package/public/pwa-192.png +0 -0
  146. package/public/pwa-512.png +0 -0
  147. package/public/pwa-maskable-192.png +0 -0
  148. package/public/pwa-maskable-512.png +0 -0
  149. package/public/site.webmanifest +21 -0
  150. package/server/TERMINAL_WS_PROTOCOL.md +48 -0
  151. package/server/index.d.ts +39 -0
  152. package/server/index.js +1311 -0
  153. package/server/lib/cloudflare-tunnel.js +650 -0
  154. package/server/lib/event-stream/DOCUMENTATION.md +61 -0
  155. package/server/lib/event-stream/directory-ws-bridge.js +185 -0
  156. package/server/lib/event-stream/global-hub.js +158 -0
  157. package/server/lib/event-stream/global-hub.test.js +140 -0
  158. package/server/lib/event-stream/global-ws-bridge.js +206 -0
  159. package/server/lib/event-stream/index.js +25 -0
  160. package/server/lib/event-stream/protocol.js +131 -0
  161. package/server/lib/event-stream/protocol.test.js +182 -0
  162. package/server/lib/event-stream/runtime.js +180 -0
  163. package/server/lib/event-stream/runtime.test.js +512 -0
  164. package/server/lib/event-stream/upstream-reader.js +226 -0
  165. package/server/lib/event-stream/upstream-reader.test.js +276 -0
  166. package/server/lib/fs/DOCUMENTATION.md +36 -0
  167. package/server/lib/fs/routes.js +1040 -0
  168. package/server/lib/fs/search.js +238 -0
  169. package/server/lib/git/DOCUMENTATION.md +152 -0
  170. package/server/lib/git/credentials.js +74 -0
  171. package/server/lib/git/identity-storage.js +112 -0
  172. package/server/lib/git/index.js +6 -0
  173. package/server/lib/git/routes.js +972 -0
  174. package/server/lib/git/service.js +3432 -0
  175. package/server/lib/git/service.test.js +39 -0
  176. package/server/lib/github/DOCUMENTATION.md +171 -0
  177. package/server/lib/github/auth.js +307 -0
  178. package/server/lib/github/device-flow.js +50 -0
  179. package/server/lib/github/index.js +24 -0
  180. package/server/lib/github/octokit.js +10 -0
  181. package/server/lib/github/pr-status.js +519 -0
  182. package/server/lib/github/repo/fork-detection.js +102 -0
  183. package/server/lib/github/repo/index.js +55 -0
  184. package/server/lib/github/routes.js +1560 -0
  185. package/server/lib/magic-prompts/routes.js +63 -0
  186. package/server/lib/magic-prompts/runtime.js +119 -0
  187. package/server/lib/notifications/DOCUMENTATION.md +122 -0
  188. package/server/lib/notifications/emitter-runtime.js +102 -0
  189. package/server/lib/notifications/index.js +4 -0
  190. package/server/lib/notifications/message.js +52 -0
  191. package/server/lib/notifications/message.test.js +34 -0
  192. package/server/lib/notifications/push-runtime.js +304 -0
  193. package/server/lib/notifications/routes.js +315 -0
  194. package/server/lib/notifications/runtime.js +566 -0
  195. package/server/lib/notifications/template-runtime.js +349 -0
  196. package/server/lib/notifications/template-runtime.test.js +26 -0
  197. package/server/lib/opencode/DOCUMENTATION.md +362 -0
  198. package/server/lib/opencode/agents.js +634 -0
  199. package/server/lib/opencode/auth-state-runtime.js +88 -0
  200. package/server/lib/opencode/auth.js +83 -0
  201. package/server/lib/opencode/bootstrap-runtime.js +131 -0
  202. package/server/lib/opencode/cli-entry-runtime.js +43 -0
  203. package/server/lib/opencode/cli-options.js +128 -0
  204. package/server/lib/opencode/commands.js +339 -0
  205. package/server/lib/opencode/config-entity-routes.js +370 -0
  206. package/server/lib/opencode/core-routes.js +500 -0
  207. package/server/lib/opencode/core-routes.test.js +26 -0
  208. package/server/lib/opencode/env-config.js +74 -0
  209. package/server/lib/opencode/env-keys.js +68 -0
  210. package/server/lib/opencode/env-runtime.js +1162 -0
  211. package/server/lib/opencode/env-runtime.test.js +116 -0
  212. package/server/lib/opencode/feature-routes-runtime.js +244 -0
  213. package/server/lib/opencode/hmr-state-runtime.js +85 -0
  214. package/server/lib/opencode/index.js +66 -0
  215. package/server/lib/opencode/lifecycle.js +1019 -0
  216. package/server/lib/opencode/lifecycle.test.js +240 -0
  217. package/server/lib/opencode/mcp.js +278 -0
  218. package/server/lib/opencode/network-runtime.js +104 -0
  219. package/server/lib/opencode/network-runtime.test.js +37 -0
  220. package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
  221. package/server/lib/opencode/path-utils.js +100 -0
  222. package/server/lib/opencode/path-utils.test.js +71 -0
  223. package/server/lib/opencode/project-directory-runtime.js +124 -0
  224. package/server/lib/opencode/project-icon-routes.js +399 -0
  225. package/server/lib/opencode/project-icon-routes.test.js +107 -0
  226. package/server/lib/opencode/providers.js +96 -0
  227. package/server/lib/opencode/proxy.js +445 -0
  228. package/server/lib/opencode/pwa-manifest-routes.js +257 -0
  229. package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
  230. package/server/lib/opencode/routes.js +541 -0
  231. package/server/lib/opencode/server-startup-runtime.js +156 -0
  232. package/server/lib/opencode/server-utils-runtime.js +168 -0
  233. package/server/lib/opencode/server-utils-runtime.test.js +135 -0
  234. package/server/lib/opencode/session-runtime.js +356 -0
  235. package/server/lib/opencode/session-runtime.test.js +151 -0
  236. package/server/lib/opencode/settings-helpers.js +770 -0
  237. package/server/lib/opencode/settings-helpers.test.js +109 -0
  238. package/server/lib/opencode/settings-normalization-runtime.js +428 -0
  239. package/server/lib/opencode/settings-runtime.js +826 -0
  240. package/server/lib/opencode/settings-runtime.test.js +85 -0
  241. package/server/lib/opencode/shared.js +615 -0
  242. package/server/lib/opencode/shutdown-runtime.js +139 -0
  243. package/server/lib/opencode/shutdown-runtime.test.js +58 -0
  244. package/server/lib/opencode/skill-routes.js +701 -0
  245. package/server/lib/opencode/skills.js +548 -0
  246. package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
  247. package/server/lib/opencode/static-routes-runtime.js +65 -0
  248. package/server/lib/opencode/theme-runtime.js +167 -0
  249. package/server/lib/opencode/tunnel-auth.js +591 -0
  250. package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
  251. package/server/lib/opencode/vinci-routes.js +76 -0
  252. package/server/lib/opencode/watcher.js +115 -0
  253. package/server/lib/opencode/watcher.test.js +239 -0
  254. package/server/lib/preview/proxy-runtime.js +1333 -0
  255. package/server/lib/preview/proxy-runtime.test.js +144 -0
  256. package/server/lib/projects/project-config.js +567 -0
  257. package/server/lib/projects/project-config.test.js +175 -0
  258. package/server/lib/projects/project-id.js +13 -0
  259. package/server/lib/quota/DOCUMENTATION.md +58 -0
  260. package/server/lib/quota/index.js +25 -0
  261. package/server/lib/quota/providers/claude.js +107 -0
  262. package/server/lib/quota/providers/codex.js +113 -0
  263. package/server/lib/quota/providers/copilot.js +165 -0
  264. package/server/lib/quota/providers/google/api.js +92 -0
  265. package/server/lib/quota/providers/google/auth.js +108 -0
  266. package/server/lib/quota/providers/google/index.js +124 -0
  267. package/server/lib/quota/providers/google/transforms.js +109 -0
  268. package/server/lib/quota/providers/index.js +168 -0
  269. package/server/lib/quota/providers/interface.js +55 -0
  270. package/server/lib/quota/providers/kimi.js +108 -0
  271. package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
  272. package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
  273. package/server/lib/quota/providers/nanogpt.js +124 -0
  274. package/server/lib/quota/providers/ollama-cloud.js +112 -0
  275. package/server/lib/quota/providers/openai.js +91 -0
  276. package/server/lib/quota/providers/openrouter.js +92 -0
  277. package/server/lib/quota/providers/zai.js +91 -0
  278. package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
  279. package/server/lib/quota/providers/zhipuai.js +114 -0
  280. package/server/lib/quota/routes.js +27 -0
  281. package/server/lib/quota/utils/auth.js +50 -0
  282. package/server/lib/quota/utils/formatters.js +85 -0
  283. package/server/lib/quota/utils/formatters.test.js +54 -0
  284. package/server/lib/quota/utils/index.js +10 -0
  285. package/server/lib/quota/utils/transformers.js +55 -0
  286. package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
  287. package/server/lib/scheduled-tasks/routes.js +235 -0
  288. package/server/lib/scheduled-tasks/runtime.js +773 -0
  289. package/server/lib/scheduled-tasks/runtime.test.js +100 -0
  290. package/server/lib/security/request-security.js +115 -0
  291. package/server/lib/session-folders/routes.js +63 -0
  292. package/server/lib/session-folders/routes.test.js +102 -0
  293. package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
  294. package/server/lib/skills-catalog/cache.js +29 -0
  295. package/server/lib/skills-catalog/clawdhub/api.js +158 -0
  296. package/server/lib/skills-catalog/clawdhub/index.js +30 -0
  297. package/server/lib/skills-catalog/clawdhub/install.js +238 -0
  298. package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
  299. package/server/lib/skills-catalog/curated-sources.js +21 -0
  300. package/server/lib/skills-catalog/git.js +77 -0
  301. package/server/lib/skills-catalog/index.js +42 -0
  302. package/server/lib/skills-catalog/install.js +294 -0
  303. package/server/lib/skills-catalog/scan.js +221 -0
  304. package/server/lib/skills-catalog/source.js +87 -0
  305. package/server/lib/terminal/DOCUMENTATION.md +76 -0
  306. package/server/lib/terminal/index.js +31 -0
  307. package/server/lib/terminal/output-replay-buffer.js +78 -0
  308. package/server/lib/terminal/output-replay-buffer.test.js +75 -0
  309. package/server/lib/terminal/runtime.js +850 -0
  310. package/server/lib/terminal/runtime.test.js +96 -0
  311. package/server/lib/terminal/terminal-ws-protocol.js +68 -0
  312. package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
  313. package/server/lib/text/DOCUMENTATION.md +35 -0
  314. package/server/lib/text/summarization.js +138 -0
  315. package/server/lib/text/summarization.test.js +34 -0
  316. package/server/lib/tts/DOCUMENTATION.md +146 -0
  317. package/server/lib/tts/base-url.js +62 -0
  318. package/server/lib/tts/capability-runtime.js +31 -0
  319. package/server/lib/tts/index.js +19 -0
  320. package/server/lib/tts/routes.js +261 -0
  321. package/server/lib/tts/routes.test.js +53 -0
  322. package/server/lib/tts/service.js +178 -0
  323. package/server/lib/tts/stt.js +75 -0
  324. package/server/lib/tunnels/DOCUMENTATION.md +18 -0
  325. package/server/lib/tunnels/index.js +166 -0
  326. package/server/lib/tunnels/managed-config.js +201 -0
  327. package/server/lib/tunnels/providers/cloudflare.js +260 -0
  328. package/server/lib/tunnels/registry.js +51 -0
  329. package/server/lib/tunnels/routes.js +605 -0
  330. package/server/lib/tunnels/types.js +219 -0
  331. package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
  332. package/server/lib/ui-auth/ui-auth.js +673 -0
  333. package/server/lib/ui-auth/ui-passkeys.js +545 -0
  334. package/server/opencode-proxy.test.js +151 -0
  335. package/server/proxy-headers.js +61 -0
  336. package/server/proxy-headers.test.js +58 -0
  337. package/server/sse-routes.test.js +152 -0
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createSettingsHelpers } from './settings-helpers.js';
4
+
5
+ const createTestHelpers = () => createSettingsHelpers({
6
+ normalizePathForPersistence: (value) => value,
7
+ normalizeDirectoryPath: (value) => value,
8
+ normalizeTunnelBootstrapTtlMs: (value) => value,
9
+ normalizeTunnelSessionTtlMs: (value) => value,
10
+ normalizeTunnelProvider: (value) => value,
11
+ normalizeTunnelMode: (value) => value,
12
+ normalizeOptionalPath: (value) => value,
13
+ normalizeManagedRemoteTunnelHostname: (value) => value,
14
+ normalizeManagedRemoteTunnelPresets: () => undefined,
15
+ normalizeManagedRemoteTunnelPresetTokens: () => undefined,
16
+ sanitizeTypographySizesPartial: () => undefined,
17
+ normalizeStringArray: (input) => input,
18
+ sanitizeModelRefs: () => undefined,
19
+ sanitizeSkillCatalogs: () => undefined,
20
+ sanitizeProjects: () => undefined,
21
+ });
22
+
23
+ describe('settings helpers', () => {
24
+ it('accepts messageStreamTransport as a persisted shared setting', () => {
25
+ const helpers = createTestHelpers();
26
+
27
+ expect(helpers.sanitizeSettingsUpdate({ messageStreamTransport: 'ws' })).toEqual({
28
+ messageStreamTransport: 'ws',
29
+ });
30
+ expect(helpers.sanitizeSettingsUpdate({ messageStreamTransport: 'sse' })).toEqual({
31
+ messageStreamTransport: 'sse',
32
+ });
33
+ expect(helpers.sanitizeSettingsUpdate({ messageStreamTransport: 'auto' })).toEqual({
34
+ messageStreamTransport: 'auto',
35
+ });
36
+ });
37
+
38
+ it('rejects invalid messageStreamTransport values', () => {
39
+ const helpers = createTestHelpers();
40
+
41
+ expect(helpers.sanitizeSettingsUpdate({ messageStreamTransport: 'websocket' })).toEqual({});
42
+ });
43
+
44
+ it('accepts desktopLanAccessEnabled as a persisted shared setting', () => {
45
+ const helpers = createTestHelpers();
46
+
47
+ expect(helpers.sanitizeSettingsUpdate({ desktopLanAccessEnabled: true })).toEqual({
48
+ desktopLanAccessEnabled: true,
49
+ });
50
+ expect(helpers.sanitizeSettingsUpdate({ desktopLanAccessEnabled: false })).toEqual({
51
+ desktopLanAccessEnabled: false,
52
+ });
53
+ });
54
+
55
+ it('accepts mobileKeyboardMode as a persisted shared setting', () => {
56
+ const helpers = createTestHelpers();
57
+
58
+ expect(helpers.sanitizeSettingsUpdate({ mobileKeyboardMode: 'native' })).toEqual({
59
+ mobileKeyboardMode: 'native',
60
+ });
61
+ expect(helpers.sanitizeSettingsUpdate({ mobileKeyboardMode: 'resize-content' })).toEqual({
62
+ mobileKeyboardMode: 'resize-content',
63
+ });
64
+ expect(helpers.sanitizeSettingsUpdate({ mobileKeyboardMode: ' resize-content ' })).toEqual({
65
+ mobileKeyboardMode: 'resize-content',
66
+ });
67
+ });
68
+
69
+ it('rejects invalid mobileKeyboardMode values', () => {
70
+ const helpers = createTestHelpers();
71
+
72
+ expect(helpers.sanitizeSettingsUpdate({ mobileKeyboardMode: 'fixed-layout' })).toEqual({});
73
+ });
74
+
75
+ it('accepts collapsibleThinkingBlocks as a persisted shared setting', () => {
76
+ const helpers = createTestHelpers();
77
+
78
+ expect(helpers.sanitizeSettingsUpdate({ collapsibleThinkingBlocks: true })).toEqual({
79
+ collapsibleThinkingBlocks: true,
80
+ });
81
+ expect(helpers.sanitizeSettingsUpdate({ collapsibleThinkingBlocks: false })).toEqual({
82
+ collapsibleThinkingBlocks: false,
83
+ });
84
+ });
85
+
86
+ it('rejects non-boolean collapsibleThinkingBlocks values', () => {
87
+ const helpers = createTestHelpers();
88
+
89
+ expect(helpers.sanitizeSettingsUpdate({ collapsibleThinkingBlocks: 'true' })).toEqual({});
90
+ expect(helpers.sanitizeSettingsUpdate({ collapsibleThinkingBlocks: 1 })).toEqual({});
91
+ });
92
+
93
+ it('includes collapsibleThinkingBlocks in formatSettingsResponse', () => {
94
+ const helpers = createTestHelpers();
95
+
96
+ const response = helpers.formatSettingsResponse({ collapsibleThinkingBlocks: false });
97
+ expect(response.collapsibleThinkingBlocks).toBe(false);
98
+
99
+ const responseTrue = helpers.formatSettingsResponse({ collapsibleThinkingBlocks: true });
100
+ expect(responseTrue.collapsibleThinkingBlocks).toBe(true);
101
+ });
102
+
103
+ it('defaults collapsibleThinkingBlocks to true in formatSettingsResponse when absent', () => {
104
+ const helpers = createTestHelpers();
105
+
106
+ const response = helpers.formatSettingsResponse({});
107
+ expect(response.collapsibleThinkingBlocks).toBe(true);
108
+ });
109
+ });
@@ -0,0 +1,428 @@
1
+ export const createSettingsNormalizationRuntime = (dependencies) => {
2
+ const {
3
+ os,
4
+ path,
5
+ processLike,
6
+ tunnelBootstrapTtlDefaultMs,
7
+ tunnelBootstrapTtlMinMs,
8
+ tunnelBootstrapTtlMaxMs,
9
+ tunnelSessionTtlDefaultMs,
10
+ tunnelSessionTtlMinMs,
11
+ tunnelSessionTtlMaxMs,
12
+ } = dependencies;
13
+
14
+ const normalizeDirectoryPath = (value) => {
15
+ if (typeof value !== 'string') {
16
+ return value;
17
+ }
18
+
19
+ const trimmed = value.trim();
20
+ if (!trimmed) {
21
+ return trimmed;
22
+ }
23
+
24
+ if (trimmed === '~') {
25
+ return os.homedir();
26
+ }
27
+
28
+ if (trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
29
+ return path.join(os.homedir(), trimmed.slice(2));
30
+ }
31
+
32
+ return trimmed;
33
+ };
34
+
35
+ const normalizePathForPersistence = (value) => {
36
+ if (typeof value !== 'string') {
37
+ return value;
38
+ }
39
+
40
+ const normalized = normalizeDirectoryPath(value);
41
+ if (typeof normalized !== 'string') {
42
+ return normalized;
43
+ }
44
+
45
+ const trimmed = normalized.trim();
46
+ if (!trimmed) {
47
+ return trimmed;
48
+ }
49
+
50
+ if (processLike.platform !== 'win32') {
51
+ return trimmed;
52
+ }
53
+
54
+ return trimmed.replace(/\//g, '\\');
55
+ };
56
+
57
+ const areStringArraysEqual = (a, b) => {
58
+ if (!Array.isArray(a) || !Array.isArray(b)) {
59
+ return false;
60
+ }
61
+ if (a.length !== b.length) {
62
+ return false;
63
+ }
64
+ for (let i = 0; i < a.length; i += 1) {
65
+ if (a[i] !== b[i]) {
66
+ return false;
67
+ }
68
+ }
69
+ return true;
70
+ };
71
+
72
+ const normalizeStringArray = (input) => {
73
+ if (!Array.isArray(input)) {
74
+ return [];
75
+ }
76
+ return Array.from(
77
+ new Set(
78
+ input.filter((entry) => typeof entry === 'string' && entry.length > 0)
79
+ )
80
+ );
81
+ };
82
+
83
+ const sanitizeProjects = (input) => {
84
+ if (!Array.isArray(input)) {
85
+ return undefined;
86
+ }
87
+
88
+ const hexColorPattern = /^#(?:[\da-fA-F]{3}|[\da-fA-F]{6})$/;
89
+ const normalizeIconBackground = (value) => {
90
+ if (typeof value !== 'string') {
91
+ return null;
92
+ }
93
+ const trimmed = value.trim();
94
+ if (!trimmed) {
95
+ return null;
96
+ }
97
+ return hexColorPattern.test(trimmed) ? trimmed.toLowerCase() : null;
98
+ };
99
+
100
+ const result = [];
101
+ const seenIds = new Set();
102
+ const seenPaths = new Set();
103
+
104
+ for (const entry of input) {
105
+ if (!entry || typeof entry !== 'object') continue;
106
+
107
+ const candidate = entry;
108
+ const id = typeof candidate.id === 'string' ? candidate.id.trim() : '';
109
+ const rawPath = typeof candidate.path === 'string' ? candidate.path.trim() : '';
110
+ const resolvedPath = rawPath ? path.resolve(normalizeDirectoryPath(rawPath)) : '';
111
+ const normalizedPath = resolvedPath ? normalizePathForPersistence(resolvedPath) : '';
112
+ const label = typeof candidate.label === 'string' ? candidate.label.trim() : '';
113
+ const icon = typeof candidate.icon === 'string' ? candidate.icon.trim() : '';
114
+ const iconImage = candidate.iconImage && typeof candidate.iconImage === 'object'
115
+ ? candidate.iconImage
116
+ : null;
117
+ const iconBackground = normalizeIconBackground(candidate.iconBackground);
118
+ const color = typeof candidate.color === 'string' ? candidate.color.trim() : '';
119
+ const addedAt = Number.isFinite(candidate.addedAt) ? Number(candidate.addedAt) : null;
120
+ const lastOpenedAt = Number.isFinite(candidate.lastOpenedAt)
121
+ ? Number(candidate.lastOpenedAt)
122
+ : null;
123
+
124
+ if (!id || !normalizedPath) continue;
125
+ if (seenIds.has(id)) continue;
126
+ if (seenPaths.has(normalizedPath)) continue;
127
+
128
+ seenIds.add(id);
129
+ seenPaths.add(normalizedPath);
130
+
131
+ const project = {
132
+ id,
133
+ path: normalizedPath,
134
+ ...(label ? { label } : {}),
135
+ ...(icon ? { icon } : {}),
136
+ ...(iconBackground ? { iconBackground } : {}),
137
+ ...(color ? { color } : {}),
138
+ ...(Number.isFinite(addedAt) && addedAt >= 0 ? { addedAt } : {}),
139
+ ...(Number.isFinite(lastOpenedAt) && lastOpenedAt >= 0 ? { lastOpenedAt } : {}),
140
+ };
141
+
142
+ if (candidate.iconImage === null) {
143
+ project.iconImage = null;
144
+ } else if (iconImage) {
145
+ const mime = typeof iconImage.mime === 'string' ? iconImage.mime.trim() : '';
146
+ const updatedAt = typeof iconImage.updatedAt === 'number' && Number.isFinite(iconImage.updatedAt)
147
+ ? Math.max(0, Math.round(iconImage.updatedAt))
148
+ : 0;
149
+ const source = iconImage.source === 'custom' || iconImage.source === 'auto'
150
+ ? iconImage.source
151
+ : null;
152
+ if (mime && updatedAt > 0 && source) {
153
+ project.iconImage = { mime, updatedAt, source };
154
+ }
155
+ }
156
+
157
+ if (candidate.iconBackground === null) {
158
+ project.iconBackground = null;
159
+ }
160
+
161
+ if (typeof candidate.sidebarCollapsed === 'boolean') {
162
+ project.sidebarCollapsed = candidate.sidebarCollapsed;
163
+ }
164
+
165
+ result.push(project);
166
+ }
167
+
168
+ return result;
169
+ };
170
+
171
+ const normalizeSettingsPaths = (input) => {
172
+ const settings = input && typeof input === 'object' ? input : {};
173
+ let next = settings;
174
+ let changed = false;
175
+
176
+ const ensureNext = () => {
177
+ if (next === settings) {
178
+ next = { ...settings };
179
+ }
180
+ };
181
+
182
+ const normalizePathField = (key) => {
183
+ if (typeof settings[key] !== 'string' || settings[key].length === 0) {
184
+ return;
185
+ }
186
+ const normalized = normalizePathForPersistence(settings[key]);
187
+ if (normalized !== settings[key]) {
188
+ ensureNext();
189
+ next[key] = normalized;
190
+ changed = true;
191
+ }
192
+ };
193
+
194
+ const normalizePathArrayField = (key) => {
195
+ if (!Array.isArray(settings[key])) {
196
+ return;
197
+ }
198
+
199
+ const normalized = normalizeStringArray(
200
+ settings[key]
201
+ .map((entry) => (typeof entry === 'string' ? normalizePathForPersistence(entry) : entry))
202
+ .filter((entry) => typeof entry === 'string' && entry.length > 0)
203
+ );
204
+
205
+ if (!areStringArraysEqual(normalized, settings[key])) {
206
+ ensureNext();
207
+ next[key] = normalized;
208
+ changed = true;
209
+ }
210
+ };
211
+
212
+ normalizePathField('lastDirectory');
213
+ normalizePathField('homeDirectory');
214
+ normalizePathArrayField('approvedDirectories');
215
+ normalizePathArrayField('pinnedDirectories');
216
+
217
+ if (Array.isArray(settings.projects)) {
218
+ const normalizedProjects = sanitizeProjects(settings.projects) || [];
219
+ if (JSON.stringify(normalizedProjects) !== JSON.stringify(settings.projects)) {
220
+ ensureNext();
221
+ next.projects = normalizedProjects;
222
+ changed = true;
223
+ }
224
+ }
225
+
226
+ return { settings: next, changed };
227
+ };
228
+
229
+ const clampNumber = (value, min, max) => Math.max(min, Math.min(max, value));
230
+
231
+ const normalizeTunnelBootstrapTtlMs = (value) => {
232
+ if (value === null) {
233
+ return null;
234
+ }
235
+ if (!Number.isFinite(value)) {
236
+ return tunnelBootstrapTtlDefaultMs;
237
+ }
238
+ return clampNumber(Math.round(value), tunnelBootstrapTtlMinMs, tunnelBootstrapTtlMaxMs);
239
+ };
240
+
241
+ const normalizeTunnelSessionTtlMs = (value) => {
242
+ if (!Number.isFinite(value)) {
243
+ return tunnelSessionTtlDefaultMs;
244
+ }
245
+ return clampNumber(Math.round(value), tunnelSessionTtlMinMs, tunnelSessionTtlMaxMs);
246
+ };
247
+
248
+ const normalizeManagedRemoteTunnelHostname = (value) => {
249
+ if (typeof value !== 'string') {
250
+ return undefined;
251
+ }
252
+ const trimmed = value.trim();
253
+ if (!trimmed) {
254
+ return undefined;
255
+ }
256
+
257
+ const parsed = (() => {
258
+ try {
259
+ if (trimmed.includes('://')) {
260
+ return new URL(trimmed);
261
+ }
262
+ return new URL(`https://${trimmed}`);
263
+ } catch {
264
+ return null;
265
+ }
266
+ })();
267
+
268
+ const hostname = parsed?.hostname?.trim().toLowerCase() || '';
269
+ if (!hostname) {
270
+ return undefined;
271
+ }
272
+ return hostname;
273
+ };
274
+
275
+ const normalizeManagedRemoteTunnelPresets = (value) => {
276
+ if (!Array.isArray(value)) {
277
+ return undefined;
278
+ }
279
+
280
+ const result = [];
281
+ const seenIds = new Set();
282
+ const seenHostnames = new Set();
283
+
284
+ for (const entry of value) {
285
+ if (!entry || typeof entry !== 'object') continue;
286
+ const candidate = entry;
287
+ const id = typeof candidate.id === 'string' ? candidate.id.trim() : '';
288
+ const name = typeof candidate.name === 'string' ? candidate.name.trim() : '';
289
+ const hostname = normalizeManagedRemoteTunnelHostname(candidate.hostname);
290
+ if (!id || !name || !hostname) continue;
291
+ if (seenIds.has(id) || seenHostnames.has(hostname)) continue;
292
+ seenIds.add(id);
293
+ seenHostnames.add(hostname);
294
+ result.push({ id, name, hostname });
295
+ }
296
+
297
+ return result;
298
+ };
299
+
300
+ const normalizeManagedRemoteTunnelPresetTokens = (value) => {
301
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
302
+ return undefined;
303
+ }
304
+
305
+ const result = {};
306
+ for (const [rawId, rawToken] of Object.entries(value)) {
307
+ const id = typeof rawId === 'string' ? rawId.trim() : '';
308
+ const token = typeof rawToken === 'string' ? rawToken.trim() : '';
309
+ if (!id || !token) {
310
+ continue;
311
+ }
312
+ result[id] = token;
313
+ }
314
+
315
+ return Object.keys(result).length > 0 ? result : undefined;
316
+ };
317
+
318
+ const isUnsafeSkillRelativePath = (value) => {
319
+ if (typeof value !== 'string' || value.length === 0) {
320
+ return true;
321
+ }
322
+
323
+ const normalized = value.replace(/\\/g, '/');
324
+ if (path.posix.isAbsolute(normalized)) {
325
+ return true;
326
+ }
327
+
328
+ return normalized.split('/').some((segment) => segment === '..');
329
+ };
330
+
331
+ const sanitizeTypographySizesPartial = (input) => {
332
+ if (!input || typeof input !== 'object') {
333
+ return undefined;
334
+ }
335
+ const candidate = input;
336
+ const result = {};
337
+ let populated = false;
338
+
339
+ const assign = (key) => {
340
+ if (typeof candidate[key] === 'string' && candidate[key].length > 0) {
341
+ result[key] = candidate[key];
342
+ populated = true;
343
+ }
344
+ };
345
+
346
+ assign('markdown');
347
+ assign('code');
348
+ assign('uiHeader');
349
+ assign('uiLabel');
350
+ assign('meta');
351
+ assign('micro');
352
+
353
+ return populated ? result : undefined;
354
+ };
355
+
356
+ const sanitizeModelRefs = (input, limit) => {
357
+ if (!Array.isArray(input)) {
358
+ return undefined;
359
+ }
360
+
361
+ const result = [];
362
+ const seen = new Set();
363
+
364
+ for (const entry of input) {
365
+ if (!entry || typeof entry !== 'object') continue;
366
+ const providerID = typeof entry.providerID === 'string' ? entry.providerID.trim() : '';
367
+ const modelID = typeof entry.modelID === 'string' ? entry.modelID.trim() : '';
368
+ if (!providerID || !modelID) continue;
369
+ const key = `${providerID}/${modelID}`;
370
+ if (seen.has(key)) continue;
371
+ seen.add(key);
372
+ result.push({ providerID, modelID });
373
+ if (result.length >= limit) break;
374
+ }
375
+
376
+ return result;
377
+ };
378
+
379
+ const sanitizeSkillCatalogs = (input) => {
380
+ if (!Array.isArray(input)) {
381
+ return undefined;
382
+ }
383
+
384
+ const result = [];
385
+ const seen = new Set();
386
+
387
+ for (const entry of input) {
388
+ if (!entry || typeof entry !== 'object') continue;
389
+
390
+ const id = typeof entry.id === 'string' ? entry.id.trim() : '';
391
+ const label = typeof entry.label === 'string' ? entry.label.trim() : '';
392
+ const source = typeof entry.source === 'string' ? entry.source.trim() : '';
393
+ const subpath = typeof entry.subpath === 'string' ? entry.subpath.trim() : '';
394
+ const gitIdentityId = typeof entry.gitIdentityId === 'string' ? entry.gitIdentityId.trim() : '';
395
+
396
+ if (!id || !label || !source) continue;
397
+ if (seen.has(id)) continue;
398
+ seen.add(id);
399
+
400
+ result.push({
401
+ id,
402
+ label,
403
+ source,
404
+ ...(subpath ? { subpath } : {}),
405
+ ...(gitIdentityId ? { gitIdentityId } : {}),
406
+ });
407
+ }
408
+
409
+ return result;
410
+ };
411
+
412
+ return {
413
+ normalizeDirectoryPath,
414
+ normalizePathForPersistence,
415
+ normalizeSettingsPaths,
416
+ normalizeTunnelBootstrapTtlMs,
417
+ normalizeTunnelSessionTtlMs,
418
+ normalizeManagedRemoteTunnelHostname,
419
+ normalizeManagedRemoteTunnelPresets,
420
+ normalizeManagedRemoteTunnelPresetTokens,
421
+ isUnsafeSkillRelativePath,
422
+ sanitizeTypographySizesPartial,
423
+ normalizeStringArray,
424
+ sanitizeModelRefs,
425
+ sanitizeSkillCatalogs,
426
+ sanitizeProjects,
427
+ };
428
+ };