@neuralnomads/codenomad 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/dist/api-types.js +1 -0
  2. package/dist/bin.js +24 -0
  3. package/dist/config/binaries.js +118 -0
  4. package/dist/config/schema.js +34 -0
  5. package/dist/config/store.js +68 -0
  6. package/dist/events/bus.js +32 -0
  7. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  8. package/dist/filesystem/browser.js +238 -0
  9. package/dist/filesystem/search-cache.js +43 -0
  10. package/dist/filesystem/search.js +135 -0
  11. package/dist/index.js +142 -0
  12. package/dist/launcher.js +149 -0
  13. package/dist/loader.js +21 -0
  14. package/dist/logger.js +109 -0
  15. package/dist/server/http-server.js +224 -0
  16. package/dist/server/routes/config.js +44 -0
  17. package/dist/server/routes/events.js +35 -0
  18. package/dist/server/routes/filesystem.js +19 -0
  19. package/dist/server/routes/meta.js +3 -0
  20. package/dist/server/routes/storage.js +52 -0
  21. package/dist/server/routes/workspaces.js +82 -0
  22. package/dist/storage/instance-store.js +56 -0
  23. package/dist/workspaces/manager.js +147 -0
  24. package/dist/workspaces/runtime.js +168 -0
  25. package/package.json +38 -0
  26. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  27. package/public/assets/abap-BdImnpbu.js +1 -0
  28. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  29. package/public/assets/ada-bCR0ucgS.js +1 -0
  30. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  31. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  32. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  33. package/public/assets/apache-Pmp26Uib.js +1 -0
  34. package/public/assets/apex-DhZLUxFE.js +1 -0
  35. package/public/assets/apl-dKokRX4l.js +1 -0
  36. package/public/assets/applescript-Co6uUVPk.js +1 -0
  37. package/public/assets/ara-BRHolxvo.js +1 -0
  38. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  39. package/public/assets/asm-D_Q5rh1f.js +1 -0
  40. package/public/assets/astro-CbQHKStN.js +1 -0
  41. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  42. package/public/assets/awk-DMzUqQB5.js +1 -0
  43. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  44. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  45. package/public/assets/bat-BkioyH1T.js +1 -0
  46. package/public/assets/beancount-k_qm7-4y.js +1 -0
  47. package/public/assets/berry-D08WgyRC.js +1 -0
  48. package/public/assets/bibtex-CHM0blh-.js +1 -0
  49. package/public/assets/bicep-Bmn6On1c.js +1 -0
  50. package/public/assets/blade-DVc8C-J4.js +1 -0
  51. package/public/assets/bsl-BO_Y6i37.js +1 -0
  52. package/public/assets/c-BIGW1oBm.js +1 -0
  53. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  54. package/public/assets/cairo-KRGpt6FW.js +1 -0
  55. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  56. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  57. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  58. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  59. package/public/assets/clarity-D53aC0YG.js +1 -0
  60. package/public/assets/clojure-P80f7IUj.js +1 -0
  61. package/public/assets/cmake-D1j8_8rp.js +1 -0
  62. package/public/assets/cobol-nwyudZeR.js +1 -0
  63. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  64. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  65. package/public/assets/coffee-Ch7k5sss.js +1 -0
  66. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  67. package/public/assets/coq-DkFqJrB1.js +1 -0
  68. package/public/assets/cpp-CofmeUqb.js +1 -0
  69. package/public/assets/crystal-tKQVLTB8.js +1 -0
  70. package/public/assets/csharp-CX12Zw3r.js +1 -0
  71. package/public/assets/css-DPfMkruS.js +1 -0
  72. package/public/assets/csv-fuZLfV_i.js +1 -0
  73. package/public/assets/cue-D82EKSYY.js +1 -0
  74. package/public/assets/cypher-COkxafJQ.js +1 -0
  75. package/public/assets/d-85-TOEBH.js +1 -0
  76. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  77. package/public/assets/dart-CF10PKvl.js +1 -0
  78. package/public/assets/dax-CEL-wOlO.js +1 -0
  79. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  80. package/public/assets/diff-D97Zzqfu.js +1 -0
  81. package/public/assets/docker-BcOcwvcX.js +1 -0
  82. package/public/assets/dotenv-Da5cRb03.js +1 -0
  83. package/public/assets/dracula-BzJJZx-M.js +1 -0
  84. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  85. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  86. package/public/assets/edge-BkV0erSs.js +1 -0
  87. package/public/assets/elixir-CDX3lj18.js +1 -0
  88. package/public/assets/elm-DbKCFpqz.js +1 -0
  89. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  90. package/public/assets/erb-BOJIQeun.js +1 -0
  91. package/public/assets/erlang-DsQrWhSR.js +1 -0
  92. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  93. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  94. package/public/assets/fennel-BYunw83y.js +1 -0
  95. package/public/assets/fish-BvzEVeQv.js +1 -0
  96. package/public/assets/fluent-C4IJs8-o.js +1 -0
  97. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  98. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  99. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  100. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  101. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  102. package/public/assets/gdshader-DkwncUOv.js +1 -0
  103. package/public/assets/genie-D0YGMca9.js +1 -0
  104. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  105. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  106. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  107. package/public/assets/github-dark-DHJKELXO.js +1 -0
  108. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  109. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  110. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  111. package/public/assets/github-light-DAi9KRSo.js +1 -0
  112. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  113. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  114. package/public/assets/gleam-BspZqrRM.js +1 -0
  115. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  116. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  117. package/public/assets/glsl-DplSGwfg.js +1 -0
  118. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  119. package/public/assets/go-Dn2_MT6a.js +1 -0
  120. package/public/assets/graphql-ChdNCCLP.js +1 -0
  121. package/public/assets/groovy-gcz8RCvz.js +1 -0
  122. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  123. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  124. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  125. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  126. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  127. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  128. package/public/assets/hack-CaT9iCJl.js +1 -0
  129. package/public/assets/haml-B8DHNrY2.js +1 -0
  130. package/public/assets/handlebars-BL8al0AC.js +1 -0
  131. package/public/assets/haskell-Df6bDoY_.js +1 -0
  132. package/public/assets/haxe-CzTSHFRz.js +1 -0
  133. package/public/assets/hcl-BWvSN4gD.js +1 -0
  134. package/public/assets/hjson-D5-asLiD.js +1 -0
  135. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  136. package/public/assets/houston-DnULxvSX.js +1 -0
  137. package/public/assets/html-GMplVEZG.js +1 -0
  138. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  139. package/public/assets/http-jrhK8wxY.js +1 -0
  140. package/public/assets/hurl-irOxFIW8.js +1 -0
  141. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  142. package/public/assets/hy-DFXneXwc.js +1 -0
  143. package/public/assets/imba-DGztddWO.js +1 -0
  144. package/public/assets/index-CoTu_3WL.js +147 -0
  145. package/public/assets/index-Cyy59ha1.css +19 -0
  146. package/public/assets/ini-BEwlwnbL.js +1 -0
  147. package/public/assets/java-CylS5w8V.js +1 -0
  148. package/public/assets/javascript-wDzz0qaB.js +1 -0
  149. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  150. package/public/assets/jison-wvAkD_A8.js +1 -0
  151. package/public/assets/json-Cp-IABpG.js +1 -0
  152. package/public/assets/json5-C9tS-k6U.js +1 -0
  153. package/public/assets/jsonc-Des-eS-w.js +1 -0
  154. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  155. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  156. package/public/assets/jssm-C2t-YnRu.js +1 -0
  157. package/public/assets/jsx-g9-lgVsj.js +1 -0
  158. package/public/assets/julia-C8NyazO9.js +1 -0
  159. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  160. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  161. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  162. package/public/assets/kdl-DV7GczEv.js +1 -0
  163. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  164. package/public/assets/kusto-BvAqAH-y.js +1 -0
  165. package/public/assets/laserwave-DUszq2jm.js +1 -0
  166. package/public/assets/latex-BUKiar2Z.js +1 -0
  167. package/public/assets/lean-DP1Csr6i.js +1 -0
  168. package/public/assets/less-B1dDrJ26.js +1 -0
  169. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  170. package/public/assets/liquid-DYVedYrR.js +1 -0
  171. package/public/assets/llvm-BtvRca6l.js +1 -0
  172. package/public/assets/log-2UxHyX5q.js +1 -0
  173. package/public/assets/logo-BtOb2qkB.js +1 -0
  174. package/public/assets/lua-BbnMAYS6.js +1 -0
  175. package/public/assets/luau-CXu1NL6O.js +1 -0
  176. package/public/assets/make-CHLpvVh8.js +1 -0
  177. package/public/assets/markdown-Cvjx9yec.js +1 -0
  178. package/public/assets/marko-CPi9NSCl.js +1 -0
  179. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  180. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  181. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  182. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  183. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  184. package/public/assets/matlab-D7o27uSR.js +1 -0
  185. package/public/assets/mdc-DUICxH0z.js +1 -0
  186. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  187. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  188. package/public/assets/min-dark-CafNBF8u.js +1 -0
  189. package/public/assets/min-light-CTRr51gU.js +1 -0
  190. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  191. package/public/assets/mojo-1DNp92w6.js +1 -0
  192. package/public/assets/monokai-D4h5O-jR.js +1 -0
  193. package/public/assets/move-Bu9oaDYs.js +1 -0
  194. package/public/assets/narrat-DRg8JJMk.js +1 -0
  195. package/public/assets/nextflow-CUEJCptM.js +1 -0
  196. package/public/assets/nginx-DknmC5AR.js +1 -0
  197. package/public/assets/night-owl-C39BiMTA.js +1 -0
  198. package/public/assets/nim-CVrawwO9.js +1 -0
  199. package/public/assets/nix-BbRYJGeE.js +1 -0
  200. package/public/assets/nord-Ddv68eIx.js +1 -0
  201. package/public/assets/nushell-C-sUppwS.js +1 -0
  202. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  203. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  204. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  205. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  206. package/public/assets/one-light-PoHY5YXO.js +1 -0
  207. package/public/assets/pascal-D93ZcfNL.js +1 -0
  208. package/public/assets/perl-C0TMdlhV.js +1 -0
  209. package/public/assets/php-CDn_0X-4.js +1 -0
  210. package/public/assets/pkl-u5AG7uiY.js +1 -0
  211. package/public/assets/plastic-3e1v2bzS.js +1 -0
  212. package/public/assets/plsql-ChMvpjG-.js +1 -0
  213. package/public/assets/po-BTJTHyun.js +1 -0
  214. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  215. package/public/assets/polar-C0HS_06l.js +1 -0
  216. package/public/assets/postcss-CXtECtnM.js +1 -0
  217. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  218. package/public/assets/powershell-Dpen1YoG.js +1 -0
  219. package/public/assets/prisma-Dd19v3D-.js +1 -0
  220. package/public/assets/prolog-CbFg5uaA.js +1 -0
  221. package/public/assets/proto-DyJlTyXw.js +1 -0
  222. package/public/assets/pug-CGlum2m_.js +1 -0
  223. package/public/assets/puppet-BMWR74SV.js +1 -0
  224. package/public/assets/purescript-CklMAg4u.js +1 -0
  225. package/public/assets/python-B6aJPvgy.js +1 -0
  226. package/public/assets/qml-3beO22l8.js +1 -0
  227. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  228. package/public/assets/qss-IeuSbFQv.js +1 -0
  229. package/public/assets/r-DiinP2Uv.js +1 -0
  230. package/public/assets/racket-BqYA7rlc.js +1 -0
  231. package/public/assets/raku-DXvB9xmW.js +1 -0
  232. package/public/assets/razor-WgofotgN.js +1 -0
  233. package/public/assets/red-bN70gL4F.js +1 -0
  234. package/public/assets/reg-C-SQnVFl.js +1 -0
  235. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  236. package/public/assets/rel-C3B-1QV4.js +1 -0
  237. package/public/assets/riscv-BM1_JUlF.js +1 -0
  238. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  239. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  240. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  241. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  242. package/public/assets/rst-B0xPkSld.js +1 -0
  243. package/public/assets/ruby-BvKwtOVI.js +1 -0
  244. package/public/assets/rust-B1yitclQ.js +1 -0
  245. package/public/assets/sas-cz2c8ADy.js +1 -0
  246. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  247. package/public/assets/scala-C151Ov-r.js +1 -0
  248. package/public/assets/scheme-C98Dy4si.js +1 -0
  249. package/public/assets/scss-OYdSNvt2.js +1 -0
  250. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  251. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  252. package/public/assets/shellscript-Yzrsuije.js +1 -0
  253. package/public/assets/shellsession-BADoaaVG.js +1 -0
  254. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  255. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  256. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  257. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  258. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  259. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  260. package/public/assets/solidity-BbcW6ACK.js +1 -0
  261. package/public/assets/soy-Brmx7dQM.js +1 -0
  262. package/public/assets/sparql-rVzFXLq3.js +1 -0
  263. package/public/assets/splunk-BtCnVYZw.js +1 -0
  264. package/public/assets/sql-BLtJtn59.js +1 -0
  265. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  266. package/public/assets/stata-BH5u7GGu.js +1 -0
  267. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  268. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  269. package/public/assets/swift-Dg5xB15N.js +1 -0
  270. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  271. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  272. package/public/assets/systemd-4A_iFExJ.js +1 -0
  273. package/public/assets/talonscript-CkByrt1z.js +1 -0
  274. package/public/assets/tasl-QIJgUcNo.js +1 -0
  275. package/public/assets/tcl-dwOrl1Do.js +1 -0
  276. package/public/assets/templ-W15q3VgB.js +1 -0
  277. package/public/assets/terraform-BETggiCN.js +1 -0
  278. package/public/assets/tex-Cppo0RY3.js +1 -0
  279. package/public/assets/tokyo-night-hegEt444.js +1 -0
  280. package/public/assets/toml-vGWfd6FD.js +1 -0
  281. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  282. package/public/assets/tsv-B_m7g4N7.js +1 -0
  283. package/public/assets/tsx-COt5Ahok.js +1 -0
  284. package/public/assets/turtle-BsS91CYL.js +1 -0
  285. package/public/assets/twig-CO9l9SDP.js +1 -0
  286. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  287. package/public/assets/typespec-Df68jz8_.js +1 -0
  288. package/public/assets/typst-DHCkPAjA.js +1 -0
  289. package/public/assets/v-BcVCzyr7.js +1 -0
  290. package/public/assets/vala-CsfeWuGM.js +1 -0
  291. package/public/assets/vb-D17OF-Vu.js +1 -0
  292. package/public/assets/verilog-BQ8w6xss.js +1 -0
  293. package/public/assets/vesper-DU1UobuO.js +1 -0
  294. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  295. package/public/assets/viml-CJc9bBzg.js +1 -0
  296. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  297. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  298. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  299. package/public/assets/vue-CCoi5OLL.js +1 -0
  300. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  301. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  302. package/public/assets/vyper-CDx5xZoG.js +1 -0
  303. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  304. package/public/assets/wasm-MzD3tlZU.js +1 -0
  305. package/public/assets/wenyan-BV7otONQ.js +1 -0
  306. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  307. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  308. package/public/assets/wit-5i3qLPDT.js +1 -0
  309. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  310. package/public/assets/xml-sdJ4AIDG.js +1 -0
  311. package/public/assets/xsl-CtQFsRM5.js +1 -0
  312. package/public/assets/yaml-Buea-lGh.js +1 -0
  313. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  314. package/public/assets/zig-VOosw3JB.js +1 -0
  315. package/public/index.html +44 -0
