@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,253 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { tool } from "@opencode-ai/plugin/tool"
|
|
3
|
+
import { createCodeNomadRequester, type CodeNomadConfig } from "./request"
|
|
4
|
+
|
|
5
|
+
type BackgroundProcess = {
|
|
6
|
+
id: string
|
|
7
|
+
title: string
|
|
8
|
+
command: string
|
|
9
|
+
status: "running" | "stopped" | "error"
|
|
10
|
+
startedAt: string
|
|
11
|
+
stoppedAt?: string
|
|
12
|
+
exitCode?: number
|
|
13
|
+
outputSizeBytes?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type BackgroundProcessOptions = {
|
|
17
|
+
baseDir: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ParsedCommand = {
|
|
21
|
+
head: string
|
|
22
|
+
args: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createBackgroundProcessTools(config: CodeNomadConfig, options: BackgroundProcessOptions) {
|
|
26
|
+
const requester = createCodeNomadRequester(config)
|
|
27
|
+
|
|
28
|
+
const request = async <T>(path: string, init?: RequestInit): Promise<T> => {
|
|
29
|
+
return requester.requestJson<T>(`/background-processes${path}`, init)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
run_background_process: tool({
|
|
34
|
+
description:
|
|
35
|
+
"Run a long-lived background process (dev servers, DBs, watchers) so it keeps running while you do other tasks. Use it for running processes that timeout otherwise or produce a lot of output.",
|
|
36
|
+
args: {
|
|
37
|
+
title: tool.schema.string().describe("Short label for the process (e.g. Dev server, DB server)"),
|
|
38
|
+
command: tool.schema.string().describe("Shell command to run in the workspace"),
|
|
39
|
+
},
|
|
40
|
+
async execute(args) {
|
|
41
|
+
assertCommandWithinBase(args.command, options.baseDir)
|
|
42
|
+
const process = await request<BackgroundProcess>("", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body: JSON.stringify({ title: args.title, command: args.command }),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return `Started background process ${process.id} (${process.title})\nStatus: ${process.status}\nCommand: ${process.command}`
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
list_background_processes: tool({
|
|
51
|
+
description: "List background processes running for this workspace.",
|
|
52
|
+
args: {},
|
|
53
|
+
async execute() {
|
|
54
|
+
const response = await request<{ processes: BackgroundProcess[] }>("")
|
|
55
|
+
if (response.processes.length === 0) {
|
|
56
|
+
return "No background processes running."
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return response.processes
|
|
60
|
+
.map((process) => {
|
|
61
|
+
const status = process.status === "running" ? "running" : process.status
|
|
62
|
+
const exit = process.exitCode !== undefined ? ` (exit ${process.exitCode})` : ""
|
|
63
|
+
const size =
|
|
64
|
+
typeof process.outputSizeBytes === "number" ? ` | ${Math.round(process.outputSizeBytes / 1024)}KB` : ""
|
|
65
|
+
return `- ${process.id} | ${process.title} | ${status}${exit}${size}\n ${process.command}`
|
|
66
|
+
})
|
|
67
|
+
.join("\n")
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
read_background_process_output: tool({
|
|
71
|
+
description: "Read output from a background process. Use full, grep, head, or tail.",
|
|
72
|
+
args: {
|
|
73
|
+
id: tool.schema.string().describe("Background process ID"),
|
|
74
|
+
method: tool.schema
|
|
75
|
+
.enum(["full", "grep", "head", "tail"])
|
|
76
|
+
.default("full")
|
|
77
|
+
.describe("Method to read output"),
|
|
78
|
+
pattern: tool.schema.string().optional().describe("Pattern for grep method"),
|
|
79
|
+
lines: tool.schema.number().optional().describe("Number of lines for head/tail methods"),
|
|
80
|
+
},
|
|
81
|
+
async execute(args) {
|
|
82
|
+
if (args.method === "grep" && !args.pattern) {
|
|
83
|
+
return "Pattern is required for grep method."
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const params = new URLSearchParams({ method: args.method })
|
|
87
|
+
if (args.pattern) {
|
|
88
|
+
params.set("pattern", args.pattern)
|
|
89
|
+
}
|
|
90
|
+
if (args.lines) {
|
|
91
|
+
params.set("lines", String(args.lines))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const response = await request<{ id: string; content: string; truncated: boolean; sizeBytes: number }>(
|
|
95
|
+
`/${args.id}/output?${params.toString()}`,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const header = response.truncated
|
|
99
|
+
? `Output (truncated, ${Math.round(response.sizeBytes / 1024)}KB):`
|
|
100
|
+
: `Output (${Math.round(response.sizeBytes / 1024)}KB):`
|
|
101
|
+
|
|
102
|
+
return `${header}\n\n${response.content}`
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
stop_background_process: tool({
|
|
106
|
+
description: "Stop a background process (SIGTERM) but keep its output and entry.",
|
|
107
|
+
args: {
|
|
108
|
+
id: tool.schema.string().describe("Background process ID"),
|
|
109
|
+
},
|
|
110
|
+
async execute(args) {
|
|
111
|
+
const process = await request<BackgroundProcess>(`/${args.id}/stop`, { method: "POST" })
|
|
112
|
+
return `Stopped background process ${process.id} (${process.title}). Status: ${process.status}`
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
terminate_background_process: tool({
|
|
116
|
+
description: "Terminate a background process and delete its output + entry.",
|
|
117
|
+
args: {
|
|
118
|
+
id: tool.schema.string().describe("Background process ID"),
|
|
119
|
+
},
|
|
120
|
+
async execute(args) {
|
|
121
|
+
await request<void>(`/${args.id}/terminate`, { method: "POST" })
|
|
122
|
+
return `Terminated background process ${args.id} and removed its output.`
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const FILE_COMMANDS = new Set(["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"])
|
|
129
|
+
const EXPANSION_CHARS = /[~*$?\[\]`$]/
|
|
130
|
+
|
|
131
|
+
function assertCommandWithinBase(command: string, baseDir: string) {
|
|
132
|
+
const normalizedBase = path.resolve(baseDir)
|
|
133
|
+
const commands = splitCommands(command)
|
|
134
|
+
|
|
135
|
+
for (const item of commands) {
|
|
136
|
+
if (!FILE_COMMANDS.has(item.head)) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const arg of item.args) {
|
|
141
|
+
if (!arg) continue
|
|
142
|
+
if (arg.startsWith("-") || (item.head === "chmod" && arg.startsWith("+"))) continue
|
|
143
|
+
|
|
144
|
+
const literalArg = unquote(arg)
|
|
145
|
+
if (EXPANSION_CHARS.test(literalArg)) {
|
|
146
|
+
throw new Error(`Background process commands may only reference paths within ${normalizedBase}.`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const resolved = path.isAbsolute(literalArg) ? path.normalize(literalArg) : path.resolve(normalizedBase, literalArg)
|
|
150
|
+
if (!isWithinBase(normalizedBase, resolved)) {
|
|
151
|
+
throw new Error(`Background process commands may only reference paths within ${normalizedBase}.`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function splitCommands(command: string): ParsedCommand[] {
|
|
158
|
+
const tokens = tokenize(command)
|
|
159
|
+
const commands: ParsedCommand[] = []
|
|
160
|
+
let current: string[] = []
|
|
161
|
+
|
|
162
|
+
for (const token of tokens) {
|
|
163
|
+
if (isSeparator(token)) {
|
|
164
|
+
if (current.length > 0) {
|
|
165
|
+
commands.push({ head: current[0], args: current.slice(1) })
|
|
166
|
+
current = []
|
|
167
|
+
}
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
current.push(token)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (current.length > 0) {
|
|
174
|
+
commands.push({ head: current[0], args: current.slice(1) })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return commands
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function tokenize(input: string): string[] {
|
|
181
|
+
const tokens: string[] = []
|
|
182
|
+
let current = ""
|
|
183
|
+
let quote: "'" | '"' | null = null
|
|
184
|
+
let escape = false
|
|
185
|
+
|
|
186
|
+
const flush = () => {
|
|
187
|
+
if (current.length > 0) {
|
|
188
|
+
tokens.push(current)
|
|
189
|
+
current = ""
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
194
|
+
const char = input[index]
|
|
195
|
+
|
|
196
|
+
if (escape) {
|
|
197
|
+
current += char
|
|
198
|
+
escape = false
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (char === "\\" && quote !== "'") {
|
|
203
|
+
escape = true
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (quote) {
|
|
208
|
+
current += char
|
|
209
|
+
if (char === quote) {
|
|
210
|
+
quote = null
|
|
211
|
+
}
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (char === "'" || char === '"') {
|
|
216
|
+
quote = char
|
|
217
|
+
current += char
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (char === " " || char === "\n" || char === "\t") {
|
|
222
|
+
flush()
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (char === "|" || char === "&" || char === ";") {
|
|
227
|
+
flush()
|
|
228
|
+
tokens.push(char)
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
current += char
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
flush()
|
|
236
|
+
return tokens
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isSeparator(token: string): boolean {
|
|
240
|
+
return token === "|" || token === "&" || token === ";"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function unquote(token: string): string {
|
|
244
|
+
if ((token.startsWith('"') && token.endsWith('"')) || (token.startsWith("'") && token.endsWith("'"))) {
|
|
245
|
+
return token.slice(1, -1)
|
|
246
|
+
}
|
|
247
|
+
return token
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isWithinBase(base: string, candidate: string): boolean {
|
|
251
|
+
const relative = path.relative(base, candidate)
|
|
252
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
253
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createCodeNomadRequester, type CodeNomadConfig, type PluginEvent } from "./request"
|
|
2
|
+
|
|
3
|
+
export { getCodeNomadConfig, type CodeNomadConfig, type PluginEvent } from "./request"
|
|
4
|
+
|
|
5
|
+
export function createCodeNomadClient(config: CodeNomadConfig) {
|
|
6
|
+
const requester = createCodeNomadRequester(config)
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
postEvent: (event: PluginEvent) =>
|
|
10
|
+
requester.requestVoid("/event", {
|
|
11
|
+
method: "POST",
|
|
12
|
+
body: JSON.stringify(event),
|
|
13
|
+
}),
|
|
14
|
+
startEvents: (onEvent: (event: PluginEvent) => void) => startPluginEvents(requester, onEvent),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function delay(ms: number) {
|
|
19
|
+
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function startPluginEvents(
|
|
23
|
+
requester: ReturnType<typeof createCodeNomadRequester>,
|
|
24
|
+
onEvent: (event: PluginEvent) => void,
|
|
25
|
+
) {
|
|
26
|
+
// Fail plugin startup if we cannot establish the initial connection.
|
|
27
|
+
const initialBody = await connectWithRetries(requester, 3)
|
|
28
|
+
|
|
29
|
+
// After startup, keep reconnecting; throw after 3 consecutive failures.
|
|
30
|
+
void consumeWithReconnect(requester, onEvent, initialBody)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function connectWithRetries(requester: ReturnType<typeof createCodeNomadRequester>, maxAttempts: number) {
|
|
34
|
+
let lastError: unknown
|
|
35
|
+
|
|
36
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
37
|
+
try {
|
|
38
|
+
return await requester.requestSseBody("/events")
|
|
39
|
+
} catch (error) {
|
|
40
|
+
lastError = error
|
|
41
|
+
await delay(500 * attempt)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const reason = lastError instanceof Error ? lastError.message : String(lastError)
|
|
46
|
+
const url = requester.buildUrl("/events")
|
|
47
|
+
throw new Error(`[CodeNomadPlugin] Failed to connect to CodeNomad at ${url} after ${maxAttempts} retries: ${reason}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function consumeWithReconnect(
|
|
51
|
+
requester: ReturnType<typeof createCodeNomadRequester>,
|
|
52
|
+
onEvent: (event: PluginEvent) => void,
|
|
53
|
+
initialBody: ReadableStream<Uint8Array>,
|
|
54
|
+
) {
|
|
55
|
+
let consecutiveFailures = 0
|
|
56
|
+
let body: ReadableStream<Uint8Array> | null = initialBody
|
|
57
|
+
|
|
58
|
+
while (true) {
|
|
59
|
+
try {
|
|
60
|
+
if (!body) {
|
|
61
|
+
body = await connectWithRetries(requester, 3)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await consumeSseBody(body, onEvent)
|
|
65
|
+
body = null
|
|
66
|
+
consecutiveFailures = 0
|
|
67
|
+
} catch (error) {
|
|
68
|
+
body = null
|
|
69
|
+
consecutiveFailures += 1
|
|
70
|
+
if (consecutiveFailures >= 3) {
|
|
71
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
72
|
+
throw new Error(`[CodeNomadPlugin] Plugin event stream failed after 3 retries: ${reason}`)
|
|
73
|
+
}
|
|
74
|
+
await delay(500 * consecutiveFailures)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function consumeSseBody(body: ReadableStream<Uint8Array>, onEvent: (event: PluginEvent) => void) {
|
|
80
|
+
const reader = body.getReader()
|
|
81
|
+
const decoder = new TextDecoder()
|
|
82
|
+
let buffer = ""
|
|
83
|
+
|
|
84
|
+
while (true) {
|
|
85
|
+
const { done, value } = await reader.read()
|
|
86
|
+
if (done || !value) {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
buffer += decoder.decode(value, { stream: true })
|
|
91
|
+
|
|
92
|
+
let separatorIndex = buffer.indexOf("\n\n")
|
|
93
|
+
while (separatorIndex >= 0) {
|
|
94
|
+
const chunk = buffer.slice(0, separatorIndex)
|
|
95
|
+
buffer = buffer.slice(separatorIndex + 2)
|
|
96
|
+
separatorIndex = buffer.indexOf("\n\n")
|
|
97
|
+
|
|
98
|
+
const event = parseSseChunk(chunk)
|
|
99
|
+
if (event) {
|
|
100
|
+
onEvent(event)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error("SSE stream ended")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseSseChunk(chunk: string): PluginEvent | null {
|
|
109
|
+
const lines = chunk.split(/\r?\n/)
|
|
110
|
+
const dataLines: string[] = []
|
|
111
|
+
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
if (line.startsWith(":")) continue
|
|
114
|
+
if (line.startsWith("data:")) {
|
|
115
|
+
dataLines.push(line.slice(5).trimStart())
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (dataLines.length === 0) return null
|
|
120
|
+
|
|
121
|
+
const payload = dataLines.join("\n").trim()
|
|
122
|
+
if (!payload) return null
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(payload)
|
|
126
|
+
if (!parsed || typeof parsed !== "object" || typeof (parsed as any).type !== "string") {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
return parsed as PluginEvent
|
|
130
|
+
} catch {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import http from "http"
|
|
2
|
+
import https from "https"
|
|
3
|
+
import { Readable } from "stream"
|
|
4
|
+
|
|
5
|
+
export type PluginEvent = {
|
|
6
|
+
type: string
|
|
7
|
+
properties?: Record<string, unknown>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type CodeNomadConfig = {
|
|
11
|
+
instanceId: string
|
|
12
|
+
baseUrl: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCodeNomadConfig(): CodeNomadConfig {
|
|
16
|
+
return {
|
|
17
|
+
instanceId: requireEnv("CODENOMAD_INSTANCE_ID"),
|
|
18
|
+
baseUrl: requireEnv("CODENOMAD_BASE_URL"),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createCodeNomadRequester(config: CodeNomadConfig) {
|
|
23
|
+
const rawBaseUrl = (config.baseUrl ?? "").trim()
|
|
24
|
+
const baseUrl = rawBaseUrl.replace(/\/+$/, "")
|
|
25
|
+
const pluginBase = `${baseUrl}/workspaces/${encodeURIComponent(config.instanceId)}/plugin`
|
|
26
|
+
const authorization = buildInstanceAuthorizationHeader()
|
|
27
|
+
|
|
28
|
+
const buildUrl = (path: string) => {
|
|
29
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
30
|
+
return path
|
|
31
|
+
}
|
|
32
|
+
const normalized = path.startsWith("/") ? path : `/${path}`
|
|
33
|
+
return `${pluginBase}${normalized}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const buildHeaders = (headers: HeadersInit | undefined, hasBody: boolean): Record<string, string> => {
|
|
37
|
+
const output: Record<string, string> = normalizeHeaders(headers)
|
|
38
|
+
output.Authorization = authorization
|
|
39
|
+
if (hasBody) {
|
|
40
|
+
output["Content-Type"] = output["Content-Type"] ?? "application/json"
|
|
41
|
+
}
|
|
42
|
+
return output
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fetchWithAuth = async (path: string, init?: RequestInit): Promise<Response> => {
|
|
46
|
+
const url = buildUrl(path)
|
|
47
|
+
const hasBody = init?.body !== undefined
|
|
48
|
+
const headers = buildHeaders(init?.headers, hasBody)
|
|
49
|
+
|
|
50
|
+
// The CodeNomad plugin only talks to the local CodeNomad server.
|
|
51
|
+
// Use a single request implementation that tolerates custom/self-signed certs
|
|
52
|
+
// without disabling TLS verification for the whole Node process.
|
|
53
|
+
return nodeFetch(url, { ...init, headers }, { rejectUnauthorized: false })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const requestJson = async <T>(path: string, init?: RequestInit): Promise<T> => {
|
|
57
|
+
const response = await fetchWithAuth(path, init)
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const message = await response.text().catch(() => "")
|
|
60
|
+
throw new Error(message || `Request failed with ${response.status}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (response.status === 204) {
|
|
64
|
+
return undefined as T
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (await response.json()) as T
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const requestVoid = async (path: string, init?: RequestInit): Promise<void> => {
|
|
71
|
+
const response = await fetchWithAuth(path, init)
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const message = await response.text().catch(() => "")
|
|
74
|
+
throw new Error(message || `Request failed with ${response.status}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const requestSseBody = async (path: string): Promise<ReadableStream<Uint8Array>> => {
|
|
79
|
+
const response = await fetchWithAuth(path, { headers: { Accept: "text/event-stream" } })
|
|
80
|
+
if (!response.ok || !response.body) {
|
|
81
|
+
throw new Error(`SSE unavailable (${response.status})`)
|
|
82
|
+
}
|
|
83
|
+
return response.body as ReadableStream<Uint8Array>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
buildUrl,
|
|
88
|
+
fetch: fetchWithAuth,
|
|
89
|
+
requestJson,
|
|
90
|
+
requestVoid,
|
|
91
|
+
requestSseBody,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function nodeFetch(
|
|
96
|
+
url: string,
|
|
97
|
+
init: RequestInit & { headers?: Record<string, string> },
|
|
98
|
+
tls: { rejectUnauthorized: boolean },
|
|
99
|
+
): Promise<Response> {
|
|
100
|
+
const parsed = new URL(url)
|
|
101
|
+
const isHttps = parsed.protocol === "https:"
|
|
102
|
+
const requestFn = isHttps ? https.request : http.request
|
|
103
|
+
|
|
104
|
+
const method = (init.method ?? "GET").toUpperCase()
|
|
105
|
+
const headers = init.headers ?? {}
|
|
106
|
+
const body = init.body
|
|
107
|
+
|
|
108
|
+
return await new Promise<Response>((resolve, reject) => {
|
|
109
|
+
const req = requestFn(
|
|
110
|
+
{
|
|
111
|
+
protocol: parsed.protocol,
|
|
112
|
+
hostname: parsed.hostname,
|
|
113
|
+
port: parsed.port ? Number(parsed.port) : undefined,
|
|
114
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
115
|
+
method,
|
|
116
|
+
headers,
|
|
117
|
+
...(isHttps ? { rejectUnauthorized: tls.rejectUnauthorized } : {}),
|
|
118
|
+
},
|
|
119
|
+
(res) => {
|
|
120
|
+
const responseHeaders = new Headers()
|
|
121
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
122
|
+
if (value === undefined) continue
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
responseHeaders.set(key, value.join(", "))
|
|
125
|
+
} else {
|
|
126
|
+
responseHeaders.set(key, String(value))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Convert Node stream -> Web ReadableStream for Response.
|
|
131
|
+
const webBody = Readable.toWeb(res) as unknown as ReadableStream<Uint8Array>
|
|
132
|
+
resolve(new Response(webBody, { status: res.statusCode ?? 0, headers: responseHeaders }))
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const signal = init.signal
|
|
137
|
+
const abort = () => {
|
|
138
|
+
const err = new Error("Request aborted")
|
|
139
|
+
;(err as any).name = "AbortError"
|
|
140
|
+
req.destroy(err)
|
|
141
|
+
reject(err)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (signal) {
|
|
145
|
+
if (signal.aborted) {
|
|
146
|
+
abort()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
signal.addEventListener("abort", abort, { once: true })
|
|
150
|
+
req.once("close", () => signal.removeEventListener("abort", abort))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
req.once("error", reject)
|
|
154
|
+
|
|
155
|
+
if (body === undefined || body === null) {
|
|
156
|
+
req.end()
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (typeof body === "string") {
|
|
161
|
+
req.end(body)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (body instanceof Uint8Array) {
|
|
166
|
+
req.end(Buffer.from(body))
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (body instanceof ArrayBuffer) {
|
|
171
|
+
req.end(Buffer.from(new Uint8Array(body)))
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fallback for less common BodyInit types.
|
|
176
|
+
req.end(String(body))
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function requireEnv(key: string): string {
|
|
181
|
+
const value = process.env[key]
|
|
182
|
+
if (!value || !value.trim()) {
|
|
183
|
+
throw new Error(`[CodeNomadPlugin] Missing required env var ${key}`)
|
|
184
|
+
}
|
|
185
|
+
return value
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildInstanceAuthorizationHeader(): string {
|
|
189
|
+
const username = requireEnv("OPENCODE_SERVER_USERNAME")
|
|
190
|
+
const password = requireEnv("OPENCODE_SERVER_PASSWORD")
|
|
191
|
+
const token = Buffer.from(`${username}:${password}`, "utf8").toString("base64")
|
|
192
|
+
return `Basic ${token}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> {
|
|
196
|
+
const output: Record<string, string> = {}
|
|
197
|
+
if (!headers) return output
|
|
198
|
+
|
|
199
|
+
if (headers instanceof Headers) {
|
|
200
|
+
headers.forEach((value, key) => {
|
|
201
|
+
output[key] = value
|
|
202
|
+
})
|
|
203
|
+
return output
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (Array.isArray(headers)) {
|
|
207
|
+
for (const [key, value] of headers) {
|
|
208
|
+
output[key] = value
|
|
209
|
+
}
|
|
210
|
+
return output
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { ...headers }
|
|
214
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# opencode-config
|
|
2
|
+
|
|
3
|
+
## TLDR
|
|
4
|
+
Template config + plugins injected into every OpenCode instance that CodeNomad launches. It provides a CodeNomad bridge plugin for local event exchange between the CLI server and opencode.
|
|
5
|
+
|
|
6
|
+
## What it is
|
|
7
|
+
A packaged config directory that CodeNomad copies into `~/.config/codenomad/opencode-config` for production builds or uses directly in dev. OpenCode autoloads any `plugin/*.ts` or `plugin/*.js` from this directory.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
- CodeNomad sets `OPENCODE_CONFIG_DIR` when spawning each opencode instance (`packages/server/src/workspaces/manager.ts`).
|
|
11
|
+
- This template is synced from `packages/opencode-config` (`packages/server/src/opencode-config.ts`, `packages/server/scripts/copy-opencode-config.mjs`).
|
|
12
|
+
- OpenCode autoloads plugins from `plugin/` (`packages/opencode-config/plugin/codenomad.ts`).
|
|
13
|
+
- The `CodeNomadPlugin` reads `CODENOMAD_INSTANCE_ID` + `CODENOMAD_BASE_URL`, connects to `GET /workspaces/:id/plugin/events`, and posts to `POST /workspaces/:id/plugin/event` (`packages/opencode-config/plugin/lib/client.ts`).
|
|
14
|
+
- The server exposes the plugin routes and maps events into the UI SSE pipeline (`packages/server/src/server/routes/plugin.ts`, `packages/server/src/plugins/handlers.ts`).
|
|
15
|
+
|
|
16
|
+
## Expectations
|
|
17
|
+
- Local-only bridge (no auth/token yet).
|
|
18
|
+
- Plugin must fail startup if it cannot connect after 3 retries.
|
|
19
|
+
- Keep plugin entrypoints thin; put shared logic under `plugin/lib/` to avoid autoloaded helpers.
|
|
20
|
+
- Keep event shapes small and explicit; use `type` + `properties` only.
|
|
21
|
+
|
|
22
|
+
## Ideas
|
|
23
|
+
- Add feature modules under `plugin/lib/features/` (tool lifecycle, permission prompts, custom commands).
|
|
24
|
+
- Expand `/workspaces/:id/plugin/*` with dedicated endpoints as needed.
|
|
25
|
+
- Promote stable event shapes and version tags once the protocol settles.
|
|
26
|
+
|
|
27
|
+
## Pointers
|
|
28
|
+
- Plugin entry: `packages/opencode-config/plugin/codenomad.ts`
|
|
29
|
+
- Plugin client: `packages/opencode-config/plugin/lib/client.ts`
|
|
30
|
+
- Plugin server routes: `packages/server/src/server/routes/plugin.ts`
|
|
31
|
+
- Plugin event handling: `packages/server/src/plugins/handlers.ts`
|
|
32
|
+
- Workspace env injection: `packages/server/src/workspaces/manager.ts`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createCodeNomadClient, getCodeNomadConfig } from "./lib/client"
|
|
2
|
+
import { createBackgroundProcessTools } from "./lib/background-process"
|
|
3
|
+
|
|
4
|
+
export async function CodeNomadPlugin() {
|
|
5
|
+
const config = getCodeNomadConfig()
|
|
6
|
+
const client = createCodeNomadClient(config)
|
|
7
|
+
const backgroundProcessTools = createBackgroundProcessTools(config)
|
|
8
|
+
|
|
9
|
+
await client.startEvents((event) => {
|
|
10
|
+
if (event.type === "codenomad.ping") {
|
|
11
|
+
void client.postEvent({
|
|
12
|
+
type: "codenomad.pong",
|
|
13
|
+
properties: {
|
|
14
|
+
ts: Date.now(),
|
|
15
|
+
pingTs: (event.properties as any)?.ts,
|
|
16
|
+
},
|
|
17
|
+
}).catch(() => {})
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
tool: {
|
|
23
|
+
...backgroundProcessTools,
|
|
24
|
+
},
|
|
25
|
+
async event(input: { event: any }) {
|
|
26
|
+
const opencodeEvent = input?.event
|
|
27
|
+
if (!opencodeEvent || typeof opencodeEvent !== "object") return
|
|
28
|
+
|
|
29
|
+
if (opencodeEvent.type === "session.idle") {
|
|
30
|
+
const sessionID = (opencodeEvent as any).properties?.sessionID
|
|
31
|
+
void client.postEvent({
|
|
32
|
+
type: "opencode.session.idle",
|
|
33
|
+
properties: {
|
|
34
|
+
sessionID,
|
|
35
|
+
},
|
|
36
|
+
}).catch(() => {})
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|