@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,192 @@
1
+ import {
2
+ BinaryCreateRequest,
3
+ BinaryRecord,
4
+ BinaryUpdateRequest,
5
+ BinaryValidationResult,
6
+ } from "../api-types"
7
+ import { spawnSync } from "child_process"
8
+ import { ConfigStore } from "./store"
9
+ import { EventBus } from "../events/bus"
10
+ import type { ConfigFile } from "./schema"
11
+ import { Logger } from "../logger"
12
+ import { buildSpawnSpec } from "../workspaces/runtime"
13
+
14
+ export class BinaryRegistry {
15
+ constructor(
16
+ private readonly configStore: ConfigStore,
17
+ private readonly eventBus: EventBus | undefined,
18
+ private readonly logger: Logger,
19
+ ) {}
20
+
21
+ list(): BinaryRecord[] {
22
+ return this.mapRecords()
23
+ }
24
+
25
+ resolveDefault(): BinaryRecord {
26
+ const binaries = this.mapRecords()
27
+ if (binaries.length === 0) {
28
+ this.logger.warn("No configured binaries found, falling back to opencode")
29
+ return this.buildFallbackRecord("opencode")
30
+ }
31
+ return binaries.find((binary) => binary.isDefault) ?? binaries[0]
32
+ }
33
+
34
+ create(request: BinaryCreateRequest): BinaryRecord {
35
+ this.logger.debug({ path: request.path }, "Registering OpenCode binary")
36
+ const entry = {
37
+ path: request.path,
38
+ version: undefined,
39
+ lastUsed: Date.now(),
40
+ label: request.label,
41
+ }
42
+
43
+ const config = this.configStore.get()
44
+ const nextConfig = this.cloneConfig(config)
45
+ const deduped = nextConfig.opencodeBinaries.filter((binary) => binary.path !== request.path)
46
+ nextConfig.opencodeBinaries = [entry, ...deduped]
47
+
48
+ if (request.makeDefault) {
49
+ nextConfig.preferences.lastUsedBinary = request.path
50
+ }
51
+
52
+ this.configStore.replace(nextConfig)
53
+ const record = this.getById(request.path)
54
+ this.emitChange()
55
+ return record
56
+ }
57
+
58
+ update(id: string, updates: BinaryUpdateRequest): BinaryRecord {
59
+ this.logger.debug({ id }, "Updating OpenCode binary")
60
+ const config = this.configStore.get()
61
+ const nextConfig = this.cloneConfig(config)
62
+ nextConfig.opencodeBinaries = nextConfig.opencodeBinaries.map((binary) =>
63
+ binary.path === id ? { ...binary, label: updates.label ?? binary.label } : binary,
64
+ )
65
+
66
+ if (updates.makeDefault) {
67
+ nextConfig.preferences.lastUsedBinary = id
68
+ }
69
+
70
+ this.configStore.replace(nextConfig)
71
+ const record = this.getById(id)
72
+ this.emitChange()
73
+ return record
74
+ }
75
+
76
+ remove(id: string) {
77
+ this.logger.debug({ id }, "Removing OpenCode binary")
78
+ const config = this.configStore.get()
79
+ const nextConfig = this.cloneConfig(config)
80
+ const remaining = nextConfig.opencodeBinaries.filter((binary) => binary.path !== id)
81
+ nextConfig.opencodeBinaries = remaining
82
+
83
+ if (nextConfig.preferences.lastUsedBinary === id) {
84
+ nextConfig.preferences.lastUsedBinary = remaining[0]?.path
85
+ }
86
+
87
+ this.configStore.replace(nextConfig)
88
+ this.emitChange()
89
+ }
90
+
91
+ validatePath(path: string): BinaryValidationResult {
92
+ this.logger.debug({ path }, "Validating OpenCode binary path")
93
+ return this.validateRecord({
94
+ id: path,
95
+ path,
96
+ label: this.prettyLabel(path),
97
+ isDefault: false,
98
+ })
99
+ }
100
+
101
+ private cloneConfig(config: ConfigFile): ConfigFile {
102
+ return JSON.parse(JSON.stringify(config)) as ConfigFile
103
+ }
104
+
105
+ private mapRecords(): BinaryRecord[] {
106
+
107
+ const config = this.configStore.get()
108
+ const configuredBinaries = config.opencodeBinaries.map<BinaryRecord>((binary) => ({
109
+ id: binary.path,
110
+ path: binary.path,
111
+ label: binary.label ?? this.prettyLabel(binary.path),
112
+ version: binary.version,
113
+ isDefault: false,
114
+ }))
115
+
116
+ const defaultPath = config.preferences.lastUsedBinary ?? configuredBinaries[0]?.path ?? "opencode"
117
+
118
+ const annotated = configuredBinaries.map((binary) => ({
119
+ ...binary,
120
+ isDefault: binary.path === defaultPath,
121
+ }))
122
+
123
+ if (!annotated.some((binary) => binary.path === defaultPath)) {
124
+ annotated.unshift(this.buildFallbackRecord(defaultPath))
125
+ }
126
+
127
+ return annotated
128
+ }
129
+
130
+ private getById(id: string): BinaryRecord {
131
+ return this.mapRecords().find((binary) => binary.id === id) ?? this.buildFallbackRecord(id)
132
+ }
133
+
134
+ private emitChange() {
135
+ this.logger.debug("Emitting binaries changed event")
136
+ this.eventBus?.publish({ type: "config.binariesChanged", binaries: this.mapRecords() })
137
+ }
138
+
139
+ private validateRecord(record: BinaryRecord): BinaryValidationResult {
140
+ const inputPath = record.path
141
+ if (!inputPath) {
142
+ return { valid: false, error: "Missing binary path" }
143
+ }
144
+
145
+ const spec = buildSpawnSpec(inputPath, ["--version"])
146
+
147
+ try {
148
+ const result = spawnSync(spec.command, spec.args, {
149
+ encoding: "utf8",
150
+ windowsVerbatimArguments: Boolean((spec.options as { windowsVerbatimArguments?: boolean }).windowsVerbatimArguments),
151
+ })
152
+
153
+ if (result.error) {
154
+ return { valid: false, error: result.error.message }
155
+ }
156
+
157
+ if (result.status !== 0) {
158
+ const stderr = result.stderr?.trim()
159
+ const stdout = result.stdout?.trim()
160
+ const combined = stderr || stdout
161
+ const error = combined ? `Exited with code ${result.status}: ${combined}` : `Exited with code ${result.status}`
162
+ return { valid: false, error }
163
+ }
164
+
165
+ const stdout = (result.stdout ?? "").trim()
166
+ const firstLine = stdout.split(/\r?\n/).find((line) => line.trim().length > 0)
167
+ const normalized = firstLine?.trim()
168
+
169
+ const versionMatch = normalized?.match(/([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/)
170
+ const version = versionMatch?.[1]
171
+
172
+ return { valid: true, version }
173
+ } catch (error) {
174
+ return { valid: false, error: error instanceof Error ? error.message : String(error) }
175
+ }
176
+ }
177
+
178
+ private buildFallbackRecord(path: string): BinaryRecord {
179
+ return {
180
+ id: path,
181
+ path,
182
+ label: this.prettyLabel(path),
183
+ isDefault: true,
184
+ }
185
+ }
186
+
187
+ private prettyLabel(path: string) {
188
+ const parts = path.split(/[\\/]/)
189
+ const last = parts[parts.length - 1] || path
190
+ return last || path
191
+ }
192
+ }
@@ -0,0 +1,78 @@
1
+ import os from "os"
2
+ import path from "path"
3
+
4
+ export interface ConfigLocation {
5
+ /** Resolved absolute base directory containing all persisted server data. */
6
+ baseDir: string
7
+ /** Canonical YAML config file path (may be custom when input points to a YAML file). */
8
+ configYamlPath: string
9
+ /** Canonical YAML state file path (always in baseDir). */
10
+ stateYamlPath: string
11
+ /** Legacy JSON config file path used for migration (always in baseDir, or explicit JSON input). */
12
+ legacyJsonPath: string
13
+ /** Directory for per-instance persisted data (chat history etc.). */
14
+ instancesDir: string
15
+ }
16
+
17
+ function resolvePath(inputPath: string): string {
18
+ if (inputPath.startsWith("~/")) {
19
+ return path.join(os.homedir(), inputPath.slice(2))
20
+ }
21
+ return path.resolve(inputPath)
22
+ }
23
+
24
+ function isYamlPath(filePath: string): boolean {
25
+ const lower = filePath.toLowerCase()
26
+ return lower.endsWith(".yaml") || lower.endsWith(".yml")
27
+ }
28
+
29
+ function isJsonPath(filePath: string): boolean {
30
+ return filePath.toLowerCase().endsWith(".json")
31
+ }
32
+
33
+ /**
34
+ * Resolve CodeNomad's config location into a stable base directory + derived file paths.
35
+ *
36
+ * Supported inputs:
37
+ * - Directory: "~/.config/codenomad"
38
+ * - YAML file: "~/.config/codenomad/config.yaml" (or any *.yml/*.yaml)
39
+ * - Legacy JSON file: "~/.config/codenomad/config.json"
40
+ */
41
+ export function resolveConfigLocation(raw: string): ConfigLocation {
42
+ const trimmed = (raw ?? "").trim()
43
+ const fallback = "~/.config/codenomad/config.json"
44
+ const input = trimmed.length > 0 ? trimmed : fallback
45
+
46
+ const resolvedInput = resolvePath(input)
47
+
48
+ if (isYamlPath(resolvedInput)) {
49
+ const baseDir = path.dirname(resolvedInput)
50
+ return {
51
+ baseDir,
52
+ configYamlPath: resolvedInput,
53
+ stateYamlPath: path.join(baseDir, "state.yaml"),
54
+ legacyJsonPath: path.join(baseDir, "config.json"),
55
+ instancesDir: path.join(baseDir, "instances"),
56
+ }
57
+ }
58
+
59
+ if (isJsonPath(resolvedInput)) {
60
+ const baseDir = path.dirname(resolvedInput)
61
+ return {
62
+ baseDir,
63
+ configYamlPath: path.join(baseDir, "config.yaml"),
64
+ stateYamlPath: path.join(baseDir, "state.yaml"),
65
+ legacyJsonPath: resolvedInput,
66
+ instancesDir: path.join(baseDir, "instances"),
67
+ }
68
+ }
69
+
70
+ const baseDir = resolvedInput
71
+ return {
72
+ baseDir,
73
+ configYamlPath: path.join(baseDir, "config.yaml"),
74
+ stateYamlPath: path.join(baseDir, "state.yaml"),
75
+ legacyJsonPath: path.join(baseDir, "config.json"),
76
+ instancesDir: path.join(baseDir, "instances"),
77
+ }
78
+ }
@@ -0,0 +1,104 @@
1
+ import { z } from "zod"
2
+
3
+ const ModelPreferenceSchema = z.object({
4
+ providerId: z.string(),
5
+ modelId: z.string(),
6
+ })
7
+
8
+ const AgentModelSelectionSchema = z.record(z.string(), ModelPreferenceSchema)
9
+ const AgentModelSelectionsSchema = z.record(z.string(), AgentModelSelectionSchema)
10
+
11
+ const PreferencesSchema = z
12
+ .object({
13
+ showThinkingBlocks: z.boolean().default(false),
14
+ thinkingBlocksExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
15
+ showTimelineTools: z.boolean().default(true),
16
+ promptSubmitOnEnter: z.boolean().default(false),
17
+ lastUsedBinary: z.string().optional(),
18
+ locale: z.string().optional(),
19
+ environmentVariables: z.record(z.string()).default({}),
20
+ modelRecents: z.array(ModelPreferenceSchema).default([]),
21
+ modelFavorites: z.array(ModelPreferenceSchema).default([]),
22
+ modelThinkingSelections: z.record(z.string(), z.string()).default({}),
23
+ diffViewMode: z.enum(["split", "unified"]).default("split"),
24
+ toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
25
+ diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
26
+ showUsageMetrics: z.boolean().default(true),
27
+ autoCleanupBlankSessions: z.boolean().default(true),
28
+ listeningMode: z.enum(["local", "all"]).default("local"),
29
+
30
+ // OS notifications
31
+ osNotificationsEnabled: z.boolean().default(false),
32
+ osNotificationsAllowWhenVisible: z.boolean().default(false),
33
+ notifyOnNeedsInput: z.boolean().default(true),
34
+ notifyOnIdle: z.boolean().default(true),
35
+ })
36
+ // Preserve unknown preference keys so newer configs survive older binaries.
37
+ .passthrough()
38
+
39
+ const RecentFolderSchema = z.object({
40
+ path: z.string(),
41
+ lastAccessed: z.number().nonnegative(),
42
+ })
43
+
44
+ const OpenCodeBinarySchema = z.object({
45
+ path: z.string(),
46
+ version: z.string().optional(),
47
+ lastUsed: z.number().nonnegative(),
48
+ label: z.string().optional(),
49
+ })
50
+
51
+ const ConfigFileSchema = z
52
+ .object({
53
+ preferences: PreferencesSchema.default({}),
54
+ recentFolders: z.array(RecentFolderSchema).default([]),
55
+ opencodeBinaries: z.array(OpenCodeBinarySchema).default([]),
56
+ theme: z.enum(["light", "dark", "system"]).optional(),
57
+ })
58
+ // Preserve unknown top-level keys so optional future features survive downgrades.
59
+ .passthrough()
60
+
61
+ // On-disk config.yaml only stores stable configuration (not volatile state like recent folders).
62
+ const ConfigYamlSchema = z
63
+ .object({
64
+ preferences: PreferencesSchema.default({}),
65
+ opencodeBinaries: z.array(OpenCodeBinarySchema).default([]),
66
+ theme: z.enum(["light", "dark", "system"]).optional(),
67
+ })
68
+ .passthrough()
69
+
70
+ // On-disk state.yaml stores server-scoped mutable state (per-server, not per-client).
71
+ const StateFileSchema = z
72
+ .object({
73
+ recentFolders: z.array(RecentFolderSchema).default([]),
74
+ })
75
+ .passthrough()
76
+
77
+ const DEFAULT_CONFIG = ConfigFileSchema.parse({})
78
+ const DEFAULT_CONFIG_YAML = ConfigYamlSchema.parse({})
79
+ const DEFAULT_STATE = StateFileSchema.parse({})
80
+
81
+ export {
82
+ ModelPreferenceSchema,
83
+ AgentModelSelectionSchema,
84
+ AgentModelSelectionsSchema,
85
+ PreferencesSchema,
86
+ RecentFolderSchema,
87
+ OpenCodeBinarySchema,
88
+ ConfigFileSchema,
89
+ ConfigYamlSchema,
90
+ StateFileSchema,
91
+ DEFAULT_CONFIG,
92
+ DEFAULT_CONFIG_YAML,
93
+ DEFAULT_STATE,
94
+ }
95
+
96
+ export type ModelPreference = z.infer<typeof ModelPreferenceSchema>
97
+ export type AgentModelSelection = z.infer<typeof AgentModelSelectionSchema>
98
+ export type AgentModelSelections = z.infer<typeof AgentModelSelectionsSchema>
99
+ export type Preferences = z.infer<typeof PreferencesSchema>
100
+ export type RecentFolder = z.infer<typeof RecentFolderSchema>
101
+ export type OpenCodeBinary = z.infer<typeof OpenCodeBinarySchema>
102
+ export type ConfigFile = z.infer<typeof ConfigFileSchema>
103
+ export type ConfigYamlFile = z.infer<typeof ConfigYamlSchema>
104
+ export type StateFile = z.infer<typeof StateFileSchema>
@@ -0,0 +1,244 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml"
4
+ import { EventBus } from "../events/bus"
5
+ import { Logger } from "../logger"
6
+ import {
7
+ ConfigFile,
8
+ ConfigFileSchema,
9
+ ConfigYamlSchema,
10
+ DEFAULT_CONFIG,
11
+ DEFAULT_CONFIG_YAML,
12
+ DEFAULT_STATE,
13
+ StateFile,
14
+ StateFileSchema,
15
+ } from "./schema"
16
+ import type { ConfigLocation } from "./location"
17
+
18
+ export class ConfigStore {
19
+ private cache: ConfigFile = DEFAULT_CONFIG
20
+ private state: StateFile = DEFAULT_STATE
21
+ private loaded = false
22
+
23
+ constructor(
24
+ private readonly location: ConfigLocation,
25
+ private readonly eventBus: EventBus | undefined,
26
+ private readonly logger: Logger,
27
+ ) {}
28
+
29
+ load(): ConfigFile {
30
+ if (this.loaded) {
31
+ return this.cache
32
+ }
33
+
34
+ try {
35
+ const configYamlPath = this.location.configYamlPath
36
+ const stateYamlPath = this.location.stateYamlPath
37
+ const legacyJsonPath = this.location.legacyJsonPath
38
+
39
+ if (fs.existsSync(configYamlPath)) {
40
+ const configDoc = this.readYamlFile(configYamlPath, DEFAULT_CONFIG_YAML, ConfigYamlSchema, "config")
41
+ const stateDoc = fs.existsSync(stateYamlPath)
42
+ ? this.readYamlFile(stateYamlPath, DEFAULT_STATE, StateFileSchema, "state")
43
+ : DEFAULT_STATE
44
+
45
+ this.state = stateDoc
46
+ this.cache = this.mergeDocs(configDoc, stateDoc)
47
+ this.logger.debug({ configYamlPath, stateYamlPath }, "Loaded existing YAML config/state")
48
+ } else if (fs.existsSync(legacyJsonPath)) {
49
+ const migrated = this.migrateFromLegacyJson(legacyJsonPath)
50
+ this.state = migrated.state
51
+ this.cache = migrated.config
52
+ } else {
53
+ // Fresh install: write defaults.
54
+ this.state = DEFAULT_STATE
55
+ this.cache = this.mergeDocs(DEFAULT_CONFIG_YAML, DEFAULT_STATE)
56
+ this.persist()
57
+ this.logger.debug(
58
+ { configYamlPath, stateYamlPath },
59
+ "No config files found, created default YAML config/state",
60
+ )
61
+ }
62
+ } catch (error) {
63
+ this.logger.warn({ err: error }, "Failed to load config/state, using defaults")
64
+ this.state = DEFAULT_STATE
65
+ this.cache = this.mergeDocs(DEFAULT_CONFIG_YAML, DEFAULT_STATE)
66
+ }
67
+
68
+ this.loaded = true
69
+ return this.cache
70
+ }
71
+
72
+ get(): ConfigFile {
73
+ return this.load()
74
+ }
75
+
76
+ replace(config: ConfigFile) {
77
+ const validated = ConfigFileSchema.parse(config)
78
+ this.commit(validated)
79
+ }
80
+
81
+ /**
82
+ * Apply a merge-patch update to the current config.
83
+ * - Missing keys are preserved.
84
+ * - Object values are merged recursively.
85
+ * - Explicit `null` deletes keys.
86
+ * - Arrays are replaced.
87
+ */
88
+ mergePatch(patch: unknown) {
89
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
90
+ throw new Error("Config patch must be a JSON object")
91
+ }
92
+ const current = this.get()
93
+ const next = applyMergePatch(current as any, patch as any)
94
+ const validated = ConfigFileSchema.parse(next)
95
+ this.commit(validated)
96
+ }
97
+
98
+ private commit(next: ConfigFile) {
99
+ this.cache = next
100
+ this.loaded = true
101
+ this.state = {
102
+ ...this.state,
103
+ recentFolders: next.recentFolders,
104
+ }
105
+ this.persist()
106
+ const published = Boolean(this.eventBus)
107
+ this.eventBus?.publish({ type: "config.appChanged", config: this.cache })
108
+ this.logger.debug({ broadcast: published }, "Config SSE event emitted")
109
+ this.logger.trace({ config: this.cache }, "Config payload")
110
+ }
111
+
112
+ private persist() {
113
+ try {
114
+ const configYamlPath = this.location.configYamlPath
115
+ const stateYamlPath = this.location.stateYamlPath
116
+
117
+ fs.mkdirSync(this.location.baseDir, { recursive: true })
118
+ fs.mkdirSync(path.dirname(configYamlPath), { recursive: true })
119
+
120
+ const configYaml = stringifyYaml(stripRecentFolders(this.cache) as any)
121
+ const stateYaml = stringifyYaml(this.state as any)
122
+
123
+ fs.writeFileSync(configYamlPath, ensureTrailingNewline(configYaml), "utf-8")
124
+ fs.writeFileSync(stateYamlPath, ensureTrailingNewline(stateYaml), "utf-8")
125
+
126
+ this.logger.debug({ configYamlPath, stateYamlPath }, "Persisted YAML config/state")
127
+ } catch (error) {
128
+ this.logger.warn({ err: error }, "Failed to persist config")
129
+ }
130
+ }
131
+
132
+ private mergeDocs(configDoc: unknown, stateDoc: StateFile): ConfigFile {
133
+ const merged = {
134
+ ...(configDoc as any),
135
+ // State wins for recent folders.
136
+ recentFolders: stateDoc.recentFolders ?? [],
137
+ }
138
+
139
+ return ConfigFileSchema.parse(merged)
140
+ }
141
+
142
+ private readYamlFile<T>(
143
+ filePath: string,
144
+ fallback: T,
145
+ schema: { parse: (value: unknown) => T },
146
+ label: string,
147
+ ): T {
148
+ try {
149
+ const content = fs.readFileSync(filePath, "utf-8")
150
+ const parsed = parseYaml(content)
151
+ return schema.parse(parsed ?? {})
152
+ } catch (error) {
153
+ this.logger.warn({ err: error, filePath, label }, "Failed to read YAML file, using defaults")
154
+ return fallback
155
+ }
156
+ }
157
+
158
+ private migrateFromLegacyJson(legacyJsonPath: string): { config: ConfigFile; state: StateFile } {
159
+ const configYamlPath = this.location.configYamlPath
160
+ const stateYamlPath = this.location.stateYamlPath
161
+
162
+ const content = fs.readFileSync(legacyJsonPath, "utf-8")
163
+ const parsed = JSON.parse(content)
164
+ const legacy = ConfigFileSchema.parse(parsed)
165
+
166
+ const state: StateFile = StateFileSchema.parse({
167
+ ...DEFAULT_STATE,
168
+ recentFolders: legacy.recentFolders ?? [],
169
+ })
170
+
171
+ const merged = this.mergeDocs(stripRecentFolders(legacy), state)
172
+
173
+ // Persist YAML docs first, then move legacy aside.
174
+ try {
175
+ fs.mkdirSync(this.location.baseDir, { recursive: true })
176
+ fs.writeFileSync(configYamlPath, ensureTrailingNewline(stringifyYaml(stripRecentFolders(merged) as any)), "utf-8")
177
+ fs.writeFileSync(stateYamlPath, ensureTrailingNewline(stringifyYaml(state as any)), "utf-8")
178
+ this.logger.info({ legacyJsonPath, configYamlPath, stateYamlPath }, "Migrated config.json -> YAML")
179
+ } catch (error) {
180
+ this.logger.warn({ err: error }, "Failed to persist migrated YAML config/state")
181
+ }
182
+
183
+ try {
184
+ const bakPath = pickBackupPath(legacyJsonPath)
185
+ fs.renameSync(legacyJsonPath, bakPath)
186
+ this.logger.info({ legacyJsonPath, bakPath }, "Moved legacy config.json to backup")
187
+ } catch (error) {
188
+ this.logger.warn({ err: error, legacyJsonPath }, "Failed to rename legacy config.json to backup")
189
+ }
190
+
191
+ return { config: merged, state }
192
+ }
193
+ }
194
+
195
+ function ensureTrailingNewline(content: string): string {
196
+ if (!content) return "\n"
197
+ return content.endsWith("\n") ? content : `${content}\n`
198
+ }
199
+
200
+ function stripRecentFolders(config: ConfigFile): Omit<ConfigFile, "recentFolders"> & Record<string, unknown> {
201
+ const clone: Record<string, unknown> = { ...(config as any) }
202
+ delete clone.recentFolders
203
+ return clone as any
204
+ }
205
+
206
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
207
+ if (!value || typeof value !== "object") return false
208
+ if (Array.isArray(value)) return false
209
+ const proto = Object.getPrototypeOf(value)
210
+ return proto === Object.prototype || proto === null
211
+ }
212
+
213
+ function applyMergePatch(current: any, patch: any): any {
214
+ // RFC 7396-ish merge patch with explicit null deletes.
215
+ if (!isPlainObject(patch)) {
216
+ return patch
217
+ }
218
+
219
+ const base = isPlainObject(current) ? { ...current } : {}
220
+ for (const [key, value] of Object.entries(patch)) {
221
+ if (value === null) {
222
+ delete base[key]
223
+ continue
224
+ }
225
+
226
+ if (isPlainObject(value) && isPlainObject(base[key])) {
227
+ base[key] = applyMergePatch(base[key], value)
228
+ continue
229
+ }
230
+
231
+ // Arrays and scalars replace.
232
+ base[key] = value
233
+ }
234
+ return base
235
+ }
236
+
237
+ function pickBackupPath(legacyJsonPath: string): string {
238
+ const base = legacyJsonPath.endsWith(".json") ? legacyJsonPath.slice(0, -".json".length) : legacyJsonPath
239
+ const preferred = `${base}.json.bak`
240
+ if (!fs.existsSync(preferred)) {
241
+ return preferred
242
+ }
243
+ return `${base}.json.bak.${Date.now()}`
244
+ }
@@ -0,0 +1,45 @@
1
+ import { EventEmitter } from "events"
2
+ import { WorkspaceEventPayload } from "../api-types"
3
+ import { Logger } from "../logger"
4
+
5
+ export class EventBus extends EventEmitter {
6
+ constructor(private readonly logger?: Logger) {
7
+ super()
8
+ }
9
+
10
+ publish(event: WorkspaceEventPayload): boolean {
11
+ if (event.type !== "instance.event" && event.type !== "instance.eventStatus") {
12
+ this.logger?.debug({ type: event.type }, "Publishing workspace event")
13
+ if (this.logger?.isLevelEnabled("trace")) {
14
+ this.logger.trace({ event }, "Workspace event payload")
15
+ }
16
+ }
17
+ return super.emit(event.type, event)
18
+ }
19
+
20
+ onEvent(listener: (event: WorkspaceEventPayload) => void) {
21
+ const handler = (event: WorkspaceEventPayload) => listener(event)
22
+ this.on("workspace.created", handler)
23
+ this.on("workspace.started", handler)
24
+ this.on("workspace.error", handler)
25
+ this.on("workspace.stopped", handler)
26
+ this.on("workspace.log", handler)
27
+ this.on("config.appChanged", handler)
28
+ this.on("config.binariesChanged", handler)
29
+ this.on("instance.dataChanged", handler)
30
+ this.on("instance.event", handler)
31
+ this.on("instance.eventStatus", handler)
32
+ return () => {
33
+ this.off("workspace.created", handler)
34
+ this.off("workspace.started", handler)
35
+ this.off("workspace.error", handler)
36
+ this.off("workspace.stopped", handler)
37
+ this.off("workspace.log", handler)
38
+ this.off("config.appChanged", handler)
39
+ this.off("config.binariesChanged", handler)
40
+ this.off("instance.dataChanged", handler)
41
+ this.off("instance.event", handler)
42
+ this.off("instance.eventStatus", handler)
43
+ }
44
+ }
45
+ }