@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
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# CodeNomad Server
|
|
2
|
+
|
|
3
|
+
**CodeNomad Server** is the high-performance engine behind the CodeNomad cockpit. It transforms your machine into a robust development host, managing the lifecycle of multiple OpenCode instances and providing the low-latency data streams that long-haul builders demand. It bridges your local filesystem with the UI, ensuring that whether you are on localhost or a remote tunnel, you have the speed, clarity, and control of a native workspace.
|
|
4
|
+
|
|
5
|
+
## Features & Capabilities
|
|
6
|
+
|
|
7
|
+
### 🌍 Deployment Freedom
|
|
8
|
+
- **Remote Access**: Host CodeNomad on a powerful workstation and access it from your lightweight laptop.
|
|
9
|
+
- **Code Anywhere**: Tunnel in via VPN or SSH to code securely from coffee shops or while traveling.
|
|
10
|
+
- **Multi-Device**: The responsive web client works on tablets and iPads, turning any screen into a dev terminal.
|
|
11
|
+
- **Always-On**: Run as a background service so your sessions are always ready when you connect.
|
|
12
|
+
|
|
13
|
+
### ⚡️ Workspace Power
|
|
14
|
+
- **Multi-Instance**: Juggle multiple OpenCode sessions side-by-side with per-instance tabs.
|
|
15
|
+
- **Long-Context Native**: Scroll through massive transcripts without hitches.
|
|
16
|
+
- **Deep Task Awareness**: Monitor background tasks and child sessions without losing your flow.
|
|
17
|
+
- **Command Palette**: A single, global palette to jump tabs, launch tools, and fire shortcuts.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
- **OpenCode**: `opencode` must be installed and configured on your system.
|
|
21
|
+
- Node.js 18+ and npm (for running or building from source).
|
|
22
|
+
- A workspace folder on disk you want to serve.
|
|
23
|
+
- Optional: a Chromium-based browser if you want `--launch` to open the UI automatically.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Run via npx (Recommended)
|
|
28
|
+
You can run CodeNomad directly without installing it:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx @neuralnomads/codenomad --launch
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
On startup, CodeNomad prints two URLs:
|
|
35
|
+
|
|
36
|
+
- `Local Connection URL : ...` (used by desktop shells)
|
|
37
|
+
- `Remote Connection URL : ...` (used by browsers/other machines when remote access is enabled)
|
|
38
|
+
|
|
39
|
+
### Install Globally
|
|
40
|
+
Or install it globally to use the `codenomad` command:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm install -g @neuralnomads/codenomad
|
|
44
|
+
codenomad --launch
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Common Flags
|
|
48
|
+
You can configure the server using flags or environment variables:
|
|
49
|
+
|
|
50
|
+
| Flag | Env Variable | Description |
|
|
51
|
+
|------|--------------|-------------|
|
|
52
|
+
| `--https <enabled>` | `CLI_HTTPS` | Enable HTTPS listener (default `true`) |
|
|
53
|
+
| `--http <enabled>` | `CLI_HTTP` | Enable HTTP listener (default `false`) |
|
|
54
|
+
| `--https-port <number>` | `CLI_HTTPS_PORT` | HTTPS port (default `9898`, use `0` for auto) |
|
|
55
|
+
| `--http-port <number>` | `CLI_HTTP_PORT` | HTTP port (default `9899`, use `0` for auto) |
|
|
56
|
+
| `--tls-key <path>` | `CLI_TLS_KEY` | TLS private key (PEM). Requires `--tls-cert`. |
|
|
57
|
+
| `--tls-cert <path>` | `CLI_TLS_CERT` | TLS certificate (PEM). Requires `--tls-key`. |
|
|
58
|
+
| `--tls-ca <path>` | `CLI_TLS_CA` | Optional CA chain/bundle (PEM) |
|
|
59
|
+
| `--tlsSANs <list>` | `CLI_TLS_SANS` | Additional TLS SANs (comma-separated) |
|
|
60
|
+
| `--host <addr>` | `CLI_HOST` | Interface to bind (default 127.0.0.1) |
|
|
61
|
+
| `--workspace-root <path>` | `CLI_WORKSPACE_ROOT` | Default root for new workspaces |
|
|
62
|
+
| `--unrestricted-root` | `CLI_UNRESTRICTED_ROOT` | Allow full-filesystem browsing |
|
|
63
|
+
| `--config <path>` | `CLI_CONFIG` | Config file location |
|
|
64
|
+
| `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser |
|
|
65
|
+
| `--log-level <level>` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) |
|
|
66
|
+
| `--username <username>` | `CODENOMAD_SERVER_USERNAME` | Username for CodeNomad's internal auth (default `codenomad`) |
|
|
67
|
+
| `--password <password>` | `CODENOMAD_SERVER_PASSWORD` | Password for CodeNomad's internal auth |
|
|
68
|
+
| `--generate-token` | `CODENOMAD_GENERATE_TOKEN` | Emit a one-time local bootstrap token for desktop flows |
|
|
69
|
+
| `--dangerously-skip-auth` | `CODENOMAD_SKIP_AUTH` | Disable CodeNomad's internal auth (use only behind a trusted perimeter) |
|
|
70
|
+
|
|
71
|
+
### HTTP vs HTTPS
|
|
72
|
+
|
|
73
|
+
- Default: `--https=true --http=false` (HTTPS only).
|
|
74
|
+
- To run plain HTTP only (useful for development):
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
codenomad --https=false --http=true
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- To run both HTTPS (for remote) and HTTP loopback (for desktop):
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
codenomad --https=true --http=true
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Remote Access Binding Rules
|
|
87
|
+
|
|
88
|
+
- When remote access is enabled (bind host is non-loopback, e.g. `--host 0.0.0.0`):
|
|
89
|
+
- HTTP listens on `127.0.0.1` only.
|
|
90
|
+
- HTTPS listens on `--host` (LAN/all interfaces).
|
|
91
|
+
- When remote access is disabled (bind host is loopback, e.g. `--host 127.0.0.1`):
|
|
92
|
+
- Both HTTP and HTTPS listen on `127.0.0.1`.
|
|
93
|
+
|
|
94
|
+
### Self-Signed Certificates
|
|
95
|
+
|
|
96
|
+
If `--https=true` and you do not provide `--tls-key/--tls-cert`, CodeNomad generates a local certificate automatically under your config directory:
|
|
97
|
+
|
|
98
|
+
- `~/.config/codenomad/tls/ca-cert.pem`
|
|
99
|
+
- `~/.config/codenomad/tls/server-cert.pem`
|
|
100
|
+
|
|
101
|
+
Certificates are valid for about 30 days and rotate automatically on startup when needed. You can add extra SANs via:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
codenomad --tlsSANs "localhost,127.0.0.1,my-hostname,192.168.1.10"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Authentication
|
|
108
|
+
- Default behavior: CodeNomad requires a login (username/password) and stores a session cookie in the browser.
|
|
109
|
+
- `--dangerously-skip-auth` / `CODENOMAD_SKIP_AUTH=true` disables the login prompt and treats all requests as authenticated.
|
|
110
|
+
Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.).
|
|
111
|
+
If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API.
|
|
112
|
+
|
|
113
|
+
### Progressive Web App (PWA)
|
|
114
|
+
When running as a server CodeNomad can also be installed as a PWA from any supported browser, giving you a native app experience just like the Electron installation but executing on the remote server instead.
|
|
115
|
+
|
|
116
|
+
1. Open the CodeNomad UI in a Chromium-based browser (Chrome, Edge, Brave, etc.).
|
|
117
|
+
2. Click the install icon in the address bar, or use the browser menu → "Install CodeNomad".
|
|
118
|
+
3. The app will open in a standalone window and appear in your OS app list.
|
|
119
|
+
|
|
120
|
+
> **TLS requirement**
|
|
121
|
+
> Browsers require a secure (`https://`) connection for PWA installation.
|
|
122
|
+
> If you host CodeNomad on a remote machine, use HTTPS. Self-signed certificates generally won't work unless they are explicitly trusted by the device/browser (e.g., via a custom CA).
|
|
123
|
+
|
|
124
|
+
### Data Storage
|
|
125
|
+
- **Config**: `~/.config/codenomad/config.json`
|
|
126
|
+
- **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WINDOWS_DRIVES_ROOT = "__drives__";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { hashPassword, verifyPassword } from "./password-hash";
|
|
4
|
+
export class AuthStore {
|
|
5
|
+
constructor(authFilePath, logger) {
|
|
6
|
+
this.authFilePath = authFilePath;
|
|
7
|
+
this.logger = logger;
|
|
8
|
+
this.cachedFile = null;
|
|
9
|
+
this.overrideAuth = null;
|
|
10
|
+
this.bootstrapUsername = null;
|
|
11
|
+
}
|
|
12
|
+
getAuthFilePath() {
|
|
13
|
+
return this.authFilePath;
|
|
14
|
+
}
|
|
15
|
+
load() {
|
|
16
|
+
if (this.overrideAuth) {
|
|
17
|
+
return this.overrideAuth;
|
|
18
|
+
}
|
|
19
|
+
if (this.cachedFile) {
|
|
20
|
+
return this.cachedFile;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (!fs.existsSync(this.authFilePath)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const raw = fs.readFileSync(this.authFilePath, "utf-8");
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (!parsed || parsed.version !== 1) {
|
|
29
|
+
this.logger.warn({ authFilePath: this.authFilePath }, "Auth file has unsupported version");
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
this.cachedFile = parsed;
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
this.logger.warn({ err: error, authFilePath: this.authFilePath }, "Failed to load auth file");
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
ensureInitialized(params) {
|
|
41
|
+
const password = params.password?.trim();
|
|
42
|
+
if (password) {
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
const runtime = {
|
|
45
|
+
version: 1,
|
|
46
|
+
username: params.username,
|
|
47
|
+
password: hashPassword(password),
|
|
48
|
+
userProvided: true,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
};
|
|
51
|
+
this.overrideAuth = runtime;
|
|
52
|
+
this.cachedFile = null;
|
|
53
|
+
this.bootstrapUsername = null;
|
|
54
|
+
this.logger.debug({ authFilePath: this.authFilePath }, "Using runtime auth password override; ignoring auth file");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const existing = this.load();
|
|
58
|
+
if (existing) {
|
|
59
|
+
if (existing.username !== params.username) {
|
|
60
|
+
// Keep existing username unless explicitly overridden later.
|
|
61
|
+
this.logger.debug({ existing: existing.username, requested: params.username }, "Auth username differs from requested");
|
|
62
|
+
}
|
|
63
|
+
this.bootstrapUsername = null;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (params.allowBootstrapWithoutPassword) {
|
|
67
|
+
this.bootstrapUsername = params.username;
|
|
68
|
+
this.logger.debug({ authFilePath: this.authFilePath }, "No auth file present; bootstrap-only mode enabled");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`No server password configured. Create ${this.authFilePath} or start with --password / CODENOMAD_SERVER_PASSWORD.`);
|
|
72
|
+
}
|
|
73
|
+
validateCredentials(username, password) {
|
|
74
|
+
const auth = this.load();
|
|
75
|
+
if (!auth) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (username !== auth.username) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return verifyPassword(password, auth.password);
|
|
82
|
+
}
|
|
83
|
+
setPassword(params) {
|
|
84
|
+
if (this.overrideAuth) {
|
|
85
|
+
throw new Error("Server password is provided via CLI/env and cannot be changed while running. Restart without --password / CODENOMAD_SERVER_PASSWORD to use auth.json.");
|
|
86
|
+
}
|
|
87
|
+
const current = this.load();
|
|
88
|
+
if (!current) {
|
|
89
|
+
if (!this.bootstrapUsername) {
|
|
90
|
+
throw new Error("Auth is not initialized");
|
|
91
|
+
}
|
|
92
|
+
const created = {
|
|
93
|
+
version: 1,
|
|
94
|
+
username: this.bootstrapUsername,
|
|
95
|
+
password: hashPassword(params.password),
|
|
96
|
+
userProvided: params.markUserProvided,
|
|
97
|
+
updatedAt: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
this.persist(created);
|
|
100
|
+
this.bootstrapUsername = null;
|
|
101
|
+
return { username: created.username, passwordUserProvided: created.userProvided };
|
|
102
|
+
}
|
|
103
|
+
const next = {
|
|
104
|
+
...current,
|
|
105
|
+
password: hashPassword(params.password),
|
|
106
|
+
userProvided: params.markUserProvided,
|
|
107
|
+
updatedAt: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
this.persist(next);
|
|
110
|
+
return { username: next.username, passwordUserProvided: next.userProvided };
|
|
111
|
+
}
|
|
112
|
+
getStatus() {
|
|
113
|
+
const current = this.load();
|
|
114
|
+
if (current) {
|
|
115
|
+
return { username: current.username, passwordUserProvided: current.userProvided };
|
|
116
|
+
}
|
|
117
|
+
if (this.bootstrapUsername) {
|
|
118
|
+
return { username: this.bootstrapUsername, passwordUserProvided: false };
|
|
119
|
+
}
|
|
120
|
+
throw new Error("Auth is not initialized");
|
|
121
|
+
}
|
|
122
|
+
persist(auth) {
|
|
123
|
+
try {
|
|
124
|
+
fs.mkdirSync(path.dirname(this.authFilePath), { recursive: true });
|
|
125
|
+
fs.writeFileSync(this.authFilePath, JSON.stringify(auth, null, 2), "utf-8");
|
|
126
|
+
this.cachedFile = auth;
|
|
127
|
+
this.logger.debug({ authFilePath: this.authFilePath }, "Persisted auth file");
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
this.logger.error({ err: error, authFilePath: this.authFilePath }, "Failed to persist auth file");
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function parseCookies(header) {
|
|
2
|
+
const result = {};
|
|
3
|
+
if (!header)
|
|
4
|
+
return result;
|
|
5
|
+
const parts = header.split(";");
|
|
6
|
+
for (const part of parts) {
|
|
7
|
+
const index = part.indexOf("=");
|
|
8
|
+
if (index < 0)
|
|
9
|
+
continue;
|
|
10
|
+
const key = part.slice(0, index).trim();
|
|
11
|
+
const value = part.slice(index + 1).trim();
|
|
12
|
+
if (!key)
|
|
13
|
+
continue;
|
|
14
|
+
result[key] = decodeURIComponent(value);
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
export function isLoopbackAddress(remoteAddress) {
|
|
19
|
+
if (!remoteAddress)
|
|
20
|
+
return false;
|
|
21
|
+
if (remoteAddress === "127.0.0.1" || remoteAddress === "::1")
|
|
22
|
+
return true;
|
|
23
|
+
if (remoteAddress === "::ffff:127.0.0.1")
|
|
24
|
+
return true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function wantsHtml(request) {
|
|
28
|
+
const accept = (request.headers["accept"] ?? "").toString().toLowerCase();
|
|
29
|
+
return accept.includes("text/html") || accept.includes("application/xhtml");
|
|
30
|
+
}
|
|
31
|
+
export function sendUnauthorized(request, reply) {
|
|
32
|
+
if (request.method === "GET" && !request.url.startsWith("/api/") && wantsHtml(request)) {
|
|
33
|
+
reply.redirect("/login");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
reply.code(401).send({ error: "Unauthorized" });
|
|
37
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { AuthStore } from "./auth-store";
|
|
3
|
+
import { TokenManager } from "./token-manager";
|
|
4
|
+
import { SessionManager } from "./session-manager";
|
|
5
|
+
import { isLoopbackAddress, parseCookies } from "./http-auth";
|
|
6
|
+
export const BOOTSTRAP_TOKEN_STDOUT_PREFIX = "CODENOMAD_BOOTSTRAP_TOKEN:";
|
|
7
|
+
export const DEFAULT_AUTH_USERNAME = "codenomad";
|
|
8
|
+
export const DEFAULT_AUTH_COOKIE_NAME = "codenomad_session";
|
|
9
|
+
export class AuthManager {
|
|
10
|
+
constructor(init, logger) {
|
|
11
|
+
this.init = init;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.sessionManager = new SessionManager();
|
|
14
|
+
this.cookieName = DEFAULT_AUTH_COOKIE_NAME;
|
|
15
|
+
this.authEnabled = !Boolean(init.dangerouslySkipAuth);
|
|
16
|
+
if (!this.authEnabled) {
|
|
17
|
+
this.authStore = null;
|
|
18
|
+
this.tokenManager = null;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const authFilePath = resolveAuthFilePath(init.configPath);
|
|
22
|
+
this.authStore = new AuthStore(authFilePath, logger.child({ component: "auth" }));
|
|
23
|
+
// Startup: password comes from CLI/env, auth.json, or bootstrap-only mode.
|
|
24
|
+
this.authStore.ensureInitialized({
|
|
25
|
+
username: init.username,
|
|
26
|
+
password: init.password,
|
|
27
|
+
allowBootstrapWithoutPassword: init.generateToken,
|
|
28
|
+
});
|
|
29
|
+
this.tokenManager = init.generateToken ? new TokenManager(60000) : null;
|
|
30
|
+
}
|
|
31
|
+
isAuthEnabled() {
|
|
32
|
+
return this.authEnabled;
|
|
33
|
+
}
|
|
34
|
+
getCookieName() {
|
|
35
|
+
return this.cookieName;
|
|
36
|
+
}
|
|
37
|
+
isTokenBootstrapEnabled() {
|
|
38
|
+
return Boolean(this.tokenManager);
|
|
39
|
+
}
|
|
40
|
+
issueBootstrapToken() {
|
|
41
|
+
if (!this.tokenManager)
|
|
42
|
+
return null;
|
|
43
|
+
return this.tokenManager.generate();
|
|
44
|
+
}
|
|
45
|
+
consumeBootstrapToken(token) {
|
|
46
|
+
if (!this.tokenManager)
|
|
47
|
+
return false;
|
|
48
|
+
return this.tokenManager.consume(token);
|
|
49
|
+
}
|
|
50
|
+
validateLogin(username, password) {
|
|
51
|
+
if (!this.authEnabled) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return this.requireAuthStore().validateCredentials(username, password);
|
|
55
|
+
}
|
|
56
|
+
createSession(username) {
|
|
57
|
+
if (!this.authEnabled) {
|
|
58
|
+
return { id: "auth-disabled", createdAt: Date.now(), username: this.init.username };
|
|
59
|
+
}
|
|
60
|
+
return this.sessionManager.createSession(username);
|
|
61
|
+
}
|
|
62
|
+
getStatus() {
|
|
63
|
+
if (!this.authEnabled) {
|
|
64
|
+
return { username: this.init.username, passwordUserProvided: false };
|
|
65
|
+
}
|
|
66
|
+
return this.requireAuthStore().getStatus();
|
|
67
|
+
}
|
|
68
|
+
setPassword(password) {
|
|
69
|
+
if (!this.authEnabled) {
|
|
70
|
+
throw new Error("Internal authentication is disabled");
|
|
71
|
+
}
|
|
72
|
+
return this.requireAuthStore().setPassword({ password, markUserProvided: true });
|
|
73
|
+
}
|
|
74
|
+
isLoopbackRequest(request) {
|
|
75
|
+
return isLoopbackAddress(request.socket.remoteAddress);
|
|
76
|
+
}
|
|
77
|
+
getSessionFromRequest(request) {
|
|
78
|
+
if (!this.authEnabled) {
|
|
79
|
+
// When auth is disabled, treat all requests as authenticated.
|
|
80
|
+
// We still return a stable username so callers can display it.
|
|
81
|
+
return { username: this.init.username, sessionId: "auth-disabled" };
|
|
82
|
+
}
|
|
83
|
+
const cookies = parseCookies(request.headers.cookie);
|
|
84
|
+
const sessionId = cookies[this.cookieName];
|
|
85
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
86
|
+
if (!session)
|
|
87
|
+
return null;
|
|
88
|
+
return { username: session.username, sessionId: session.id };
|
|
89
|
+
}
|
|
90
|
+
setSessionCookie(reply, sessionId) {
|
|
91
|
+
reply.header("Set-Cookie", buildSessionCookie(this.cookieName, sessionId));
|
|
92
|
+
}
|
|
93
|
+
setSessionCookieWithOptions(reply, sessionId, options) {
|
|
94
|
+
reply.header("Set-Cookie", buildSessionCookie(this.cookieName, sessionId, options));
|
|
95
|
+
}
|
|
96
|
+
clearSessionCookie(reply) {
|
|
97
|
+
reply.header("Set-Cookie", buildSessionCookie(this.cookieName, "", { maxAgeSeconds: 0 }));
|
|
98
|
+
}
|
|
99
|
+
clearSessionCookieWithOptions(reply, options) {
|
|
100
|
+
reply.header("Set-Cookie", buildSessionCookie(this.cookieName, "", { maxAgeSeconds: 0, ...options }));
|
|
101
|
+
}
|
|
102
|
+
requireAuthStore() {
|
|
103
|
+
if (!this.authStore) {
|
|
104
|
+
throw new Error("Auth store is unavailable");
|
|
105
|
+
}
|
|
106
|
+
return this.authStore;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function resolveAuthFilePath(configPath) {
|
|
110
|
+
const resolvedConfigPath = resolvePath(configPath);
|
|
111
|
+
return path.join(path.dirname(resolvedConfigPath), "auth.json");
|
|
112
|
+
}
|
|
113
|
+
function resolvePath(filePath) {
|
|
114
|
+
if (filePath.startsWith("~/")) {
|
|
115
|
+
return path.join(process.env.HOME ?? "", filePath.slice(2));
|
|
116
|
+
}
|
|
117
|
+
return path.resolve(filePath);
|
|
118
|
+
}
|
|
119
|
+
function buildSessionCookie(name, value, options) {
|
|
120
|
+
const parts = [`${name}=${encodeURIComponent(value)}`, "HttpOnly", "Path=/", "SameSite=Lax"];
|
|
121
|
+
if (options?.secure) {
|
|
122
|
+
parts.push("Secure");
|
|
123
|
+
}
|
|
124
|
+
if (options?.maxAgeSeconds !== undefined) {
|
|
125
|
+
parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAgeSeconds))}`);
|
|
126
|
+
}
|
|
127
|
+
return parts.join("; ");
|
|
128
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
const DEFAULT_SCRYPT_PARAMS = {
|
|
3
|
+
N: 16384,
|
|
4
|
+
r: 8,
|
|
5
|
+
p: 1,
|
|
6
|
+
maxmem: 32 * 1024 * 1024,
|
|
7
|
+
};
|
|
8
|
+
export function hashPassword(password) {
|
|
9
|
+
const salt = crypto.randomBytes(16);
|
|
10
|
+
const params = DEFAULT_SCRYPT_PARAMS;
|
|
11
|
+
const keyLength = 64;
|
|
12
|
+
const derived = crypto.scryptSync(password, salt, keyLength, params);
|
|
13
|
+
return {
|
|
14
|
+
algorithm: "scrypt",
|
|
15
|
+
saltBase64: salt.toString("base64"),
|
|
16
|
+
hashBase64: Buffer.from(derived).toString("base64"),
|
|
17
|
+
keyLength,
|
|
18
|
+
params,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function verifyPassword(password, record) {
|
|
22
|
+
if (record.algorithm !== "scrypt") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const salt = Buffer.from(record.saltBase64, "base64");
|
|
26
|
+
const expected = Buffer.from(record.hashBase64, "base64");
|
|
27
|
+
const derived = crypto.scryptSync(password, salt, record.keyLength, record.params);
|
|
28
|
+
if (expected.length !== derived.length) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return crypto.timingSafeEqual(expected, Buffer.from(derived));
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
export class SessionManager {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.sessions = new Map();
|
|
5
|
+
}
|
|
6
|
+
createSession(username) {
|
|
7
|
+
const id = crypto.randomBytes(32).toString("base64url");
|
|
8
|
+
const info = { id, createdAt: Date.now(), username };
|
|
9
|
+
this.sessions.set(id, info);
|
|
10
|
+
return info;
|
|
11
|
+
}
|
|
12
|
+
getSession(id) {
|
|
13
|
+
if (!id)
|
|
14
|
+
return undefined;
|
|
15
|
+
return this.sessions.get(id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
export class TokenManager {
|
|
3
|
+
constructor(ttlMs) {
|
|
4
|
+
this.ttlMs = ttlMs;
|
|
5
|
+
this.token = null;
|
|
6
|
+
}
|
|
7
|
+
generate() {
|
|
8
|
+
const token = crypto.randomBytes(32).toString("base64url");
|
|
9
|
+
this.token = { token, createdAt: Date.now(), consumed: false };
|
|
10
|
+
return token;
|
|
11
|
+
}
|
|
12
|
+
consume(token) {
|
|
13
|
+
if (!this.token)
|
|
14
|
+
return false;
|
|
15
|
+
if (this.token.consumed)
|
|
16
|
+
return false;
|
|
17
|
+
if (Date.now() - this.token.createdAt > this.ttlMs)
|
|
18
|
+
return false;
|
|
19
|
+
if (token !== this.token.token)
|
|
20
|
+
return false;
|
|
21
|
+
this.token.consumed = true;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
peek() {
|
|
25
|
+
return this.token?.token ?? null;
|
|
26
|
+
}
|
|
27
|
+
}
|