@neuralnomads/codenomad-dev 0.10.3-dev-20260213-ba418a85

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 (462) hide show
  1. package/README.md +126 -0
  2. package/dist/api-types.js +1 -0
  3. package/dist/auth/auth-store.js +134 -0
  4. package/dist/auth/http-auth.js +37 -0
  5. package/dist/auth/manager.js +128 -0
  6. package/dist/auth/password-hash.js +32 -0
  7. package/dist/auth/session-manager.js +17 -0
  8. package/dist/auth/token-manager.js +27 -0
  9. package/dist/background-processes/manager.js +437 -0
  10. package/dist/bin.js +24 -0
  11. package/dist/config/binaries.js +148 -0
  12. package/dist/config/location.js +57 -0
  13. package/dist/config/schema.js +70 -0
  14. package/dist/config/store.js +200 -0
  15. package/dist/events/bus.js +41 -0
  16. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  17. package/dist/filesystem/browser.js +285 -0
  18. package/dist/filesystem/search-cache.js +43 -0
  19. package/dist/filesystem/search.js +135 -0
  20. package/dist/index.js +410 -0
  21. package/dist/integrations/github/bot-signature.js +11 -0
  22. package/dist/integrations/github/git-ops.js +133 -0
  23. package/dist/integrations/github/github-types.js +1 -0
  24. package/dist/integrations/github/job-runner.js +608 -0
  25. package/dist/integrations/github/octokit.js +58 -0
  26. package/dist/integrations/github/sanitize-webhook.js +42 -0
  27. package/dist/integrations/github/webhook-verify.js +21 -0
  28. package/dist/integrations/github/workspace-context.js +10 -0
  29. package/dist/integrations/github/worktree-context.js +15 -0
  30. package/dist/launcher.js +149 -0
  31. package/dist/loader.js +21 -0
  32. package/dist/logger.js +109 -0
  33. package/dist/opencode/request-context.js +39 -0
  34. package/dist/opencode/worktree-directory.js +42 -0
  35. package/dist/opencode-config/README.md +32 -0
  36. package/dist/opencode-config/opencode.jsonc +3 -0
  37. package/dist/opencode-config/package.json +9 -0
  38. package/dist/opencode-config/plugin/codenomad.ts +32 -0
  39. package/dist/opencode-config/plugin/lib/background-process.ts +253 -0
  40. package/dist/opencode-config/plugin/lib/client.ts +133 -0
  41. package/dist/opencode-config/plugin/lib/request.ts +214 -0
  42. package/dist/opencode-config-template/README.md +32 -0
  43. package/dist/opencode-config-template/opencode.jsonc +3 -0
  44. package/dist/opencode-config-template/plugin/codenomad.ts +40 -0
  45. package/dist/opencode-config-template/plugin/lib/background-process.ts +160 -0
  46. package/dist/opencode-config-template/plugin/lib/client.ts +165 -0
  47. package/dist/opencode-config.js +26 -0
  48. package/dist/plugins/channel.js +40 -0
  49. package/dist/plugins/handlers.js +17 -0
  50. package/dist/releases/dev-release-monitor.js +75 -0
  51. package/dist/releases/release-monitor.js +107 -0
  52. package/dist/server/http-server.js +547 -0
  53. package/dist/server/network-addresses.js +72 -0
  54. package/dist/server/routes/auth-pages/login.html +134 -0
  55. package/dist/server/routes/auth-pages/token.html +93 -0
  56. package/dist/server/routes/auth.js +134 -0
  57. package/dist/server/routes/background-processes.js +60 -0
  58. package/dist/server/routes/config.js +59 -0
  59. package/dist/server/routes/events.js +43 -0
  60. package/dist/server/routes/filesystem.js +43 -0
  61. package/dist/server/routes/github-plugin.js +215 -0
  62. package/dist/server/routes/github-webhook.js +32 -0
  63. package/dist/server/routes/meta.js +47 -0
  64. package/dist/server/routes/plugin.js +52 -0
  65. package/dist/server/routes/storage.js +52 -0
  66. package/dist/server/routes/workspaces.js +89 -0
  67. package/dist/server/routes/worktrees.js +156 -0
  68. package/dist/server/tls.js +224 -0
  69. package/dist/storage/instance-store.js +56 -0
  70. package/dist/ui/__tests__/remote-ui.test.js +46 -0
  71. package/dist/ui/remote-ui.js +462 -0
  72. package/dist/workspaces/git-worktrees.js +199 -0
  73. package/dist/workspaces/instance-events.js +180 -0
  74. package/dist/workspaces/manager.js +375 -0
  75. package/dist/workspaces/opencode-auth.js +16 -0
  76. package/dist/workspaces/runtime.js +346 -0
  77. package/dist/workspaces/worktree-map.js +116 -0
  78. package/package.json +49 -0
  79. package/public/apple-touch-icon-180x180.png +0 -0
  80. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  81. package/public/assets/abap-BdImnpbu.js +1 -0
  82. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  83. package/public/assets/ada-bCR0ucgS.js +1 -0
  84. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  85. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  86. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  87. package/public/assets/apache-Pmp26Uib.js +1 -0
  88. package/public/assets/apex-DhZLUxFE.js +1 -0
  89. package/public/assets/apl-dKokRX4l.js +1 -0
  90. package/public/assets/applescript-Co6uUVPk.js +1 -0
  91. package/public/assets/ara-BRHolxvo.js +1 -0
  92. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  93. package/public/assets/asm-D_Q5rh1f.js +1 -0
  94. package/public/assets/astro-CbQHKStN.js +1 -0
  95. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  96. package/public/assets/awk-DMzUqQB5.js +1 -0
  97. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  98. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  99. package/public/assets/bat-BkioyH1T.js +1 -0
  100. package/public/assets/beancount-k_qm7-4y.js +1 -0
  101. package/public/assets/berry-D08WgyRC.js +1 -0
  102. package/public/assets/bibtex-CHM0blh-.js +1 -0
  103. package/public/assets/bicep-Bmn6On1c.js +1 -0
  104. package/public/assets/blade-DVc8C-J4.js +1 -0
  105. package/public/assets/bsl-BO_Y6i37.js +1 -0
  106. package/public/assets/c-BIGW1oBm.js +1 -0
  107. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  108. package/public/assets/cairo-KRGpt6FW.js +1 -0
  109. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  110. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  111. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  112. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  113. package/public/assets/clarity-D53aC0YG.js +1 -0
  114. package/public/assets/clojure-P80f7IUj.js +1 -0
  115. package/public/assets/cmake-D1j8_8rp.js +1 -0
  116. package/public/assets/cobol-nwyudZeR.js +1 -0
  117. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  118. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  119. package/public/assets/coffee-Ch7k5sss.js +1 -0
  120. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  121. package/public/assets/coq-DkFqJrB1.js +1 -0
  122. package/public/assets/core-BSTVzpXI.js +1 -0
  123. package/public/assets/cpp-CofmeUqb.js +1 -0
  124. package/public/assets/crystal-tKQVLTB8.js +1 -0
  125. package/public/assets/csharp-CX12Zw3r.js +1 -0
  126. package/public/assets/css-DPfMkruS.js +1 -0
  127. package/public/assets/csv-fuZLfV_i.js +1 -0
  128. package/public/assets/cue-D82EKSYY.js +1 -0
  129. package/public/assets/cypher-COkxafJQ.js +1 -0
  130. package/public/assets/d-85-TOEBH.js +1 -0
  131. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  132. package/public/assets/dart-CF10PKvl.js +1 -0
  133. package/public/assets/dax-CEL-wOlO.js +1 -0
  134. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  135. package/public/assets/diff-D97Zzqfu.js +1 -0
  136. package/public/assets/docker-BcOcwvcX.js +1 -0
  137. package/public/assets/dotenv-Da5cRb03.js +1 -0
  138. package/public/assets/dracula-BzJJZx-M.js +1 -0
  139. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  140. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  141. package/public/assets/edge-BkV0erSs.js +1 -0
  142. package/public/assets/elixir-CDX3lj18.js +1 -0
  143. package/public/assets/elm-DbKCFpqz.js +1 -0
  144. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  145. package/public/assets/erb-BOJIQeun.js +1 -0
  146. package/public/assets/erlang-DsQrWhSR.js +1 -0
  147. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  148. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  149. package/public/assets/fennel-BYunw83y.js +1 -0
  150. package/public/assets/fish-BvzEVeQv.js +1 -0
  151. package/public/assets/fluent-C4IJs8-o.js +1 -0
  152. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  153. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  154. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  155. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  156. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  157. package/public/assets/gdshader-DkwncUOv.js +1 -0
  158. package/public/assets/genie-D0YGMca9.js +1 -0
  159. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  160. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  161. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  162. package/public/assets/github-dark-DHJKELXO.js +1 -0
  163. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  164. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  165. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  166. package/public/assets/github-light-DAi9KRSo.js +1 -0
  167. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  168. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  169. package/public/assets/gleam-BspZqrRM.js +1 -0
  170. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  171. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  172. package/public/assets/glsl-DplSGwfg.js +1 -0
  173. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  174. package/public/assets/go-Dn2_MT6a.js +1 -0
  175. package/public/assets/graphql-ChdNCCLP.js +1 -0
  176. package/public/assets/groovy-gcz8RCvz.js +1 -0
  177. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  178. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  179. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  180. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  181. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  182. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  183. package/public/assets/hack-CaT9iCJl.js +1 -0
  184. package/public/assets/haml-B8DHNrY2.js +1 -0
  185. package/public/assets/handlebars-BL8al0AC.js +1 -0
  186. package/public/assets/haskell-Df6bDoY_.js +1 -0
  187. package/public/assets/haxe-CzTSHFRz.js +1 -0
  188. package/public/assets/hcl-BWvSN4gD.js +1 -0
  189. package/public/assets/hjson-D5-asLiD.js +1 -0
  190. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  191. package/public/assets/houston-DnULxvSX.js +1 -0
  192. package/public/assets/html-GMplVEZG.js +1 -0
  193. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  194. package/public/assets/http-jrhK8wxY.js +1 -0
  195. package/public/assets/hurl-irOxFIW8.js +1 -0
  196. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  197. package/public/assets/hy-DFXneXwc.js +1 -0
  198. package/public/assets/imba-DGztddWO.js +1 -0
  199. package/public/assets/index-D4PT0yE4.js +1 -0
  200. package/public/assets/index-DN20ggb1.js +1 -0
  201. package/public/assets/index-DdQ7zIzB.js +1 -0
  202. package/public/assets/index-Dl-rJJuP.js +1 -0
  203. package/public/assets/index-Dlo2gDiy.css +1 -0
  204. package/public/assets/ini-BEwlwnbL.js +1 -0
  205. package/public/assets/java-CylS5w8V.js +1 -0
  206. package/public/assets/javascript-wDzz0qaB.js +1 -0
  207. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  208. package/public/assets/jison-wvAkD_A8.js +1 -0
  209. package/public/assets/json-Cp-IABpG.js +1 -0
  210. package/public/assets/json5-C9tS-k6U.js +1 -0
  211. package/public/assets/jsonc-Des-eS-w.js +1 -0
  212. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  213. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  214. package/public/assets/jssm-C2t-YnRu.js +1 -0
  215. package/public/assets/jsx-g9-lgVsj.js +1 -0
  216. package/public/assets/julia-C8NyazO9.js +1 -0
  217. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  218. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  219. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  220. package/public/assets/kdl-DV7GczEv.js +1 -0
  221. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  222. package/public/assets/kusto-BvAqAH-y.js +1 -0
  223. package/public/assets/laserwave-DUszq2jm.js +1 -0
  224. package/public/assets/latex-BUKiar2Z.js +1 -0
  225. package/public/assets/lean-DP1Csr6i.js +1 -0
  226. package/public/assets/less-B1dDrJ26.js +1 -0
  227. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  228. package/public/assets/liquid-DYVedYrR.js +1 -0
  229. package/public/assets/llvm-BtvRca6l.js +1 -0
  230. package/public/assets/loading-CmEVQgyj.css +1 -0
  231. package/public/assets/loading-DgqIiz-T.js +1 -0
  232. package/public/assets/log-2UxHyX5q.js +1 -0
  233. package/public/assets/logo-BtOb2qkB.js +1 -0
  234. package/public/assets/lua-BbnMAYS6.js +1 -0
  235. package/public/assets/luau-CXu1NL6O.js +1 -0
  236. package/public/assets/main-CSlDZj4f.js +188 -0
  237. package/public/assets/main-HAZkIolJ.css +19 -0
  238. package/public/assets/make-CHLpvVh8.js +1 -0
  239. package/public/assets/markdown-Cvjx9yec.js +1 -0
  240. package/public/assets/marko-CPi9NSCl.js +1 -0
  241. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  242. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  243. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  244. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  245. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  246. package/public/assets/matlab-D7o27uSR.js +1 -0
  247. package/public/assets/mdc-DUICxH0z.js +1 -0
  248. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  249. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  250. package/public/assets/min-dark-CafNBF8u.js +1 -0
  251. package/public/assets/min-light-CTRr51gU.js +1 -0
  252. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  253. package/public/assets/mojo-1DNp92w6.js +1 -0
  254. package/public/assets/monokai-D4h5O-jR.js +1 -0
  255. package/public/assets/move-Bu9oaDYs.js +1 -0
  256. package/public/assets/narrat-DRg8JJMk.js +1 -0
  257. package/public/assets/nextflow-CUEJCptM.js +1 -0
  258. package/public/assets/nginx-DknmC5AR.js +1 -0
  259. package/public/assets/night-owl-C39BiMTA.js +1 -0
  260. package/public/assets/nim-CVrawwO9.js +1 -0
  261. package/public/assets/nix-BbRYJGeE.js +1 -0
  262. package/public/assets/nord-Ddv68eIx.js +1 -0
  263. package/public/assets/nushell-C-sUppwS.js +1 -0
  264. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  265. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  266. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  267. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  268. package/public/assets/one-light-PoHY5YXO.js +1 -0
  269. package/public/assets/pascal-D93ZcfNL.js +1 -0
  270. package/public/assets/perl-C0TMdlhV.js +1 -0
  271. package/public/assets/php-CDn_0X-4.js +1 -0
  272. package/public/assets/pkl-u5AG7uiY.js +1 -0
  273. package/public/assets/plastic-3e1v2bzS.js +1 -0
  274. package/public/assets/plsql-ChMvpjG-.js +1 -0
  275. package/public/assets/po-BTJTHyun.js +1 -0
  276. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  277. package/public/assets/polar-C0HS_06l.js +1 -0
  278. package/public/assets/postcss-CXtECtnM.js +1 -0
  279. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  280. package/public/assets/powershell-Dpen1YoG.js +1 -0
  281. package/public/assets/prisma-Dd19v3D-.js +1 -0
  282. package/public/assets/prolog-CbFg5uaA.js +1 -0
  283. package/public/assets/proto-DyJlTyXw.js +1 -0
  284. package/public/assets/pug-CGlum2m_.js +1 -0
  285. package/public/assets/puppet-BMWR74SV.js +1 -0
  286. package/public/assets/purescript-CklMAg4u.js +1 -0
  287. package/public/assets/python-B6aJPvgy.js +1 -0
  288. package/public/assets/qml-3beO22l8.js +1 -0
  289. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  290. package/public/assets/qss-IeuSbFQv.js +1 -0
  291. package/public/assets/r-DiinP2Uv.js +1 -0
  292. package/public/assets/racket-BqYA7rlc.js +1 -0
  293. package/public/assets/raku-DXvB9xmW.js +1 -0
  294. package/public/assets/razor-WgofotgN.js +1 -0
  295. package/public/assets/red-bN70gL4F.js +1 -0
  296. package/public/assets/reg-C-SQnVFl.js +1 -0
  297. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  298. package/public/assets/rel-C3B-1QV4.js +1 -0
  299. package/public/assets/riscv-BM1_JUlF.js +1 -0
  300. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  301. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  302. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  303. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  304. package/public/assets/rst-B0xPkSld.js +1 -0
  305. package/public/assets/ruby-BvKwtOVI.js +1 -0
  306. package/public/assets/rust-B1yitclQ.js +1 -0
  307. package/public/assets/sas-cz2c8ADy.js +1 -0
  308. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  309. package/public/assets/scala-C151Ov-r.js +1 -0
  310. package/public/assets/scheme-C98Dy4si.js +1 -0
  311. package/public/assets/scss-OYdSNvt2.js +1 -0
  312. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  313. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  314. package/public/assets/shellscript-Yzrsuije.js +1 -0
  315. package/public/assets/shellsession-BADoaaVG.js +1 -0
  316. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  317. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  318. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  319. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  320. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  321. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  322. package/public/assets/solidity-BbcW6ACK.js +1 -0
  323. package/public/assets/soy-Brmx7dQM.js +1 -0
  324. package/public/assets/sparql-rVzFXLq3.js +1 -0
  325. package/public/assets/splunk-BtCnVYZw.js +1 -0
  326. package/public/assets/sql-BLtJtn59.js +1 -0
  327. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  328. package/public/assets/stata-BH5u7GGu.js +1 -0
  329. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  330. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  331. package/public/assets/swift-Dg5xB15N.js +1 -0
  332. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  333. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  334. package/public/assets/systemd-4A_iFExJ.js +1 -0
  335. package/public/assets/talonscript-CkByrt1z.js +1 -0
  336. package/public/assets/tasl-QIJgUcNo.js +1 -0
  337. package/public/assets/tcl-dwOrl1Do.js +1 -0
  338. package/public/assets/templ-W15q3VgB.js +1 -0
  339. package/public/assets/terraform-BETggiCN.js +1 -0
  340. package/public/assets/tex-Cppo0RY3.js +1 -0
  341. package/public/assets/tokyo-night-hegEt444.js +1 -0
  342. package/public/assets/toml-vGWfd6FD.js +1 -0
  343. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  344. package/public/assets/tsv-B_m7g4N7.js +1 -0
  345. package/public/assets/tsx-COt5Ahok.js +1 -0
  346. package/public/assets/turtle-BsS91CYL.js +1 -0
  347. package/public/assets/twig-CO9l9SDP.js +1 -0
  348. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  349. package/public/assets/typespec-Df68jz8_.js +1 -0
  350. package/public/assets/typst-DHCkPAjA.js +1 -0
  351. package/public/assets/v-BcVCzyr7.js +1 -0
  352. package/public/assets/vala-CsfeWuGM.js +1 -0
  353. package/public/assets/vb-D17OF-Vu.js +1 -0
  354. package/public/assets/verilog-BQ8w6xss.js +1 -0
  355. package/public/assets/vesper-DU1UobuO.js +1 -0
  356. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  357. package/public/assets/viml-CJc9bBzg.js +1 -0
  358. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  359. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  360. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  361. package/public/assets/vue-CCoi5OLL.js +1 -0
  362. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  363. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  364. package/public/assets/vyper-CDx5xZoG.js +1 -0
  365. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  366. package/public/assets/wasm-MzD3tlZU.js +1 -0
  367. package/public/assets/wenyan-BV7otONQ.js +1 -0
  368. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  369. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  370. package/public/assets/wit-5i3qLPDT.js +1 -0
  371. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  372. package/public/assets/xml-sdJ4AIDG.js +1 -0
  373. package/public/assets/xsl-CtQFsRM5.js +1 -0
  374. package/public/assets/yaml-Buea-lGh.js +1 -0
  375. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  376. package/public/assets/zig-VOosw3JB.js +1 -0
  377. package/public/favicon.ico +0 -0
  378. package/public/index.html +38 -0
  379. package/public/loading.html +28 -0
  380. package/public/logo.png +0 -0
  381. package/public/manifest.webmanifest +1 -0
  382. package/public/maskable-icon-512x512.png +0 -0
  383. package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  384. package/public/monaco/vs/base/worker/workerMain.js +31 -0
  385. package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  386. package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  387. package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  388. package/public/monaco/vs/basic-languages/python/python.js +10 -0
  389. package/public/monaco/vs/editor/editor.main.css +8 -0
  390. package/public/monaco/vs/editor/editor.main.js +798 -0
  391. package/public/monaco/vs/language/css/cssMode.js +13 -0
  392. package/public/monaco/vs/language/css/cssWorker.js +77 -0
  393. package/public/monaco/vs/language/html/htmlMode.js +13 -0
  394. package/public/monaco/vs/language/html/htmlWorker.js +454 -0
  395. package/public/monaco/vs/language/json/jsonMode.js +19 -0
  396. package/public/monaco/vs/language/json/jsonWorker.js +42 -0
  397. package/public/monaco/vs/language/typescript/tsMode.js +20 -0
  398. package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
  399. package/public/monaco/vs/loader.js +11 -0
  400. package/public/monaco.worker.js +7 -0
  401. package/public/pwa-192x192.png +0 -0
  402. package/public/pwa-512x512.png +0 -0
  403. package/public/pwa-64x64.png +0 -0
  404. package/public/registerSW.js +1 -0
  405. package/public/sw.js +1 -0
  406. package/public/ui-version.json +3 -0
  407. package/public/workbox-60d14903.js +1 -0
  408. package/scripts/copy-auth-pages.mjs +22 -0
  409. package/scripts/copy-opencode-config.mjs +61 -0
  410. package/scripts/copy-ui-dist.mjs +21 -0
  411. package/src/api-types.ts +326 -0
  412. package/src/auth/auth-store.ts +175 -0
  413. package/src/auth/http-auth.ts +38 -0
  414. package/src/auth/manager.ts +163 -0
  415. package/src/auth/password-hash.ts +49 -0
  416. package/src/auth/session-manager.ts +23 -0
  417. package/src/auth/token-manager.ts +32 -0
  418. package/src/background-processes/manager.ts +519 -0
  419. package/src/bin.ts +29 -0
  420. package/src/config/binaries.ts +192 -0
  421. package/src/config/location.ts +78 -0
  422. package/src/config/schema.ts +104 -0
  423. package/src/config/store.ts +244 -0
  424. package/src/events/bus.ts +45 -0
  425. package/src/filesystem/__tests__/search-cache.test.ts +61 -0
  426. package/src/filesystem/browser.ts +353 -0
  427. package/src/filesystem/search-cache.ts +66 -0
  428. package/src/filesystem/search.ts +184 -0
  429. package/src/index.ts +540 -0
  430. package/src/launcher.ts +177 -0
  431. package/src/loader.ts +21 -0
  432. package/src/logger.ts +133 -0
  433. package/src/opencode-config.ts +31 -0
  434. package/src/plugins/channel.ts +55 -0
  435. package/src/plugins/handlers.ts +36 -0
  436. package/src/releases/dev-release-monitor.ts +118 -0
  437. package/src/releases/release-monitor.ts +149 -0
  438. package/src/server/http-server.ts +693 -0
  439. package/src/server/network-addresses.ts +75 -0
  440. package/src/server/routes/auth-pages/login.html +134 -0
  441. package/src/server/routes/auth-pages/token.html +93 -0
  442. package/src/server/routes/auth.ts +164 -0
  443. package/src/server/routes/background-processes.ts +85 -0
  444. package/src/server/routes/config.ts +76 -0
  445. package/src/server/routes/events.ts +61 -0
  446. package/src/server/routes/filesystem.ts +54 -0
  447. package/src/server/routes/meta.ts +58 -0
  448. package/src/server/routes/plugin.ts +75 -0
  449. package/src/server/routes/storage.ts +66 -0
  450. package/src/server/routes/workspaces.ts +113 -0
  451. package/src/server/routes/worktrees.ts +195 -0
  452. package/src/server/tls.ts +283 -0
  453. package/src/storage/instance-store.ts +64 -0
  454. package/src/ui/__tests__/remote-ui.test.ts +58 -0
  455. package/src/ui/remote-ui.ts +571 -0
  456. package/src/workspaces/git-worktrees.ts +241 -0
  457. package/src/workspaces/instance-events.ts +226 -0
  458. package/src/workspaces/manager.ts +493 -0
  459. package/src/workspaces/opencode-auth.ts +22 -0
  460. package/src/workspaces/runtime.ts +428 -0
  461. package/src/workspaces/worktree-map.ts +129 -0
  462. package/tsconfig.json +17 -0