@@ -0,0 +1 @@
1
+ export const WINDOWS_DRIVES_ROOT = "__drives__";
package/dist/bin.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "child_process";
3
+ import path from "path";
4
+ import { fileURLToPath, pathToFileURL } from "url";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const cliEntry = path.join(__dirname, "index.js");
8
+ const loaderFileUrl = pathToFileURL(path.join(__dirname, "loader.js")).href;
9
+ const registerScript = `import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("${encodeURI(loaderFileUrl)}", pathToFileURL("./"));`;
10
+ const loaderArg = `data:text/javascript,${registerScript}`;
11
+ const child = spawn(process.execPath, ["--import", loaderArg, cliEntry, ...process.argv.slice(2)], {
12
+ stdio: "inherit",
13
+ });
14
+ child.on("exit", (code, signal) => {
15
+ if (signal) {
16
+ process.kill(process.pid, signal);
17
+ return;
18
+ }
19
+ process.exit(code ?? 0);
20
+ });
21
+ child.on("error", (error) => {
22
+ console.error("Failed to launch CLI runtime", error);
23
+ process.exit(1);
24
+ });
@@ -0,0 +1,118 @@
1
+ export class BinaryRegistry {
2
+ constructor(configStore, eventBus, logger) {
3
+ this.configStore = configStore;
4
+ this.eventBus = eventBus;
5
+ this.logger = logger;
6
+ }
7
+ list() {
8
+ return this.mapRecords();
9
+ }
10
+ resolveDefault() {
11
+ const binaries = this.mapRecords();
12
+ if (binaries.length === 0) {
13
+ this.logger.warn("No configured binaries found, falling back to opencode");
14
+ return this.buildFallbackRecord("opencode");
15
+ }
16
+ return binaries.find((binary) => binary.isDefault) ?? binaries[0];
17
+ }
18
+ create(request) {
19
+ this.logger.debug({ path: request.path }, "Registering OpenCode binary");
20
+ const entry = {
21
+ path: request.path,
22
+ version: undefined,
23
+ lastUsed: Date.now(),
24
+ label: request.label,
25
+ };
26
+ const config = this.configStore.get();
27
+ const nextConfig = this.cloneConfig(config);
28
+ const deduped = nextConfig.opencodeBinaries.filter((binary) => binary.path !== request.path);
29
+ nextConfig.opencodeBinaries = [entry, ...deduped];
30
+ if (request.makeDefault) {
31
+ nextConfig.preferences.lastUsedBinary = request.path;
32
+ }
33
+ this.configStore.replace(nextConfig);
34
+ const record = this.getById(request.path);
35
+ this.emitChange();
36
+ return record;
37
+ }
38
+ update(id, updates) {
39
+ this.logger.debug({ id }, "Updating OpenCode binary");
40
+ const config = this.configStore.get();
41
+ const nextConfig = this.cloneConfig(config);
42
+ nextConfig.opencodeBinaries = nextConfig.opencodeBinaries.map((binary) => binary.path === id ? { ...binary, label: updates.label ?? binary.label } : binary);
43
+ if (updates.makeDefault) {
44
+ nextConfig.preferences.lastUsedBinary = id;
45
+ }
46
+ this.configStore.replace(nextConfig);
47
+ const record = this.getById(id);
48
+ this.emitChange();
49
+ return record;
50
+ }
51
+ remove(id) {
52
+ this.logger.debug({ id }, "Removing OpenCode binary");
53
+ const config = this.configStore.get();
54
+ const nextConfig = this.cloneConfig(config);
55
+ const remaining = nextConfig.opencodeBinaries.filter((binary) => binary.path !== id);
56
+ nextConfig.opencodeBinaries = remaining;
57
+ if (nextConfig.preferences.lastUsedBinary === id) {
58
+ nextConfig.preferences.lastUsedBinary = remaining[0]?.path;
59
+ }
60
+ this.configStore.replace(nextConfig);
61
+ this.emitChange();
62
+ }
63
+ validatePath(path) {
64
+ this.logger.debug({ path }, "Validating OpenCode binary path");
65
+ return this.validateRecord({
66
+ id: path,
67
+ path,
68
+ label: this.prettyLabel(path),
69
+ isDefault: false,
70
+ });
71
+ }
72
+ cloneConfig(config) {
73
+ return JSON.parse(JSON.stringify(config));
74
+ }
75
+ mapRecords() {
76
+ const config = this.configStore.get();
77
+ const configuredBinaries = config.opencodeBinaries.map((binary) => ({
78
+ id: binary.path,
79
+ path: binary.path,
80
+ label: binary.label ?? this.prettyLabel(binary.path),
81
+ version: binary.version,
82
+ isDefault: false,
83
+ }));
84
+ const defaultPath = config.preferences.lastUsedBinary ?? configuredBinaries[0]?.path ?? "opencode";
85
+ const annotated = configuredBinaries.map((binary) => ({
86
+ ...binary,
87
+ isDefault: binary.path === defaultPath,
88
+ }));
89
+ if (!annotated.some((binary) => binary.path === defaultPath)) {
90
+ annotated.unshift(this.buildFallbackRecord(defaultPath));
91
+ }
92
+ return annotated;
93
+ }
94
+ getById(id) {
95
+ return this.mapRecords().find((binary) => binary.id === id) ?? this.buildFallbackRecord(id);
96
+ }
97
+ emitChange() {
98
+ this.logger.debug("Emitting binaries changed event");
99
+ this.eventBus?.publish({ type: "config.binariesChanged", binaries: this.mapRecords() });
100
+ }
101
+ validateRecord(record) {
102
+ // TODO: call actual binary -v check.
103
+ return { valid: true, version: record.version };
104
+ }
105
+ buildFallbackRecord(path) {
106
+ return {
107
+ id: path,
108
+ path,
109
+ label: this.prettyLabel(path),
110
+ isDefault: true,
111
+ };
112
+ }
113
+ prettyLabel(path) {
114
+ const parts = path.split(/[\\/]/);
115
+ const last = parts[parts.length - 1] || path;
116
+ return last || path;
117
+ }
118
+ }
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ const ModelPreferenceSchema = z.object({
3
+ providerId: z.string(),
4
+ modelId: z.string(),
5
+ });
6
+ const AgentModelSelectionSchema = z.record(z.string(), ModelPreferenceSchema);
7
+ const AgentModelSelectionsSchema = z.record(z.string(), AgentModelSelectionSchema);
8
+ const PreferencesSchema = z.object({
9
+ showThinkingBlocks: z.boolean().default(false),
10
+ lastUsedBinary: z.string().optional(),
11
+ environmentVariables: z.record(z.string()).default({}),
12
+ modelRecents: z.array(ModelPreferenceSchema).default([]),
13
+ diffViewMode: z.enum(["split", "unified"]).default("split"),
14
+ toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
15
+ diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
16
+ });
17
+ const RecentFolderSchema = z.object({
18
+ path: z.string(),
19
+ lastAccessed: z.number().nonnegative(),
20
+ });
21
+ const OpenCodeBinarySchema = z.object({
22
+ path: z.string(),
23
+ version: z.string().optional(),
24
+ lastUsed: z.number().nonnegative(),
25
+ label: z.string().optional(),
26
+ });
27
+ const ConfigFileSchema = z.object({
28
+ preferences: PreferencesSchema.default({}),
29
+ recentFolders: z.array(RecentFolderSchema).default([]),
30
+ opencodeBinaries: z.array(OpenCodeBinarySchema).default([]),
31
+ theme: z.enum(["light", "dark", "system"]).optional(),
32
+ });
33
+ const DEFAULT_CONFIG = ConfigFileSchema.parse({});
34
+ export { ModelPreferenceSchema, AgentModelSelectionSchema, AgentModelSelectionsSchema, PreferencesSchema, RecentFolderSchema, OpenCodeBinarySchema, ConfigFileSchema, DEFAULT_CONFIG, };
@@ -0,0 +1,68 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ConfigFileSchema, DEFAULT_CONFIG } from "./schema";
4
+ export class ConfigStore {
5
+ constructor(configPath, eventBus, logger) {
6
+ this.configPath = configPath;
7
+ this.eventBus = eventBus;
8
+ this.logger = logger;
9
+ this.cache = DEFAULT_CONFIG;
10
+ this.loaded = false;
11
+ }
12
+ load() {
13
+ if (this.loaded) {
14
+ return this.cache;
15
+ }
16
+ try {
17
+ const resolved = this.resolvePath(this.configPath);
18
+ if (fs.existsSync(resolved)) {
19
+ const content = fs.readFileSync(resolved, "utf-8");
20
+ const parsed = JSON.parse(content);
21
+ this.cache = ConfigFileSchema.parse(parsed);
22
+ this.logger.debug({ resolved }, "Loaded existing config file");
23
+ }
24
+ else {
25
+ this.cache = DEFAULT_CONFIG;
26
+ this.logger.debug({ resolved }, "No config file found, using defaults");
27
+ }
28
+ }
29
+ catch (error) {
30
+ this.logger.warn({ err: error }, "Failed to load config, using defaults");
31
+ this.cache = DEFAULT_CONFIG;
32
+ }
33
+ this.loaded = true;
34
+ return this.cache;
35
+ }
36
+ get() {
37
+ return this.load();
38
+ }
39
+ replace(config) {
40
+ const validated = ConfigFileSchema.parse(config);
41
+ this.commit(validated);
42
+ }
43
+ commit(next) {
44
+ this.cache = next;
45
+ this.loaded = true;
46
+ this.persist();
47
+ this.eventBus?.publish({ type: "config.appChanged", config: this.cache });
48
+ this.logger.info("Config updated");
49
+ this.logger.debug({ config: this.cache }, "Config payload");
50
+ }
51
+ persist() {
52
+ try {
53
+ const resolved = this.resolvePath(this.configPath);
54
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
55
+ fs.writeFileSync(resolved, JSON.stringify(this.cache, null, 2), "utf-8");
56
+ this.logger.debug({ resolved }, "Persisted config file");
57
+ }
58
+ catch (error) {
59
+ this.logger.warn({ err: error }, "Failed to persist config");
60
+ }
61
+ }
62
+ resolvePath(filePath) {
63
+ if (filePath.startsWith("~/")) {
64
+ return path.join(process.env.HOME ?? "", filePath.slice(2));
65
+ }
66
+ return path.resolve(filePath);
67
+ }
68
+ }
@@ -0,0 +1,32 @@
1
+ import { EventEmitter } from "events";
2
+ export class EventBus extends EventEmitter {
3
+ constructor(logger) {
4
+ super();
5
+ this.logger = logger;
6
+ }
7
+ publish(event) {
8
+ this.logger?.debug({ event }, "Publishing workspace event");
9
+ return super.emit(event.type, event);
10
+ }
11
+ onEvent(listener) {
12
+ const handler = (event) => listener(event);
13
+ this.on("workspace.created", handler);
14
+ this.on("workspace.started", handler);
15
+ this.on("workspace.error", handler);
16
+ this.on("workspace.stopped", handler);
17
+ this.on("workspace.log", handler);
18
+ this.on("config.appChanged", handler);
19
+ this.on("config.binariesChanged", handler);
20
+ this.on("instance.dataChanged", handler);
21
+ return () => {
22
+ this.off("workspace.created", handler);
23
+ this.off("workspace.started", handler);
24
+ this.off("workspace.error", handler);
25
+ this.off("workspace.stopped", handler);
26
+ this.off("workspace.log", handler);
27
+ this.off("config.appChanged", handler);
28
+ this.off("config.binariesChanged", handler);
29
+ this.off("instance.dataChanged", handler);
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ import assert from "node:assert/strict";
2
+ import { beforeEach, describe, it } from "node:test";
3
+ import { clearWorkspaceSearchCache, getWorkspaceCandidates, refreshWorkspaceCandidates, WORKSPACE_CANDIDATE_CACHE_TTL_MS, } from "../search-cache";
4
+ describe("workspace search cache", () => {
5
+ beforeEach(() => {
6
+ clearWorkspaceSearchCache();
7
+ });
8
+ it("expires cached candidates after the TTL", () => {
9
+ const workspacePath = "/tmp/workspace";
10
+ const startTime = 1000;
11
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], startTime);
12
+ const beforeExpiry = getWorkspaceCandidates(workspacePath, startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS - 1);
13
+ assert.ok(beforeExpiry);
14
+ assert.equal(beforeExpiry.length, 1);
15
+ assert.equal(beforeExpiry[0].name, "file-a");
16
+ const afterExpiry = getWorkspaceCandidates(workspacePath, startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS + 1);
17
+ assert.equal(afterExpiry, undefined);
18
+ });
19
+ it("replaces cached entries when manually refreshed", () => {
20
+ const workspacePath = "/tmp/workspace";
21
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], 5000);
22
+ const initial = getWorkspaceCandidates(workspacePath);
23
+ assert.ok(initial);
24
+ assert.equal(initial[0].name, "file-a");
25
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-b")], 6000);
26
+ const refreshed = getWorkspaceCandidates(workspacePath);
27
+ assert.ok(refreshed);
28
+ assert.equal(refreshed[0].name, "file-b");
29
+ });
30
+ });
31
+ function createEntry(name) {
32
+ return {
33
+ name,
34
+ path: name,
35
+ absolutePath: `/tmp/${name}`,
36
+ type: "file",
37
+ size: 1,
38
+ modifiedAt: new Date().toISOString(),
39
+ };
40
+ }
@@ -0,0 +1,238 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { WINDOWS_DRIVES_ROOT, } from "../api-types";
5
+ const WINDOWS_DRIVE_LETTERS = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i));
6
+ export class FileSystemBrowser {
7
+ constructor(options) {
8
+ this.root = path.resolve(options.rootDir);
9
+ this.unrestricted = Boolean(options.unrestricted);
10
+ this.homeDir = os.homedir();
11
+ this.isWindows = process.platform === "win32";
12
+ }
13
+ list(relativePath = ".", options = {}) {
14
+ if (this.unrestricted) {
15
+ throw new Error("Relative listing is unavailable when running with unrestricted root");
16
+ }
17
+ const includeFiles = options.includeFiles ?? true;
18
+ const normalizedPath = this.normalizeRelativePath(relativePath);
19
+ const absolutePath = this.toRestrictedAbsolute(normalizedPath);
20
+ return this.readDirectoryEntries(absolutePath, {
21
+ includeFiles,
22
+ formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName),
23
+ formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName),
24
+ });
25
+ }
26
+ browse(targetPath, options = {}) {
27
+ const includeFiles = options.includeFiles ?? true;
28
+ if (this.unrestricted) {
29
+ return this.listUnrestricted(targetPath, includeFiles);
30
+ }
31
+ return this.listRestrictedWithMetadata(targetPath, includeFiles);
32
+ }
33
+ readFile(relativePath) {
34
+ if (this.unrestricted) {
35
+ throw new Error("readFile is not available in unrestricted mode");
36
+ }
37
+ const resolved = this.toRestrictedAbsolute(relativePath);
38
+ return fs.readFileSync(resolved, "utf-8");
39
+ }
40
+ listRestrictedWithMetadata(relativePath, includeFiles) {
41
+ const normalizedPath = this.normalizeRelativePath(relativePath);
42
+ const absolutePath = this.toRestrictedAbsolute(normalizedPath);
43
+ const entries = this.readDirectoryEntries(absolutePath, {
44
+ includeFiles,
45
+ formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName),
46
+ formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName),
47
+ });
48
+ const metadata = {
49
+ scope: "restricted",
50
+ currentPath: normalizedPath,
51
+ parentPath: normalizedPath === "." ? undefined : this.getRestrictedParent(normalizedPath),
52
+ rootPath: this.root,
53
+ homePath: this.homeDir,
54
+ displayPath: this.resolveRestrictedAbsolute(normalizedPath),
55
+ pathKind: "relative",
56
+ };
57
+ return { entries, metadata };
58
+ }
59
+ listUnrestricted(targetPath, includeFiles) {
60
+ const resolvedPath = this.resolveUnrestrictedPath(targetPath);
61
+ if (this.isWindows && resolvedPath === WINDOWS_DRIVES_ROOT) {
62
+ return this.listWindowsDrives();
63
+ }
64
+ const entries = this.readDirectoryEntries(resolvedPath, {
65
+ includeFiles,
66
+ formatPath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName),
67
+ formatAbsolutePath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName),
68
+ });
69
+ const parentPath = this.getUnrestrictedParent(resolvedPath);
70
+ const metadata = {
71
+ scope: "unrestricted",
72
+ currentPath: resolvedPath,
73
+ parentPath,
74
+ rootPath: this.homeDir,
75
+ homePath: this.homeDir,
76
+ displayPath: resolvedPath,
77
+ pathKind: "absolute",
78
+ };
79
+ return { entries, metadata };
80
+ }
81
+ listWindowsDrives() {
82
+ if (!this.isWindows) {
83
+ throw new Error("Drive listing is only supported on Windows hosts");
84
+ }
85
+ const entries = [];
86
+ for (const letter of WINDOWS_DRIVE_LETTERS) {
87
+ const drivePath = `${letter}:\\`;
88
+ try {
89
+ if (fs.existsSync(drivePath)) {
90
+ entries.push({
91
+ name: `${letter}:`,
92
+ path: drivePath,
93
+ absolutePath: drivePath,
94
+ type: "directory",
95
+ });
96
+ }
97
+ }
98
+ catch {
99
+ // Ignore inaccessible drives
100
+ }
101
+ }
102
+ // Provide a generic UNC root entry so users can navigate to network shares manually.
103
+ entries.push({
104
+ name: "UNC Network",
105
+ path: "\\\\",
106
+ absolutePath: "\\\\",
107
+ type: "directory",
108
+ });
109
+ const metadata = {
110
+ scope: "unrestricted",
111
+ currentPath: WINDOWS_DRIVES_ROOT,
112
+ parentPath: undefined,
113
+ rootPath: this.homeDir,
114
+ homePath: this.homeDir,
115
+ displayPath: "Drives",
116
+ pathKind: "drives",
117
+ };
118
+ return { entries, metadata };
119
+ }
120
+ readDirectoryEntries(directory, options) {
121
+ const dirents = fs.readdirSync(directory, { withFileTypes: true });
122
+ const results = [];
123
+ for (const entry of dirents) {
124
+ if (!options.includeFiles && !entry.isDirectory()) {
125
+ continue;
126
+ }
127
+ const absoluteEntryPath = path.join(directory, entry.name);
128
+ let stats;
129
+ try {
130
+ stats = fs.statSync(absoluteEntryPath);
131
+ }
132
+ catch {
133
+ // Skip entries we cannot stat (insufficient permissions, etc.)
134
+ continue;
135
+ }
136
+ const isDirectory = entry.isDirectory();
137
+ if (!options.includeFiles && !isDirectory) {
138
+ continue;
139
+ }
140
+ results.push({
141
+ name: entry.name,
142
+ path: options.formatPath(entry.name),
143
+ absolutePath: options.formatAbsolutePath(entry.name),
144
+ type: isDirectory ? "directory" : "file",
145
+ size: isDirectory ? undefined : stats.size,
146
+ modifiedAt: stats.mtime.toISOString(),
147
+ });
148
+ }
149
+ return results.sort((a, b) => a.name.localeCompare(b.name));
150
+ }
151
+ normalizeRelativePath(input) {
152
+ if (!input || input === "." || input === "./" || input === "/") {
153
+ return ".";
154
+ }
155
+ let normalized = input.replace(/\\+/g, "/");
156
+ if (normalized.startsWith("./")) {
157
+ normalized = normalized.replace(/^\.\/+/, "");
158
+ }
159
+ if (normalized.startsWith("/")) {
160
+ normalized = normalized.replace(/^\/+/g, "");
161
+ }
162
+ return normalized === "" ? "." : normalized;
163
+ }
164
+ buildRelativePath(parent, child) {
165
+ if (!parent || parent === ".") {
166
+ return this.normalizeRelativePath(child);
167
+ }
168
+ return this.normalizeRelativePath(`${parent}/${child}`);
169
+ }
170
+ resolveRestrictedAbsolute(relativePath) {
171
+ return this.toRestrictedAbsolute(relativePath);
172
+ }
173
+ resolveRestrictedAbsoluteChild(parent, child) {
174
+ const normalized = this.buildRelativePath(parent, child);
175
+ return this.toRestrictedAbsolute(normalized);
176
+ }
177
+ toRestrictedAbsolute(relativePath) {
178
+ const normalized = this.normalizeRelativePath(relativePath);
179
+ const target = path.resolve(this.root, normalized);
180
+ const relativeToRoot = path.relative(this.root, target);
181
+ if (relativeToRoot.startsWith("..") || path.isAbsolute(relativeToRoot) && relativeToRoot !== "") {
182
+ throw new Error("Access outside of root is not allowed");
183
+ }
184
+ return target;
185
+ }
186
+ resolveUnrestrictedPath(input) {
187
+ if (!input || input === "." || input === "./") {
188
+ return this.homeDir;
189
+ }
190
+ if (this.isWindows) {
191
+ if (input === WINDOWS_DRIVES_ROOT) {
192
+ return WINDOWS_DRIVES_ROOT;
193
+ }
194
+ const normalized = path.win32.normalize(input);
195
+ if (/^[a-zA-Z]:/.test(normalized) || normalized.startsWith("\\\\")) {
196
+ return normalized;
197
+ }
198
+ return path.win32.resolve(this.homeDir, normalized);
199
+ }
200
+ if (input.startsWith("/")) {
201
+ return path.posix.normalize(input);
202
+ }
203
+ return path.posix.resolve(this.homeDir, input);
204
+ }
205
+ resolveAbsoluteChild(parent, child) {
206
+ if (this.isWindows) {
207
+ return path.win32.normalize(path.win32.join(parent, child));
208
+ }
209
+ return path.posix.normalize(path.posix.join(parent, child));
210
+ }
211
+ getRestrictedParent(relativePath) {
212
+ const normalized = this.normalizeRelativePath(relativePath);
213
+ if (normalized === ".") {
214
+ return undefined;
215
+ }
216
+ const segments = normalized.split("/");
217
+ segments.pop();
218
+ return segments.length === 0 ? "." : segments.join("/");
219
+ }
220
+ getUnrestrictedParent(currentPath) {
221
+ if (this.isWindows) {
222
+ const normalized = path.win32.normalize(currentPath);
223
+ const parsed = path.win32.parse(normalized);
224
+ if (normalized === WINDOWS_DRIVES_ROOT) {
225
+ return undefined;
226
+ }
227
+ if (normalized === parsed.root) {
228
+ return WINDOWS_DRIVES_ROOT;
229
+ }
230
+ return path.win32.dirname(normalized);
231
+ }
232
+ const normalized = path.posix.normalize(currentPath);
233
+ if (normalized === "/") {
234
+ return undefined;
235
+ }
236
+ return path.posix.dirname(normalized);
237
+ }
238
+ }
@@ -0,0 +1,43 @@
1
+ import path from "path";
2
+ export const WORKSPACE_CANDIDATE_CACHE_TTL_MS = 30000;
3
+ const workspaceCandidateCache = new Map();
4
+ export function getWorkspaceCandidates(rootDir, now = Date.now()) {
5
+ const key = normalizeKey(rootDir);
6
+ const cached = workspaceCandidateCache.get(key);
7
+ if (!cached) {
8
+ return undefined;
9
+ }
10
+ if (cached.expiresAt <= now) {
11
+ workspaceCandidateCache.delete(key);
12
+ return undefined;
13
+ }
14
+ return cloneEntries(cached.candidates);
15
+ }
16
+ export function refreshWorkspaceCandidates(rootDir, builder, now = Date.now()) {
17
+ const key = normalizeKey(rootDir);
18
+ const freshCandidates = builder();
19
+ if (!freshCandidates || freshCandidates.length === 0) {
20
+ workspaceCandidateCache.delete(key);
21
+ return [];
22
+ }
23
+ const storedCandidates = cloneEntries(freshCandidates);
24
+ workspaceCandidateCache.set(key, {
25
+ expiresAt: now + WORKSPACE_CANDIDATE_CACHE_TTL_MS,
26
+ candidates: storedCandidates,
27
+ });
28
+ return cloneEntries(storedCandidates);
29
+ }
30
+ export function clearWorkspaceSearchCache(rootDir) {
31
+ if (typeof rootDir === "undefined") {
32
+ workspaceCandidateCache.clear();
33
+ return;
34
+ }
35
+ const key = normalizeKey(rootDir);
36
+ workspaceCandidateCache.delete(key);
37
+ }
38
+ function cloneEntries(entries) {
39
+ return entries.map((entry) => ({ ...entry }));
40
+ }
41
+ function normalizeKey(rootDir) {
42
+ return path.resolve(rootDir);
43
+ }