@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.
- package/README.md +126 -0
- package/dist/api-types.js +1 -0
- package/dist/auth/auth-store.js +134 -0
- package/dist/auth/http-auth.js +37 -0
- package/dist/auth/manager.js +128 -0
- package/dist/auth/password-hash.js +32 -0
- package/dist/auth/session-manager.js +17 -0
- package/dist/auth/token-manager.js +27 -0
- package/dist/background-processes/manager.js +437 -0
- package/dist/bin.js +24 -0
- package/dist/config/binaries.js +148 -0
- package/dist/config/location.js +57 -0
- package/dist/config/schema.js +70 -0
- package/dist/config/store.js +200 -0
- package/dist/events/bus.js +41 -0
- package/dist/filesystem/__tests__/search-cache.test.js +40 -0
- package/dist/filesystem/browser.js +285 -0
- package/dist/filesystem/search-cache.js +43 -0
- package/dist/filesystem/search.js +135 -0
- package/dist/index.js +410 -0
- package/dist/integrations/github/bot-signature.js +11 -0
- package/dist/integrations/github/git-ops.js +133 -0
- package/dist/integrations/github/github-types.js +1 -0
- package/dist/integrations/github/job-runner.js +608 -0
- package/dist/integrations/github/octokit.js +58 -0
- package/dist/integrations/github/sanitize-webhook.js +42 -0
- package/dist/integrations/github/webhook-verify.js +21 -0
- package/dist/integrations/github/workspace-context.js +10 -0
- package/dist/integrations/github/worktree-context.js +15 -0
- package/dist/launcher.js +149 -0
- package/dist/loader.js +21 -0
- package/dist/logger.js +109 -0
- package/dist/opencode/request-context.js +39 -0
- package/dist/opencode/worktree-directory.js +42 -0
- package/dist/opencode-config/README.md +32 -0
- package/dist/opencode-config/opencode.jsonc +3 -0
- package/dist/opencode-config/package.json +9 -0
- package/dist/opencode-config/plugin/codenomad.ts +32 -0
- package/dist/opencode-config/plugin/lib/background-process.ts +253 -0
- package/dist/opencode-config/plugin/lib/client.ts +133 -0
- package/dist/opencode-config/plugin/lib/request.ts +214 -0
- package/dist/opencode-config-template/README.md +32 -0
- package/dist/opencode-config-template/opencode.jsonc +3 -0
- package/dist/opencode-config-template/plugin/codenomad.ts +40 -0
- package/dist/opencode-config-template/plugin/lib/background-process.ts +160 -0
- package/dist/opencode-config-template/plugin/lib/client.ts +165 -0
- package/dist/opencode-config.js +26 -0
- package/dist/plugins/channel.js +40 -0
- package/dist/plugins/handlers.js +17 -0
- package/dist/releases/dev-release-monitor.js +75 -0
- package/dist/releases/release-monitor.js +107 -0
- package/dist/server/http-server.js +547 -0
- package/dist/server/network-addresses.js +72 -0
- package/dist/server/routes/auth-pages/login.html +134 -0
- package/dist/server/routes/auth-pages/token.html +93 -0
- package/dist/server/routes/auth.js +134 -0
- package/dist/server/routes/background-processes.js +60 -0
- package/dist/server/routes/config.js +59 -0
- package/dist/server/routes/events.js +43 -0
- package/dist/server/routes/filesystem.js +43 -0
- package/dist/server/routes/github-plugin.js +215 -0
- package/dist/server/routes/github-webhook.js +32 -0
- package/dist/server/routes/meta.js +47 -0
- package/dist/server/routes/plugin.js +52 -0
- package/dist/server/routes/storage.js +52 -0
- package/dist/server/routes/workspaces.js +89 -0
- package/dist/server/routes/worktrees.js +156 -0
- package/dist/server/tls.js +224 -0
- package/dist/storage/instance-store.js +56 -0
- package/dist/ui/__tests__/remote-ui.test.js +46 -0
- package/dist/ui/remote-ui.js +462 -0
- package/dist/workspaces/git-worktrees.js +199 -0
- package/dist/workspaces/instance-events.js +180 -0
- package/dist/workspaces/manager.js +375 -0
- package/dist/workspaces/opencode-auth.js +16 -0
- package/dist/workspaces/runtime.js +346 -0
- package/dist/workspaces/worktree-map.js +116 -0
- package/package.json +49 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
- package/public/assets/abap-BdImnpbu.js +1 -0
- package/public/assets/actionscript-3-CfeIJUat.js +1 -0
- package/public/assets/ada-bCR0ucgS.js +1 -0
- package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
- package/public/assets/angular-html-CU67Zn6k.js +1 -0
- package/public/assets/angular-ts-BwZT4LLn.js +1 -0
- package/public/assets/apache-Pmp26Uib.js +1 -0
- package/public/assets/apex-DhZLUxFE.js +1 -0
- package/public/assets/apl-dKokRX4l.js +1 -0
- package/public/assets/applescript-Co6uUVPk.js +1 -0
- package/public/assets/ara-BRHolxvo.js +1 -0
- package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
- package/public/assets/asm-D_Q5rh1f.js +1 -0
- package/public/assets/astro-CbQHKStN.js +1 -0
- package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/public/assets/awk-DMzUqQB5.js +1 -0
- package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/public/assets/ballerina-BFfxhgS-.js +1 -0
- package/public/assets/bat-BkioyH1T.js +1 -0
- package/public/assets/beancount-k_qm7-4y.js +1 -0
- package/public/assets/berry-D08WgyRC.js +1 -0
- package/public/assets/bibtex-CHM0blh-.js +1 -0
- package/public/assets/bicep-Bmn6On1c.js +1 -0
- package/public/assets/blade-DVc8C-J4.js +1 -0
- package/public/assets/bsl-BO_Y6i37.js +1 -0
- package/public/assets/c-BIGW1oBm.js +1 -0
- package/public/assets/cadence-Bv_4Rxtq.js +1 -0
- package/public/assets/cairo-KRGpt6FW.js +1 -0
- package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/public/assets/clarity-D53aC0YG.js +1 -0
- package/public/assets/clojure-P80f7IUj.js +1 -0
- package/public/assets/cmake-D1j8_8rp.js +1 -0
- package/public/assets/cobol-nwyudZeR.js +1 -0
- package/public/assets/codeowners-Bp6g37R7.js +1 -0
- package/public/assets/codeql-DsOJ9woJ.js +1 -0
- package/public/assets/coffee-Ch7k5sss.js +1 -0
- package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/public/assets/coq-DkFqJrB1.js +1 -0
- package/public/assets/core-BSTVzpXI.js +1 -0
- package/public/assets/cpp-CofmeUqb.js +1 -0
- package/public/assets/crystal-tKQVLTB8.js +1 -0
- package/public/assets/csharp-CX12Zw3r.js +1 -0
- package/public/assets/css-DPfMkruS.js +1 -0
- package/public/assets/csv-fuZLfV_i.js +1 -0
- package/public/assets/cue-D82EKSYY.js +1 -0
- package/public/assets/cypher-COkxafJQ.js +1 -0
- package/public/assets/d-85-TOEBH.js +1 -0
- package/public/assets/dark-plus-eOWES_5F.js +1 -0
- package/public/assets/dart-CF10PKvl.js +1 -0
- package/public/assets/dax-CEL-wOlO.js +1 -0
- package/public/assets/desktop-BmXAJ9_W.js +1 -0
- package/public/assets/diff-D97Zzqfu.js +1 -0
- package/public/assets/docker-BcOcwvcX.js +1 -0
- package/public/assets/dotenv-Da5cRb03.js +1 -0
- package/public/assets/dracula-BzJJZx-M.js +1 -0
- package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/public/assets/dream-maker-BtqSS_iP.js +1 -0
- package/public/assets/edge-BkV0erSs.js +1 -0
- package/public/assets/elixir-CDX3lj18.js +1 -0
- package/public/assets/elm-DbKCFpqz.js +1 -0
- package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/public/assets/erb-BOJIQeun.js +1 -0
- package/public/assets/erlang-DsQrWhSR.js +1 -0
- package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/public/assets/everforest-light-C8M2exoo.js +1 -0
- package/public/assets/fennel-BYunw83y.js +1 -0
- package/public/assets/fish-BvzEVeQv.js +1 -0
- package/public/assets/fluent-C4IJs8-o.js +1 -0
- package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
- package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
- package/public/assets/fsharp-CXgrBDvD.js +1 -0
- package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
- package/public/assets/gdscript-DTMYz4Jt.js +1 -0
- package/public/assets/gdshader-DkwncUOv.js +1 -0
- package/public/assets/genie-D0YGMca9.js +1 -0
- package/public/assets/gherkin-DyxjwDmM.js +1 -0
- package/public/assets/git-commit-F4YmCXRG.js +1 -0
- package/public/assets/git-rebase-r7XF79zn.js +1 -0
- package/public/assets/github-dark-DHJKELXO.js +1 -0
- package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/public/assets/github-light-DAi9KRSo.js +1 -0
- package/public/assets/github-light-default-D7oLnXFd.js +1 -0
- package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/public/assets/gleam-BspZqrRM.js +1 -0
- package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/public/assets/glimmer-ts-U6CK756n.js +1 -0
- package/public/assets/glsl-DplSGwfg.js +1 -0
- package/public/assets/gnuplot-DdkO51Og.js +1 -0
- package/public/assets/go-Dn2_MT6a.js +1 -0
- package/public/assets/graphql-ChdNCCLP.js +1 -0
- package/public/assets/groovy-gcz8RCvz.js +1 -0
- package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/public/assets/hack-CaT9iCJl.js +1 -0
- package/public/assets/haml-B8DHNrY2.js +1 -0
- package/public/assets/handlebars-BL8al0AC.js +1 -0
- package/public/assets/haskell-Df6bDoY_.js +1 -0
- package/public/assets/haxe-CzTSHFRz.js +1 -0
- package/public/assets/hcl-BWvSN4gD.js +1 -0
- package/public/assets/hjson-D5-asLiD.js +1 -0
- package/public/assets/hlsl-D3lLCCz7.js +1 -0
- package/public/assets/houston-DnULxvSX.js +1 -0
- package/public/assets/html-GMplVEZG.js +1 -0
- package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/public/assets/http-jrhK8wxY.js +1 -0
- package/public/assets/hurl-irOxFIW8.js +1 -0
- package/public/assets/hxml-Bvhsp5Yf.js +1 -0
- package/public/assets/hy-DFXneXwc.js +1 -0
- package/public/assets/imba-DGztddWO.js +1 -0
- package/public/assets/index-D4PT0yE4.js +1 -0
- package/public/assets/index-DN20ggb1.js +1 -0
- package/public/assets/index-DdQ7zIzB.js +1 -0
- package/public/assets/index-Dl-rJJuP.js +1 -0
- package/public/assets/index-Dlo2gDiy.css +1 -0
- package/public/assets/ini-BEwlwnbL.js +1 -0
- package/public/assets/java-CylS5w8V.js +1 -0
- package/public/assets/javascript-wDzz0qaB.js +1 -0
- package/public/assets/jinja-4LBKfQ-Z.js +1 -0
- package/public/assets/jison-wvAkD_A8.js +1 -0
- package/public/assets/json-Cp-IABpG.js +1 -0
- package/public/assets/json5-C9tS-k6U.js +1 -0
- package/public/assets/jsonc-Des-eS-w.js +1 -0
- package/public/assets/jsonl-DcaNXYhu.js +1 -0
- package/public/assets/jsonnet-DFQXde-d.js +1 -0
- package/public/assets/jssm-C2t-YnRu.js +1 -0
- package/public/assets/jsx-g9-lgVsj.js +1 -0
- package/public/assets/julia-C8NyazO9.js +1 -0
- package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/public/assets/kdl-DV7GczEv.js +1 -0
- package/public/assets/kotlin-BdnUsdx6.js +1 -0
- package/public/assets/kusto-BvAqAH-y.js +1 -0
- package/public/assets/laserwave-DUszq2jm.js +1 -0
- package/public/assets/latex-BUKiar2Z.js +1 -0
- package/public/assets/lean-DP1Csr6i.js +1 -0
- package/public/assets/less-B1dDrJ26.js +1 -0
- package/public/assets/light-plus-B7mTdjB0.js +1 -0
- package/public/assets/liquid-DYVedYrR.js +1 -0
- package/public/assets/llvm-BtvRca6l.js +1 -0
- package/public/assets/loading-CmEVQgyj.css +1 -0
- package/public/assets/loading-DgqIiz-T.js +1 -0
- package/public/assets/log-2UxHyX5q.js +1 -0
- package/public/assets/logo-BtOb2qkB.js +1 -0
- package/public/assets/lua-BbnMAYS6.js +1 -0
- package/public/assets/luau-CXu1NL6O.js +1 -0
- package/public/assets/main-CSlDZj4f.js +188 -0
- package/public/assets/main-HAZkIolJ.css +19 -0
- package/public/assets/make-CHLpvVh8.js +1 -0
- package/public/assets/markdown-Cvjx9yec.js +1 -0
- package/public/assets/marko-CPi9NSCl.js +1 -0
- package/public/assets/material-theme-D5KoaKCx.js +1 -0
- package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/public/assets/matlab-D7o27uSR.js +1 -0
- package/public/assets/mdc-DUICxH0z.js +1 -0
- package/public/assets/mdx-Cmh6b_Ma.js +1 -0
- package/public/assets/mermaid-DKYwYmdq.js +1 -0
- package/public/assets/min-dark-CafNBF8u.js +1 -0
- package/public/assets/min-light-CTRr51gU.js +1 -0
- package/public/assets/mipsasm-CKIfxQSi.js +1 -0
- package/public/assets/mojo-1DNp92w6.js +1 -0
- package/public/assets/monokai-D4h5O-jR.js +1 -0
- package/public/assets/move-Bu9oaDYs.js +1 -0
- package/public/assets/narrat-DRg8JJMk.js +1 -0
- package/public/assets/nextflow-CUEJCptM.js +1 -0
- package/public/assets/nginx-DknmC5AR.js +1 -0
- package/public/assets/night-owl-C39BiMTA.js +1 -0
- package/public/assets/nim-CVrawwO9.js +1 -0
- package/public/assets/nix-BbRYJGeE.js +1 -0
- package/public/assets/nord-Ddv68eIx.js +1 -0
- package/public/assets/nushell-C-sUppwS.js +1 -0
- package/public/assets/objective-c-DXmwc3jG.js +1 -0
- package/public/assets/objective-cpp-CLxacb5B.js +1 -0
- package/public/assets/ocaml-C0hk2d4L.js +1 -0
- package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/public/assets/one-light-PoHY5YXO.js +1 -0
- package/public/assets/pascal-D93ZcfNL.js +1 -0
- package/public/assets/perl-C0TMdlhV.js +1 -0
- package/public/assets/php-CDn_0X-4.js +1 -0
- package/public/assets/pkl-u5AG7uiY.js +1 -0
- package/public/assets/plastic-3e1v2bzS.js +1 -0
- package/public/assets/plsql-ChMvpjG-.js +1 -0
- package/public/assets/po-BTJTHyun.js +1 -0
- package/public/assets/poimandres-CS3Unz2-.js +1 -0
- package/public/assets/polar-C0HS_06l.js +1 -0
- package/public/assets/postcss-CXtECtnM.js +1 -0
- package/public/assets/powerquery-CEu0bR-o.js +1 -0
- package/public/assets/powershell-Dpen1YoG.js +1 -0
- package/public/assets/prisma-Dd19v3D-.js +1 -0
- package/public/assets/prolog-CbFg5uaA.js +1 -0
- package/public/assets/proto-DyJlTyXw.js +1 -0
- package/public/assets/pug-CGlum2m_.js +1 -0
- package/public/assets/puppet-BMWR74SV.js +1 -0
- package/public/assets/purescript-CklMAg4u.js +1 -0
- package/public/assets/python-B6aJPvgy.js +1 -0
- package/public/assets/qml-3beO22l8.js +1 -0
- package/public/assets/qmldir-C8lEn-DE.js +1 -0
- package/public/assets/qss-IeuSbFQv.js +1 -0
- package/public/assets/r-DiinP2Uv.js +1 -0
- package/public/assets/racket-BqYA7rlc.js +1 -0
- package/public/assets/raku-DXvB9xmW.js +1 -0
- package/public/assets/razor-WgofotgN.js +1 -0
- package/public/assets/red-bN70gL4F.js +1 -0
- package/public/assets/reg-C-SQnVFl.js +1 -0
- package/public/assets/regexp-CDVJQ6XC.js +1 -0
- package/public/assets/rel-C3B-1QV4.js +1 -0
- package/public/assets/riscv-BM1_JUlF.js +1 -0
- package/public/assets/rose-pine-BHrmToEH.js +1 -0
- package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
- package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
- package/public/assets/rosmsg-BJDFO7_C.js +1 -0
- package/public/assets/rst-B0xPkSld.js +1 -0
- package/public/assets/ruby-BvKwtOVI.js +1 -0
- package/public/assets/rust-B1yitclQ.js +1 -0
- package/public/assets/sas-cz2c8ADy.js +1 -0
- package/public/assets/sass-Cj5Yp3dK.js +1 -0
- package/public/assets/scala-C151Ov-r.js +1 -0
- package/public/assets/scheme-C98Dy4si.js +1 -0
- package/public/assets/scss-OYdSNvt2.js +1 -0
- package/public/assets/sdbl-DVxCFoDh.js +1 -0
- package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/public/assets/shellscript-Yzrsuije.js +1 -0
- package/public/assets/shellsession-BADoaaVG.js +1 -0
- package/public/assets/slack-dark-BthQWCQV.js +1 -0
- package/public/assets/slack-ochin-DqwNpetd.js +1 -0
- package/public/assets/smalltalk-BERRCDM3.js +1 -0
- package/public/assets/snazzy-light-Bw305WKR.js +1 -0
- package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/public/assets/solarized-light-L9t79GZl.js +1 -0
- package/public/assets/solidity-BbcW6ACK.js +1 -0
- package/public/assets/soy-Brmx7dQM.js +1 -0
- package/public/assets/sparql-rVzFXLq3.js +1 -0
- package/public/assets/splunk-BtCnVYZw.js +1 -0
- package/public/assets/sql-BLtJtn59.js +1 -0
- package/public/assets/ssh-config-_ykCGR6B.js +1 -0
- package/public/assets/stata-BH5u7GGu.js +1 -0
- package/public/assets/stylus-BEDo0Tqx.js +1 -0
- package/public/assets/svelte-3Dk4HxPD.js +1 -0
- package/public/assets/swift-Dg5xB15N.js +1 -0
- package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/public/assets/system-verilog-CnnmHF94.js +1 -0
- package/public/assets/systemd-4A_iFExJ.js +1 -0
- package/public/assets/talonscript-CkByrt1z.js +1 -0
- package/public/assets/tasl-QIJgUcNo.js +1 -0
- package/public/assets/tcl-dwOrl1Do.js +1 -0
- package/public/assets/templ-W15q3VgB.js +1 -0
- package/public/assets/terraform-BETggiCN.js +1 -0
- package/public/assets/tex-Cppo0RY3.js +1 -0
- package/public/assets/tokyo-night-hegEt444.js +1 -0
- package/public/assets/toml-vGWfd6FD.js +1 -0
- package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/public/assets/tsv-B_m7g4N7.js +1 -0
- package/public/assets/tsx-COt5Ahok.js +1 -0
- package/public/assets/turtle-BsS91CYL.js +1 -0
- package/public/assets/twig-CO9l9SDP.js +1 -0
- package/public/assets/typescript-BPQ3VLAy.js +1 -0
- package/public/assets/typespec-Df68jz8_.js +1 -0
- package/public/assets/typst-DHCkPAjA.js +1 -0
- package/public/assets/v-BcVCzyr7.js +1 -0
- package/public/assets/vala-CsfeWuGM.js +1 -0
- package/public/assets/vb-D17OF-Vu.js +1 -0
- package/public/assets/verilog-BQ8w6xss.js +1 -0
- package/public/assets/vesper-DU1UobuO.js +1 -0
- package/public/assets/vhdl-CeAyd5Ju.js +1 -0
- package/public/assets/viml-CJc9bBzg.js +1 -0
- package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/public/assets/vue-CCoi5OLL.js +1 -0
- package/public/assets/vue-html-DAAvJJDi.js +1 -0
- package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
- package/public/assets/vyper-CDx5xZoG.js +1 -0
- package/public/assets/wasm-CG6Dc4jp.js +1 -0
- package/public/assets/wasm-MzD3tlZU.js +1 -0
- package/public/assets/wenyan-BV7otONQ.js +1 -0
- package/public/assets/wgsl-Dx-B1_4e.js +1 -0
- package/public/assets/wikitext-BhOHFoWU.js +1 -0
- package/public/assets/wit-5i3qLPDT.js +1 -0
- package/public/assets/wolfram-lXgVvXCa.js +1 -0
- package/public/assets/xml-sdJ4AIDG.js +1 -0
- package/public/assets/xsl-CtQFsRM5.js +1 -0
- package/public/assets/yaml-Buea-lGh.js +1 -0
- package/public/assets/zenscript-DVFEvuxE.js +1 -0
- package/public/assets/zig-VOosw3JB.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +38 -0
- package/public/loading.html +28 -0
- package/public/logo.png +0 -0
- package/public/manifest.webmanifest +1 -0
- package/public/maskable-icon-512x512.png +0 -0
- package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- package/public/monaco/vs/base/worker/workerMain.js +31 -0
- package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
- package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
- package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
- package/public/monaco/vs/basic-languages/python/python.js +10 -0
- package/public/monaco/vs/editor/editor.main.css +8 -0
- package/public/monaco/vs/editor/editor.main.js +798 -0
- package/public/monaco/vs/language/css/cssMode.js +13 -0
- package/public/monaco/vs/language/css/cssWorker.js +77 -0
- package/public/monaco/vs/language/html/htmlMode.js +13 -0
- package/public/monaco/vs/language/html/htmlWorker.js +454 -0
- package/public/monaco/vs/language/json/jsonMode.js +19 -0
- package/public/monaco/vs/language/json/jsonWorker.js +42 -0
- package/public/monaco/vs/language/typescript/tsMode.js +20 -0
- package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
- package/public/monaco/vs/loader.js +11 -0
- package/public/monaco.worker.js +7 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-64x64.png +0 -0
- package/public/registerSW.js +1 -0
- package/public/sw.js +1 -0
- package/public/ui-version.json +3 -0
- package/public/workbox-60d14903.js +1 -0
- package/scripts/copy-auth-pages.mjs +22 -0
- package/scripts/copy-opencode-config.mjs +61 -0
- package/scripts/copy-ui-dist.mjs +21 -0
- package/src/api-types.ts +326 -0
- package/src/auth/auth-store.ts +175 -0
- package/src/auth/http-auth.ts +38 -0
- package/src/auth/manager.ts +163 -0
- package/src/auth/password-hash.ts +49 -0
- package/src/auth/session-manager.ts +23 -0
- package/src/auth/token-manager.ts +32 -0
- package/src/background-processes/manager.ts +519 -0
- package/src/bin.ts +29 -0
- package/src/config/binaries.ts +192 -0
- package/src/config/location.ts +78 -0
- package/src/config/schema.ts +104 -0
- package/src/config/store.ts +244 -0
- package/src/events/bus.ts +45 -0
- package/src/filesystem/__tests__/search-cache.test.ts +61 -0
- package/src/filesystem/browser.ts +353 -0
- package/src/filesystem/search-cache.ts +66 -0
- package/src/filesystem/search.ts +184 -0
- package/src/index.ts +540 -0
- package/src/launcher.ts +177 -0
- package/src/loader.ts +21 -0
- package/src/logger.ts +133 -0
- package/src/opencode-config.ts +31 -0
- package/src/plugins/channel.ts +55 -0
- package/src/plugins/handlers.ts +36 -0
- package/src/releases/dev-release-monitor.ts +118 -0
- package/src/releases/release-monitor.ts +149 -0
- package/src/server/http-server.ts +693 -0
- package/src/server/network-addresses.ts +75 -0
- package/src/server/routes/auth-pages/login.html +134 -0
- package/src/server/routes/auth-pages/token.html +93 -0
- package/src/server/routes/auth.ts +164 -0
- package/src/server/routes/background-processes.ts +85 -0
- package/src/server/routes/config.ts +76 -0
- package/src/server/routes/events.ts +61 -0
- package/src/server/routes/filesystem.ts +54 -0
- package/src/server/routes/meta.ts +58 -0
- package/src/server/routes/plugin.ts +75 -0
- package/src/server/routes/storage.ts +66 -0
- package/src/server/routes/workspaces.ts +113 -0
- package/src/server/routes/worktrees.ts +195 -0
- package/src/server/tls.ts +283 -0
- package/src/storage/instance-store.ts +64 -0
- package/src/ui/__tests__/remote-ui.test.ts +58 -0
- package/src/ui/remote-ui.ts +571 -0
- package/src/workspaces/git-worktrees.ts +241 -0
- package/src/workspaces/instance-events.ts +226 -0
- package/src/workspaces/manager.ts +493 -0
- package/src/workspaces/opencode-auth.ts +22 -0
- package/src/workspaces/runtime.ts +428 -0
- package/src/workspaces/worktree-map.ts +129 -0
- 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
|
+
}
|