@@ -0,0 +1,547 @@
1
+ import Fastify from "fastify";
2
+ import cors from "@fastify/cors";
3
+ import fastifyStatic from "@fastify/static";
4
+ import replyFrom from "@fastify/reply-from";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { fetch } from "undici";
8
+ import { isValidWorktreeSlug, listWorktrees, resolveRepoRoot } from "../workspaces/git-worktrees";
9
+ import { registerWorkspaceRoutes } from "./routes/workspaces";
10
+ import { registerConfigRoutes } from "./routes/config";
11
+ import { registerFilesystemRoutes } from "./routes/filesystem";
12
+ import { registerMetaRoutes } from "./routes/meta";
13
+ import { registerEventRoutes } from "./routes/events";
14
+ import { registerStorageRoutes } from "./routes/storage";
15
+ import { registerPluginRoutes } from "./routes/plugin";
16
+ import { registerBackgroundProcessRoutes } from "./routes/background-processes";
17
+ import { registerWorktreeRoutes } from "./routes/worktrees";
18
+ import { BackgroundProcessManager } from "../background-processes/manager";
19
+ import { registerAuthRoutes } from "./routes/auth";
20
+ import { sendUnauthorized, wantsHtml } from "../auth/http-auth";
21
+ export function createHttpServer(deps) {
22
+ // Fastify's type-level RawServer inference gets noisy when toggling HTTP vs HTTPS.
23
+ // We keep the runtime behavior correct and cast the instance to a generic FastifyInstance.
24
+ const app = Fastify({
25
+ logger: false,
26
+ ...(deps.protocol === "https" && deps.httpsOptions ? { https: deps.httpsOptions } : {}),
27
+ });
28
+ const proxyLogger = deps.logger.child({ component: "proxy" });
29
+ const apiLogger = deps.logger.child({ component: "http" });
30
+ const sseLogger = deps.logger.child({ component: "sse" });
31
+ const sseClients = new Set();
32
+ const registerSseClient = (cleanup) => {
33
+ sseClients.add(cleanup);
34
+ return () => sseClients.delete(cleanup);
35
+ };
36
+ const closeSseClients = () => {
37
+ for (const cleanup of Array.from(sseClients)) {
38
+ cleanup();
39
+ }
40
+ sseClients.clear();
41
+ };
42
+ app.addHook("onRequest", (request, _reply, done) => {
43
+ ;
44
+ request.__logMeta = {
45
+ start: process.hrtime.bigint(),
46
+ };
47
+ done();
48
+ });
49
+ app.addHook("onResponse", (request, reply, done) => {
50
+ const meta = request.__logMeta;
51
+ const durationMs = meta ? Number((process.hrtime.bigint() - meta.start) / BigInt(1000000)) : undefined;
52
+ const base = {
53
+ method: request.method,
54
+ url: request.url,
55
+ status: reply.statusCode,
56
+ durationMs,
57
+ };
58
+ apiLogger.debug(base, "HTTP request completed");
59
+ if (apiLogger.isLevelEnabled("trace")) {
60
+ apiLogger.trace({ ...base, params: request.params, query: request.query, body: request.body }, "HTTP request payload");
61
+ }
62
+ done();
63
+ });
64
+ const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]);
65
+ const isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
66
+ const getSelfOrigins = () => {
67
+ const origins = new Set();
68
+ const candidates = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl];
69
+ for (const candidate of candidates) {
70
+ if (!candidate)
71
+ continue;
72
+ try {
73
+ origins.add(new URL(candidate).origin);
74
+ }
75
+ catch {
76
+ // ignore
77
+ }
78
+ }
79
+ for (const addr of deps.serverMeta.addresses ?? []) {
80
+ try {
81
+ origins.add(new URL(addr.remoteUrl).origin);
82
+ }
83
+ catch {
84
+ // ignore
85
+ }
86
+ }
87
+ return origins;
88
+ };
89
+ app.register(cors, {
90
+ origin: (origin, cb) => {
91
+ if (!origin) {
92
+ cb(null, true);
93
+ return;
94
+ }
95
+ const selfOrigins = getSelfOrigins();
96
+ if (selfOrigins.has(origin)) {
97
+ cb(null, true);
98
+ return;
99
+ }
100
+ if (allowedDevOrigins.has(origin)) {
101
+ cb(null, true);
102
+ return;
103
+ }
104
+ // When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
105
+ if (deps.bindHost === "0.0.0.0" || !isLoopbackHost(deps.bindHost)) {
106
+ cb(null, true);
107
+ return;
108
+ }
109
+ cb(null, false);
110
+ },
111
+ credentials: true,
112
+ });
113
+ app.register(replyFrom, {
114
+ contentTypesToEncode: [],
115
+ undici: {
116
+ connections: 16,
117
+ pipelining: 1,
118
+ bodyTimeout: 0,
119
+ headersTimeout: 0,
120
+ },
121
+ });
122
+ const backgroundProcessManager = new BackgroundProcessManager({
123
+ workspaceManager: deps.workspaceManager,
124
+ eventBus: deps.eventBus,
125
+ logger: deps.logger.child({ component: "background-processes" }),
126
+ });
127
+ registerAuthRoutes(app, { authManager: deps.authManager });
128
+ app.addHook("preHandler", (request, reply, done) => {
129
+ const rawUrl = request.raw.url ?? request.url;
130
+ const pathname = (rawUrl.split("?")[0] ?? "").trim();
131
+ const publicApiPaths = new Set(["/api/auth/login", "/api/auth/token", "/api/auth/status", "/api/auth/logout"]);
132
+ const publicPagePaths = new Set(["/login"]);
133
+ if (deps.authManager.isTokenBootstrapEnabled()) {
134
+ publicPagePaths.add("/auth/token");
135
+ }
136
+ if (publicApiPaths.has(pathname) || publicPagePaths.has(pathname)) {
137
+ done();
138
+ return;
139
+ }
140
+ const session = deps.authManager.getSessionFromRequest(request);
141
+ const requiresAuthForApi = pathname.startsWith("/api/") || pathname.startsWith("/workspaces/");
142
+ if (requiresAuthForApi && !session) {
143
+ // Allow OpenCode plugin -> CodeNomad calls with per-instance basic auth.
144
+ const pluginMatch = pathname.match(/^\/workspaces\/([^/]+)\/plugin(?:\/|$)/);
145
+ if (pluginMatch) {
146
+ const workspaceId = pluginMatch[1];
147
+ const expected = deps.workspaceManager.getInstanceAuthorizationHeader(workspaceId);
148
+ const provided = Array.isArray(request.headers.authorization)
149
+ ? request.headers.authorization[0]
150
+ : request.headers.authorization;
151
+ if (expected && provided && provided === expected) {
152
+ done();
153
+ return;
154
+ }
155
+ }
156
+ sendUnauthorized(request, reply);
157
+ return;
158
+ }
159
+ if (!session && wantsHtml(request)) {
160
+ reply.redirect("/login");
161
+ return;
162
+ }
163
+ done();
164
+ });
165
+ app.get("/", async (request, reply) => {
166
+ const session = deps.authManager.getSessionFromRequest(request);
167
+ if (!session) {
168
+ reply.redirect("/login");
169
+ return;
170
+ }
171
+ if (deps.uiDevServerUrl) {
172
+ await proxyToDevServer(request, reply, deps.uiDevServerUrl);
173
+ return;
174
+ }
175
+ const uiDir = deps.uiStaticDir;
176
+ const indexPath = path.join(uiDir, "index.html");
177
+ if (uiDir && fs.existsSync(indexPath)) {
178
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"));
179
+ return;
180
+ }
181
+ reply.code(404).send({ message: "UI bundle missing" });
182
+ });
183
+ registerWorkspaceRoutes(app, { workspaceManager: deps.workspaceManager });
184
+ registerConfigRoutes(app, { configStore: deps.configStore, binaryRegistry: deps.binaryRegistry });
185
+ registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser });
186
+ registerMetaRoutes(app, { serverMeta: deps.serverMeta });
187
+ registerEventRoutes(app, { eventBus: deps.eventBus, registerClient: registerSseClient, logger: sseLogger });
188
+ registerWorktreeRoutes(app, { workspaceManager: deps.workspaceManager });
189
+ registerStorageRoutes(app, {
190
+ instanceStore: deps.instanceStore,
191
+ eventBus: deps.eventBus,
192
+ workspaceManager: deps.workspaceManager,
193
+ });
194
+ registerPluginRoutes(app, { workspaceManager: deps.workspaceManager, eventBus: deps.eventBus, logger: proxyLogger });
195
+ registerBackgroundProcessRoutes(app, { backgroundProcessManager });
196
+ registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger });
197
+ if (deps.uiDevServerUrl) {
198
+ setupDevProxy(app, deps.uiDevServerUrl, deps.authManager);
199
+ }
200
+ else {
201
+ setupStaticUi(app, deps.uiStaticDir, deps.authManager);
202
+ }
203
+ return {
204
+ instance: app,
205
+ start: async () => {
206
+ const attemptListen = async (requestedPort) => {
207
+ const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost });
208
+ return { addressInfo, requestedPort };
209
+ };
210
+ const autoPortRequested = deps.bindPort === 0;
211
+ const primaryPort = autoPortRequested ? deps.defaultPort : deps.bindPort;
212
+ const shouldRetryWithEphemeral = (error) => {
213
+ if (!autoPortRequested)
214
+ return false;
215
+ const err = error;
216
+ return Boolean(err && err.code === "EADDRINUSE");
217
+ };
218
+ let listenResult;
219
+ try {
220
+ listenResult = await attemptListen(primaryPort);
221
+ }
222
+ catch (error) {
223
+ if (!shouldRetryWithEphemeral(error)) {
224
+ throw error;
225
+ }
226
+ deps.logger.warn({ err: error, port: primaryPort }, "Preferred port unavailable, retrying on ephemeral port");
227
+ listenResult = await attemptListen(0);
228
+ }
229
+ let actualPort = listenResult.requestedPort;
230
+ if (typeof listenResult.addressInfo === "string") {
231
+ try {
232
+ const parsed = new URL(listenResult.addressInfo);
233
+ actualPort = Number(parsed.port) || listenResult.requestedPort;
234
+ }
235
+ catch {
236
+ actualPort = listenResult.requestedPort;
237
+ }
238
+ }
239
+ else {
240
+ const address = app.server.address();
241
+ if (typeof address === "object" && address) {
242
+ actualPort = address.port;
243
+ }
244
+ }
245
+ const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost;
246
+ const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`;
247
+ deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening");
248
+ return { port: actualPort, url: serverUrl, displayHost };
249
+ },
250
+ stop: () => {
251
+ closeSseClients();
252
+ return app.close();
253
+ },
254
+ };
255
+ }
256
+ function registerInstanceProxyRoutes(app, deps) {
257
+ app.register(async (instance) => {
258
+ instance.removeAllContentTypeParsers();
259
+ instance.addContentTypeParser("*", (req, body, done) => done(null, body));
260
+ const proxyBaseHandler = async (request, reply) => {
261
+ await proxyWorkspaceRequest({
262
+ request,
263
+ reply,
264
+ workspaceManager: deps.workspaceManager,
265
+ worktreeSlug: request.params.slug,
266
+ pathSuffix: "",
267
+ logger: deps.logger,
268
+ });
269
+ };
270
+ const proxyWildcardHandler = async (request, reply) => {
271
+ await proxyWorkspaceRequest({
272
+ request,
273
+ reply,
274
+ workspaceManager: deps.workspaceManager,
275
+ worktreeSlug: request.params.slug,
276
+ pathSuffix: request.params["*"] ?? "",
277
+ logger: deps.logger,
278
+ });
279
+ };
280
+ instance.all("/workspaces/:id/worktrees/:slug/instance", proxyBaseHandler);
281
+ instance.all("/workspaces/:id/worktrees/:slug/instance/*", proxyWildcardHandler);
282
+ });
283
+ }
284
+ const INSTANCE_PROXY_HOST = "127.0.0.1";
285
+ async function proxyWorkspaceRequest(args) {
286
+ const { request, reply, workspaceManager, logger, worktreeSlug } = args;
287
+ const workspaceId = request.params.id;
288
+ const workspace = workspaceManager.get(workspaceId);
289
+ const bodyToJson = (body) => {
290
+ if (body == null)
291
+ return null;
292
+ const anyBody = body;
293
+ if (anyBody && typeof anyBody.pipe === "function") {
294
+ // Don't consume streams (would break proxying).
295
+ // Best-effort: if the stream already has buffered chunks, parse those.
296
+ try {
297
+ const buffered = anyBody?._readableState?.buffer;
298
+ if (Array.isArray(buffered) && buffered.length > 0) {
299
+ const chunks = [];
300
+ for (const entry of buffered) {
301
+ if (!entry)
302
+ continue;
303
+ if (Buffer.isBuffer(entry)) {
304
+ chunks.push(entry);
305
+ continue;
306
+ }
307
+ const data = entry.data;
308
+ if (Buffer.isBuffer(data)) {
309
+ chunks.push(data);
310
+ }
311
+ }
312
+ if (chunks.length > 0) {
313
+ const text = Buffer.concat(chunks).toString("utf-8");
314
+ try {
315
+ return JSON.parse(text);
316
+ }
317
+ catch {
318
+ return { __raw: text };
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // fall through
325
+ }
326
+ return { __stream: true };
327
+ }
328
+ const maybeParse = (input) => {
329
+ try {
330
+ return JSON.parse(input);
331
+ }
332
+ catch {
333
+ return { __raw: input };
334
+ }
335
+ };
336
+ if (Buffer.isBuffer(body)) {
337
+ return maybeParse(body.toString("utf-8"));
338
+ }
339
+ if (typeof body === "string") {
340
+ return maybeParse(body);
341
+ }
342
+ if (typeof body === "object") {
343
+ return body;
344
+ }
345
+ return body;
346
+ };
347
+ if (!workspace) {
348
+ reply.code(404).send({ error: "Workspace not found" });
349
+ return;
350
+ }
351
+ const port = workspaceManager.getInstancePort(workspaceId);
352
+ if (!port) {
353
+ reply.code(502).send({ error: "Workspace instance is not ready" });
354
+ return;
355
+ }
356
+ if (!isValidWorktreeSlug(worktreeSlug)) {
357
+ reply.code(400).send({ error: "Invalid worktree slug" });
358
+ return;
359
+ }
360
+ const directory = await resolveWorktreeDirectory({
361
+ workspaceId,
362
+ workspacePath: workspace.path,
363
+ worktreeSlug,
364
+ logger,
365
+ });
366
+ if (!directory) {
367
+ reply.code(404).send({ error: "Worktree not found" });
368
+ return;
369
+ }
370
+ const normalizedSuffix = normalizeInstanceSuffix(args.pathSuffix);
371
+ const queryIndex = (request.raw.url ?? "").indexOf("?");
372
+ const search = queryIndex >= 0 ? (request.raw.url ?? "").slice(queryIndex) : "";
373
+ const targetUrl = `http://${INSTANCE_PROXY_HOST}:${port}${normalizedSuffix}${search}`;
374
+ const instanceAuthHeader = workspaceManager.getInstanceAuthorizationHeader(workspaceId);
375
+ logger.debug({ workspaceId, method: request.method, targetUrl }, "Proxying request to instance");
376
+ if (logger.isLevelEnabled("trace")) {
377
+ logger.trace({ workspaceId, targetUrl, body: request.body }, "Instance proxy payload");
378
+ }
379
+ return reply.from(targetUrl, {
380
+ rewriteRequestHeaders: (_originalRequest, headers) => {
381
+ if (instanceAuthHeader) {
382
+ headers.authorization = instanceAuthHeader;
383
+ }
384
+ // OpenCode expects the *full* path; we send it via header to avoid query tampering.
385
+ const isNonASCII = /[^\x00-\x7F]/.test(directory);
386
+ const encodedDirectory = isNonASCII ? encodeURIComponent(directory) : directory;
387
+ headers["x-opencode-directory"] = encodedDirectory;
388
+ if (logger.isLevelEnabled("trace")) {
389
+ const outgoing = {};
390
+ for (const [key, value] of Object.entries(headers)) {
391
+ outgoing[key] = value;
392
+ }
393
+ // Redact sensitive headers.
394
+ for (const key of Object.keys(outgoing)) {
395
+ const lower = key.toLowerCase();
396
+ if (lower === "authorization" || lower === "cookie" || lower === "set-cookie") {
397
+ outgoing[key] = "<redacted>";
398
+ }
399
+ }
400
+ logger.trace({
401
+ workspaceId,
402
+ method: request.method,
403
+ targetUrl,
404
+ worktreeSlug,
405
+ directory,
406
+ contentType: request.headers["content-type"],
407
+ body: bodyToJson(request.body),
408
+ headers: outgoing,
409
+ }, "Proxy -> OpenCode request");
410
+ }
411
+ return headers;
412
+ },
413
+ onError: (proxyReply, { error }) => {
414
+ logger.error({ err: error, workspaceId, targetUrl }, "Failed to proxy workspace request");
415
+ if (!proxyReply.sent) {
416
+ proxyReply.code(502).send({ error: "Workspace instance proxy failed" });
417
+ }
418
+ },
419
+ });
420
+ }
421
+ function normalizeInstanceSuffix(pathSuffix) {
422
+ if (!pathSuffix || pathSuffix === "/") {
423
+ return "/";
424
+ }
425
+ const trimmed = pathSuffix.replace(/^\/+/, "");
426
+ return trimmed.length === 0 ? "/" : `/${trimmed}`;
427
+ }
428
+ const WORKTREE_CACHE_TTL_MS = 2000;
429
+ const worktreeCache = new Map();
430
+ async function getCachedWorktrees(params) {
431
+ const cached = worktreeCache.get(params.workspaceId);
432
+ const now = Date.now();
433
+ if (cached && cached.expiresAt > now) {
434
+ return cached;
435
+ }
436
+ const { repoRoot } = await resolveRepoRoot(params.workspacePath, params.logger);
437
+ const worktrees = await listWorktrees({ repoRoot, workspaceFolder: params.workspacePath, logger: params.logger });
438
+ const entry = {
439
+ expiresAt: now + WORKTREE_CACHE_TTL_MS,
440
+ repoRoot,
441
+ worktrees: worktrees.map((wt) => ({ slug: wt.slug, directory: wt.directory })),
442
+ };
443
+ worktreeCache.set(params.workspaceId, entry);
444
+ return entry;
445
+ }
446
+ async function resolveWorktreeDirectory(params) {
447
+ const { worktreeSlug } = params;
448
+ const cached = await getCachedWorktrees({ workspaceId: params.workspaceId, workspacePath: params.workspacePath, logger: params.logger });
449
+ const match = cached.worktrees.find((wt) => wt.slug === worktreeSlug);
450
+ if (match) {
451
+ return match.directory;
452
+ }
453
+ // If the slug is new (e.g., created moments ago), refresh once.
454
+ worktreeCache.delete(params.workspaceId);
455
+ const refreshed = await getCachedWorktrees({ workspaceId: params.workspaceId, workspacePath: params.workspacePath, logger: params.logger });
456
+ return refreshed.worktrees.find((wt) => wt.slug === worktreeSlug)?.directory ?? null;
457
+ }
458
+ function setupStaticUi(app, uiDir, authManager) {
459
+ if (!uiDir) {
460
+ app.log.warn("UI static directory not provided; API endpoints only");
461
+ return;
462
+ }
463
+ if (!fs.existsSync(uiDir)) {
464
+ app.log.warn({ uiDir }, "UI static directory missing; API endpoints only");
465
+ return;
466
+ }
467
+ app.register(fastifyStatic, {
468
+ root: uiDir,
469
+ prefix: "/",
470
+ decorateReply: false,
471
+ });
472
+ const indexPath = path.join(uiDir, "index.html");
473
+ app.setNotFoundHandler((request, reply) => {
474
+ const url = request.raw.url ?? "";
475
+ if (isApiRequest(url)) {
476
+ reply.code(404).send({ message: "Not Found" });
477
+ return;
478
+ }
479
+ const session = authManager.getSessionFromRequest(request);
480
+ if (!session && wantsHtml(request)) {
481
+ reply.redirect("/login");
482
+ return;
483
+ }
484
+ if (fs.existsSync(indexPath)) {
485
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"));
486
+ }
487
+ else {
488
+ reply.code(404).send({ message: "UI bundle missing" });
489
+ }
490
+ });
491
+ }
492
+ function setupDevProxy(app, upstreamBase, authManager) {
493
+ app.log.info({ upstreamBase }, "Proxying UI requests to development server");
494
+ app.setNotFoundHandler((request, reply) => {
495
+ const url = request.raw.url ?? "";
496
+ if (isApiRequest(url)) {
497
+ reply.code(404).send({ message: "Not Found" });
498
+ return;
499
+ }
500
+ const session = authManager.getSessionFromRequest(request);
501
+ if (!session && wantsHtml(request)) {
502
+ reply.redirect("/login");
503
+ return;
504
+ }
505
+ void proxyToDevServer(request, reply, upstreamBase);
506
+ });
507
+ }
508
+ async function proxyToDevServer(request, reply, upstreamBase) {
509
+ try {
510
+ const targetUrl = new URL(request.raw.url ?? "/", upstreamBase);
511
+ const response = await fetch(targetUrl, {
512
+ method: request.method,
513
+ headers: buildProxyHeaders(request.headers),
514
+ });
515
+ response.headers.forEach((value, key) => {
516
+ reply.header(key, value);
517
+ });
518
+ reply.code(response.status);
519
+ if (!response.body || request.method === "HEAD") {
520
+ reply.send();
521
+ return;
522
+ }
523
+ const buffer = Buffer.from(await response.arrayBuffer());
524
+ reply.send(buffer);
525
+ }
526
+ catch (error) {
527
+ request.log.error({ err: error }, "Failed to proxy UI request to dev server");
528
+ if (!reply.sent) {
529
+ reply.code(502).send("UI dev server is unavailable");
530
+ }
531
+ }
532
+ }
533
+ function isApiRequest(rawUrl) {
534
+ if (!rawUrl)
535
+ return false;
536
+ const pathname = rawUrl.split("?")[0] ?? "";
537
+ return pathname === "/api" || pathname.startsWith("/api/");
538
+ }
539
+ function buildProxyHeaders(headers) {
540
+ const result = {};
541
+ for (const [key, value] of Object.entries(headers ?? {})) {
542
+ if (!value || key.toLowerCase() === "host")
543
+ continue;
544
+ result[key] = Array.isArray(value) ? value.join(",") : value;
545
+ }
546
+ return result;
547
+ }
@@ -0,0 +1,72 @@
1
+ import os from "os";
2
+ export function resolveNetworkAddresses(args) {
3
+ const { host, protocol, port } = args;
4
+ const interfaces = os.networkInterfaces();
5
+ const seen = new Set();
6
+ const results = [];
7
+ const addAddress = (ip, scope) => {
8
+ if (!ip || ip === "0.0.0.0")
9
+ return;
10
+ const key = `ipv4-${ip}`;
11
+ if (seen.has(key))
12
+ return;
13
+ seen.add(key);
14
+ results.push({ ip, family: "ipv4", scope, remoteUrl: `${protocol}://${ip}:${port}` });
15
+ };
16
+ const normalizeFamily = (value) => {
17
+ if (typeof value === "string") {
18
+ const lowered = value.toLowerCase();
19
+ if (lowered === "ipv4") {
20
+ return "ipv4";
21
+ }
22
+ }
23
+ if (value === 4)
24
+ return "ipv4";
25
+ return null;
26
+ };
27
+ if (host === "0.0.0.0") {
28
+ // Enumerate system interfaces (IPv4 only)
29
+ for (const entries of Object.values(interfaces)) {
30
+ if (!entries)
31
+ continue;
32
+ for (const entry of entries) {
33
+ const family = normalizeFamily(entry.family);
34
+ if (!family)
35
+ continue;
36
+ if (!entry.address || entry.address === "0.0.0.0")
37
+ continue;
38
+ const scope = entry.internal ? "loopback" : "external";
39
+ addAddress(entry.address, scope);
40
+ }
41
+ }
42
+ }
43
+ // Always include loopback address
44
+ addAddress("127.0.0.1", "loopback");
45
+ // Include explicitly configured host if it was IPv4
46
+ if (isIPv4Address(host) && host !== "0.0.0.0") {
47
+ const isLoopback = host.startsWith("127.");
48
+ addAddress(host, isLoopback ? "loopback" : "external");
49
+ }
50
+ const scopeWeight = { external: 0, internal: 1, loopback: 2 };
51
+ return results.sort((a, b) => {
52
+ const scopeDelta = scopeWeight[a.scope] - scopeWeight[b.scope];
53
+ if (scopeDelta !== 0)
54
+ return scopeDelta;
55
+ return a.ip.localeCompare(b.ip);
56
+ });
57
+ }
58
+ function isIPv4Address(value) {
59
+ if (!value)
60
+ return false;
61
+ const parts = value.split(".");
62
+ if (parts.length !== 4)
63
+ return false;
64
+ return parts.every((part) => {
65
+ if (part.length === 0 || part.length > 3)
66
+ return false;
67
+ if (!/^[0-9]+$/.test(part))
68
+ return false;
69
+ const num = Number(part);
70
+ return Number.isInteger(num) && num >= 0 && num <= 255;
71
+ });
72
+ }