@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,61 @@
1
+ import assert from "node:assert/strict"
2
+ import { beforeEach, describe, it } from "node:test"
3
+ import type { FileSystemEntry } from "../../api-types"
4
+ import {
5
+ clearWorkspaceSearchCache,
6
+ getWorkspaceCandidates,
7
+ refreshWorkspaceCandidates,
8
+ WORKSPACE_CANDIDATE_CACHE_TTL_MS,
9
+ } from "../search-cache"
10
+
11
+ describe("workspace search cache", () => {
12
+ beforeEach(() => {
13
+ clearWorkspaceSearchCache()
14
+ })
15
+
16
+ it("expires cached candidates after the TTL", () => {
17
+ const workspacePath = "/tmp/workspace"
18
+ const startTime = 1_000
19
+
20
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], startTime)
21
+
22
+ const beforeExpiry = getWorkspaceCandidates(
23
+ workspacePath,
24
+ startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS - 1,
25
+ )
26
+ assert.ok(beforeExpiry)
27
+ assert.equal(beforeExpiry.length, 1)
28
+ assert.equal(beforeExpiry[0].name, "file-a")
29
+
30
+ const afterExpiry = getWorkspaceCandidates(
31
+ workspacePath,
32
+ startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS + 1,
33
+ )
34
+ assert.equal(afterExpiry, undefined)
35
+ })
36
+
37
+ it("replaces cached entries when manually refreshed", () => {
38
+ const workspacePath = "/tmp/workspace"
39
+
40
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], 5_000)
41
+ const initial = getWorkspaceCandidates(workspacePath)
42
+ assert.ok(initial)
43
+ assert.equal(initial[0].name, "file-a")
44
+
45
+ refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-b")], 6_000)
46
+ const refreshed = getWorkspaceCandidates(workspacePath)
47
+ assert.ok(refreshed)
48
+ assert.equal(refreshed[0].name, "file-b")
49
+ })
50
+ })
51
+
52
+ function createEntry(name: string): FileSystemEntry {
53
+ return {
54
+ name,
55
+ path: name,
56
+ absolutePath: `/tmp/${name}`,
57
+ type: "file",
58
+ size: 1,
59
+ modifiedAt: new Date().toISOString(),
60
+ }
61
+ }
@@ -0,0 +1,353 @@
1
+ import fs from "fs"
2
+ import os from "os"
3
+ import path from "path"
4
+ import {
5
+ FileSystemCreateFolderResponse,
6
+ FileSystemEntry,
7
+ FileSystemListResponse,
8
+ FileSystemListingMetadata,
9
+ WINDOWS_DRIVES_ROOT,
10
+ } from "../api-types"
11
+
12
+ interface FileSystemBrowserOptions {
13
+ rootDir: string
14
+ unrestricted?: boolean
15
+ }
16
+
17
+ interface DirectoryReadOptions {
18
+ includeFiles: boolean
19
+ formatPath: (entryName: string) => string
20
+ formatAbsolutePath: (entryName: string) => string
21
+ }
22
+
23
+ const WINDOWS_DRIVE_LETTERS = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i))
24
+
25
+ export class FileSystemBrowser {
26
+ private readonly root: string
27
+ private readonly unrestricted: boolean
28
+ private readonly homeDir: string
29
+ private readonly isWindows: boolean
30
+
31
+ constructor(options: FileSystemBrowserOptions) {
32
+ this.root = path.resolve(options.rootDir)
33
+ this.unrestricted = Boolean(options.unrestricted)
34
+ this.homeDir = os.homedir()
35
+ this.isWindows = process.platform === "win32"
36
+ }
37
+
38
+ list(relativePath = ".", options: { includeFiles?: boolean } = {}): FileSystemEntry[] {
39
+ if (this.unrestricted) {
40
+ throw new Error("Relative listing is unavailable when running with unrestricted root")
41
+ }
42
+ const includeFiles = options.includeFiles ?? true
43
+ const normalizedPath = this.normalizeRelativePath(relativePath)
44
+ const absolutePath = this.toRestrictedAbsolute(normalizedPath)
45
+ return this.readDirectoryEntries(absolutePath, {
46
+ includeFiles,
47
+ formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName),
48
+ formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName),
49
+ })
50
+ }
51
+
52
+ browse(targetPath?: string, options: { includeFiles?: boolean } = {}): FileSystemListResponse {
53
+ const includeFiles = options.includeFiles ?? true
54
+ if (this.unrestricted) {
55
+ return this.listUnrestricted(targetPath, includeFiles)
56
+ }
57
+ return this.listRestrictedWithMetadata(targetPath, includeFiles)
58
+ }
59
+
60
+ createFolder(parentPath: string | undefined, folderName: string): FileSystemCreateFolderResponse {
61
+ const name = this.normalizeFolderName(folderName)
62
+
63
+ if (this.unrestricted) {
64
+ const resolvedParent = this.resolveUnrestrictedPath(parentPath)
65
+ if (this.isWindows && resolvedParent === WINDOWS_DRIVES_ROOT) {
66
+ throw new Error("Cannot create folders at drive root")
67
+ }
68
+ this.assertDirectoryExists(resolvedParent)
69
+ const absolutePath = this.resolveAbsoluteChild(resolvedParent, name)
70
+ fs.mkdirSync(absolutePath)
71
+ return { path: absolutePath, absolutePath }
72
+ }
73
+
74
+ const normalizedParent = this.normalizeRelativePath(parentPath)
75
+ const parentAbsolute = this.toRestrictedAbsolute(normalizedParent)
76
+ this.assertDirectoryExists(parentAbsolute)
77
+
78
+ const relativePath = this.buildRelativePath(normalizedParent, name)
79
+ const absolutePath = this.toRestrictedAbsolute(relativePath)
80
+ fs.mkdirSync(absolutePath)
81
+ return { path: relativePath, absolutePath }
82
+ }
83
+
84
+ readFile(relativePath: string): string {
85
+ if (this.unrestricted) {
86
+ throw new Error("readFile is not available in unrestricted mode")
87
+ }
88
+ const resolved = this.toRestrictedAbsolute(relativePath)
89
+ return fs.readFileSync(resolved, "utf-8")
90
+ }
91
+
92
+ private listRestrictedWithMetadata(relativePath: string | undefined, includeFiles: boolean): FileSystemListResponse {
93
+ const normalizedPath = this.normalizeRelativePath(relativePath)
94
+ const absolutePath = this.toRestrictedAbsolute(normalizedPath)
95
+ const entries = this.readDirectoryEntries(absolutePath, {
96
+ includeFiles,
97
+ formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName),
98
+ formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName),
99
+ })
100
+
101
+ const metadata: FileSystemListingMetadata = {
102
+ scope: "restricted",
103
+ currentPath: normalizedPath,
104
+ parentPath: normalizedPath === "." ? undefined : this.getRestrictedParent(normalizedPath),
105
+ rootPath: this.root,
106
+ homePath: this.homeDir,
107
+ displayPath: this.resolveRestrictedAbsolute(normalizedPath),
108
+ pathKind: "relative",
109
+ }
110
+
111
+ return { entries, metadata }
112
+ }
113
+
114
+ private listUnrestricted(targetPath: string | undefined, includeFiles: boolean): FileSystemListResponse {
115
+ const resolvedPath = this.resolveUnrestrictedPath(targetPath)
116
+
117
+ if (this.isWindows && resolvedPath === WINDOWS_DRIVES_ROOT) {
118
+ return this.listWindowsDrives()
119
+ }
120
+
121
+ const entries = this.readDirectoryEntries(resolvedPath, {
122
+ includeFiles,
123
+ formatPath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName),
124
+ formatAbsolutePath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName),
125
+ })
126
+
127
+ const parentPath = this.getUnrestrictedParent(resolvedPath)
128
+
129
+ const metadata: FileSystemListingMetadata = {
130
+ scope: "unrestricted",
131
+ currentPath: resolvedPath,
132
+ parentPath,
133
+ rootPath: this.homeDir,
134
+ homePath: this.homeDir,
135
+ displayPath: resolvedPath,
136
+ pathKind: "absolute",
137
+ }
138
+
139
+ return { entries, metadata }
140
+ }
141
+
142
+ private listWindowsDrives(): FileSystemListResponse {
143
+ if (!this.isWindows) {
144
+ throw new Error("Drive listing is only supported on Windows hosts")
145
+ }
146
+
147
+ const entries: FileSystemEntry[] = []
148
+ for (const letter of WINDOWS_DRIVE_LETTERS) {
149
+ const drivePath = `${letter}:\\`
150
+ try {
151
+ if (fs.existsSync(drivePath)) {
152
+ entries.push({
153
+ name: `${letter}:`,
154
+ path: drivePath,
155
+ absolutePath: drivePath,
156
+ type: "directory",
157
+ })
158
+ }
159
+ } catch {
160
+ // Ignore inaccessible drives
161
+ }
162
+ }
163
+
164
+ // Provide a generic UNC root entry so users can navigate to network shares manually.
165
+ entries.push({
166
+ name: "UNC Network",
167
+ path: "\\\\",
168
+ absolutePath: "\\\\",
169
+ type: "directory",
170
+ })
171
+
172
+ const metadata: FileSystemListingMetadata = {
173
+ scope: "unrestricted",
174
+ currentPath: WINDOWS_DRIVES_ROOT,
175
+ parentPath: undefined,
176
+ rootPath: this.homeDir,
177
+ homePath: this.homeDir,
178
+ displayPath: "Drives",
179
+ pathKind: "drives",
180
+ }
181
+
182
+ return { entries, metadata }
183
+ }
184
+
185
+ private normalizeFolderName(input: string): string {
186
+ const name = input.trim()
187
+ if (!name) {
188
+ throw new Error("Folder name is required")
189
+ }
190
+
191
+ if (name === "." || name === "..") {
192
+ throw new Error("Invalid folder name")
193
+ }
194
+
195
+ if (name.startsWith("~")) {
196
+ throw new Error("Invalid folder name")
197
+ }
198
+
199
+ if (name.includes("/") || name.includes("\\")) {
200
+ throw new Error("Folder name must not include path separators")
201
+ }
202
+
203
+ if (name.includes("\u0000")) {
204
+ throw new Error("Invalid folder name")
205
+ }
206
+
207
+ return name
208
+ }
209
+
210
+ private assertDirectoryExists(directory: string) {
211
+ if (!fs.existsSync(directory)) {
212
+ throw new Error(`Directory does not exist: ${directory}`)
213
+ }
214
+ const stats = fs.statSync(directory)
215
+ if (!stats.isDirectory()) {
216
+ throw new Error(`Path is not a directory: ${directory}`)
217
+ }
218
+ }
219
+
220
+ private readDirectoryEntries(directory: string, options: DirectoryReadOptions): FileSystemEntry[] {
221
+ const dirents = fs.readdirSync(directory, { withFileTypes: true })
222
+ const results: FileSystemEntry[] = []
223
+
224
+ for (const entry of dirents) {
225
+ const absoluteEntryPath = path.join(directory, entry.name)
226
+ let stats: fs.Stats
227
+ try {
228
+ // Use fs.statSync (not Dirent.isDirectory) so symlinks to directories
229
+ // are treated as directories in directory-only listings.
230
+ stats = fs.statSync(absoluteEntryPath)
231
+ } catch {
232
+ // Skip entries we cannot stat (insufficient permissions, etc.)
233
+ continue
234
+ }
235
+
236
+ const isDirectory = stats.isDirectory()
237
+ if (!options.includeFiles && !isDirectory) {
238
+ continue
239
+ }
240
+
241
+ results.push({
242
+ name: entry.name,
243
+ path: options.formatPath(entry.name),
244
+ absolutePath: options.formatAbsolutePath(entry.name),
245
+ type: isDirectory ? "directory" : "file",
246
+ size: isDirectory ? undefined : stats.size,
247
+ modifiedAt: stats.mtime.toISOString(),
248
+ })
249
+ }
250
+
251
+ return results.sort((a, b) => a.name.localeCompare(b.name))
252
+ }
253
+
254
+ private normalizeRelativePath(input: string | undefined) {
255
+ if (!input || input === "." || input === "./" || input === "/") {
256
+ return "."
257
+ }
258
+ let normalized = input.replace(/\\+/g, "/")
259
+ if (normalized.startsWith("./")) {
260
+ normalized = normalized.replace(/^\.\/+/, "")
261
+ }
262
+ if (normalized.startsWith("/")) {
263
+ normalized = normalized.replace(/^\/+/g, "")
264
+ }
265
+ return normalized === "" ? "." : normalized
266
+ }
267
+
268
+ private buildRelativePath(parent: string, child: string) {
269
+ if (!parent || parent === ".") {
270
+ return this.normalizeRelativePath(child)
271
+ }
272
+ return this.normalizeRelativePath(`${parent}/${child}`)
273
+ }
274
+
275
+ private resolveRestrictedAbsolute(relativePath: string) {
276
+ return this.toRestrictedAbsolute(relativePath)
277
+ }
278
+
279
+ private resolveRestrictedAbsoluteChild(parent: string, child: string) {
280
+ const normalized = this.buildRelativePath(parent, child)
281
+ return this.toRestrictedAbsolute(normalized)
282
+ }
283
+
284
+ private toRestrictedAbsolute(relativePath: string) {
285
+ const normalized = this.normalizeRelativePath(relativePath)
286
+ const target = path.resolve(this.root, normalized)
287
+ const relativeToRoot = path.relative(this.root, target)
288
+ if (relativeToRoot.startsWith("..") || path.isAbsolute(relativeToRoot) && relativeToRoot !== "") {
289
+ throw new Error("Access outside of root is not allowed")
290
+ }
291
+ return target
292
+ }
293
+
294
+ private resolveUnrestrictedPath(input: string | undefined): string {
295
+ if (!input || input === "." || input === "./") {
296
+ return this.homeDir
297
+ }
298
+
299
+ if (this.isWindows) {
300
+ if (input === WINDOWS_DRIVES_ROOT) {
301
+ return WINDOWS_DRIVES_ROOT
302
+ }
303
+ const normalized = path.win32.normalize(input)
304
+ if (/^[a-zA-Z]:/.test(normalized) || normalized.startsWith("\\\\")) {
305
+ return normalized
306
+ }
307
+ return path.win32.resolve(this.homeDir, normalized)
308
+ }
309
+
310
+ if (input.startsWith("/")) {
311
+ return path.posix.normalize(input)
312
+ }
313
+
314
+ return path.posix.resolve(this.homeDir, input)
315
+ }
316
+
317
+ private resolveAbsoluteChild(parent: string, child: string) {
318
+ if (this.isWindows) {
319
+ return path.win32.normalize(path.win32.join(parent, child))
320
+ }
321
+ return path.posix.normalize(path.posix.join(parent, child))
322
+ }
323
+
324
+ private getRestrictedParent(relativePath: string) {
325
+ const normalized = this.normalizeRelativePath(relativePath)
326
+ if (normalized === ".") {
327
+ return undefined
328
+ }
329
+ const segments = normalized.split("/")
330
+ segments.pop()
331
+ return segments.length === 0 ? "." : segments.join("/")
332
+ }
333
+
334
+ private getUnrestrictedParent(currentPath: string) {
335
+ if (this.isWindows) {
336
+ const normalized = path.win32.normalize(currentPath)
337
+ const parsed = path.win32.parse(normalized)
338
+ if (normalized === WINDOWS_DRIVES_ROOT) {
339
+ return undefined
340
+ }
341
+ if (normalized === parsed.root) {
342
+ return WINDOWS_DRIVES_ROOT
343
+ }
344
+ return path.win32.dirname(normalized)
345
+ }
346
+
347
+ const normalized = path.posix.normalize(currentPath)
348
+ if (normalized === "/") {
349
+ return undefined
350
+ }
351
+ return path.posix.dirname(normalized)
352
+ }
353
+ }
@@ -0,0 +1,66 @@
1
+ import path from "path"
2
+ import type { FileSystemEntry } from "../api-types"
3
+
4
+ export const WORKSPACE_CANDIDATE_CACHE_TTL_MS = 30_000
5
+
6
+ interface WorkspaceCandidateCacheEntry {
7
+ expiresAt: number
8
+ candidates: FileSystemEntry[]
9
+ }
10
+
11
+ const workspaceCandidateCache = new Map<string, WorkspaceCandidateCacheEntry>()
12
+
13
+ export function getWorkspaceCandidates(rootDir: string, now = Date.now()): FileSystemEntry[] | undefined {
14
+ const key = normalizeKey(rootDir)
15
+ const cached = workspaceCandidateCache.get(key)
16
+ if (!cached) {
17
+ return undefined
18
+ }
19
+
20
+ if (cached.expiresAt <= now) {
21
+ workspaceCandidateCache.delete(key)
22
+ return undefined
23
+ }
24
+
25
+ return cloneEntries(cached.candidates)
26
+ }
27
+
28
+ export function refreshWorkspaceCandidates(
29
+ rootDir: string,
30
+ builder: () => FileSystemEntry[],
31
+ now = Date.now(),
32
+ ): FileSystemEntry[] {
33
+ const key = normalizeKey(rootDir)
34
+ const freshCandidates = builder()
35
+
36
+ if (!freshCandidates || freshCandidates.length === 0) {
37
+ workspaceCandidateCache.delete(key)
38
+ return []
39
+ }
40
+
41
+ const storedCandidates = cloneEntries(freshCandidates)
42
+ workspaceCandidateCache.set(key, {
43
+ expiresAt: now + WORKSPACE_CANDIDATE_CACHE_TTL_MS,
44
+ candidates: storedCandidates,
45
+ })
46
+
47
+ return cloneEntries(storedCandidates)
48
+ }
49
+
50
+ export function clearWorkspaceSearchCache(rootDir?: string) {
51
+ if (typeof rootDir === "undefined") {
52
+ workspaceCandidateCache.clear()
53
+ return
54
+ }
55
+
56
+ const key = normalizeKey(rootDir)
57
+ workspaceCandidateCache.delete(key)
58
+ }
59
+
60
+ function cloneEntries(entries: FileSystemEntry[]): FileSystemEntry[] {
61
+ return entries.map((entry) => ({ ...entry }))
62
+ }
63
+
64
+ function normalizeKey(rootDir: string) {
65
+ return path.resolve(rootDir)
66
+ }
@@ -0,0 +1,184 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import fuzzysort from "fuzzysort"
4
+ import type { FileSystemEntry } from "../api-types"
5
+ import { clearWorkspaceSearchCache, getWorkspaceCandidates, refreshWorkspaceCandidates } from "./search-cache"
6
+
7
+ const DEFAULT_LIMIT = 100
8
+ const MAX_LIMIT = 200
9
+ const MAX_CANDIDATES = 8000
10
+ const IGNORED_DIRECTORIES = new Set(
11
+ [".git", ".hg", ".svn", "node_modules", "dist", "build", ".next", ".nuxt", ".turbo", ".cache", "coverage"].map(
12
+ (name) => name.toLowerCase(),
13
+ ),
14
+ )
15
+
16
+ export type WorkspaceFileSearchType = "all" | "file" | "directory"
17
+
18
+ export interface WorkspaceFileSearchOptions {
19
+ limit?: number
20
+ type?: WorkspaceFileSearchType
21
+ refresh?: boolean
22
+ }
23
+
24
+ interface CandidateEntry {
25
+ entry: FileSystemEntry
26
+ key: string
27
+ }
28
+
29
+ export function searchWorkspaceFiles(
30
+ rootDir: string,
31
+ query: string,
32
+ options: WorkspaceFileSearchOptions = {},
33
+ ): FileSystemEntry[] {
34
+ const trimmedQuery = query.trim()
35
+ if (!trimmedQuery) {
36
+ throw new Error("Search query is required")
37
+ }
38
+
39
+ const normalizedRoot = path.resolve(rootDir)
40
+ const limit = normalizeLimit(options.limit)
41
+ const typeFilter: WorkspaceFileSearchType = options.type ?? "all"
42
+ const refreshRequested = options.refresh === true
43
+
44
+ let entries: FileSystemEntry[] | undefined
45
+
46
+ try {
47
+ if (!refreshRequested) {
48
+ entries = getWorkspaceCandidates(normalizedRoot)
49
+ }
50
+
51
+ if (!entries) {
52
+ entries = refreshWorkspaceCandidates(normalizedRoot, () => collectCandidates(normalizedRoot))
53
+ }
54
+ } catch (error) {
55
+ clearWorkspaceSearchCache(normalizedRoot)
56
+ throw error
57
+ }
58
+
59
+ if (!entries || entries.length === 0) {
60
+ clearWorkspaceSearchCache(normalizedRoot)
61
+ return []
62
+ }
63
+
64
+ const candidates = buildCandidateEntries(entries, typeFilter)
65
+
66
+ if (candidates.length === 0) {
67
+ return []
68
+ }
69
+
70
+ const matches = fuzzysort.go<CandidateEntry>(trimmedQuery, candidates, {
71
+ key: "key",
72
+ limit,
73
+ })
74
+
75
+ if (!matches || matches.length === 0) {
76
+ return []
77
+ }
78
+
79
+ return matches.map((match) => match.obj.entry)
80
+ }
81
+
82
+
83
+ function collectCandidates(rootDir: string): FileSystemEntry[] {
84
+ const queue: string[] = [""]
85
+ const entries: FileSystemEntry[] = []
86
+
87
+ while (queue.length > 0 && entries.length < MAX_CANDIDATES) {
88
+ const relativeDir = queue.pop() || ""
89
+ const absoluteDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir
90
+
91
+ let dirents: fs.Dirent[]
92
+ try {
93
+ dirents = fs.readdirSync(absoluteDir, { withFileTypes: true })
94
+ } catch {
95
+ continue
96
+ }
97
+
98
+ for (const dirent of dirents) {
99
+ const entryName = dirent.name
100
+ const lowerName = entryName.toLowerCase()
101
+ const relativePath = relativeDir ? `${relativeDir}/${entryName}` : entryName
102
+ const absolutePath = path.join(absoluteDir, entryName)
103
+
104
+ if (dirent.isDirectory() && IGNORED_DIRECTORIES.has(lowerName)) {
105
+ continue
106
+ }
107
+
108
+ let stats: fs.Stats
109
+ try {
110
+ stats = fs.statSync(absolutePath)
111
+ } catch {
112
+ continue
113
+ }
114
+
115
+ const isDirectory = stats.isDirectory()
116
+
117
+ if (isDirectory && !IGNORED_DIRECTORIES.has(lowerName)) {
118
+ if (entries.length < MAX_CANDIDATES) {
119
+ queue.push(relativePath)
120
+ }
121
+ }
122
+
123
+ const entryType: FileSystemEntry["type"] = isDirectory ? "directory" : "file"
124
+ const normalizedPath = normalizeRelativeEntryPath(relativePath)
125
+ const entry: FileSystemEntry = {
126
+ name: entryName,
127
+ path: normalizedPath,
128
+ absolutePath: path.resolve(rootDir, normalizedPath === "." ? "" : normalizedPath),
129
+ type: entryType,
130
+ size: entryType === "file" ? stats.size : undefined,
131
+ modifiedAt: stats.mtime.toISOString(),
132
+ }
133
+
134
+ entries.push(entry)
135
+
136
+ if (entries.length >= MAX_CANDIDATES) {
137
+ break
138
+ }
139
+ }
140
+ }
141
+
142
+ return entries
143
+ }
144
+
145
+ function buildCandidateEntries(entries: FileSystemEntry[], filter: WorkspaceFileSearchType): CandidateEntry[] {
146
+ const filtered: CandidateEntry[] = []
147
+ for (const entry of entries) {
148
+ if (!shouldInclude(entry.type, filter)) {
149
+ continue
150
+ }
151
+ filtered.push({ entry, key: buildSearchKey(entry) })
152
+ }
153
+ return filtered
154
+ }
155
+
156
+ function normalizeLimit(limit?: number) {
157
+ if (!limit || Number.isNaN(limit)) {
158
+ return DEFAULT_LIMIT
159
+ }
160
+ const clamped = Math.min(Math.max(limit, 1), MAX_LIMIT)
161
+ return clamped
162
+ }
163
+
164
+ function shouldInclude(entryType: FileSystemEntry["type"], filter: WorkspaceFileSearchType) {
165
+ return filter === "all" || entryType === filter
166
+ }
167
+
168
+ function normalizeRelativeEntryPath(relativePath: string): string {
169
+ if (!relativePath) {
170
+ return "."
171
+ }
172
+ let normalized = relativePath.replace(/\\+/g, "/")
173
+ if (normalized.startsWith("./")) {
174
+ normalized = normalized.replace(/^\.\/+/, "")
175
+ }
176
+ if (normalized.startsWith("/")) {
177
+ normalized = normalized.replace(/^\/+/g, "")
178
+ }
179
+ return normalized || "."
180
+ }
181
+
182
+ function buildSearchKey(entry: FileSystemEntry) {
183
+ return entry.path.toLowerCase()
184
+ }