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