openmausbot 0.1.55
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/LICENSE +202 -0
- package/README.md +14 -0
- package/cli.js +2 -0
- package/dist/app-icon.svg +29 -0
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/actionscript-3-B3316cI-.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/ahk-CsyLZFj1.js +1 -0
- package/dist/assets/ahk2-8Zs4aa1G.js +1 -0
- package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/assets/angular-html-DA-rfuFy.js +1 -0
- package/dist/assets/angular-ts-BrjP3tb8.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-DhZFqWV2.js +1 -0
- package/dist/assets/apl-CORt7UWP.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/asciidoc-CSVQ5wI8.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/astro-HNnZUWAn.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/bat-CickPsom.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/bird2-Bx8U0n9b.js +1 -0
- package/dist/assets/blade-2xfisSek.js +1 -0
- package/dist/assets/bsl-DlhNcFeZ.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/c3-Dp5svz6Z.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
- package/dist/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
- package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
- package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
- package/dist/assets/chapel-DTp_pixX.js +1 -0
- package/dist/assets/clarity-Dn5IMItf.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cobol-nBiQ_Alo.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/coq-C7JzOVbR.js +1 -0
- package/dist/assets/cpp-BMRokrvK.js +1 -0
- package/dist/assets/crystal-DGywbUpC.js +1 -0
- package/dist/assets/csharp-DSvCPggb.js +1 -0
- package/dist/assets/css-CLj8gQPS.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dart-bE4Kk8sk.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/edge-FbVlp4U3.js +1 -0
- package/dist/assets/elixir-CkH2-t6x.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/emacs-lisp-C_m_b--Z.js +1 -0
- package/dist/assets/erb-DXfck5VN.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/gdresource-TyuKm33G.js +1 -0
- package/dist/assets/gdscript-DqcFQ5yU.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-ByusRIyA.js +1 -0
- package/dist/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-C27-OAKa.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-BWmVpMyf.js +1 -0
- package/dist/assets/haml-D5jkg6IW.js +1 -0
- package/dist/assets/handlebars-BpdQsYii.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CfZj7gIn.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/html-derivative-DlHx6ybY.js +1 -0
- package/dist/assets/html-pp8916En.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-2-FPmUDs.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-B9rLIY81.js +263 -0
- package/dist/assets/index-CJBrtDbC.css +1 -0
- package/dist/assets/index-D-u0LYsV.js +153 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-f2NsQr07.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-5Bft2YPA.js +1 -0
- package/dist/assets/just-Cwhn7H3k.js +1 -0
- package/dist/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
- package/dist/assets/kanagawa-lotus-C3DzagqV.js +1 -0
- package/dist/assets/kanagawa-wave-DFGoQZhC.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-wEQ09or8.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/latex-D5pSuvFb.js +1 -0
- package/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/liquid-C0sCDyMI.js +1 -0
- package/dist/assets/llvm-BZoOZj88.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/assets/luau-BnpPk5vE.js +1 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/marko-DjSrsDqO.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/mdc-D1_yUvq7.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-CQcHuHx7.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mojo-DJz3ZmWd.js +1 -0
- package/dist/assets/monokai-BRVnQi9A.js +1 -0
- package/dist/assets/moonbit-CHtswR0a.js +1 -0
- package/dist/assets/move-el3G9tDJ.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/nextflow-C-mBbutL.js +1 -0
- package/dist/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/assets/nim-BIad80T-.js +1 -0
- package/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-BlV79W_Q.js +1 -0
- package/dist/assets/nushell-D3jzshHO.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/org-DM6o9KBp.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/perl-B9cMNwum.js +1 -0
- package/dist/assets/php-Csjmro_R.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powershell-BmBUJMz7.js +1 -0
- package/dist/assets/prisma-Vru482bI.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/assets/pug-DKIMFp6K.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-Cf5RLm7j.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-BjBPvh-w.js +1 -0
- package/dist/assets/rbs-CpoqiR4B.js +1 -0
- package/dist/assets/red-hvxz__6c.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/assets/rose-pine-5qJOZa0Y.js +1 -0
- package/dist/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
- package/dist/assets/rose-pine-moon-C9pEdX9L.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rst-bs7f0vWN.js +1 -0
- package/dist/assets/ruby-C0TQ7zu5.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/sas-DEy46yEz.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/scala-CqE71os6.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scss-D5BDwBP9.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-BOQMe2GC.js +1 -0
- package/dist/assets/smithy-cds9vsN8.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-CpvCGNkr.js +1 -0
- package/dist/assets/solarized-light-Dlz6yCKv.js +1 -0
- package/dist/assets/solidity-DijEV5ha.js +1 -0
- package/dist/assets/soy-8wufbnw4.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/sql-CRqJ_cUM.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-DI20mbqo.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/surrealql-Cjom0U5J.js +1 -0
- package/dist/assets/svelte-Cy7k_4gC.js +1 -0
- package/dist/assets/swift-C2oV4EkX.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/system-verilog-0hqHdDBg.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-DhtptRzy.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/tex-D96PA37w.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/ts-tags-D351s5mN.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/twig-27uCiNez.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typespec-CAFt9gP4.js +1 -0
- package/dist/assets/typst-BUadGCkm.js +1 -0
- package/dist/assets/v-BGw2Nkan.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vb-Cu-pLBUe.js +1 -0
- package/dist/assets/verilog-nZwndyjY.js +1 -0
- package/dist/assets/vesper-DRje8inN.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vue-BqiEGhQt.js +1 -0
- package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/assets/vue-vine-BoDAl6tE.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/index.html +14 -0
- package/dist-server/companion/src/listener.js +187 -0
- package/dist-server/computer-proxy.js +1185 -0
- package/dist-server/connector-proxy.js +186 -0
- package/dist-server/container-mcp.js +15186 -0
- package/dist-server/drivers/agents-proxy.js +887 -0
- package/dist-server/drivers/browser-proxy.js +15037 -0
- package/dist-server/drivers/dweb-proxy.js +175 -0
- package/dist-server/drivers/phone-proxy.js +339 -0
- package/dist-server/drivers/pi-mcp-extension.ts +638 -0
- package/dist-server/index.js +64869 -0
- package/dist-server/local-computer.js +198 -0
- package/dist-server/mcp-server.js +1233 -0
- package/dist-server/openmausbot.js +3671 -0
- package/dist-server/pair-cli.js +3671 -0
- package/dist-server/permission-proxy.js +126 -0
- package/dist-server/prepare-cloudflared.js +253 -0
- package/dist-server/proxy-paths.js +29 -0
- package/dist-server/server/atomic.js +40 -0
- package/dist-server/server/attachments.js +471 -0
- package/dist-server/server/auto-approve.js +162 -0
- package/dist-server/server/auto-review.js +79 -0
- package/dist-server/server/avatar-image.js +144 -0
- package/dist-server/server/bot-cwd.js +29 -0
- package/dist-server/server/bot-directory.js +127 -0
- package/dist-server/server/bot-package.js +268 -0
- package/dist-server/server/bot-profile.js +76 -0
- package/dist-server/server/box-create-idempotency.js +463 -0
- package/dist-server/server/box.js +944 -0
- package/dist-server/server/brand.js +65 -0
- package/dist-server/server/browser-connection.js +187 -0
- package/dist-server/server/browser-lifecycle-cleanup.js +359 -0
- package/dist-server/server/calendar-calls.js +314 -0
- package/dist-server/server/channel-queue.js +70 -0
- package/dist-server/server/checkpoints.js +405 -0
- package/dist-server/server/chief-of-staff.js +49 -0
- package/dist-server/server/claude-update.js +70 -0
- package/dist-server/server/cli.js +524 -0
- package/dist-server/server/cloud-backend.js +30 -0
- package/dist-server/server/comms-visibility.js +109 -0
- package/dist-server/server/composio.js +945 -0
- package/dist-server/server/computer-control.js +149 -0
- package/dist-server/server/computer-observation.js +133 -0
- package/dist-server/server/computer-proxy.js +1061 -0
- package/dist-server/server/config.js +760 -0
- package/dist-server/server/connector-proxy.js +228 -0
- package/dist-server/server/container-computer.js +840 -0
- package/dist-server/server/container-mcp.js +27 -0
- package/dist-server/server/contracts.js +24 -0
- package/dist-server/server/control-client.js +100 -0
- package/dist-server/server/data-dir-lease.js +3 -0
- package/dist-server/server/decision-log.js +106 -0
- package/dist-server/server/delegations.js +628 -0
- package/dist-server/server/drivers/acp/core.js +904 -0
- package/dist-server/server/drivers/acp/cursor.js +365 -0
- package/dist-server/server/drivers/acp/custom.js +51 -0
- package/dist-server/server/drivers/acp/droid.js +225 -0
- package/dist-server/server/drivers/acp/gemini.js +95 -0
- package/dist-server/server/drivers/acp/grok.js +249 -0
- package/dist-server/server/drivers/acp/hermes.js +429 -0
- package/dist-server/server/drivers/acp/kimi.js +559 -0
- package/dist-server/server/drivers/acp/opencode-go.js +370 -0
- package/dist-server/server/drivers/acp/qwen.js +98 -0
- package/dist-server/server/drivers/agents-proxy.js +941 -0
- package/dist-server/server/drivers/antigravity-acp.js +507 -0
- package/dist-server/server/drivers/antigravity-release.js +65 -0
- package/dist-server/server/drivers/antigravity-runtime.js +395 -0
- package/dist-server/server/drivers/antigravity.js +144 -0
- package/dist-server/server/drivers/boxagent.js +245 -0
- package/dist-server/server/drivers/browser-proxy.js +520 -0
- package/dist-server/server/drivers/builtIn.js +36 -0
- package/dist-server/server/drivers/claude.js +1337 -0
- package/dist-server/server/drivers/codex-catalog.js +409 -0
- package/dist-server/server/drivers/codex.js +1116 -0
- package/dist-server/server/drivers/dweb-proxy.js +193 -0
- package/dist-server/server/drivers/grok.js +48 -0
- package/dist-server/server/drivers/grokagent.js +4 -0
- package/dist-server/server/drivers/local-inject.js +388 -0
- package/dist-server/server/drivers/minimax.js +108 -0
- package/dist-server/server/drivers/native.js +21 -0
- package/dist-server/server/drivers/openai-chat.js +240 -0
- package/dist-server/server/drivers/openai-compat.js +143 -0
- package/dist-server/server/drivers/phone-proxy.js +372 -0
- package/dist-server/server/drivers/pi.js +855 -0
- package/dist-server/server/drivers/retry.js +96 -0
- package/dist-server/server/enterprise.js +93 -0
- package/dist-server/server/env-path.js +329 -0
- package/dist-server/server/environment.js +105 -0
- package/dist-server/server/generated-image.js +39 -0
- package/dist-server/server/graceful-shutdown.js +24 -0
- package/dist-server/server/group-goal-run.js +124 -0
- package/dist-server/server/harness/bus.js +86 -0
- package/dist-server/server/harness/registry.js +217 -0
- package/dist-server/server/index.js +11634 -0
- package/dist-server/server/installed-playbooks.js +34 -0
- package/dist-server/server/local-computer.js +278 -0
- package/dist-server/server/local-routing.js +9 -0
- package/dist-server/server/local-vm-idle.js +44 -0
- package/dist-server/server/local-vm-inventory.js +34 -0
- package/dist-server/server/local-vm-lease.js +58 -0
- package/dist-server/server/mcp-bridge.js +251 -0
- package/dist-server/server/mcp-probe.js +153 -0
- package/dist-server/server/mcp-registry.js +130 -0
- package/dist-server/server/member-turn.js +6 -0
- package/dist-server/server/message-db.js +358 -0
- package/dist-server/server/message-file.js +439 -0
- package/dist-server/server/names.js +26 -0
- package/dist-server/server/notify.js +69 -0
- package/dist-server/server/openmaus-status-capsule.js +329 -0
- package/dist-server/server/openmausbot.js +6 -0
- package/dist-server/server/package-export.js +157 -0
- package/dist-server/server/pair-cli.js +7 -0
- package/dist-server/server/peer-approval-key.js +5 -0
- package/dist-server/server/peer-approval.js +182 -0
- package/dist-server/server/peer-provenance.js +44 -0
- package/dist-server/server/peer-roster.js +139 -0
- package/dist-server/server/permission-proxy.js +157 -0
- package/dist-server/server/phone-secret.js +358 -0
- package/dist-server/server/private-screen-capture.js +17 -0
- package/dist-server/server/procs.js +123 -0
- package/dist-server/server/project-scout.js +304 -0
- package/dist-server/server/proxy-paths.js +47 -0
- package/dist-server/server/redact.js +102 -0
- package/dist-server/server/remote-computer.js +170 -0
- package/dist-server/server/repeat-detector.js +48 -0
- package/dist-server/server/replies.js +38 -0
- package/dist-server/server/request-auth.js +327 -0
- package/dist-server/server/room-cwd.js +15 -0
- package/dist-server/server/room-post-budget.js +112 -0
- package/dist-server/server/room-turn-timeout.js +115 -0
- package/dist-server/server/routine-requests.js +871 -0
- package/dist-server/server/routines.js +1111 -0
- package/dist-server/server/schema.js +11 -0
- package/dist-server/server/screen-frame-gate.js +80 -0
- package/dist-server/server/section-context.js +85 -0
- package/dist-server/server/send-idempotency.js +53 -0
- package/dist-server/server/sessions.js +319 -0
- package/dist-server/server/skill-fetch.js +148 -0
- package/dist-server/server/skill-learn.js +61 -0
- package/dist-server/server/skill-library.js +116 -0
- package/dist-server/server/skills.js +1223 -0
- package/dist-server/server/steer-queue.js +114 -0
- package/dist-server/server/store.js +1217 -0
- package/dist-server/server/surface.js +107 -0
- package/dist-server/server/tailscale.js +103 -0
- package/dist-server/server/team-backup.js +183 -0
- package/dist-server/server/team-library.js +205 -0
- package/dist-server/server/team-manifest.js +224 -0
- package/dist-server/server/thread-events.js +226 -0
- package/dist-server/server/tts/elevenlabs.js +92 -0
- package/dist-server/server/tts/index.js +79 -0
- package/dist-server/server/tts/speech-text.js +195 -0
- package/dist-server/server/tts/system-voices.js +74 -0
- package/dist-server/server/tunnel.js +202 -0
- package/dist-server/server/turn-context.js +40 -0
- package/dist-server/server/turn-dispatch-guard.js +152 -0
- package/dist-server/server/turn-images.js +138 -0
- package/dist-server/server/turn-watchdog.js +65 -0
- package/dist-server/server/vps-computer.js +1099 -0
- package/dist-server/server/vps-container-mcp.js +31 -0
- package/dist-server/server/webhook-ingress.js +173 -0
- package/dist-server/server/webhooks.js +491 -0
- package/dist-server/server/workspace.js +178 -0
- package/dist-server/shared/approval-mode.js +39 -0
- package/dist-server/shared/bot-avatar.js +33 -0
- package/dist-server/shared/bot-profile.js +7 -0
- package/dist-server/shared/connector-availability.js +12 -0
- package/dist-server/shared/credential-request.js +82 -0
- package/dist-server/shared/group-goal-run.js +1 -0
- package/dist-server/shared/import-name.js +11 -0
- package/dist-server/shared/mascot-bodies.js +107 -0
- package/dist-server/shared/routine-request.js +10 -0
- package/dist-server/shared/routine-run.js +1 -0
- package/dist-server/shared/skill-request.js +16 -0
- package/dist-server/shared/team-backup.js +148 -0
- package/dist-server/src/lib/composer-attachments.js +676 -0
- package/dist-server/tunnel-guardian.js +390 -0
- package/dist-server/vps-container-mcp.js +15209 -0
- package/package.json +32 -0
- package/skills/create-verification-skill/SKILL.md +65 -0
- package/skills/create-verification-skill/manifest.json +13 -0
- package/skills/phone-harness/SKILL.md +26 -0
- package/skills/phone-harness/agents/openai.yaml +13 -0
- package/skills/phone-harness/manifest.json +17 -0
|
@@ -0,0 +1,3671 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
try {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
} catch (e) {
|
|
11
|
+
throw mod = 0, e;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
|
|
32
|
+
var require_QRMode = __commonJS({
|
|
33
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMode.js"(exports, module) {
|
|
34
|
+
module.exports = {
|
|
35
|
+
MODE_NUMBER: 1 << 0,
|
|
36
|
+
MODE_ALPHA_NUM: 1 << 1,
|
|
37
|
+
MODE_8BIT_BYTE: 1 << 2,
|
|
38
|
+
MODE_KANJI: 1 << 3
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QR8bitByte.js
|
|
44
|
+
var require_QR8bitByte = __commonJS({
|
|
45
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QR8bitByte.js"(exports, module) {
|
|
46
|
+
var QRMode = require_QRMode();
|
|
47
|
+
function QR8bitByte(data) {
|
|
48
|
+
this.mode = QRMode.MODE_8BIT_BYTE;
|
|
49
|
+
this.data = data;
|
|
50
|
+
}
|
|
51
|
+
QR8bitByte.prototype = {
|
|
52
|
+
getLength: function() {
|
|
53
|
+
return this.data.length;
|
|
54
|
+
},
|
|
55
|
+
write: function(buffer) {
|
|
56
|
+
for (var i = 0; i < this.data.length; i++) {
|
|
57
|
+
buffer.put(this.data.charCodeAt(i), 8);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
module.exports = QR8bitByte;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMath.js
|
|
66
|
+
var require_QRMath = __commonJS({
|
|
67
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMath.js"(exports, module) {
|
|
68
|
+
var QRMath = {
|
|
69
|
+
glog: function(n) {
|
|
70
|
+
if (n < 1) {
|
|
71
|
+
throw new Error("glog(" + n + ")");
|
|
72
|
+
}
|
|
73
|
+
return QRMath.LOG_TABLE[n];
|
|
74
|
+
},
|
|
75
|
+
gexp: function(n) {
|
|
76
|
+
while (n < 0) {
|
|
77
|
+
n += 255;
|
|
78
|
+
}
|
|
79
|
+
while (n >= 256) {
|
|
80
|
+
n -= 255;
|
|
81
|
+
}
|
|
82
|
+
return QRMath.EXP_TABLE[n];
|
|
83
|
+
},
|
|
84
|
+
EXP_TABLE: new Array(256),
|
|
85
|
+
LOG_TABLE: new Array(256)
|
|
86
|
+
};
|
|
87
|
+
for (i = 0; i < 8; i++) {
|
|
88
|
+
QRMath.EXP_TABLE[i] = 1 << i;
|
|
89
|
+
}
|
|
90
|
+
var i;
|
|
91
|
+
for (i = 8; i < 256; i++) {
|
|
92
|
+
QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8];
|
|
93
|
+
}
|
|
94
|
+
var i;
|
|
95
|
+
for (i = 0; i < 255; i++) {
|
|
96
|
+
QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
|
|
97
|
+
}
|
|
98
|
+
var i;
|
|
99
|
+
module.exports = QRMath;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRPolynomial.js
|
|
104
|
+
var require_QRPolynomial = __commonJS({
|
|
105
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRPolynomial.js"(exports, module) {
|
|
106
|
+
var QRMath = require_QRMath();
|
|
107
|
+
function QRPolynomial(num, shift) {
|
|
108
|
+
if (num.length === void 0) {
|
|
109
|
+
throw new Error(num.length + "/" + shift);
|
|
110
|
+
}
|
|
111
|
+
var offset = 0;
|
|
112
|
+
while (offset < num.length && num[offset] === 0) {
|
|
113
|
+
offset++;
|
|
114
|
+
}
|
|
115
|
+
this.num = new Array(num.length - offset + shift);
|
|
116
|
+
for (var i = 0; i < num.length - offset; i++) {
|
|
117
|
+
this.num[i] = num[i + offset];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
QRPolynomial.prototype = {
|
|
121
|
+
get: function(index) {
|
|
122
|
+
return this.num[index];
|
|
123
|
+
},
|
|
124
|
+
getLength: function() {
|
|
125
|
+
return this.num.length;
|
|
126
|
+
},
|
|
127
|
+
multiply: function(e) {
|
|
128
|
+
var num = new Array(this.getLength() + e.getLength() - 1);
|
|
129
|
+
for (var i = 0; i < this.getLength(); i++) {
|
|
130
|
+
for (var j = 0; j < e.getLength(); j++) {
|
|
131
|
+
num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return new QRPolynomial(num, 0);
|
|
135
|
+
},
|
|
136
|
+
mod: function(e) {
|
|
137
|
+
if (this.getLength() - e.getLength() < 0) {
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0));
|
|
141
|
+
var num = new Array(this.getLength());
|
|
142
|
+
for (var i = 0; i < this.getLength(); i++) {
|
|
143
|
+
num[i] = this.get(i);
|
|
144
|
+
}
|
|
145
|
+
for (var x = 0; x < e.getLength(); x++) {
|
|
146
|
+
num[x] ^= QRMath.gexp(QRMath.glog(e.get(x)) + ratio);
|
|
147
|
+
}
|
|
148
|
+
return new QRPolynomial(num, 0).mod(e);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
module.exports = QRPolynomial;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMaskPattern.js
|
|
156
|
+
var require_QRMaskPattern = __commonJS({
|
|
157
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMaskPattern.js"(exports, module) {
|
|
158
|
+
module.exports = {
|
|
159
|
+
PATTERN000: 0,
|
|
160
|
+
PATTERN001: 1,
|
|
161
|
+
PATTERN010: 2,
|
|
162
|
+
PATTERN011: 3,
|
|
163
|
+
PATTERN100: 4,
|
|
164
|
+
PATTERN101: 5,
|
|
165
|
+
PATTERN110: 6,
|
|
166
|
+
PATTERN111: 7
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRUtil.js
|
|
172
|
+
var require_QRUtil = __commonJS({
|
|
173
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRUtil.js"(exports, module) {
|
|
174
|
+
var QRMode = require_QRMode();
|
|
175
|
+
var QRPolynomial = require_QRPolynomial();
|
|
176
|
+
var QRMath = require_QRMath();
|
|
177
|
+
var QRMaskPattern = require_QRMaskPattern();
|
|
178
|
+
var QRUtil = {
|
|
179
|
+
PATTERN_POSITION_TABLE: [
|
|
180
|
+
[],
|
|
181
|
+
[6, 18],
|
|
182
|
+
[6, 22],
|
|
183
|
+
[6, 26],
|
|
184
|
+
[6, 30],
|
|
185
|
+
[6, 34],
|
|
186
|
+
[6, 22, 38],
|
|
187
|
+
[6, 24, 42],
|
|
188
|
+
[6, 26, 46],
|
|
189
|
+
[6, 28, 50],
|
|
190
|
+
[6, 30, 54],
|
|
191
|
+
[6, 32, 58],
|
|
192
|
+
[6, 34, 62],
|
|
193
|
+
[6, 26, 46, 66],
|
|
194
|
+
[6, 26, 48, 70],
|
|
195
|
+
[6, 26, 50, 74],
|
|
196
|
+
[6, 30, 54, 78],
|
|
197
|
+
[6, 30, 56, 82],
|
|
198
|
+
[6, 30, 58, 86],
|
|
199
|
+
[6, 34, 62, 90],
|
|
200
|
+
[6, 28, 50, 72, 94],
|
|
201
|
+
[6, 26, 50, 74, 98],
|
|
202
|
+
[6, 30, 54, 78, 102],
|
|
203
|
+
[6, 28, 54, 80, 106],
|
|
204
|
+
[6, 32, 58, 84, 110],
|
|
205
|
+
[6, 30, 58, 86, 114],
|
|
206
|
+
[6, 34, 62, 90, 118],
|
|
207
|
+
[6, 26, 50, 74, 98, 122],
|
|
208
|
+
[6, 30, 54, 78, 102, 126],
|
|
209
|
+
[6, 26, 52, 78, 104, 130],
|
|
210
|
+
[6, 30, 56, 82, 108, 134],
|
|
211
|
+
[6, 34, 60, 86, 112, 138],
|
|
212
|
+
[6, 30, 58, 86, 114, 142],
|
|
213
|
+
[6, 34, 62, 90, 118, 146],
|
|
214
|
+
[6, 30, 54, 78, 102, 126, 150],
|
|
215
|
+
[6, 24, 50, 76, 102, 128, 154],
|
|
216
|
+
[6, 28, 54, 80, 106, 132, 158],
|
|
217
|
+
[6, 32, 58, 84, 110, 136, 162],
|
|
218
|
+
[6, 26, 54, 82, 110, 138, 166],
|
|
219
|
+
[6, 30, 58, 86, 114, 142, 170]
|
|
220
|
+
],
|
|
221
|
+
G15: 1 << 10 | 1 << 8 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 1 | 1 << 0,
|
|
222
|
+
G18: 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0,
|
|
223
|
+
G15_MASK: 1 << 14 | 1 << 12 | 1 << 10 | 1 << 4 | 1 << 1,
|
|
224
|
+
getBCHTypeInfo: function(data) {
|
|
225
|
+
var d = data << 10;
|
|
226
|
+
while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
|
|
227
|
+
d ^= QRUtil.G15 << QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15);
|
|
228
|
+
}
|
|
229
|
+
return (data << 10 | d) ^ QRUtil.G15_MASK;
|
|
230
|
+
},
|
|
231
|
+
getBCHTypeNumber: function(data) {
|
|
232
|
+
var d = data << 12;
|
|
233
|
+
while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
|
|
234
|
+
d ^= QRUtil.G18 << QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18);
|
|
235
|
+
}
|
|
236
|
+
return data << 12 | d;
|
|
237
|
+
},
|
|
238
|
+
getBCHDigit: function(data) {
|
|
239
|
+
var digit = 0;
|
|
240
|
+
while (data !== 0) {
|
|
241
|
+
digit++;
|
|
242
|
+
data >>>= 1;
|
|
243
|
+
}
|
|
244
|
+
return digit;
|
|
245
|
+
},
|
|
246
|
+
getPatternPosition: function(typeNumber) {
|
|
247
|
+
return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
|
|
248
|
+
},
|
|
249
|
+
getMask: function(maskPattern, i, j) {
|
|
250
|
+
switch (maskPattern) {
|
|
251
|
+
case QRMaskPattern.PATTERN000:
|
|
252
|
+
return (i + j) % 2 === 0;
|
|
253
|
+
case QRMaskPattern.PATTERN001:
|
|
254
|
+
return i % 2 === 0;
|
|
255
|
+
case QRMaskPattern.PATTERN010:
|
|
256
|
+
return j % 3 === 0;
|
|
257
|
+
case QRMaskPattern.PATTERN011:
|
|
258
|
+
return (i + j) % 3 === 0;
|
|
259
|
+
case QRMaskPattern.PATTERN100:
|
|
260
|
+
return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0;
|
|
261
|
+
case QRMaskPattern.PATTERN101:
|
|
262
|
+
return i * j % 2 + i * j % 3 === 0;
|
|
263
|
+
case QRMaskPattern.PATTERN110:
|
|
264
|
+
return (i * j % 2 + i * j % 3) % 2 === 0;
|
|
265
|
+
case QRMaskPattern.PATTERN111:
|
|
266
|
+
return (i * j % 3 + (i + j) % 2) % 2 === 0;
|
|
267
|
+
default:
|
|
268
|
+
throw new Error("bad maskPattern:" + maskPattern);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
getErrorCorrectPolynomial: function(errorCorrectLength) {
|
|
272
|
+
var a = new QRPolynomial([1], 0);
|
|
273
|
+
for (var i = 0; i < errorCorrectLength; i++) {
|
|
274
|
+
a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
|
|
275
|
+
}
|
|
276
|
+
return a;
|
|
277
|
+
},
|
|
278
|
+
getLengthInBits: function(mode, type) {
|
|
279
|
+
if (1 <= type && type < 10) {
|
|
280
|
+
switch (mode) {
|
|
281
|
+
case QRMode.MODE_NUMBER:
|
|
282
|
+
return 10;
|
|
283
|
+
case QRMode.MODE_ALPHA_NUM:
|
|
284
|
+
return 9;
|
|
285
|
+
case QRMode.MODE_8BIT_BYTE:
|
|
286
|
+
return 8;
|
|
287
|
+
case QRMode.MODE_KANJI:
|
|
288
|
+
return 8;
|
|
289
|
+
default:
|
|
290
|
+
throw new Error("mode:" + mode);
|
|
291
|
+
}
|
|
292
|
+
} else if (type < 27) {
|
|
293
|
+
switch (mode) {
|
|
294
|
+
case QRMode.MODE_NUMBER:
|
|
295
|
+
return 12;
|
|
296
|
+
case QRMode.MODE_ALPHA_NUM:
|
|
297
|
+
return 11;
|
|
298
|
+
case QRMode.MODE_8BIT_BYTE:
|
|
299
|
+
return 16;
|
|
300
|
+
case QRMode.MODE_KANJI:
|
|
301
|
+
return 10;
|
|
302
|
+
default:
|
|
303
|
+
throw new Error("mode:" + mode);
|
|
304
|
+
}
|
|
305
|
+
} else if (type < 41) {
|
|
306
|
+
switch (mode) {
|
|
307
|
+
case QRMode.MODE_NUMBER:
|
|
308
|
+
return 14;
|
|
309
|
+
case QRMode.MODE_ALPHA_NUM:
|
|
310
|
+
return 13;
|
|
311
|
+
case QRMode.MODE_8BIT_BYTE:
|
|
312
|
+
return 16;
|
|
313
|
+
case QRMode.MODE_KANJI:
|
|
314
|
+
return 12;
|
|
315
|
+
default:
|
|
316
|
+
throw new Error("mode:" + mode);
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
throw new Error("type:" + type);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
getLostPoint: function(qrCode) {
|
|
323
|
+
var moduleCount = qrCode.getModuleCount();
|
|
324
|
+
var lostPoint = 0;
|
|
325
|
+
var row = 0;
|
|
326
|
+
var col = 0;
|
|
327
|
+
for (row = 0; row < moduleCount; row++) {
|
|
328
|
+
for (col = 0; col < moduleCount; col++) {
|
|
329
|
+
var sameCount = 0;
|
|
330
|
+
var dark = qrCode.isDark(row, col);
|
|
331
|
+
for (var r = -1; r <= 1; r++) {
|
|
332
|
+
if (row + r < 0 || moduleCount <= row + r) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
for (var c = -1; c <= 1; c++) {
|
|
336
|
+
if (col + c < 0 || moduleCount <= col + c) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (r === 0 && c === 0) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (dark === qrCode.isDark(row + r, col + c)) {
|
|
343
|
+
sameCount++;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (sameCount > 5) {
|
|
348
|
+
lostPoint += 3 + sameCount - 5;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (row = 0; row < moduleCount - 1; row++) {
|
|
353
|
+
for (col = 0; col < moduleCount - 1; col++) {
|
|
354
|
+
var count = 0;
|
|
355
|
+
if (qrCode.isDark(row, col)) count++;
|
|
356
|
+
if (qrCode.isDark(row + 1, col)) count++;
|
|
357
|
+
if (qrCode.isDark(row, col + 1)) count++;
|
|
358
|
+
if (qrCode.isDark(row + 1, col + 1)) count++;
|
|
359
|
+
if (count === 0 || count === 4) {
|
|
360
|
+
lostPoint += 3;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (row = 0; row < moduleCount; row++) {
|
|
365
|
+
for (col = 0; col < moduleCount - 6; col++) {
|
|
366
|
+
if (qrCode.isDark(row, col) && !qrCode.isDark(row, col + 1) && qrCode.isDark(row, col + 2) && qrCode.isDark(row, col + 3) && qrCode.isDark(row, col + 4) && !qrCode.isDark(row, col + 5) && qrCode.isDark(row, col + 6)) {
|
|
367
|
+
lostPoint += 40;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (col = 0; col < moduleCount; col++) {
|
|
372
|
+
for (row = 0; row < moduleCount - 6; row++) {
|
|
373
|
+
if (qrCode.isDark(row, col) && !qrCode.isDark(row + 1, col) && qrCode.isDark(row + 2, col) && qrCode.isDark(row + 3, col) && qrCode.isDark(row + 4, col) && !qrCode.isDark(row + 5, col) && qrCode.isDark(row + 6, col)) {
|
|
374
|
+
lostPoint += 40;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var darkCount = 0;
|
|
379
|
+
for (col = 0; col < moduleCount; col++) {
|
|
380
|
+
for (row = 0; row < moduleCount; row++) {
|
|
381
|
+
if (qrCode.isDark(row, col)) {
|
|
382
|
+
darkCount++;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
|
|
387
|
+
lostPoint += ratio * 10;
|
|
388
|
+
return lostPoint;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
module.exports = QRUtil;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js
|
|
396
|
+
var require_QRErrorCorrectLevel = __commonJS({
|
|
397
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js"(exports, module) {
|
|
398
|
+
module.exports = {
|
|
399
|
+
L: 1,
|
|
400
|
+
M: 0,
|
|
401
|
+
Q: 3,
|
|
402
|
+
H: 2
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRRSBlock.js
|
|
408
|
+
var require_QRRSBlock = __commonJS({
|
|
409
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRRSBlock.js"(exports, module) {
|
|
410
|
+
var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
|
|
411
|
+
function QRRSBlock(totalCount, dataCount) {
|
|
412
|
+
this.totalCount = totalCount;
|
|
413
|
+
this.dataCount = dataCount;
|
|
414
|
+
}
|
|
415
|
+
QRRSBlock.RS_BLOCK_TABLE = [
|
|
416
|
+
// L
|
|
417
|
+
// M
|
|
418
|
+
// Q
|
|
419
|
+
// H
|
|
420
|
+
// 1
|
|
421
|
+
[1, 26, 19],
|
|
422
|
+
[1, 26, 16],
|
|
423
|
+
[1, 26, 13],
|
|
424
|
+
[1, 26, 9],
|
|
425
|
+
// 2
|
|
426
|
+
[1, 44, 34],
|
|
427
|
+
[1, 44, 28],
|
|
428
|
+
[1, 44, 22],
|
|
429
|
+
[1, 44, 16],
|
|
430
|
+
// 3
|
|
431
|
+
[1, 70, 55],
|
|
432
|
+
[1, 70, 44],
|
|
433
|
+
[2, 35, 17],
|
|
434
|
+
[2, 35, 13],
|
|
435
|
+
// 4
|
|
436
|
+
[1, 100, 80],
|
|
437
|
+
[2, 50, 32],
|
|
438
|
+
[2, 50, 24],
|
|
439
|
+
[4, 25, 9],
|
|
440
|
+
// 5
|
|
441
|
+
[1, 134, 108],
|
|
442
|
+
[2, 67, 43],
|
|
443
|
+
[2, 33, 15, 2, 34, 16],
|
|
444
|
+
[2, 33, 11, 2, 34, 12],
|
|
445
|
+
// 6
|
|
446
|
+
[2, 86, 68],
|
|
447
|
+
[4, 43, 27],
|
|
448
|
+
[4, 43, 19],
|
|
449
|
+
[4, 43, 15],
|
|
450
|
+
// 7
|
|
451
|
+
[2, 98, 78],
|
|
452
|
+
[4, 49, 31],
|
|
453
|
+
[2, 32, 14, 4, 33, 15],
|
|
454
|
+
[4, 39, 13, 1, 40, 14],
|
|
455
|
+
// 8
|
|
456
|
+
[2, 121, 97],
|
|
457
|
+
[2, 60, 38, 2, 61, 39],
|
|
458
|
+
[4, 40, 18, 2, 41, 19],
|
|
459
|
+
[4, 40, 14, 2, 41, 15],
|
|
460
|
+
// 9
|
|
461
|
+
[2, 146, 116],
|
|
462
|
+
[3, 58, 36, 2, 59, 37],
|
|
463
|
+
[4, 36, 16, 4, 37, 17],
|
|
464
|
+
[4, 36, 12, 4, 37, 13],
|
|
465
|
+
// 10
|
|
466
|
+
[2, 86, 68, 2, 87, 69],
|
|
467
|
+
[4, 69, 43, 1, 70, 44],
|
|
468
|
+
[6, 43, 19, 2, 44, 20],
|
|
469
|
+
[6, 43, 15, 2, 44, 16],
|
|
470
|
+
// 11
|
|
471
|
+
[4, 101, 81],
|
|
472
|
+
[1, 80, 50, 4, 81, 51],
|
|
473
|
+
[4, 50, 22, 4, 51, 23],
|
|
474
|
+
[3, 36, 12, 8, 37, 13],
|
|
475
|
+
// 12
|
|
476
|
+
[2, 116, 92, 2, 117, 93],
|
|
477
|
+
[6, 58, 36, 2, 59, 37],
|
|
478
|
+
[4, 46, 20, 6, 47, 21],
|
|
479
|
+
[7, 42, 14, 4, 43, 15],
|
|
480
|
+
// 13
|
|
481
|
+
[4, 133, 107],
|
|
482
|
+
[8, 59, 37, 1, 60, 38],
|
|
483
|
+
[8, 44, 20, 4, 45, 21],
|
|
484
|
+
[12, 33, 11, 4, 34, 12],
|
|
485
|
+
// 14
|
|
486
|
+
[3, 145, 115, 1, 146, 116],
|
|
487
|
+
[4, 64, 40, 5, 65, 41],
|
|
488
|
+
[11, 36, 16, 5, 37, 17],
|
|
489
|
+
[11, 36, 12, 5, 37, 13],
|
|
490
|
+
// 15
|
|
491
|
+
[5, 109, 87, 1, 110, 88],
|
|
492
|
+
[5, 65, 41, 5, 66, 42],
|
|
493
|
+
[5, 54, 24, 7, 55, 25],
|
|
494
|
+
[11, 36, 12],
|
|
495
|
+
// 16
|
|
496
|
+
[5, 122, 98, 1, 123, 99],
|
|
497
|
+
[7, 73, 45, 3, 74, 46],
|
|
498
|
+
[15, 43, 19, 2, 44, 20],
|
|
499
|
+
[3, 45, 15, 13, 46, 16],
|
|
500
|
+
// 17
|
|
501
|
+
[1, 135, 107, 5, 136, 108],
|
|
502
|
+
[10, 74, 46, 1, 75, 47],
|
|
503
|
+
[1, 50, 22, 15, 51, 23],
|
|
504
|
+
[2, 42, 14, 17, 43, 15],
|
|
505
|
+
// 18
|
|
506
|
+
[5, 150, 120, 1, 151, 121],
|
|
507
|
+
[9, 69, 43, 4, 70, 44],
|
|
508
|
+
[17, 50, 22, 1, 51, 23],
|
|
509
|
+
[2, 42, 14, 19, 43, 15],
|
|
510
|
+
// 19
|
|
511
|
+
[3, 141, 113, 4, 142, 114],
|
|
512
|
+
[3, 70, 44, 11, 71, 45],
|
|
513
|
+
[17, 47, 21, 4, 48, 22],
|
|
514
|
+
[9, 39, 13, 16, 40, 14],
|
|
515
|
+
// 20
|
|
516
|
+
[3, 135, 107, 5, 136, 108],
|
|
517
|
+
[3, 67, 41, 13, 68, 42],
|
|
518
|
+
[15, 54, 24, 5, 55, 25],
|
|
519
|
+
[15, 43, 15, 10, 44, 16],
|
|
520
|
+
// 21
|
|
521
|
+
[4, 144, 116, 4, 145, 117],
|
|
522
|
+
[17, 68, 42],
|
|
523
|
+
[17, 50, 22, 6, 51, 23],
|
|
524
|
+
[19, 46, 16, 6, 47, 17],
|
|
525
|
+
// 22
|
|
526
|
+
[2, 139, 111, 7, 140, 112],
|
|
527
|
+
[17, 74, 46],
|
|
528
|
+
[7, 54, 24, 16, 55, 25],
|
|
529
|
+
[34, 37, 13],
|
|
530
|
+
// 23
|
|
531
|
+
[4, 151, 121, 5, 152, 122],
|
|
532
|
+
[4, 75, 47, 14, 76, 48],
|
|
533
|
+
[11, 54, 24, 14, 55, 25],
|
|
534
|
+
[16, 45, 15, 14, 46, 16],
|
|
535
|
+
// 24
|
|
536
|
+
[6, 147, 117, 4, 148, 118],
|
|
537
|
+
[6, 73, 45, 14, 74, 46],
|
|
538
|
+
[11, 54, 24, 16, 55, 25],
|
|
539
|
+
[30, 46, 16, 2, 47, 17],
|
|
540
|
+
// 25
|
|
541
|
+
[8, 132, 106, 4, 133, 107],
|
|
542
|
+
[8, 75, 47, 13, 76, 48],
|
|
543
|
+
[7, 54, 24, 22, 55, 25],
|
|
544
|
+
[22, 45, 15, 13, 46, 16],
|
|
545
|
+
// 26
|
|
546
|
+
[10, 142, 114, 2, 143, 115],
|
|
547
|
+
[19, 74, 46, 4, 75, 47],
|
|
548
|
+
[28, 50, 22, 6, 51, 23],
|
|
549
|
+
[33, 46, 16, 4, 47, 17],
|
|
550
|
+
// 27
|
|
551
|
+
[8, 152, 122, 4, 153, 123],
|
|
552
|
+
[22, 73, 45, 3, 74, 46],
|
|
553
|
+
[8, 53, 23, 26, 54, 24],
|
|
554
|
+
[12, 45, 15, 28, 46, 16],
|
|
555
|
+
// 28
|
|
556
|
+
[3, 147, 117, 10, 148, 118],
|
|
557
|
+
[3, 73, 45, 23, 74, 46],
|
|
558
|
+
[4, 54, 24, 31, 55, 25],
|
|
559
|
+
[11, 45, 15, 31, 46, 16],
|
|
560
|
+
// 29
|
|
561
|
+
[7, 146, 116, 7, 147, 117],
|
|
562
|
+
[21, 73, 45, 7, 74, 46],
|
|
563
|
+
[1, 53, 23, 37, 54, 24],
|
|
564
|
+
[19, 45, 15, 26, 46, 16],
|
|
565
|
+
// 30
|
|
566
|
+
[5, 145, 115, 10, 146, 116],
|
|
567
|
+
[19, 75, 47, 10, 76, 48],
|
|
568
|
+
[15, 54, 24, 25, 55, 25],
|
|
569
|
+
[23, 45, 15, 25, 46, 16],
|
|
570
|
+
// 31
|
|
571
|
+
[13, 145, 115, 3, 146, 116],
|
|
572
|
+
[2, 74, 46, 29, 75, 47],
|
|
573
|
+
[42, 54, 24, 1, 55, 25],
|
|
574
|
+
[23, 45, 15, 28, 46, 16],
|
|
575
|
+
// 32
|
|
576
|
+
[17, 145, 115],
|
|
577
|
+
[10, 74, 46, 23, 75, 47],
|
|
578
|
+
[10, 54, 24, 35, 55, 25],
|
|
579
|
+
[19, 45, 15, 35, 46, 16],
|
|
580
|
+
// 33
|
|
581
|
+
[17, 145, 115, 1, 146, 116],
|
|
582
|
+
[14, 74, 46, 21, 75, 47],
|
|
583
|
+
[29, 54, 24, 19, 55, 25],
|
|
584
|
+
[11, 45, 15, 46, 46, 16],
|
|
585
|
+
// 34
|
|
586
|
+
[13, 145, 115, 6, 146, 116],
|
|
587
|
+
[14, 74, 46, 23, 75, 47],
|
|
588
|
+
[44, 54, 24, 7, 55, 25],
|
|
589
|
+
[59, 46, 16, 1, 47, 17],
|
|
590
|
+
// 35
|
|
591
|
+
[12, 151, 121, 7, 152, 122],
|
|
592
|
+
[12, 75, 47, 26, 76, 48],
|
|
593
|
+
[39, 54, 24, 14, 55, 25],
|
|
594
|
+
[22, 45, 15, 41, 46, 16],
|
|
595
|
+
// 36
|
|
596
|
+
[6, 151, 121, 14, 152, 122],
|
|
597
|
+
[6, 75, 47, 34, 76, 48],
|
|
598
|
+
[46, 54, 24, 10, 55, 25],
|
|
599
|
+
[2, 45, 15, 64, 46, 16],
|
|
600
|
+
// 37
|
|
601
|
+
[17, 152, 122, 4, 153, 123],
|
|
602
|
+
[29, 74, 46, 14, 75, 47],
|
|
603
|
+
[49, 54, 24, 10, 55, 25],
|
|
604
|
+
[24, 45, 15, 46, 46, 16],
|
|
605
|
+
// 38
|
|
606
|
+
[4, 152, 122, 18, 153, 123],
|
|
607
|
+
[13, 74, 46, 32, 75, 47],
|
|
608
|
+
[48, 54, 24, 14, 55, 25],
|
|
609
|
+
[42, 45, 15, 32, 46, 16],
|
|
610
|
+
// 39
|
|
611
|
+
[20, 147, 117, 4, 148, 118],
|
|
612
|
+
[40, 75, 47, 7, 76, 48],
|
|
613
|
+
[43, 54, 24, 22, 55, 25],
|
|
614
|
+
[10, 45, 15, 67, 46, 16],
|
|
615
|
+
// 40
|
|
616
|
+
[19, 148, 118, 6, 149, 119],
|
|
617
|
+
[18, 75, 47, 31, 76, 48],
|
|
618
|
+
[34, 54, 24, 34, 55, 25],
|
|
619
|
+
[20, 45, 15, 61, 46, 16]
|
|
620
|
+
];
|
|
621
|
+
QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) {
|
|
622
|
+
var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
|
|
623
|
+
if (rsBlock === void 0) {
|
|
624
|
+
throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel);
|
|
625
|
+
}
|
|
626
|
+
var length = rsBlock.length / 3;
|
|
627
|
+
var list = [];
|
|
628
|
+
for (var i = 0; i < length; i++) {
|
|
629
|
+
var count = rsBlock[i * 3 + 0];
|
|
630
|
+
var totalCount = rsBlock[i * 3 + 1];
|
|
631
|
+
var dataCount = rsBlock[i * 3 + 2];
|
|
632
|
+
for (var j = 0; j < count; j++) {
|
|
633
|
+
list.push(new QRRSBlock(totalCount, dataCount));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return list;
|
|
637
|
+
};
|
|
638
|
+
QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) {
|
|
639
|
+
switch (errorCorrectLevel) {
|
|
640
|
+
case QRErrorCorrectLevel.L:
|
|
641
|
+
return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
|
|
642
|
+
case QRErrorCorrectLevel.M:
|
|
643
|
+
return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
|
|
644
|
+
case QRErrorCorrectLevel.Q:
|
|
645
|
+
return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
|
|
646
|
+
case QRErrorCorrectLevel.H:
|
|
647
|
+
return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
|
|
648
|
+
default:
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
module.exports = QRRSBlock;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRBitBuffer.js
|
|
657
|
+
var require_QRBitBuffer = __commonJS({
|
|
658
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRBitBuffer.js"(exports, module) {
|
|
659
|
+
function QRBitBuffer() {
|
|
660
|
+
this.buffer = [];
|
|
661
|
+
this.length = 0;
|
|
662
|
+
}
|
|
663
|
+
QRBitBuffer.prototype = {
|
|
664
|
+
get: function(index) {
|
|
665
|
+
var bufIndex = Math.floor(index / 8);
|
|
666
|
+
return (this.buffer[bufIndex] >>> 7 - index % 8 & 1) == 1;
|
|
667
|
+
},
|
|
668
|
+
put: function(num, length) {
|
|
669
|
+
for (var i = 0; i < length; i++) {
|
|
670
|
+
this.putBit((num >>> length - i - 1 & 1) == 1);
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
getLengthInBits: function() {
|
|
674
|
+
return this.length;
|
|
675
|
+
},
|
|
676
|
+
putBit: function(bit) {
|
|
677
|
+
var bufIndex = Math.floor(this.length / 8);
|
|
678
|
+
if (this.buffer.length <= bufIndex) {
|
|
679
|
+
this.buffer.push(0);
|
|
680
|
+
}
|
|
681
|
+
if (bit) {
|
|
682
|
+
this.buffer[bufIndex] |= 128 >>> this.length % 8;
|
|
683
|
+
}
|
|
684
|
+
this.length++;
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
module.exports = QRBitBuffer;
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/index.js
|
|
692
|
+
var require_QRCode = __commonJS({
|
|
693
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/index.js"(exports, module) {
|
|
694
|
+
var QR8bitByte = require_QR8bitByte();
|
|
695
|
+
var QRUtil = require_QRUtil();
|
|
696
|
+
var QRPolynomial = require_QRPolynomial();
|
|
697
|
+
var QRRSBlock = require_QRRSBlock();
|
|
698
|
+
var QRBitBuffer = require_QRBitBuffer();
|
|
699
|
+
function QRCode(typeNumber, errorCorrectLevel) {
|
|
700
|
+
this.typeNumber = typeNumber;
|
|
701
|
+
this.errorCorrectLevel = errorCorrectLevel;
|
|
702
|
+
this.modules = null;
|
|
703
|
+
this.moduleCount = 0;
|
|
704
|
+
this.dataCache = null;
|
|
705
|
+
this.dataList = [];
|
|
706
|
+
}
|
|
707
|
+
QRCode.prototype = {
|
|
708
|
+
addData: function(data) {
|
|
709
|
+
var newData = new QR8bitByte(data);
|
|
710
|
+
this.dataList.push(newData);
|
|
711
|
+
this.dataCache = null;
|
|
712
|
+
},
|
|
713
|
+
isDark: function(row, col) {
|
|
714
|
+
if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) {
|
|
715
|
+
throw new Error(row + "," + col);
|
|
716
|
+
}
|
|
717
|
+
return this.modules[row][col];
|
|
718
|
+
},
|
|
719
|
+
getModuleCount: function() {
|
|
720
|
+
return this.moduleCount;
|
|
721
|
+
},
|
|
722
|
+
make: function() {
|
|
723
|
+
if (this.typeNumber < 1) {
|
|
724
|
+
var typeNumber = 1;
|
|
725
|
+
for (typeNumber = 1; typeNumber < 40; typeNumber++) {
|
|
726
|
+
var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, this.errorCorrectLevel);
|
|
727
|
+
var buffer = new QRBitBuffer();
|
|
728
|
+
var totalDataCount = 0;
|
|
729
|
+
for (var i = 0; i < rsBlocks.length; i++) {
|
|
730
|
+
totalDataCount += rsBlocks[i].dataCount;
|
|
731
|
+
}
|
|
732
|
+
for (var x = 0; x < this.dataList.length; x++) {
|
|
733
|
+
var data = this.dataList[x];
|
|
734
|
+
buffer.put(data.mode, 4);
|
|
735
|
+
buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
|
|
736
|
+
data.write(buffer);
|
|
737
|
+
}
|
|
738
|
+
if (buffer.getLengthInBits() <= totalDataCount * 8)
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
this.typeNumber = typeNumber;
|
|
742
|
+
}
|
|
743
|
+
this.makeImpl(false, this.getBestMaskPattern());
|
|
744
|
+
},
|
|
745
|
+
makeImpl: function(test, maskPattern) {
|
|
746
|
+
this.moduleCount = this.typeNumber * 4 + 17;
|
|
747
|
+
this.modules = new Array(this.moduleCount);
|
|
748
|
+
for (var row = 0; row < this.moduleCount; row++) {
|
|
749
|
+
this.modules[row] = new Array(this.moduleCount);
|
|
750
|
+
for (var col = 0; col < this.moduleCount; col++) {
|
|
751
|
+
this.modules[row][col] = null;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
this.setupPositionProbePattern(0, 0);
|
|
755
|
+
this.setupPositionProbePattern(this.moduleCount - 7, 0);
|
|
756
|
+
this.setupPositionProbePattern(0, this.moduleCount - 7);
|
|
757
|
+
this.setupPositionAdjustPattern();
|
|
758
|
+
this.setupTimingPattern();
|
|
759
|
+
this.setupTypeInfo(test, maskPattern);
|
|
760
|
+
if (this.typeNumber >= 7) {
|
|
761
|
+
this.setupTypeNumber(test);
|
|
762
|
+
}
|
|
763
|
+
if (this.dataCache === null) {
|
|
764
|
+
this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
|
|
765
|
+
}
|
|
766
|
+
this.mapData(this.dataCache, maskPattern);
|
|
767
|
+
},
|
|
768
|
+
setupPositionProbePattern: function(row, col) {
|
|
769
|
+
for (var r = -1; r <= 7; r++) {
|
|
770
|
+
if (row + r <= -1 || this.moduleCount <= row + r) continue;
|
|
771
|
+
for (var c = -1; c <= 7; c++) {
|
|
772
|
+
if (col + c <= -1 || this.moduleCount <= col + c) continue;
|
|
773
|
+
if (0 <= r && r <= 6 && (c === 0 || c === 6) || 0 <= c && c <= 6 && (r === 0 || r === 6) || 2 <= r && r <= 4 && 2 <= c && c <= 4) {
|
|
774
|
+
this.modules[row + r][col + c] = true;
|
|
775
|
+
} else {
|
|
776
|
+
this.modules[row + r][col + c] = false;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
getBestMaskPattern: function() {
|
|
782
|
+
var minLostPoint = 0;
|
|
783
|
+
var pattern = 0;
|
|
784
|
+
for (var i = 0; i < 8; i++) {
|
|
785
|
+
this.makeImpl(true, i);
|
|
786
|
+
var lostPoint = QRUtil.getLostPoint(this);
|
|
787
|
+
if (i === 0 || minLostPoint > lostPoint) {
|
|
788
|
+
minLostPoint = lostPoint;
|
|
789
|
+
pattern = i;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return pattern;
|
|
793
|
+
},
|
|
794
|
+
createMovieClip: function(target_mc, instance_name, depth) {
|
|
795
|
+
var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth);
|
|
796
|
+
var cs = 1;
|
|
797
|
+
this.make();
|
|
798
|
+
for (var row = 0; row < this.modules.length; row++) {
|
|
799
|
+
var y = row * cs;
|
|
800
|
+
for (var col = 0; col < this.modules[row].length; col++) {
|
|
801
|
+
var x = col * cs;
|
|
802
|
+
var dark = this.modules[row][col];
|
|
803
|
+
if (dark) {
|
|
804
|
+
qr_mc.beginFill(0, 100);
|
|
805
|
+
qr_mc.moveTo(x, y);
|
|
806
|
+
qr_mc.lineTo(x + cs, y);
|
|
807
|
+
qr_mc.lineTo(x + cs, y + cs);
|
|
808
|
+
qr_mc.lineTo(x, y + cs);
|
|
809
|
+
qr_mc.endFill();
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return qr_mc;
|
|
814
|
+
},
|
|
815
|
+
setupTimingPattern: function() {
|
|
816
|
+
for (var r = 8; r < this.moduleCount - 8; r++) {
|
|
817
|
+
if (this.modules[r][6] !== null) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
this.modules[r][6] = r % 2 === 0;
|
|
821
|
+
}
|
|
822
|
+
for (var c = 8; c < this.moduleCount - 8; c++) {
|
|
823
|
+
if (this.modules[6][c] !== null) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
this.modules[6][c] = c % 2 === 0;
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
setupPositionAdjustPattern: function() {
|
|
830
|
+
var pos = QRUtil.getPatternPosition(this.typeNumber);
|
|
831
|
+
for (var i = 0; i < pos.length; i++) {
|
|
832
|
+
for (var j = 0; j < pos.length; j++) {
|
|
833
|
+
var row = pos[i];
|
|
834
|
+
var col = pos[j];
|
|
835
|
+
if (this.modules[row][col] !== null) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
for (var r = -2; r <= 2; r++) {
|
|
839
|
+
for (var c = -2; c <= 2; c++) {
|
|
840
|
+
if (Math.abs(r) === 2 || Math.abs(c) === 2 || r === 0 && c === 0) {
|
|
841
|
+
this.modules[row + r][col + c] = true;
|
|
842
|
+
} else {
|
|
843
|
+
this.modules[row + r][col + c] = false;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
setupTypeNumber: function(test) {
|
|
851
|
+
var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
|
|
852
|
+
var mod;
|
|
853
|
+
for (var i = 0; i < 18; i++) {
|
|
854
|
+
mod = !test && (bits >> i & 1) === 1;
|
|
855
|
+
this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
|
|
856
|
+
}
|
|
857
|
+
for (var x = 0; x < 18; x++) {
|
|
858
|
+
mod = !test && (bits >> x & 1) === 1;
|
|
859
|
+
this.modules[x % 3 + this.moduleCount - 8 - 3][Math.floor(x / 3)] = mod;
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
setupTypeInfo: function(test, maskPattern) {
|
|
863
|
+
var data = this.errorCorrectLevel << 3 | maskPattern;
|
|
864
|
+
var bits = QRUtil.getBCHTypeInfo(data);
|
|
865
|
+
var mod;
|
|
866
|
+
for (var v = 0; v < 15; v++) {
|
|
867
|
+
mod = !test && (bits >> v & 1) === 1;
|
|
868
|
+
if (v < 6) {
|
|
869
|
+
this.modules[v][8] = mod;
|
|
870
|
+
} else if (v < 8) {
|
|
871
|
+
this.modules[v + 1][8] = mod;
|
|
872
|
+
} else {
|
|
873
|
+
this.modules[this.moduleCount - 15 + v][8] = mod;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
for (var h = 0; h < 15; h++) {
|
|
877
|
+
mod = !test && (bits >> h & 1) === 1;
|
|
878
|
+
if (h < 8) {
|
|
879
|
+
this.modules[8][this.moduleCount - h - 1] = mod;
|
|
880
|
+
} else if (h < 9) {
|
|
881
|
+
this.modules[8][15 - h - 1 + 1] = mod;
|
|
882
|
+
} else {
|
|
883
|
+
this.modules[8][15 - h - 1] = mod;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
this.modules[this.moduleCount - 8][8] = !test;
|
|
887
|
+
},
|
|
888
|
+
mapData: function(data, maskPattern) {
|
|
889
|
+
var inc = -1;
|
|
890
|
+
var row = this.moduleCount - 1;
|
|
891
|
+
var bitIndex = 7;
|
|
892
|
+
var byteIndex = 0;
|
|
893
|
+
for (var col = this.moduleCount - 1; col > 0; col -= 2) {
|
|
894
|
+
if (col === 6) col--;
|
|
895
|
+
while (true) {
|
|
896
|
+
for (var c = 0; c < 2; c++) {
|
|
897
|
+
if (this.modules[row][col - c] === null) {
|
|
898
|
+
var dark = false;
|
|
899
|
+
if (byteIndex < data.length) {
|
|
900
|
+
dark = (data[byteIndex] >>> bitIndex & 1) === 1;
|
|
901
|
+
}
|
|
902
|
+
var mask = QRUtil.getMask(maskPattern, row, col - c);
|
|
903
|
+
if (mask) {
|
|
904
|
+
dark = !dark;
|
|
905
|
+
}
|
|
906
|
+
this.modules[row][col - c] = dark;
|
|
907
|
+
bitIndex--;
|
|
908
|
+
if (bitIndex === -1) {
|
|
909
|
+
byteIndex++;
|
|
910
|
+
bitIndex = 7;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
row += inc;
|
|
915
|
+
if (row < 0 || this.moduleCount <= row) {
|
|
916
|
+
row -= inc;
|
|
917
|
+
inc = -inc;
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
QRCode.PAD0 = 236;
|
|
925
|
+
QRCode.PAD1 = 17;
|
|
926
|
+
QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) {
|
|
927
|
+
var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
|
928
|
+
var buffer = new QRBitBuffer();
|
|
929
|
+
for (var i = 0; i < dataList.length; i++) {
|
|
930
|
+
var data = dataList[i];
|
|
931
|
+
buffer.put(data.mode, 4);
|
|
932
|
+
buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
|
|
933
|
+
data.write(buffer);
|
|
934
|
+
}
|
|
935
|
+
var totalDataCount = 0;
|
|
936
|
+
for (var x = 0; x < rsBlocks.length; x++) {
|
|
937
|
+
totalDataCount += rsBlocks[x].dataCount;
|
|
938
|
+
}
|
|
939
|
+
if (buffer.getLengthInBits() > totalDataCount * 8) {
|
|
940
|
+
throw new Error("code length overflow. (" + buffer.getLengthInBits() + ">" + totalDataCount * 8 + ")");
|
|
941
|
+
}
|
|
942
|
+
if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
|
|
943
|
+
buffer.put(0, 4);
|
|
944
|
+
}
|
|
945
|
+
while (buffer.getLengthInBits() % 8 !== 0) {
|
|
946
|
+
buffer.putBit(false);
|
|
947
|
+
}
|
|
948
|
+
while (true) {
|
|
949
|
+
if (buffer.getLengthInBits() >= totalDataCount * 8) {
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
buffer.put(QRCode.PAD0, 8);
|
|
953
|
+
if (buffer.getLengthInBits() >= totalDataCount * 8) {
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
buffer.put(QRCode.PAD1, 8);
|
|
957
|
+
}
|
|
958
|
+
return QRCode.createBytes(buffer, rsBlocks);
|
|
959
|
+
};
|
|
960
|
+
QRCode.createBytes = function(buffer, rsBlocks) {
|
|
961
|
+
var offset = 0;
|
|
962
|
+
var maxDcCount = 0;
|
|
963
|
+
var maxEcCount = 0;
|
|
964
|
+
var dcdata = new Array(rsBlocks.length);
|
|
965
|
+
var ecdata = new Array(rsBlocks.length);
|
|
966
|
+
for (var r = 0; r < rsBlocks.length; r++) {
|
|
967
|
+
var dcCount = rsBlocks[r].dataCount;
|
|
968
|
+
var ecCount = rsBlocks[r].totalCount - dcCount;
|
|
969
|
+
maxDcCount = Math.max(maxDcCount, dcCount);
|
|
970
|
+
maxEcCount = Math.max(maxEcCount, ecCount);
|
|
971
|
+
dcdata[r] = new Array(dcCount);
|
|
972
|
+
for (var i = 0; i < dcdata[r].length; i++) {
|
|
973
|
+
dcdata[r][i] = 255 & buffer.buffer[i + offset];
|
|
974
|
+
}
|
|
975
|
+
offset += dcCount;
|
|
976
|
+
var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
|
|
977
|
+
var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
|
|
978
|
+
var modPoly = rawPoly.mod(rsPoly);
|
|
979
|
+
ecdata[r] = new Array(rsPoly.getLength() - 1);
|
|
980
|
+
for (var x = 0; x < ecdata[r].length; x++) {
|
|
981
|
+
var modIndex = x + modPoly.getLength() - ecdata[r].length;
|
|
982
|
+
ecdata[r][x] = modIndex >= 0 ? modPoly.get(modIndex) : 0;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
var totalCodeCount = 0;
|
|
986
|
+
for (var y = 0; y < rsBlocks.length; y++) {
|
|
987
|
+
totalCodeCount += rsBlocks[y].totalCount;
|
|
988
|
+
}
|
|
989
|
+
var data = new Array(totalCodeCount);
|
|
990
|
+
var index = 0;
|
|
991
|
+
for (var z = 0; z < maxDcCount; z++) {
|
|
992
|
+
for (var s = 0; s < rsBlocks.length; s++) {
|
|
993
|
+
if (z < dcdata[s].length) {
|
|
994
|
+
data[index++] = dcdata[s][z];
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
for (var xx = 0; xx < maxEcCount; xx++) {
|
|
999
|
+
for (var t = 0; t < rsBlocks.length; t++) {
|
|
1000
|
+
if (xx < ecdata[t].length) {
|
|
1001
|
+
data[index++] = ecdata[t][xx];
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return data;
|
|
1006
|
+
};
|
|
1007
|
+
module.exports = QRCode;
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
// node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/lib/main.js
|
|
1012
|
+
var require_main = __commonJS({
|
|
1013
|
+
"node_modules/.pnpm/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/lib/main.js"(exports, module) {
|
|
1014
|
+
var QRCode = require_QRCode();
|
|
1015
|
+
var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
|
|
1016
|
+
var black = "\x1B[40m \x1B[0m";
|
|
1017
|
+
var white = "\x1B[47m \x1B[0m";
|
|
1018
|
+
var toCell = function(isBlack) {
|
|
1019
|
+
return isBlack ? black : white;
|
|
1020
|
+
};
|
|
1021
|
+
var repeat = function(color) {
|
|
1022
|
+
return {
|
|
1023
|
+
times: function(count) {
|
|
1024
|
+
return new Array(count).join(color);
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
};
|
|
1028
|
+
var fill = function(length, value) {
|
|
1029
|
+
var arr = new Array(length);
|
|
1030
|
+
for (var i = 0; i < length; i++) {
|
|
1031
|
+
arr[i] = value;
|
|
1032
|
+
}
|
|
1033
|
+
return arr;
|
|
1034
|
+
};
|
|
1035
|
+
module.exports = {
|
|
1036
|
+
error: QRErrorCorrectLevel.L,
|
|
1037
|
+
generate: function(input, opts, cb) {
|
|
1038
|
+
if (typeof opts === "function") {
|
|
1039
|
+
cb = opts;
|
|
1040
|
+
opts = {};
|
|
1041
|
+
}
|
|
1042
|
+
var qrcode2 = new QRCode(-1, this.error);
|
|
1043
|
+
qrcode2.addData(input);
|
|
1044
|
+
qrcode2.make();
|
|
1045
|
+
var output = "";
|
|
1046
|
+
if (opts && opts.small) {
|
|
1047
|
+
var BLACK = true, WHITE = false;
|
|
1048
|
+
var moduleCount = qrcode2.getModuleCount();
|
|
1049
|
+
var moduleData = qrcode2.modules.slice();
|
|
1050
|
+
var oddRow = moduleCount % 2 === 1;
|
|
1051
|
+
if (oddRow) {
|
|
1052
|
+
moduleData.push(fill(moduleCount, WHITE));
|
|
1053
|
+
}
|
|
1054
|
+
var platte = {
|
|
1055
|
+
WHITE_ALL: "\u2588",
|
|
1056
|
+
WHITE_BLACK: "\u2580",
|
|
1057
|
+
BLACK_WHITE: "\u2584",
|
|
1058
|
+
BLACK_ALL: " "
|
|
1059
|
+
};
|
|
1060
|
+
var borderTop = repeat(platte.BLACK_WHITE).times(moduleCount + 3);
|
|
1061
|
+
var borderBottom = repeat(platte.WHITE_BLACK).times(moduleCount + 3);
|
|
1062
|
+
output += borderTop + "\n";
|
|
1063
|
+
for (var row = 0; row < moduleCount; row += 2) {
|
|
1064
|
+
output += platte.WHITE_ALL;
|
|
1065
|
+
for (var col = 0; col < moduleCount; col++) {
|
|
1066
|
+
if (moduleData[row][col] === WHITE && moduleData[row + 1][col] === WHITE) {
|
|
1067
|
+
output += platte.WHITE_ALL;
|
|
1068
|
+
} else if (moduleData[row][col] === WHITE && moduleData[row + 1][col] === BLACK) {
|
|
1069
|
+
output += platte.WHITE_BLACK;
|
|
1070
|
+
} else if (moduleData[row][col] === BLACK && moduleData[row + 1][col] === WHITE) {
|
|
1071
|
+
output += platte.BLACK_WHITE;
|
|
1072
|
+
} else {
|
|
1073
|
+
output += platte.BLACK_ALL;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
output += platte.WHITE_ALL + "\n";
|
|
1077
|
+
}
|
|
1078
|
+
if (!oddRow) {
|
|
1079
|
+
output += borderBottom;
|
|
1080
|
+
}
|
|
1081
|
+
} else {
|
|
1082
|
+
var border = repeat(white).times(qrcode2.getModuleCount() + 3);
|
|
1083
|
+
output += border + "\n";
|
|
1084
|
+
qrcode2.modules.forEach(function(row2) {
|
|
1085
|
+
output += white;
|
|
1086
|
+
output += row2.map(toCell).join("");
|
|
1087
|
+
output += white + "\n";
|
|
1088
|
+
});
|
|
1089
|
+
output += border;
|
|
1090
|
+
}
|
|
1091
|
+
if (cb) cb(output);
|
|
1092
|
+
else console.log(output);
|
|
1093
|
+
},
|
|
1094
|
+
setErrorLevel: function(error) {
|
|
1095
|
+
this.error = QRErrorCorrectLevel[error] || this.error;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
// server/cli.ts
|
|
1102
|
+
var import_qrcode_terminal = __toESM(require_main(), 1);
|
|
1103
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
1104
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
1105
|
+
import { homedir as homedir2 } from "node:os";
|
|
1106
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
1107
|
+
import { createInterface } from "node:readline/promises";
|
|
1108
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1109
|
+
|
|
1110
|
+
// server/tailscale.ts
|
|
1111
|
+
import { execFile } from "node:child_process";
|
|
1112
|
+
|
|
1113
|
+
// companion/src/listener.ts
|
|
1114
|
+
import { homedir, networkInterfaces } from "node:os";
|
|
1115
|
+
import { delimiter, join } from "node:path";
|
|
1116
|
+
function tailscaleCandidates(home = homedir()) {
|
|
1117
|
+
return [
|
|
1118
|
+
"/Applications/Tailscale.app/Contents/MacOS/Tailscale",
|
|
1119
|
+
join(home, "Applications", "Tailscale.app", "Contents", "MacOS", "Tailscale"),
|
|
1120
|
+
"/opt/homebrew/bin/tailscale",
|
|
1121
|
+
"/usr/local/bin/tailscale",
|
|
1122
|
+
"/usr/bin/tailscale",
|
|
1123
|
+
"/run/current-system/sw/bin/tailscale",
|
|
1124
|
+
"C:\\Program Files\\Tailscale\\tailscale.exe",
|
|
1125
|
+
"C:\\Program Files (x86)\\Tailscale\\tailscale.exe",
|
|
1126
|
+
"tailscale"
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
var searchPath = () => [process.env.PATH ?? "", "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(delimiter);
|
|
1130
|
+
|
|
1131
|
+
// server/tailscale.ts
|
|
1132
|
+
function classifyTailscaleStderr(text) {
|
|
1133
|
+
const t = text.toLowerCase();
|
|
1134
|
+
if (/not logged in|logged out|needs login|please log in/.test(t)) return "not-logged-in";
|
|
1135
|
+
if (/is tailscale running|tailscaled is not running|connection refused|failed to connect to local tailscaled/.test(t)) return "not-running";
|
|
1136
|
+
if (/access denied|permission denied|operator|must be root|not permitted/.test(t)) return "needs-operator";
|
|
1137
|
+
if (/https is not enabled|enable https|cert|tls certs/.test(t)) return "https-not-enabled";
|
|
1138
|
+
if (/magicdns/.test(t)) return "no-magicdns";
|
|
1139
|
+
return "unknown";
|
|
1140
|
+
}
|
|
1141
|
+
function explainTailscaleFailure(reason) {
|
|
1142
|
+
switch (reason) {
|
|
1143
|
+
case "not-installed":
|
|
1144
|
+
return "Tailscale is not installed: https://tailscale.com/download";
|
|
1145
|
+
case "not-logged-in":
|
|
1146
|
+
return "Tailscale is installed but not signed in: run `tailscale up` (or sign in from the Tailscale app)";
|
|
1147
|
+
case "not-running":
|
|
1148
|
+
return "Tailscale is not running: start it, then try again";
|
|
1149
|
+
case "no-magicdns":
|
|
1150
|
+
return "this machine has no MagicDNS name: enable MagicDNS in the tailnet's DNS settings";
|
|
1151
|
+
case "needs-operator":
|
|
1152
|
+
return "Tailscale refused: on Linux run `sudo tailscale set --operator=$USER` once, then try again";
|
|
1153
|
+
case "https-not-enabled":
|
|
1154
|
+
return "HTTPS certificates are off for this tailnet: enable them in the admin console (DNS \u2192 HTTPS Certificates)";
|
|
1155
|
+
default:
|
|
1156
|
+
return "Tailscale reported an error; run `tailscale status` and `tailscale serve status` to see it";
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
function run(cli, args, timeoutMs) {
|
|
1160
|
+
return new Promise((resolve3) => {
|
|
1161
|
+
execFile(
|
|
1162
|
+
cli,
|
|
1163
|
+
args,
|
|
1164
|
+
{ timeout: timeoutMs, killSignal: "SIGKILL", maxBuffer: 16 * 1024 * 1024, env: { ...process.env, PATH: searchPath() } },
|
|
1165
|
+
(error, stdout, stderr) => {
|
|
1166
|
+
const code = error && "code" in error && typeof error.code === "number" ? error.code : error ? null : 0;
|
|
1167
|
+
resolve3({ ok: !error, stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code });
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
function parseTailscaleStatus(cli, stdout) {
|
|
1173
|
+
let parsed;
|
|
1174
|
+
try {
|
|
1175
|
+
parsed = JSON.parse(stdout);
|
|
1176
|
+
} catch {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
const self = Reflect.get(Object(parsed), "Self");
|
|
1180
|
+
const dns = Reflect.get(Object(self), "DNSName");
|
|
1181
|
+
const ips = Reflect.get(Object(self), "TailscaleIPs");
|
|
1182
|
+
const state = Reflect.get(Object(parsed), "BackendState");
|
|
1183
|
+
return {
|
|
1184
|
+
cli,
|
|
1185
|
+
dnsName: typeof dns === "string" && dns ? dns.replace(/\.$/, "") : null,
|
|
1186
|
+
addresses: Array.isArray(ips) ? ips.filter((v) => typeof v === "string") : [],
|
|
1187
|
+
backendState: typeof state === "string" ? state : "unknown"
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
async function tailscaleStatus() {
|
|
1191
|
+
let sawCli = false;
|
|
1192
|
+
for (const cli of tailscaleCandidates()) {
|
|
1193
|
+
const result = await run(cli, ["status", "--json"], 5e3);
|
|
1194
|
+
if (!result.ok && !result.stdout && result.code === null) continue;
|
|
1195
|
+
sawCli = true;
|
|
1196
|
+
const status = parseTailscaleStatus(cli, result.stdout);
|
|
1197
|
+
if (status) {
|
|
1198
|
+
if (status.backendState === "NeedsLogin") return { failure: "not-logged-in" };
|
|
1199
|
+
if (status.backendState !== "Running") return { failure: "not-running" };
|
|
1200
|
+
return { status };
|
|
1201
|
+
}
|
|
1202
|
+
if (result.stderr) return { failure: classifyTailscaleStderr(result.stderr) };
|
|
1203
|
+
}
|
|
1204
|
+
return { failure: sawCli ? "unknown" : "not-installed" };
|
|
1205
|
+
}
|
|
1206
|
+
async function tailscaleServe(status, localPort, httpsPort = 443) {
|
|
1207
|
+
if (!status.dnsName) return { failure: "no-magicdns" };
|
|
1208
|
+
const result = await run(status.cli, ["serve", "--bg", `--https=${httpsPort}`, `http://127.0.0.1:${localPort}`], 2e4);
|
|
1209
|
+
if (!result.ok) return { failure: classifyTailscaleStderr(result.stderr || result.stdout) };
|
|
1210
|
+
return { origin: httpsPort === 443 ? `https://${status.dnsName}` : `https://${status.dnsName}:${httpsPort}` };
|
|
1211
|
+
}
|
|
1212
|
+
async function tailscaleServeOff(status, httpsPort = 443) {
|
|
1213
|
+
await run(status.cli, ["serve", `--https=${httpsPort}`, "off"], 1e4);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// server/tunnel.ts
|
|
1217
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1218
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1219
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
1220
|
+
import { hostname } from "node:os";
|
|
1221
|
+
import { dirname, join as join2, resolve } from "node:path";
|
|
1222
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1223
|
+
|
|
1224
|
+
// electron/control-plane-client.mjs
|
|
1225
|
+
var INSTALLATION_CREDENTIAL = /^omb_install_[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}$/;
|
|
1226
|
+
var INSTALLATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1227
|
+
var CLIENT_INSTANCE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
1228
|
+
var REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1229
|
+
var stringValue = (value) => typeof value === "string" ? value : null;
|
|
1230
|
+
var isPlainRecord = (value) => {
|
|
1231
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1232
|
+
try {
|
|
1233
|
+
const constructor = value.constructor;
|
|
1234
|
+
if (constructor === void 0 || typeof constructor !== "function") return true;
|
|
1235
|
+
const prototype = constructor.prototype;
|
|
1236
|
+
return typeof prototype === "object" && prototype !== null && Object.prototype.hasOwnProperty.call(prototype, "isPrototypeOf");
|
|
1237
|
+
} catch {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
var plainObject = (value) => {
|
|
1242
|
+
if (!isPlainRecord(value)) return null;
|
|
1243
|
+
const record = {};
|
|
1244
|
+
try {
|
|
1245
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
1246
|
+
if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue;
|
|
1247
|
+
if (typeof key !== "string") return null;
|
|
1248
|
+
if (key === "__proto__") continue;
|
|
1249
|
+
record[key] = value[key];
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
return record;
|
|
1255
|
+
};
|
|
1256
|
+
var validClientInstance = (value) => {
|
|
1257
|
+
const input = stringValue(value);
|
|
1258
|
+
return input !== null && CLIENT_INSTANCE.test(input);
|
|
1259
|
+
};
|
|
1260
|
+
var boundedSecret = (value, maximum = 8192) => typeof value === "string" && value.length >= 20 && value.length <= maximum && /^\S+$/.test(value) ? value : null;
|
|
1261
|
+
var ControlPlaneError = class extends Error {
|
|
1262
|
+
constructor(code, status = 0, requestId = "") {
|
|
1263
|
+
super(code);
|
|
1264
|
+
this.name = "ControlPlaneError";
|
|
1265
|
+
this.code = code;
|
|
1266
|
+
this.status = status;
|
|
1267
|
+
this.requestId = REQUEST_ID.test(requestId) ? requestId : "";
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
function statusErrorCode(status) {
|
|
1271
|
+
if (status === 400 || status === 422) return "invalid_request";
|
|
1272
|
+
if (status === 401) return "unauthorized";
|
|
1273
|
+
if (status === 403) return "forbidden";
|
|
1274
|
+
if (status === 404) return "not_found";
|
|
1275
|
+
if (status === 405) return "method_not_allowed";
|
|
1276
|
+
if (status === 409) return "conflict";
|
|
1277
|
+
if (status === 413) return "request_too_large";
|
|
1278
|
+
if (status === 415) return "unsupported_media_type";
|
|
1279
|
+
if (status === 429) return "rate_limited";
|
|
1280
|
+
if (status >= 500) return "control_plane_unavailable";
|
|
1281
|
+
return "request_failed";
|
|
1282
|
+
}
|
|
1283
|
+
function normalizeControlPlaneURL(value) {
|
|
1284
|
+
const input = stringValue(value)?.trim() ?? "";
|
|
1285
|
+
if (!input) return "";
|
|
1286
|
+
let parsed;
|
|
1287
|
+
try {
|
|
1288
|
+
parsed = new URL(input);
|
|
1289
|
+
} catch {
|
|
1290
|
+
return "";
|
|
1291
|
+
}
|
|
1292
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
1293
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback) || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
1294
|
+
return "";
|
|
1295
|
+
}
|
|
1296
|
+
return parsed.origin;
|
|
1297
|
+
}
|
|
1298
|
+
function normalizeAccountEmail(value) {
|
|
1299
|
+
const input = stringValue(value);
|
|
1300
|
+
const email = input !== null && input.length <= 254 ? input.trim().toLowerCase() : "";
|
|
1301
|
+
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "";
|
|
1302
|
+
return email;
|
|
1303
|
+
}
|
|
1304
|
+
function validatedUser(value) {
|
|
1305
|
+
const user = plainObject(value);
|
|
1306
|
+
const email = normalizeAccountEmail(user?.email);
|
|
1307
|
+
const idInput = stringValue(user?.id);
|
|
1308
|
+
const id = idInput !== null && idInput.length >= 1 && idInput.length <= 256 ? idInput : null;
|
|
1309
|
+
if (!email || !id) return null;
|
|
1310
|
+
return { id, email };
|
|
1311
|
+
}
|
|
1312
|
+
function validatedInstallation(value) {
|
|
1313
|
+
const installation = plainObject(value);
|
|
1314
|
+
const id = stringValue(installation?.id);
|
|
1315
|
+
const clientInstanceId = stringValue(installation?.clientInstanceId);
|
|
1316
|
+
if (id === null || !INSTALLATION_ID.test(id) || clientInstanceId === null || !validClientInstance(clientInstanceId)) {
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
id,
|
|
1321
|
+
clientInstanceId,
|
|
1322
|
+
name: stringValue(installation.name) ?? "This computer",
|
|
1323
|
+
platform: installation.platform,
|
|
1324
|
+
appVersion: stringValue(installation.appVersion)
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
function validatedEndpoint(value) {
|
|
1328
|
+
const endpoint = plainObject(value);
|
|
1329
|
+
const endpointURL = stringValue(endpoint?.url);
|
|
1330
|
+
if (endpointURL === null) return null;
|
|
1331
|
+
let url;
|
|
1332
|
+
try {
|
|
1333
|
+
url = new URL(endpointURL);
|
|
1334
|
+
} catch {
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
return { url: url.origin };
|
|
1341
|
+
}
|
|
1342
|
+
function createControlPlaneClient({
|
|
1343
|
+
baseURL,
|
|
1344
|
+
fetchImpl = globalThis.fetch,
|
|
1345
|
+
timeoutSignal = (milliseconds) => AbortSignal.timeout(milliseconds),
|
|
1346
|
+
timeoutMs = 15e3,
|
|
1347
|
+
healthTimeoutMs = 3e3
|
|
1348
|
+
}) {
|
|
1349
|
+
const origin = normalizeControlPlaneURL(baseURL);
|
|
1350
|
+
if (!origin) throw new ControlPlaneError("control_plane_unavailable");
|
|
1351
|
+
const request = async (path4, { method = "GET", token, body, allowEmpty = false, deadlineMs = timeoutMs } = {}) => {
|
|
1352
|
+
const headers = new Headers({ accept: "application/json" });
|
|
1353
|
+
if (token) headers.set("authorization", `Bearer ${token}`);
|
|
1354
|
+
if (path4.startsWith("/api/auth/")) headers.set("origin", origin);
|
|
1355
|
+
if (body !== void 0) {
|
|
1356
|
+
headers.set("content-type", "application/json");
|
|
1357
|
+
}
|
|
1358
|
+
let response;
|
|
1359
|
+
try {
|
|
1360
|
+
const init = {
|
|
1361
|
+
method,
|
|
1362
|
+
headers,
|
|
1363
|
+
redirect: "error",
|
|
1364
|
+
signal: timeoutSignal(deadlineMs)
|
|
1365
|
+
};
|
|
1366
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
1367
|
+
response = await fetchImpl(`${origin}${path4}`, init);
|
|
1368
|
+
} catch {
|
|
1369
|
+
throw new ControlPlaneError("network_unavailable");
|
|
1370
|
+
}
|
|
1371
|
+
let payload = null;
|
|
1372
|
+
if (response.status !== 204) {
|
|
1373
|
+
payload = await response.json().catch(() => null);
|
|
1374
|
+
}
|
|
1375
|
+
if (!response.ok) {
|
|
1376
|
+
const rawCode = stringValue(plainObject(payload)?.error);
|
|
1377
|
+
const code = rawCode !== null && /^[a-z0-9_]{1,64}$/.test(rawCode) ? rawCode : null;
|
|
1378
|
+
throw new ControlPlaneError(
|
|
1379
|
+
code ?? statusErrorCode(response.status),
|
|
1380
|
+
response.status,
|
|
1381
|
+
response.headers.get("x-request-id") ?? ""
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
if (!allowEmpty && !plainObject(payload)) {
|
|
1385
|
+
throw new ControlPlaneError("invalid_response", response.status);
|
|
1386
|
+
}
|
|
1387
|
+
return { response, payload };
|
|
1388
|
+
};
|
|
1389
|
+
const accountInstallations = async (accountToken) => {
|
|
1390
|
+
if (!boundedSecret(accountToken)) throw new ControlPlaneError("signed_out", 401);
|
|
1391
|
+
const { payload } = await request("/v1/installations", { token: accountToken });
|
|
1392
|
+
if (!Array.isArray(payload.installations)) {
|
|
1393
|
+
throw new ControlPlaneError("invalid_response");
|
|
1394
|
+
}
|
|
1395
|
+
const installations = payload.installations.map(validatedInstallation);
|
|
1396
|
+
if (installations.some((installation) => !installation)) {
|
|
1397
|
+
throw new ControlPlaneError("invalid_response");
|
|
1398
|
+
}
|
|
1399
|
+
return installations;
|
|
1400
|
+
};
|
|
1401
|
+
return {
|
|
1402
|
+
origin,
|
|
1403
|
+
async health() {
|
|
1404
|
+
const { payload } = await request("/healthz", {
|
|
1405
|
+
deadlineMs: Math.min(timeoutMs, healthTimeoutMs)
|
|
1406
|
+
});
|
|
1407
|
+
if (payload.ok !== true || payload.service !== "openmausbot-control-plane") {
|
|
1408
|
+
throw new ControlPlaneError("control_plane_unavailable");
|
|
1409
|
+
}
|
|
1410
|
+
return true;
|
|
1411
|
+
},
|
|
1412
|
+
async requestOTP(rawEmail) {
|
|
1413
|
+
const email = normalizeAccountEmail(rawEmail);
|
|
1414
|
+
if (!email) throw new ControlPlaneError("invalid_email");
|
|
1415
|
+
await request("/api/auth/email-otp/send-verification-otp", {
|
|
1416
|
+
method: "POST",
|
|
1417
|
+
body: { email, type: "sign-in" }
|
|
1418
|
+
});
|
|
1419
|
+
return { email };
|
|
1420
|
+
},
|
|
1421
|
+
async verifyOTP(rawEmail, rawOTP) {
|
|
1422
|
+
const email = normalizeAccountEmail(rawEmail);
|
|
1423
|
+
const otpInput = stringValue(rawOTP);
|
|
1424
|
+
const otp = otpInput !== null && otpInput.length <= 32 ? otpInput.replaceAll(/\s|-/g, "") : "";
|
|
1425
|
+
if (!email) throw new ControlPlaneError("invalid_email");
|
|
1426
|
+
if (!/^\d{8}$/.test(otp)) throw new ControlPlaneError("invalid_otp");
|
|
1427
|
+
const { response, payload } = await request("/api/auth/sign-in/email-otp", {
|
|
1428
|
+
method: "POST",
|
|
1429
|
+
body: { email, otp, name: email.split("@", 1)[0] }
|
|
1430
|
+
});
|
|
1431
|
+
const accountToken = boundedSecret(response.headers.get("set-auth-token"));
|
|
1432
|
+
const user = validatedUser(payload.user);
|
|
1433
|
+
if (!accountToken || !user || user.email !== email) {
|
|
1434
|
+
throw new ControlPlaneError("invalid_response", response.status);
|
|
1435
|
+
}
|
|
1436
|
+
return { accountToken, user };
|
|
1437
|
+
},
|
|
1438
|
+
async me(accountToken) {
|
|
1439
|
+
if (!boundedSecret(accountToken)) throw new ControlPlaneError("signed_out", 401);
|
|
1440
|
+
const { payload } = await request("/v1/me", { token: accountToken });
|
|
1441
|
+
const user = validatedUser(payload.user);
|
|
1442
|
+
if (!user) throw new ControlPlaneError("invalid_response");
|
|
1443
|
+
return user;
|
|
1444
|
+
},
|
|
1445
|
+
async listInstallations(accountToken) {
|
|
1446
|
+
return accountInstallations(accountToken);
|
|
1447
|
+
},
|
|
1448
|
+
async ensureInstallation({ accountToken, currentCredential, clientInstanceId, name, platform, appVersion }) {
|
|
1449
|
+
if (!validClientInstance(clientInstanceId)) {
|
|
1450
|
+
throw new ControlPlaneError("invalid_client_identity");
|
|
1451
|
+
}
|
|
1452
|
+
if (typeof currentCredential === "string" && INSTALLATION_CREDENTIAL.test(currentCredential)) {
|
|
1453
|
+
try {
|
|
1454
|
+
const { payload } = await request("/v1/installations/self", { token: currentCredential });
|
|
1455
|
+
const installation2 = validatedInstallation(payload.installation);
|
|
1456
|
+
if (installation2?.clientInstanceId === clientInstanceId) {
|
|
1457
|
+
return {
|
|
1458
|
+
installation: installation2,
|
|
1459
|
+
credential: currentCredential,
|
|
1460
|
+
credentialExpiresAt: Number.isSafeInteger(payload.credentialExpiresAt) ? payload.credentialExpiresAt : null
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
if (!(error instanceof ControlPlaneError) || error.status !== 401) throw error;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
if (!boundedSecret(accountToken)) throw new ControlPlaneError("signed_out", 401);
|
|
1468
|
+
const installations = await accountInstallations(accountToken);
|
|
1469
|
+
const existing = installations.find((item) => item.clientInstanceId === clientInstanceId);
|
|
1470
|
+
const result = existing ? await request(`/v1/installations/${encodeURIComponent(existing.id)}/credentials/rotate`, {
|
|
1471
|
+
method: "POST",
|
|
1472
|
+
token: accountToken
|
|
1473
|
+
}) : await request("/v1/installations", {
|
|
1474
|
+
method: "POST",
|
|
1475
|
+
token: accountToken,
|
|
1476
|
+
body: { clientInstanceId, name, platform, appVersion }
|
|
1477
|
+
});
|
|
1478
|
+
const installation = existing ?? validatedInstallation(result.payload.installation);
|
|
1479
|
+
const credential = stringValue(result.payload.credential);
|
|
1480
|
+
if (!installation || credential === null || !INSTALLATION_CREDENTIAL.test(credential)) {
|
|
1481
|
+
throw new ControlPlaneError("invalid_response");
|
|
1482
|
+
}
|
|
1483
|
+
return {
|
|
1484
|
+
installation,
|
|
1485
|
+
credential,
|
|
1486
|
+
credentialExpiresAt: Number.isSafeInteger(result.payload.credentialExpiresAt) ? result.payload.credentialExpiresAt : null
|
|
1487
|
+
};
|
|
1488
|
+
},
|
|
1489
|
+
async ensureEndpoint(installationCredential) {
|
|
1490
|
+
if (typeof installationCredential !== "string" || !INSTALLATION_CREDENTIAL.test(installationCredential)) {
|
|
1491
|
+
throw new ControlPlaneError("signed_out", 401);
|
|
1492
|
+
}
|
|
1493
|
+
const { payload } = await request("/v1/installations/self/endpoint", {
|
|
1494
|
+
method: "POST",
|
|
1495
|
+
token: installationCredential
|
|
1496
|
+
});
|
|
1497
|
+
const endpoint = validatedEndpoint(payload.endpoint);
|
|
1498
|
+
const connectorToken = boundedSecret(payload.connectorToken, 16384);
|
|
1499
|
+
if (!endpoint || !connectorToken) throw new ControlPlaneError("invalid_response");
|
|
1500
|
+
return { endpoint, connectorToken };
|
|
1501
|
+
},
|
|
1502
|
+
async deleteEndpoint(installationCredential) {
|
|
1503
|
+
if (typeof installationCredential !== "string" || !INSTALLATION_CREDENTIAL.test(installationCredential)) {
|
|
1504
|
+
throw new ControlPlaneError("signed_out", 401);
|
|
1505
|
+
}
|
|
1506
|
+
await request("/v1/installations/self/endpoint", {
|
|
1507
|
+
method: "DELETE",
|
|
1508
|
+
token: installationCredential,
|
|
1509
|
+
allowEmpty: true
|
|
1510
|
+
});
|
|
1511
|
+
},
|
|
1512
|
+
async revokeInstallation(accountToken, installationId) {
|
|
1513
|
+
if (!boundedSecret(accountToken) || typeof installationId !== "string" || !INSTALLATION_ID.test(installationId)) {
|
|
1514
|
+
throw new ControlPlaneError("signed_out", 401);
|
|
1515
|
+
}
|
|
1516
|
+
await request(`/v1/installations/${encodeURIComponent(installationId)}`, {
|
|
1517
|
+
method: "DELETE",
|
|
1518
|
+
token: accountToken,
|
|
1519
|
+
allowEmpty: true
|
|
1520
|
+
});
|
|
1521
|
+
},
|
|
1522
|
+
async signOut(accountToken) {
|
|
1523
|
+
if (!boundedSecret(accountToken)) return;
|
|
1524
|
+
await request("/api/auth/sign-out", {
|
|
1525
|
+
method: "POST",
|
|
1526
|
+
token: accountToken
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// electron/managed-companion-tunnel.mjs
|
|
1533
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1534
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1535
|
+
import fs2 from "node:fs";
|
|
1536
|
+
import os2 from "node:os";
|
|
1537
|
+
import path3 from "node:path";
|
|
1538
|
+
|
|
1539
|
+
// electron/companion-origin-gateway.mjs
|
|
1540
|
+
import fs from "node:fs";
|
|
1541
|
+
import http from "node:http";
|
|
1542
|
+
import os from "node:os";
|
|
1543
|
+
import path from "node:path";
|
|
1544
|
+
import { randomUUID } from "node:crypto";
|
|
1545
|
+
var MANAGED_COMPANION_ORIGIN_HOST = "127.0.0.1";
|
|
1546
|
+
var MANAGED_COMPANION_ORIGIN_PORT = 8812;
|
|
1547
|
+
var RUNTIME_PREFIX = "omb-companion-origin-";
|
|
1548
|
+
var SOCKET_NAME = "origin.sock";
|
|
1549
|
+
var currentUserId = () => {
|
|
1550
|
+
if (process.getuid) return process.getuid();
|
|
1551
|
+
const uid = os.userInfo().uid;
|
|
1552
|
+
return Number.isSafeInteger(uid) && uid >= 0 ? uid : void 0;
|
|
1553
|
+
};
|
|
1554
|
+
function validCompanionOriginTarget(target, platform = process.platform) {
|
|
1555
|
+
if (!Number.isSafeInteger(target?.pid) || target.pid <= 0) return false;
|
|
1556
|
+
if (Object.prototype.toString.call(target?.socketPath) !== "[object String]") return false;
|
|
1557
|
+
if (platform === "win32") {
|
|
1558
|
+
return /^\\\\\.\\pipe\\openmausbot-companion-origin-[1-9][0-9]*-[0-9a-f-]{36}$/i.test(
|
|
1559
|
+
target.socketPath
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
return path.isAbsolute(target.socketPath) && path.basename(target.socketPath) === SOCKET_NAME && path.basename(path.dirname(target.socketPath)).startsWith(RUNTIME_PREFIX);
|
|
1563
|
+
}
|
|
1564
|
+
function createCompanionOriginEndpoint({
|
|
1565
|
+
platform = process.platform,
|
|
1566
|
+
fileSystem = fs,
|
|
1567
|
+
processId = process.pid,
|
|
1568
|
+
identifier = randomUUID,
|
|
1569
|
+
temporaryRoot,
|
|
1570
|
+
currentUid = currentUserId()
|
|
1571
|
+
} = {}) {
|
|
1572
|
+
if (platform === "win32") {
|
|
1573
|
+
return Object.freeze({
|
|
1574
|
+
pid: processId,
|
|
1575
|
+
socketPath: `\\\\.\\pipe\\openmausbot-companion-origin-${processId}-${identifier()}`,
|
|
1576
|
+
directory: null
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
const root = temporaryRoot ?? fileSystem.realpathSync("/tmp");
|
|
1580
|
+
const directory = fileSystem.mkdtempSync(path.join(root, RUNTIME_PREFIX));
|
|
1581
|
+
try {
|
|
1582
|
+
fileSystem.chmodSync(directory, 448);
|
|
1583
|
+
const stat = fileSystem.lstatSync(directory);
|
|
1584
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || currentUid !== void 0 && stat.uid !== currentUid) {
|
|
1585
|
+
throw new Error("The companion origin directory is unsafe");
|
|
1586
|
+
}
|
|
1587
|
+
const socketPath = path.join(directory, SOCKET_NAME);
|
|
1588
|
+
if (Buffer.byteLength(socketPath) > 96) {
|
|
1589
|
+
throw new Error("The companion origin socket path is too long");
|
|
1590
|
+
}
|
|
1591
|
+
return Object.freeze({ pid: processId, socketPath, directory });
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
try {
|
|
1594
|
+
fileSystem.rmdirSync(directory);
|
|
1595
|
+
} catch {
|
|
1596
|
+
}
|
|
1597
|
+
throw error;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function cleanupCompanionOriginEndpoint(endpoint, { platform = process.platform, fileSystem = fs, currentUid = currentUserId() } = {}) {
|
|
1601
|
+
if (platform === "win32" || !endpoint?.directory) return;
|
|
1602
|
+
const directory = endpoint.directory;
|
|
1603
|
+
if (path.basename(directory).startsWith(RUNTIME_PREFIX) === false || path.dirname(endpoint.socketPath) !== directory || path.basename(endpoint.socketPath) !== SOCKET_NAME) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
const directoryStat = fileSystem.lstatSync(directory);
|
|
1608
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink() || currentUid !== void 0 && directoryStat.uid !== currentUid) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
try {
|
|
1612
|
+
const socketStat = fileSystem.lstatSync(endpoint.socketPath);
|
|
1613
|
+
if (socketStat.isSocket() && !socketStat.isSymbolicLink() && (currentUid === void 0 || socketStat.uid === currentUid)) {
|
|
1614
|
+
fileSystem.unlinkSync(endpoint.socketPath);
|
|
1615
|
+
}
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
if (error?.code !== "ENOENT") return;
|
|
1618
|
+
}
|
|
1619
|
+
fileSystem.rmdirSync(directory);
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function unavailable(response) {
|
|
1624
|
+
if (response.headersSent) return response.destroy();
|
|
1625
|
+
const body = JSON.stringify({ error: "companion origin unavailable" });
|
|
1626
|
+
response.writeHead(503, {
|
|
1627
|
+
"cache-control": "private, no-store",
|
|
1628
|
+
"content-length": Buffer.byteLength(body),
|
|
1629
|
+
"content-type": "application/json"
|
|
1630
|
+
});
|
|
1631
|
+
response.end(body);
|
|
1632
|
+
}
|
|
1633
|
+
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
1634
|
+
"connection",
|
|
1635
|
+
"keep-alive",
|
|
1636
|
+
"proxy-authenticate",
|
|
1637
|
+
"proxy-authorization",
|
|
1638
|
+
"proxy-connection",
|
|
1639
|
+
"te",
|
|
1640
|
+
"trailer",
|
|
1641
|
+
"transfer-encoding",
|
|
1642
|
+
"upgrade"
|
|
1643
|
+
]);
|
|
1644
|
+
function endToEndHeaders(headers = {}) {
|
|
1645
|
+
const blocked = new Set(HOP_BY_HOP_HEADERS);
|
|
1646
|
+
const connection = Array.isArray(headers.connection) ? headers.connection.join(",") : String(headers.connection ?? "");
|
|
1647
|
+
for (const name of connection.split(",")) blocked.add(name.trim().toLowerCase());
|
|
1648
|
+
return Object.fromEntries(
|
|
1649
|
+
Object.entries(headers).filter(([name, value]) => value !== void 0 && !blocked.has(name.toLowerCase()))
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
function upgradeHeaders(headers = {}) {
|
|
1653
|
+
if (String(headers.upgrade ?? "").toLowerCase() !== "websocket") return null;
|
|
1654
|
+
return {
|
|
1655
|
+
...endToEndHeaders(headers),
|
|
1656
|
+
connection: "Upgrade",
|
|
1657
|
+
upgrade: "websocket"
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function writeUpgrade(socket, response) {
|
|
1661
|
+
const lines = [];
|
|
1662
|
+
for (const name of [
|
|
1663
|
+
"upgrade",
|
|
1664
|
+
"connection",
|
|
1665
|
+
"sec-websocket-accept",
|
|
1666
|
+
"sec-websocket-protocol",
|
|
1667
|
+
"sec-websocket-extensions"
|
|
1668
|
+
]) {
|
|
1669
|
+
const value = response.headers[name];
|
|
1670
|
+
if (typeof value === "string") lines.push(`${name}: ${value}`);
|
|
1671
|
+
}
|
|
1672
|
+
socket.write(
|
|
1673
|
+
`HTTP/1.1 ${response.statusCode ?? 101} ${response.statusMessage ?? "Switching Protocols"}\r
|
|
1674
|
+
${lines.join("\r\n")}\r
|
|
1675
|
+
\r
|
|
1676
|
+
`
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
function destroySockets(sockets) {
|
|
1680
|
+
for (const socket of sockets) socket.destroy();
|
|
1681
|
+
sockets.clear();
|
|
1682
|
+
}
|
|
1683
|
+
function createCompanionOriginGateway({
|
|
1684
|
+
target,
|
|
1685
|
+
originHost = MANAGED_COMPANION_ORIGIN_HOST,
|
|
1686
|
+
originPort = MANAGED_COMPANION_ORIGIN_PORT,
|
|
1687
|
+
isTargetAlive = () => true,
|
|
1688
|
+
createServer = http.createServer,
|
|
1689
|
+
request = http.request,
|
|
1690
|
+
upstreamTimeoutMs = 3e4
|
|
1691
|
+
} = {}) {
|
|
1692
|
+
if (!validCompanionOriginTarget(target)) {
|
|
1693
|
+
throw new Error("The companion origin target is invalid");
|
|
1694
|
+
}
|
|
1695
|
+
let accepting = true;
|
|
1696
|
+
let listening = false;
|
|
1697
|
+
let transition = Promise.resolve();
|
|
1698
|
+
const upgradedSockets = /* @__PURE__ */ new Set();
|
|
1699
|
+
const server = createServer((incoming, outgoing) => {
|
|
1700
|
+
if (!accepting || !isTargetAlive(target)) return unavailable(outgoing);
|
|
1701
|
+
const upstream = request(
|
|
1702
|
+
{
|
|
1703
|
+
headers: endToEndHeaders(incoming.headers),
|
|
1704
|
+
method: incoming.method,
|
|
1705
|
+
path: incoming.url,
|
|
1706
|
+
socketPath: target.socketPath,
|
|
1707
|
+
timeout: upstreamTimeoutMs
|
|
1708
|
+
},
|
|
1709
|
+
(response) => {
|
|
1710
|
+
upstream.setTimeout(0);
|
|
1711
|
+
if (!accepting || !isTargetAlive(target)) {
|
|
1712
|
+
response.destroy();
|
|
1713
|
+
return unavailable(outgoing);
|
|
1714
|
+
}
|
|
1715
|
+
outgoing.writeHead(response.statusCode ?? 502, endToEndHeaders(response.headers));
|
|
1716
|
+
response.once("error", () => outgoing.destroy());
|
|
1717
|
+
response.once("aborted", () => outgoing.destroy());
|
|
1718
|
+
response.once("close", () => {
|
|
1719
|
+
if (!response.complete) outgoing.destroy();
|
|
1720
|
+
});
|
|
1721
|
+
response.pipe(outgoing);
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
upstream.once("timeout", () => upstream.destroy(new Error("companion origin timed out")));
|
|
1725
|
+
upstream.once("error", () => unavailable(outgoing));
|
|
1726
|
+
incoming.once("aborted", () => upstream.destroy());
|
|
1727
|
+
outgoing.once("close", () => upstream.destroy());
|
|
1728
|
+
incoming.pipe(upstream);
|
|
1729
|
+
});
|
|
1730
|
+
server.on("upgrade", (incoming, socket, head) => {
|
|
1731
|
+
if (!accepting || !isTargetAlive(target)) return socket.destroy();
|
|
1732
|
+
const headers = upgradeHeaders(incoming.headers);
|
|
1733
|
+
if (!headers || incoming.method !== "GET") return socket.destroy();
|
|
1734
|
+
upgradedSockets.add(socket);
|
|
1735
|
+
let remoteSocket = null;
|
|
1736
|
+
const release = () => {
|
|
1737
|
+
upgradedSockets.delete(socket);
|
|
1738
|
+
if (remoteSocket) upgradedSockets.delete(remoteSocket);
|
|
1739
|
+
};
|
|
1740
|
+
socket.once("close", release);
|
|
1741
|
+
const upstream = request({
|
|
1742
|
+
headers,
|
|
1743
|
+
method: "GET",
|
|
1744
|
+
path: incoming.url,
|
|
1745
|
+
socketPath: target.socketPath,
|
|
1746
|
+
timeout: upstreamTimeoutMs
|
|
1747
|
+
});
|
|
1748
|
+
upstream.once("timeout", () => upstream.destroy(new Error("companion origin upgrade timed out")));
|
|
1749
|
+
upstream.once("upgrade", (response, remote, remoteHead) => {
|
|
1750
|
+
upstream.setTimeout(0);
|
|
1751
|
+
remote.setTimeout(0);
|
|
1752
|
+
if (!accepting || !isTargetAlive(target)) {
|
|
1753
|
+
remote.destroy();
|
|
1754
|
+
socket.destroy();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
writeUpgrade(socket, response);
|
|
1758
|
+
remoteSocket = remote;
|
|
1759
|
+
upgradedSockets.add(remote);
|
|
1760
|
+
remote.once("close", release);
|
|
1761
|
+
if (head.length) remote.write(head);
|
|
1762
|
+
if (remoteHead.length) socket.write(remoteHead);
|
|
1763
|
+
socket.pipe(remote).pipe(socket);
|
|
1764
|
+
});
|
|
1765
|
+
upstream.once("response", (response) => {
|
|
1766
|
+
response.resume();
|
|
1767
|
+
socket.destroy();
|
|
1768
|
+
});
|
|
1769
|
+
upstream.once("error", () => socket.destroy());
|
|
1770
|
+
socket.once("close", () => upstream.destroy());
|
|
1771
|
+
upstream.end();
|
|
1772
|
+
});
|
|
1773
|
+
server.on("clientError", (_error, socket) => socket.destroy());
|
|
1774
|
+
const serialize = (work) => {
|
|
1775
|
+
const next = transition.then(work, work);
|
|
1776
|
+
transition = next.then(
|
|
1777
|
+
() => {
|
|
1778
|
+
},
|
|
1779
|
+
() => {
|
|
1780
|
+
}
|
|
1781
|
+
);
|
|
1782
|
+
return next;
|
|
1783
|
+
};
|
|
1784
|
+
return Object.freeze({
|
|
1785
|
+
start() {
|
|
1786
|
+
return serialize(async () => {
|
|
1787
|
+
if (listening) return server.address();
|
|
1788
|
+
await new Promise((resolve3, reject) => {
|
|
1789
|
+
const onError = (error) => {
|
|
1790
|
+
server.off("listening", onListening);
|
|
1791
|
+
reject(error);
|
|
1792
|
+
};
|
|
1793
|
+
const onListening = () => {
|
|
1794
|
+
server.off("error", onError);
|
|
1795
|
+
resolve3();
|
|
1796
|
+
};
|
|
1797
|
+
server.once("error", onError);
|
|
1798
|
+
server.once("listening", onListening);
|
|
1799
|
+
server.listen({ exclusive: true, host: originHost, port: originPort });
|
|
1800
|
+
});
|
|
1801
|
+
listening = true;
|
|
1802
|
+
return server.address();
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
1805
|
+
invalidate() {
|
|
1806
|
+
accepting = false;
|
|
1807
|
+
server.closeAllConnections?.();
|
|
1808
|
+
destroySockets(upgradedSockets);
|
|
1809
|
+
},
|
|
1810
|
+
close() {
|
|
1811
|
+
return serialize(async () => {
|
|
1812
|
+
accepting = false;
|
|
1813
|
+
server.closeAllConnections?.();
|
|
1814
|
+
destroySockets(upgradedSockets);
|
|
1815
|
+
if (!listening) return;
|
|
1816
|
+
await new Promise((resolve3) => server.close(() => resolve3()));
|
|
1817
|
+
listening = false;
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// electron/managed-companion-guardian.mjs
|
|
1824
|
+
import { spawn } from "node:child_process";
|
|
1825
|
+
import path2 from "node:path";
|
|
1826
|
+
import { fileURLToPath } from "node:url";
|
|
1827
|
+
var copyEnvironmentValue = (source, target, names) => {
|
|
1828
|
+
const entries = Object.entries(source);
|
|
1829
|
+
for (const name of names) {
|
|
1830
|
+
const found = entries.find(([key]) => key.toLowerCase() === name.toLowerCase());
|
|
1831
|
+
if (found) target[found[0]] = found[1];
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
function minimalCloudflaredEnvironment(environment = process.env, platform = process.platform) {
|
|
1835
|
+
const minimal = {};
|
|
1836
|
+
copyEnvironmentValue(environment, minimal, ["PATH"]);
|
|
1837
|
+
if (platform === "win32") {
|
|
1838
|
+
copyEnvironmentValue(environment, minimal, ["SystemRoot", "WINDIR", "TEMP", "TMP"]);
|
|
1839
|
+
}
|
|
1840
|
+
return minimal;
|
|
1841
|
+
}
|
|
1842
|
+
function minimalGuardianEnvironment(environment = process.env, platform = process.platform) {
|
|
1843
|
+
return {
|
|
1844
|
+
...minimalCloudflaredEnvironment(environment, platform),
|
|
1845
|
+
ELECTRON_RUN_AS_NODE: "1"
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function processIsAlive(pid) {
|
|
1849
|
+
try {
|
|
1850
|
+
process.kill(pid, 0);
|
|
1851
|
+
return true;
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
return error?.code !== "ESRCH";
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
var delay = (milliseconds) => new Promise((resolve3) => {
|
|
1857
|
+
const timer = setTimeout(resolve3, milliseconds);
|
|
1858
|
+
timer.unref?.();
|
|
1859
|
+
});
|
|
1860
|
+
async function terminateConnector(child, graceMs = 250) {
|
|
1861
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
1862
|
+
let exited = false;
|
|
1863
|
+
const exit = new Promise((resolve3) => {
|
|
1864
|
+
const finish = () => {
|
|
1865
|
+
if (exited) return;
|
|
1866
|
+
exited = true;
|
|
1867
|
+
resolve3();
|
|
1868
|
+
};
|
|
1869
|
+
child.once("exit", finish);
|
|
1870
|
+
child.once("error", () => {
|
|
1871
|
+
if (!Number.isSafeInteger(child.pid)) finish();
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
try {
|
|
1875
|
+
child.kill("SIGTERM");
|
|
1876
|
+
} catch {
|
|
1877
|
+
}
|
|
1878
|
+
await Promise.race([exit, delay(graceMs)]);
|
|
1879
|
+
if (exited || child.exitCode !== null || child.signalCode !== null) return;
|
|
1880
|
+
try {
|
|
1881
|
+
child.kill("SIGKILL");
|
|
1882
|
+
} catch {
|
|
1883
|
+
}
|
|
1884
|
+
await exit;
|
|
1885
|
+
}
|
|
1886
|
+
function ownerLoss(ownerInput, signalSource) {
|
|
1887
|
+
return new Promise((resolve3) => {
|
|
1888
|
+
let settled = false;
|
|
1889
|
+
const finish = () => {
|
|
1890
|
+
if (settled) return;
|
|
1891
|
+
settled = true;
|
|
1892
|
+
resolve3({ kind: "owner" });
|
|
1893
|
+
};
|
|
1894
|
+
ownerInput.once("end", finish);
|
|
1895
|
+
ownerInput.once("close", finish);
|
|
1896
|
+
ownerInput.once("error", finish);
|
|
1897
|
+
signalSource.once("SIGINT", finish);
|
|
1898
|
+
signalSource.once("SIGTERM", finish);
|
|
1899
|
+
ownerInput.resume?.();
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
async function runManagedCompanionGuardian({
|
|
1903
|
+
cloudflaredBinary,
|
|
1904
|
+
tokenFile,
|
|
1905
|
+
target,
|
|
1906
|
+
originPort = MANAGED_COMPANION_ORIGIN_PORT,
|
|
1907
|
+
environment = process.env,
|
|
1908
|
+
platform = process.platform,
|
|
1909
|
+
ownerInput = process.stdin,
|
|
1910
|
+
signalSource = process,
|
|
1911
|
+
spawnProcess = spawn,
|
|
1912
|
+
isTargetAlive = ({ pid }) => processIsAlive(pid),
|
|
1913
|
+
createGateway = createCompanionOriginGateway
|
|
1914
|
+
} = {}) {
|
|
1915
|
+
if (!path2.isAbsolute(cloudflaredBinary ?? "") || !path2.isAbsolute(tokenFile ?? "")) {
|
|
1916
|
+
throw new Error("The managed connector paths are invalid");
|
|
1917
|
+
}
|
|
1918
|
+
if (!validCompanionOriginTarget(target, platform)) {
|
|
1919
|
+
throw new Error("The managed companion origin target is invalid");
|
|
1920
|
+
}
|
|
1921
|
+
if (!Number.isInteger(originPort) || originPort < 1 || originPort > 65535) {
|
|
1922
|
+
throw new Error("The managed companion origin port is invalid");
|
|
1923
|
+
}
|
|
1924
|
+
const gateway = createGateway({
|
|
1925
|
+
target,
|
|
1926
|
+
originPort,
|
|
1927
|
+
isTargetAlive
|
|
1928
|
+
});
|
|
1929
|
+
await gateway.start();
|
|
1930
|
+
let connector;
|
|
1931
|
+
try {
|
|
1932
|
+
connector = spawnProcess(
|
|
1933
|
+
cloudflaredBinary,
|
|
1934
|
+
[
|
|
1935
|
+
"tunnel",
|
|
1936
|
+
"--no-autoupdate",
|
|
1937
|
+
"--loglevel",
|
|
1938
|
+
"info",
|
|
1939
|
+
"--output",
|
|
1940
|
+
"json",
|
|
1941
|
+
"run",
|
|
1942
|
+
"--token-file",
|
|
1943
|
+
tokenFile
|
|
1944
|
+
],
|
|
1945
|
+
{
|
|
1946
|
+
env: minimalCloudflaredEnvironment(environment, platform),
|
|
1947
|
+
shell: false,
|
|
1948
|
+
windowsHide: true,
|
|
1949
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
1950
|
+
}
|
|
1951
|
+
);
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
gateway.invalidate();
|
|
1954
|
+
await gateway.close();
|
|
1955
|
+
throw error;
|
|
1956
|
+
}
|
|
1957
|
+
const connectorExit = new Promise((resolve3) => {
|
|
1958
|
+
connector.once("exit", (code) => resolve3({ kind: "connector", code: code ?? 1 }));
|
|
1959
|
+
connector.once("error", () => {
|
|
1960
|
+
if (!Number.isSafeInteger(connector.pid)) {
|
|
1961
|
+
resolve3({ kind: "connector", code: 1 });
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
});
|
|
1965
|
+
const outcome = await Promise.race([connectorExit, ownerLoss(ownerInput, signalSource)]);
|
|
1966
|
+
gateway.invalidate();
|
|
1967
|
+
if (outcome.kind === "owner") await terminateConnector(connector);
|
|
1968
|
+
await gateway.close();
|
|
1969
|
+
return outcome.kind === "connector" ? outcome.code : 0;
|
|
1970
|
+
}
|
|
1971
|
+
function guardianArguments(argv) {
|
|
1972
|
+
if (argv.length !== 5) throw new Error("The managed companion guardian arguments are invalid");
|
|
1973
|
+
const [cloudflaredBinary, tokenFile, socketPath, rawPid, rawPort] = argv;
|
|
1974
|
+
const pid = Number(rawPid);
|
|
1975
|
+
const originPort = Number(rawPort);
|
|
1976
|
+
const target = { pid, socketPath };
|
|
1977
|
+
if (!validCompanionOriginTarget(target)) {
|
|
1978
|
+
throw new Error("The managed companion guardian target is invalid");
|
|
1979
|
+
}
|
|
1980
|
+
return { cloudflaredBinary, tokenFile, target, originPort };
|
|
1981
|
+
}
|
|
1982
|
+
var isDirectExecution = process.argv[1] && path2.resolve(process.argv[1]) === path2.resolve(fileURLToPath(import.meta.url));
|
|
1983
|
+
if (isDirectExecution) {
|
|
1984
|
+
runManagedCompanionGuardian(guardianArguments(process.argv.slice(2))).then(
|
|
1985
|
+
(code) => process.exit(code),
|
|
1986
|
+
() => process.exit(1)
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// electron/managed-companion-tunnel.mjs
|
|
1991
|
+
var MANAGED_COMPANION_ENDPOINT_FIELD = "managedCompanionEndpointUrl";
|
|
1992
|
+
var MANAGED_COMPANION_TOKEN_FIELD = "managedCompanionConnectorToken";
|
|
1993
|
+
var MANAGED_COMPANION_ORIGIN_VERSION_FIELD = "managedCompanionOriginVersion";
|
|
1994
|
+
var MANAGED_COMPANION_ORIGIN_VERSION = 2;
|
|
1995
|
+
var TOKEN_FILE_PATTERN = /^connector-([1-9][0-9]*)-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.token$/;
|
|
1996
|
+
var TOKEN_MIN_BYTES = 40;
|
|
1997
|
+
var TOKEN_MAX_BYTES = 16 * 1024;
|
|
1998
|
+
var isString = (value) => Object.prototype.toString.call(value) === "[object String]";
|
|
1999
|
+
function normalizeManagedCompanionEndpoint(value) {
|
|
2000
|
+
if (!isString(value) || !value.trim()) return "";
|
|
2001
|
+
let parsed;
|
|
2002
|
+
try {
|
|
2003
|
+
parsed = new URL(value.trim());
|
|
2004
|
+
} catch {
|
|
2005
|
+
return "";
|
|
2006
|
+
}
|
|
2007
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.pathname !== "" && parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
2008
|
+
return "";
|
|
2009
|
+
}
|
|
2010
|
+
return parsed.origin;
|
|
2011
|
+
}
|
|
2012
|
+
function validConnectorToken(value) {
|
|
2013
|
+
if (!isString(value)) return false;
|
|
2014
|
+
const bytes = Buffer.byteLength(value);
|
|
2015
|
+
return bytes >= TOKEN_MIN_BYTES && bytes <= TOKEN_MAX_BYTES && value.trim() === value && !/[\0\r\n\t ]/.test(value);
|
|
2016
|
+
}
|
|
2017
|
+
function normalizeManagedCompanionAccess(credentials) {
|
|
2018
|
+
const endpoint = normalizeManagedCompanionEndpoint(
|
|
2019
|
+
credentials?.[MANAGED_COMPANION_ENDPOINT_FIELD]
|
|
2020
|
+
);
|
|
2021
|
+
const token = credentials?.[MANAGED_COMPANION_TOKEN_FIELD];
|
|
2022
|
+
if (!endpoint || !validConnectorToken(token)) return null;
|
|
2023
|
+
return { endpoint, token };
|
|
2024
|
+
}
|
|
2025
|
+
function managedCompanionTunnelAccess(credentials) {
|
|
2026
|
+
if (credentials?.[MANAGED_COMPANION_ORIGIN_VERSION_FIELD] !== MANAGED_COMPANION_ORIGIN_VERSION) {
|
|
2027
|
+
return null;
|
|
2028
|
+
}
|
|
2029
|
+
return normalizeManagedCompanionAccess(credentials);
|
|
2030
|
+
}
|
|
2031
|
+
function withManagedCompanionTunnelAccess(credentials, provision) {
|
|
2032
|
+
const endpoint = normalizeManagedCompanionEndpoint(provision?.endpoint?.url);
|
|
2033
|
+
const token = provision?.connectorToken;
|
|
2034
|
+
if (!endpoint || !validConnectorToken(token)) {
|
|
2035
|
+
throw new Error("The companion service returned an invalid managed endpoint");
|
|
2036
|
+
}
|
|
2037
|
+
return {
|
|
2038
|
+
...credentials,
|
|
2039
|
+
[MANAGED_COMPANION_ENDPOINT_FIELD]: endpoint,
|
|
2040
|
+
[MANAGED_COMPANION_TOKEN_FIELD]: token,
|
|
2041
|
+
[MANAGED_COMPANION_ORIGIN_VERSION_FIELD]: MANAGED_COMPANION_ORIGIN_VERSION
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
function withoutManagedCompanionTunnelAccess(credentials) {
|
|
2045
|
+
const next = { ...credentials };
|
|
2046
|
+
delete next[MANAGED_COMPANION_ENDPOINT_FIELD];
|
|
2047
|
+
delete next[MANAGED_COMPANION_TOKEN_FIELD];
|
|
2048
|
+
delete next[MANAGED_COMPANION_ORIGIN_VERSION_FIELD];
|
|
2049
|
+
return next;
|
|
2050
|
+
}
|
|
2051
|
+
function resolveCloudflaredBinary({
|
|
2052
|
+
isPackaged,
|
|
2053
|
+
resourcesPath,
|
|
2054
|
+
appPath,
|
|
2055
|
+
platform = process.platform,
|
|
2056
|
+
arch = process.arch,
|
|
2057
|
+
environment = process.env,
|
|
2058
|
+
exists = fs2.existsSync
|
|
2059
|
+
} = {}) {
|
|
2060
|
+
const executable = platform === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
2061
|
+
const bundled = path3.join(String(resourcesPath ?? ""), "cloudflared", executable);
|
|
2062
|
+
if (isPackaged) return exists(bundled) ? bundled : null;
|
|
2063
|
+
const override = environment.OMB_CLOUDFLARED_PATH?.trim();
|
|
2064
|
+
if (override) return path3.isAbsolute(override) && exists(override) ? override : null;
|
|
2065
|
+
const staged = path3.join(
|
|
2066
|
+
String(appPath ?? ""),
|
|
2067
|
+
"dist-native",
|
|
2068
|
+
"cloudflared",
|
|
2069
|
+
`${platform}-${arch}`,
|
|
2070
|
+
executable
|
|
2071
|
+
);
|
|
2072
|
+
if (exists(staged)) return staged;
|
|
2073
|
+
const pathEntry = Object.entries(environment).find(([name]) => name.toLowerCase() === "path");
|
|
2074
|
+
const pathValue = pathEntry?.[1];
|
|
2075
|
+
if (!pathValue) return null;
|
|
2076
|
+
const delimiter2 = platform === "win32" ? ";" : ":";
|
|
2077
|
+
for (const directory of pathValue.split(delimiter2)) {
|
|
2078
|
+
if (!directory || !path3.isAbsolute(directory)) continue;
|
|
2079
|
+
const candidate = path3.join(directory, executable);
|
|
2080
|
+
if (exists(candidate)) return candidate;
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
function ensurePrivateDirectory(directory, fileSystem, currentUid) {
|
|
2085
|
+
fileSystem.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2086
|
+
const stat = fileSystem.lstatSync(directory);
|
|
2087
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
2088
|
+
throw new Error("The managed companion runtime path is unsafe");
|
|
2089
|
+
}
|
|
2090
|
+
if (currentUid !== void 0 && stat.uid !== currentUid) {
|
|
2091
|
+
throw new Error("The managed companion runtime path has an unexpected owner");
|
|
2092
|
+
}
|
|
2093
|
+
fileSystem.chmodSync(directory, 448);
|
|
2094
|
+
}
|
|
2095
|
+
function processIsAlive2(pid) {
|
|
2096
|
+
try {
|
|
2097
|
+
process.kill(pid, 0);
|
|
2098
|
+
return true;
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
return error?.code !== "ESRCH";
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
var currentUserId2 = () => process.platform === "win32" ? void 0 : process.getuid?.() ?? os2.userInfo().uid;
|
|
2104
|
+
function cleanupStaleManagedCompanionTokens(runtimeRoot, {
|
|
2105
|
+
fileSystem = fs2,
|
|
2106
|
+
currentUid = currentUserId2(),
|
|
2107
|
+
isProcessAlive = processIsAlive2
|
|
2108
|
+
} = {}) {
|
|
2109
|
+
let entries;
|
|
2110
|
+
try {
|
|
2111
|
+
ensurePrivateDirectory(runtimeRoot, fileSystem, currentUid);
|
|
2112
|
+
entries = fileSystem.readdirSync(runtimeRoot, { withFileTypes: true });
|
|
2113
|
+
} catch {
|
|
2114
|
+
return 0;
|
|
2115
|
+
}
|
|
2116
|
+
let removed = 0;
|
|
2117
|
+
for (const entry of entries) {
|
|
2118
|
+
const match = TOKEN_FILE_PATTERN.exec(entry.name);
|
|
2119
|
+
if (!match || !entry.isFile() || entry.isSymbolicLink()) continue;
|
|
2120
|
+
const ownerPid = Number(match[1]);
|
|
2121
|
+
if (!Number.isSafeInteger(ownerPid) || isProcessAlive(ownerPid)) continue;
|
|
2122
|
+
const file = path3.join(runtimeRoot, entry.name);
|
|
2123
|
+
try {
|
|
2124
|
+
const stat = fileSystem.lstatSync(file);
|
|
2125
|
+
if (!stat.isFile() || stat.isSymbolicLink() || currentUid !== void 0 && stat.uid !== currentUid || process.platform !== "win32" && (stat.mode & 63) !== 0) {
|
|
2126
|
+
continue;
|
|
2127
|
+
}
|
|
2128
|
+
fileSystem.unlinkSync(file);
|
|
2129
|
+
removed += 1;
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return removed;
|
|
2134
|
+
}
|
|
2135
|
+
function writePrivateToken(runtimeRoot, token, { fileSystem, processId, identifier, currentUid }) {
|
|
2136
|
+
ensurePrivateDirectory(runtimeRoot, fileSystem, currentUid);
|
|
2137
|
+
const file = path3.join(runtimeRoot, `connector-${processId}-${identifier()}.token`);
|
|
2138
|
+
let descriptor;
|
|
2139
|
+
try {
|
|
2140
|
+
descriptor = fileSystem.openSync(file, "wx", 384);
|
|
2141
|
+
fileSystem.writeFileSync(descriptor, token, "utf8");
|
|
2142
|
+
fileSystem.fsyncSync(descriptor);
|
|
2143
|
+
fileSystem.closeSync(descriptor);
|
|
2144
|
+
descriptor = void 0;
|
|
2145
|
+
fileSystem.chmodSync(file, 384);
|
|
2146
|
+
return file;
|
|
2147
|
+
} catch (error) {
|
|
2148
|
+
if (descriptor !== void 0) {
|
|
2149
|
+
try {
|
|
2150
|
+
fileSystem.closeSync(descriptor);
|
|
2151
|
+
} catch {
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
fileSystem.unlinkSync(file);
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
throw error;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
async function waitForExit(child, milliseconds) {
|
|
2162
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
2163
|
+
await new Promise((resolve3) => {
|
|
2164
|
+
let settled = false;
|
|
2165
|
+
const finish = () => {
|
|
2166
|
+
if (settled) return;
|
|
2167
|
+
settled = true;
|
|
2168
|
+
clearTimeout(timer);
|
|
2169
|
+
resolve3();
|
|
2170
|
+
};
|
|
2171
|
+
child.once("exit", finish);
|
|
2172
|
+
const timer = setTimeout(finish, milliseconds);
|
|
2173
|
+
timer.unref?.();
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
async function terminateChild(child, graceMs) {
|
|
2177
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
2178
|
+
try {
|
|
2179
|
+
child.stdin?.end();
|
|
2180
|
+
} catch {
|
|
2181
|
+
}
|
|
2182
|
+
await waitForExit(child, graceMs);
|
|
2183
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
2184
|
+
try {
|
|
2185
|
+
child.kill("SIGTERM");
|
|
2186
|
+
} catch {
|
|
2187
|
+
}
|
|
2188
|
+
await waitForExit(child, Math.min(graceMs, 500));
|
|
2189
|
+
}
|
|
2190
|
+
async function verifyHostedEndpoint(endpoint, { fetchImpl, timeoutSignal, timeoutMs, cancellationSignal }) {
|
|
2191
|
+
const requestSignal = timeoutSignal(timeoutMs);
|
|
2192
|
+
const response = await fetchImpl(`${endpoint}/api/health`, {
|
|
2193
|
+
headers: { accept: "application/json" },
|
|
2194
|
+
redirect: "error",
|
|
2195
|
+
signal: cancellationSignal ? AbortSignal.any([requestSignal, cancellationSignal]) : requestSignal
|
|
2196
|
+
});
|
|
2197
|
+
if (!response.ok) return false;
|
|
2198
|
+
const text = await response.text();
|
|
2199
|
+
if (Buffer.byteLength(text) > 4096) return false;
|
|
2200
|
+
try {
|
|
2201
|
+
return JSON.parse(text)?.app === "openmausbot";
|
|
2202
|
+
} catch {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function createManagedCompanionTunnel({
|
|
2207
|
+
binaryPath,
|
|
2208
|
+
guardianEntry: guardianEntry2,
|
|
2209
|
+
runtimeExecutable = process.execPath,
|
|
2210
|
+
originPort = MANAGED_COMPANION_ORIGIN_PORT,
|
|
2211
|
+
runtimeRoot,
|
|
2212
|
+
environment = process.env,
|
|
2213
|
+
fileSystem = fs2,
|
|
2214
|
+
spawnProcess = spawn2,
|
|
2215
|
+
fetchImpl = globalThis.fetch,
|
|
2216
|
+
timeoutSignal = (milliseconds) => AbortSignal.timeout(milliseconds),
|
|
2217
|
+
identifier = randomUUID2,
|
|
2218
|
+
processId = process.pid,
|
|
2219
|
+
currentUid = currentUserId2(),
|
|
2220
|
+
isProcessAlive = processIsAlive2,
|
|
2221
|
+
verifyTimeoutMs = 15e3,
|
|
2222
|
+
verifyRequestTimeoutMs = 2500,
|
|
2223
|
+
verifyIntervalMs = 250,
|
|
2224
|
+
stopGraceMs = 1500,
|
|
2225
|
+
maxRetryMs = 3e4,
|
|
2226
|
+
onChange = () => {
|
|
2227
|
+
},
|
|
2228
|
+
log = () => {
|
|
2229
|
+
}
|
|
2230
|
+
} = {}) {
|
|
2231
|
+
let desired = false;
|
|
2232
|
+
let generation = 0;
|
|
2233
|
+
let intentRevision = 0;
|
|
2234
|
+
let child = null;
|
|
2235
|
+
let tokenFile = null;
|
|
2236
|
+
let activeAttempt = null;
|
|
2237
|
+
let originTarget = null;
|
|
2238
|
+
let access = null;
|
|
2239
|
+
let retryAttempt = 0;
|
|
2240
|
+
let retryTimer = null;
|
|
2241
|
+
let state = Object.freeze({ status: "stopped", ready: false, configured: false });
|
|
2242
|
+
let transition = Promise.resolve();
|
|
2243
|
+
cleanupStaleManagedCompanionTokens(runtimeRoot, {
|
|
2244
|
+
fileSystem,
|
|
2245
|
+
currentUid,
|
|
2246
|
+
isProcessAlive
|
|
2247
|
+
});
|
|
2248
|
+
const publish = (next) => {
|
|
2249
|
+
const published = {
|
|
2250
|
+
configured: Boolean(access),
|
|
2251
|
+
ready: next.status === "ready",
|
|
2252
|
+
...next
|
|
2253
|
+
};
|
|
2254
|
+
if (access) published.endpoint = access.endpoint;
|
|
2255
|
+
state = Object.freeze(published);
|
|
2256
|
+
onChange(state);
|
|
2257
|
+
return state;
|
|
2258
|
+
};
|
|
2259
|
+
const removeTokenFile = (file = tokenFile) => {
|
|
2260
|
+
if (!file) return;
|
|
2261
|
+
if (tokenFile === file) tokenFile = null;
|
|
2262
|
+
try {
|
|
2263
|
+
fileSystem.unlinkSync(file);
|
|
2264
|
+
} catch (error) {
|
|
2265
|
+
if (error?.code !== "ENOENT") log("managed companion connector token cleanup failed");
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
const serialize = (work) => {
|
|
2269
|
+
const next = transition.then(work, work);
|
|
2270
|
+
transition = next.then(
|
|
2271
|
+
() => {
|
|
2272
|
+
},
|
|
2273
|
+
() => {
|
|
2274
|
+
}
|
|
2275
|
+
);
|
|
2276
|
+
return next;
|
|
2277
|
+
};
|
|
2278
|
+
const scheduleRetry = (reason, ownedGeneration) => {
|
|
2279
|
+
if (!desired || ownedGeneration !== generation || retryTimer) return;
|
|
2280
|
+
const delay2 = Math.min(1e3 * 2 ** retryAttempt, maxRetryMs);
|
|
2281
|
+
retryAttempt += 1;
|
|
2282
|
+
publish({ status: "retrying", ready: false, error: reason, retryInMs: delay2 });
|
|
2283
|
+
retryTimer = setTimeout(() => {
|
|
2284
|
+
retryTimer = null;
|
|
2285
|
+
if (!desired || ownedGeneration !== generation) return;
|
|
2286
|
+
void serialize(() => attempt(ownedGeneration));
|
|
2287
|
+
}, delay2);
|
|
2288
|
+
retryTimer.unref?.();
|
|
2289
|
+
};
|
|
2290
|
+
const attempt = async (ownedGeneration) => {
|
|
2291
|
+
if (!desired || ownedGeneration !== generation || !access) return state;
|
|
2292
|
+
if (!binaryPath || !path3.isAbsolute(binaryPath) || !guardianEntry2 || !path3.isAbsolute(guardianEntry2) || !path3.isAbsolute(runtimeExecutable) || !validCompanionOriginTarget(originTarget)) {
|
|
2293
|
+
publish({
|
|
2294
|
+
status: "unavailable",
|
|
2295
|
+
ready: false,
|
|
2296
|
+
error: "The managed companion connector is missing from this build."
|
|
2297
|
+
});
|
|
2298
|
+
return state;
|
|
2299
|
+
}
|
|
2300
|
+
publish({ status: "starting", ready: false });
|
|
2301
|
+
let attemptHandle = null;
|
|
2302
|
+
let attemptTokenFile = null;
|
|
2303
|
+
try {
|
|
2304
|
+
tokenFile = writePrivateToken(runtimeRoot, access.token, {
|
|
2305
|
+
fileSystem,
|
|
2306
|
+
processId,
|
|
2307
|
+
identifier,
|
|
2308
|
+
currentUid
|
|
2309
|
+
});
|
|
2310
|
+
attemptTokenFile = tokenFile;
|
|
2311
|
+
const spawned = spawnProcess(
|
|
2312
|
+
runtimeExecutable,
|
|
2313
|
+
[
|
|
2314
|
+
guardianEntry2,
|
|
2315
|
+
binaryPath,
|
|
2316
|
+
tokenFile,
|
|
2317
|
+
originTarget.socketPath,
|
|
2318
|
+
String(originTarget.pid),
|
|
2319
|
+
String(originPort)
|
|
2320
|
+
],
|
|
2321
|
+
{
|
|
2322
|
+
env: minimalGuardianEnvironment(environment),
|
|
2323
|
+
shell: false,
|
|
2324
|
+
windowsHide: true,
|
|
2325
|
+
// This open pipe is the parent-death signal. The guardian owns both
|
|
2326
|
+
// gateway and connector and tears them down on EOF.
|
|
2327
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2328
|
+
}
|
|
2329
|
+
);
|
|
2330
|
+
child = spawned;
|
|
2331
|
+
const cancellationController = new AbortController();
|
|
2332
|
+
let resolveCancellation;
|
|
2333
|
+
const cancellation = new Promise((resolve3) => {
|
|
2334
|
+
resolveCancellation = resolve3;
|
|
2335
|
+
});
|
|
2336
|
+
let cancelled = false;
|
|
2337
|
+
attemptHandle = {
|
|
2338
|
+
generation: ownedGeneration,
|
|
2339
|
+
cancel() {
|
|
2340
|
+
if (cancelled) return;
|
|
2341
|
+
cancelled = true;
|
|
2342
|
+
cancellationController.abort();
|
|
2343
|
+
resolveCancellation(false);
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
activeAttempt = attemptHandle;
|
|
2347
|
+
let terminated = false;
|
|
2348
|
+
let expectedStop = false;
|
|
2349
|
+
let resolveExit;
|
|
2350
|
+
const exit = new Promise((resolve3) => {
|
|
2351
|
+
resolveExit = resolve3;
|
|
2352
|
+
});
|
|
2353
|
+
const onTerminated = () => {
|
|
2354
|
+
if (terminated) return;
|
|
2355
|
+
terminated = true;
|
|
2356
|
+
resolveExit();
|
|
2357
|
+
if (child === spawned) child = null;
|
|
2358
|
+
removeTokenFile(attemptTokenFile);
|
|
2359
|
+
if (!expectedStop && desired && ownedGeneration === generation) {
|
|
2360
|
+
scheduleRetry("The secure connection stopped unexpectedly.", ownedGeneration);
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
spawned.once("exit", onTerminated);
|
|
2364
|
+
spawned.once("error", onTerminated);
|
|
2365
|
+
const deadline = Date.now() + verifyTimeoutMs;
|
|
2366
|
+
while (desired && ownedGeneration === generation && child === spawned && !terminated) {
|
|
2367
|
+
const verified = await Promise.race([
|
|
2368
|
+
verifyHostedEndpoint(access.endpoint, {
|
|
2369
|
+
fetchImpl,
|
|
2370
|
+
timeoutSignal,
|
|
2371
|
+
timeoutMs: verifyRequestTimeoutMs,
|
|
2372
|
+
cancellationSignal: cancellationController.signal
|
|
2373
|
+
}).catch(() => false),
|
|
2374
|
+
exit.then(() => false),
|
|
2375
|
+
cancellation
|
|
2376
|
+
]);
|
|
2377
|
+
if (verified && child === spawned && desired && ownedGeneration === generation) {
|
|
2378
|
+
removeTokenFile(attemptTokenFile);
|
|
2379
|
+
retryAttempt = 0;
|
|
2380
|
+
publish({ status: "ready", ready: true });
|
|
2381
|
+
return state;
|
|
2382
|
+
}
|
|
2383
|
+
if (Date.now() >= deadline) break;
|
|
2384
|
+
await Promise.race([
|
|
2385
|
+
new Promise((resolve3) => {
|
|
2386
|
+
const timer = setTimeout(resolve3, verifyIntervalMs);
|
|
2387
|
+
timer.unref?.();
|
|
2388
|
+
}),
|
|
2389
|
+
exit,
|
|
2390
|
+
cancellation
|
|
2391
|
+
]);
|
|
2392
|
+
}
|
|
2393
|
+
if (child === spawned) {
|
|
2394
|
+
expectedStop = true;
|
|
2395
|
+
child = null;
|
|
2396
|
+
await terminateChild(spawned, stopGraceMs);
|
|
2397
|
+
}
|
|
2398
|
+
removeTokenFile(attemptTokenFile);
|
|
2399
|
+
if (desired && ownedGeneration === generation) {
|
|
2400
|
+
scheduleRetry("The secure connection could not be verified.", ownedGeneration);
|
|
2401
|
+
}
|
|
2402
|
+
return state;
|
|
2403
|
+
} catch {
|
|
2404
|
+
const spawned = child;
|
|
2405
|
+
child = null;
|
|
2406
|
+
removeTokenFile(attemptTokenFile);
|
|
2407
|
+
if (spawned) await terminateChild(spawned, stopGraceMs);
|
|
2408
|
+
if (desired && ownedGeneration === generation) {
|
|
2409
|
+
scheduleRetry("The secure connection could not start.", ownedGeneration);
|
|
2410
|
+
}
|
|
2411
|
+
return state;
|
|
2412
|
+
} finally {
|
|
2413
|
+
if (activeAttempt === attemptHandle) activeAttempt = null;
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
return Object.freeze({
|
|
2417
|
+
getStatus() {
|
|
2418
|
+
return state;
|
|
2419
|
+
},
|
|
2420
|
+
start(rawAccess) {
|
|
2421
|
+
const requestedRevision = ++intentRevision;
|
|
2422
|
+
return serialize(async () => {
|
|
2423
|
+
if (requestedRevision !== intentRevision) return state;
|
|
2424
|
+
const normalized = normalizeManagedCompanionAccess({
|
|
2425
|
+
[MANAGED_COMPANION_ENDPOINT_FIELD]: rawAccess?.endpoint,
|
|
2426
|
+
[MANAGED_COMPANION_TOKEN_FIELD]: rawAccess?.token
|
|
2427
|
+
});
|
|
2428
|
+
const normalizedOriginTarget = validCompanionOriginTarget(rawAccess?.originTarget) ? Object.freeze({
|
|
2429
|
+
pid: rawAccess.originTarget.pid,
|
|
2430
|
+
socketPath: rawAccess.originTarget.socketPath
|
|
2431
|
+
}) : null;
|
|
2432
|
+
if (desired && normalized && access?.endpoint === normalized.endpoint && access?.token === normalized.token && originTarget?.pid === normalizedOriginTarget?.pid && originTarget?.socketPath === normalizedOriginTarget?.socketPath && child) {
|
|
2433
|
+
return state;
|
|
2434
|
+
}
|
|
2435
|
+
generation += 1;
|
|
2436
|
+
desired = Boolean(normalized);
|
|
2437
|
+
access = normalized;
|
|
2438
|
+
originTarget = normalizedOriginTarget;
|
|
2439
|
+
retryAttempt = 0;
|
|
2440
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
2441
|
+
retryTimer = null;
|
|
2442
|
+
const existing = child;
|
|
2443
|
+
child = null;
|
|
2444
|
+
if (existing) await terminateChild(existing, stopGraceMs);
|
|
2445
|
+
removeTokenFile();
|
|
2446
|
+
if (!access) {
|
|
2447
|
+
return publish({
|
|
2448
|
+
status: "unconfigured",
|
|
2449
|
+
ready: false,
|
|
2450
|
+
error: "Sign in to enable a secure connection from any network."
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
return attempt(generation);
|
|
2454
|
+
});
|
|
2455
|
+
},
|
|
2456
|
+
stop() {
|
|
2457
|
+
const requestedRevision = ++intentRevision;
|
|
2458
|
+
desired = false;
|
|
2459
|
+
generation += 1;
|
|
2460
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
2461
|
+
retryTimer = null;
|
|
2462
|
+
activeAttempt?.cancel();
|
|
2463
|
+
const existing = child;
|
|
2464
|
+
child = null;
|
|
2465
|
+
const existingTokenFile = tokenFile;
|
|
2466
|
+
removeTokenFile(existingTokenFile);
|
|
2467
|
+
const terminated = existing ? terminateChild(existing, stopGraceMs) : Promise.resolve();
|
|
2468
|
+
return serialize(async () => {
|
|
2469
|
+
await terminated;
|
|
2470
|
+
if (requestedRevision !== intentRevision) return state;
|
|
2471
|
+
return publish({ status: "stopped", ready: false });
|
|
2472
|
+
});
|
|
2473
|
+
},
|
|
2474
|
+
shutdown() {
|
|
2475
|
+
return this.stop();
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// electron/companion-account-service.mjs
|
|
2481
|
+
var DEFAULT_COMPANION_CONTROL_PLANE_URL = "https://accounts.openmausbot.com";
|
|
2482
|
+
var COMPANION_CLIENT_INSTANCE_FIELD = "companionClientInstanceId";
|
|
2483
|
+
var COMPANION_ACCOUNT_TOKEN_FIELD = "companionAccountToken";
|
|
2484
|
+
var COMPANION_ACCOUNT_USER_ID_FIELD = "companionAccountUserId";
|
|
2485
|
+
var COMPANION_ACCOUNT_EMAIL_FIELD = "companionAccountEmail";
|
|
2486
|
+
var COMPANION_INSTALLATION_ID_FIELD = "companionInstallationId";
|
|
2487
|
+
var COMPANION_INSTALLATION_CREDENTIAL_FIELD = "companionInstallationCredential";
|
|
2488
|
+
var COMPANION_INSTALLATION_EXPIRY_FIELD = "companionInstallationCredentialExpiresAt";
|
|
2489
|
+
var COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD = "companionAccountCleanupPending";
|
|
2490
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2491
|
+
var INSTALLATION_ID2 = UUID;
|
|
2492
|
+
var INSTALLATION_CREDENTIAL2 = /^omb_install_[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}$/;
|
|
2493
|
+
var DEFAULT_HEALTH_CACHE_MS = 3e4;
|
|
2494
|
+
var ownString = (document, field) => typeof document?.[field] === "string" ? document[field] : "";
|
|
2495
|
+
function resolveCompanionControlPlaneURL({
|
|
2496
|
+
isPackaged,
|
|
2497
|
+
environment = process.env
|
|
2498
|
+
} = {}) {
|
|
2499
|
+
if (Object.hasOwn(environment, "OMB_CONTROL_PLANE_URL")) {
|
|
2500
|
+
return normalizeControlPlaneURL(environment.OMB_CONTROL_PLANE_URL);
|
|
2501
|
+
}
|
|
2502
|
+
return isPackaged ? DEFAULT_COMPANION_CONTROL_PLANE_URL : "";
|
|
2503
|
+
}
|
|
2504
|
+
function companionAccountCleanupPending(credentials) {
|
|
2505
|
+
return credentials?.[COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD] === true;
|
|
2506
|
+
}
|
|
2507
|
+
function storedAccount(credentials) {
|
|
2508
|
+
const email = normalizeAccountEmail(ownString(credentials, COMPANION_ACCOUNT_EMAIL_FIELD));
|
|
2509
|
+
const userId = ownString(credentials, COMPANION_ACCOUNT_USER_ID_FIELD);
|
|
2510
|
+
if (!email || !userId || userId.length > 256) return null;
|
|
2511
|
+
return {
|
|
2512
|
+
email,
|
|
2513
|
+
userId,
|
|
2514
|
+
accountToken: ownString(credentials, COMPANION_ACCOUNT_TOKEN_FIELD),
|
|
2515
|
+
installationId: INSTALLATION_ID2.test(ownString(credentials, COMPANION_INSTALLATION_ID_FIELD)) ? credentials[COMPANION_INSTALLATION_ID_FIELD] : "",
|
|
2516
|
+
installationCredential: INSTALLATION_CREDENTIAL2.test(
|
|
2517
|
+
ownString(credentials, COMPANION_INSTALLATION_CREDENTIAL_FIELD)
|
|
2518
|
+
) ? credentials[COMPANION_INSTALLATION_CREDENTIAL_FIELD] : ""
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
function withoutCompanionAccount(credentials) {
|
|
2522
|
+
const next = withoutManagedCompanionTunnelAccess(credentials);
|
|
2523
|
+
for (const field of [
|
|
2524
|
+
COMPANION_ACCOUNT_TOKEN_FIELD,
|
|
2525
|
+
COMPANION_ACCOUNT_USER_ID_FIELD,
|
|
2526
|
+
COMPANION_ACCOUNT_EMAIL_FIELD,
|
|
2527
|
+
COMPANION_INSTALLATION_ID_FIELD,
|
|
2528
|
+
COMPANION_INSTALLATION_CREDENTIAL_FIELD,
|
|
2529
|
+
COMPANION_INSTALLATION_EXPIRY_FIELD,
|
|
2530
|
+
COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD
|
|
2531
|
+
]) {
|
|
2532
|
+
delete next[field];
|
|
2533
|
+
}
|
|
2534
|
+
return next;
|
|
2535
|
+
}
|
|
2536
|
+
function withProvisionedAccount(credentials, { accountToken, user, installation, provision }) {
|
|
2537
|
+
const withEndpoint = withManagedCompanionTunnelAccess(credentials, provision);
|
|
2538
|
+
const next = {
|
|
2539
|
+
...withEndpoint,
|
|
2540
|
+
[COMPANION_ACCOUNT_TOKEN_FIELD]: accountToken,
|
|
2541
|
+
[COMPANION_ACCOUNT_USER_ID_FIELD]: user.id,
|
|
2542
|
+
[COMPANION_ACCOUNT_EMAIL_FIELD]: user.email,
|
|
2543
|
+
[COMPANION_INSTALLATION_ID_FIELD]: installation.installation.id,
|
|
2544
|
+
[COMPANION_INSTALLATION_CREDENTIAL_FIELD]: installation.credential
|
|
2545
|
+
};
|
|
2546
|
+
if (Number.isSafeInteger(installation.credentialExpiresAt)) {
|
|
2547
|
+
next[COMPANION_INSTALLATION_EXPIRY_FIELD] = installation.credentialExpiresAt;
|
|
2548
|
+
} else {
|
|
2549
|
+
delete next[COMPANION_INSTALLATION_EXPIRY_FIELD];
|
|
2550
|
+
}
|
|
2551
|
+
delete next[COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD];
|
|
2552
|
+
return next;
|
|
2553
|
+
}
|
|
2554
|
+
function withAuthenticatedAccount(credentials, { accountToken, user, clientInstanceId, preserveCleanupPending = false }) {
|
|
2555
|
+
const next = {
|
|
2556
|
+
...credentials,
|
|
2557
|
+
[COMPANION_ACCOUNT_TOKEN_FIELD]: accountToken,
|
|
2558
|
+
[COMPANION_ACCOUNT_USER_ID_FIELD]: user.id,
|
|
2559
|
+
[COMPANION_ACCOUNT_EMAIL_FIELD]: user.email
|
|
2560
|
+
};
|
|
2561
|
+
if (!UUID.test(ownString(next, COMPANION_CLIENT_INSTANCE_FIELD))) {
|
|
2562
|
+
next[COMPANION_CLIENT_INSTANCE_FIELD] = clientInstanceId;
|
|
2563
|
+
}
|
|
2564
|
+
if (preserveCleanupPending) {
|
|
2565
|
+
next[COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD] = true;
|
|
2566
|
+
} else {
|
|
2567
|
+
delete next[COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD];
|
|
2568
|
+
}
|
|
2569
|
+
return next;
|
|
2570
|
+
}
|
|
2571
|
+
var FRIENDLY_MESSAGES = Object.freeze({
|
|
2572
|
+
invalid_email: "Enter a valid email address.",
|
|
2573
|
+
invalid_request: "The secure connection request was not accepted. Check the details and try again.",
|
|
2574
|
+
invalid_otp: "That code is not valid. Check the email and try again.",
|
|
2575
|
+
otp_expired: "That code expired. Email yourself a new one.",
|
|
2576
|
+
unauthorized: "Your sign-in expired. Email yourself a new code to reconnect.",
|
|
2577
|
+
forbidden: "The secure connection request was not allowed. Try signing in again.",
|
|
2578
|
+
signed_out: "Your sign-in expired. Email yourself a new code to reconnect.",
|
|
2579
|
+
network_unavailable: "OpenMausBot could not reach its secure connection service. Check your internet and try again.",
|
|
2580
|
+
rate_limited: "Too many attempts were made. Wait a little, then try again.",
|
|
2581
|
+
credential_rotation_rate_limited: "This computer was reconnected too often. Wait a little, then try again.",
|
|
2582
|
+
installation_limit_reached: "This account has reached its computer limit. Remove an old computer and try again.",
|
|
2583
|
+
installation_exists: "This computer is already connected. Try again to recover it.",
|
|
2584
|
+
endpoint_busy: "The secure connection is still being prepared. Try again in a moment.",
|
|
2585
|
+
endpoint_unavailable: "The secure connection service could not finish setup. Local pairing still works; try again shortly.",
|
|
2586
|
+
endpoint_cleanup_pending: "The secure connection is still being removed. Try signing out again shortly.",
|
|
2587
|
+
control_plane_unavailable: "Secure access is not available right now. Local pairing still works.",
|
|
2588
|
+
internal_error: "The secure connection service had a problem. Local pairing still works; try again.",
|
|
2589
|
+
invalid_response: "The secure connection service returned an unexpected response. Try again.",
|
|
2590
|
+
request_failed: "The secure connection request could not be completed. Local pairing still works; try again."
|
|
2591
|
+
});
|
|
2592
|
+
function friendlyCompanionAccountError(error) {
|
|
2593
|
+
const code = error instanceof ControlPlaneError ? error.code : "";
|
|
2594
|
+
const message2 = FRIENDLY_MESSAGES[code] ?? FRIENDLY_MESSAGES.request_failed;
|
|
2595
|
+
const reference = error instanceof ControlPlaneError && error.requestId ? ` Reference: ${error.requestId}.` : "";
|
|
2596
|
+
return `${message2}${reference}`;
|
|
2597
|
+
}
|
|
2598
|
+
function publicState({ available, status, email, endpoint, message: message2 }) {
|
|
2599
|
+
const state = { available: Boolean(available), status };
|
|
2600
|
+
const normalizedEmail = normalizeAccountEmail(email);
|
|
2601
|
+
if (normalizedEmail) state.email = normalizedEmail;
|
|
2602
|
+
const accessEndpoint = (() => {
|
|
2603
|
+
if (typeof endpoint !== "string") return "";
|
|
2604
|
+
try {
|
|
2605
|
+
const parsed = new URL(endpoint);
|
|
2606
|
+
return parsed.protocol === "https:" && parsed.pathname === "/" && !parsed.search && !parsed.hash ? parsed.origin : "";
|
|
2607
|
+
} catch {
|
|
2608
|
+
return "";
|
|
2609
|
+
}
|
|
2610
|
+
})();
|
|
2611
|
+
if (accessEndpoint) state.endpoint = accessEndpoint;
|
|
2612
|
+
const safeMessage = typeof message2 === "string" && message2.length >= 1 && message2.length <= 280 ? message2 : null;
|
|
2613
|
+
if (safeMessage) state.message = safeMessage;
|
|
2614
|
+
return Object.freeze(state);
|
|
2615
|
+
}
|
|
2616
|
+
function createCompanionAccountService({
|
|
2617
|
+
client,
|
|
2618
|
+
readCredentials,
|
|
2619
|
+
updateCredentials,
|
|
2620
|
+
identity,
|
|
2621
|
+
newClientInstanceId,
|
|
2622
|
+
activatePersistedEndpoint = async () => ({ status: "stopped", ready: false }),
|
|
2623
|
+
stopManagedEndpoint = async () => {
|
|
2624
|
+
},
|
|
2625
|
+
managedConnectionState = () => ({ status: "stopped", ready: false }),
|
|
2626
|
+
companionIsOn = () => false,
|
|
2627
|
+
now = Date.now,
|
|
2628
|
+
healthCacheMs = DEFAULT_HEALTH_CACHE_MS
|
|
2629
|
+
} = {}) {
|
|
2630
|
+
const configured = Boolean(client);
|
|
2631
|
+
let healthy = false;
|
|
2632
|
+
let lastHealthCheck = null;
|
|
2633
|
+
let healthProbe = null;
|
|
2634
|
+
let phase = null;
|
|
2635
|
+
let transition = Promise.resolve();
|
|
2636
|
+
const serialize = (work) => {
|
|
2637
|
+
const next = transition.then(work, work);
|
|
2638
|
+
transition = next.then(
|
|
2639
|
+
() => {
|
|
2640
|
+
},
|
|
2641
|
+
() => {
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2644
|
+
return next;
|
|
2645
|
+
};
|
|
2646
|
+
const credentials = () => readCredentials?.() ?? {};
|
|
2647
|
+
const probeControlPlane = async ({ force = false } = {}) => {
|
|
2648
|
+
if (!configured) return false;
|
|
2649
|
+
const checkedAt = now();
|
|
2650
|
+
if (!force && lastHealthCheck !== null && checkedAt - lastHealthCheck < Math.max(0, healthCacheMs)) {
|
|
2651
|
+
return healthy;
|
|
2652
|
+
}
|
|
2653
|
+
if (healthProbe) return healthProbe;
|
|
2654
|
+
healthProbe = (async () => {
|
|
2655
|
+
try {
|
|
2656
|
+
await client.health();
|
|
2657
|
+
healthy = true;
|
|
2658
|
+
} catch {
|
|
2659
|
+
healthy = false;
|
|
2660
|
+
}
|
|
2661
|
+
lastHealthCheck = now();
|
|
2662
|
+
return healthy;
|
|
2663
|
+
})().finally(() => {
|
|
2664
|
+
healthProbe = null;
|
|
2665
|
+
});
|
|
2666
|
+
return healthProbe;
|
|
2667
|
+
};
|
|
2668
|
+
const requireHealthyControlPlane = async () => {
|
|
2669
|
+
if (!await probeControlPlane({ force: true })) {
|
|
2670
|
+
throw new ControlPlaneError("control_plane_unavailable");
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
const settledState = () => {
|
|
2674
|
+
if (!configured) {
|
|
2675
|
+
return publicState({
|
|
2676
|
+
available: false,
|
|
2677
|
+
status: "signed-out",
|
|
2678
|
+
message: FRIENDLY_MESSAGES.control_plane_unavailable
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
const document = credentials();
|
|
2682
|
+
const account = storedAccount(document);
|
|
2683
|
+
const persistedAccess = managedCompanionTunnelAccess(document);
|
|
2684
|
+
const available = healthy || Boolean(account);
|
|
2685
|
+
if (!healthy) {
|
|
2686
|
+
return publicState({
|
|
2687
|
+
available,
|
|
2688
|
+
status: account ? "error" : "signed-out",
|
|
2689
|
+
email: account?.email,
|
|
2690
|
+
endpoint: persistedAccess?.endpoint,
|
|
2691
|
+
message: FRIENDLY_MESSAGES.control_plane_unavailable
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
if (phase && ["connecting", "error"].includes(phase.status) && account && persistedAccess && !companionAccountCleanupPending(document) && managedConnectionState?.()?.ready === true) {
|
|
2695
|
+
phase = null;
|
|
2696
|
+
}
|
|
2697
|
+
if (phase) return publicState({ available, ...phase });
|
|
2698
|
+
if (!account) return publicState({ available, status: "signed-out" });
|
|
2699
|
+
const access = persistedAccess;
|
|
2700
|
+
if (!access) {
|
|
2701
|
+
return publicState({
|
|
2702
|
+
available,
|
|
2703
|
+
status: "error",
|
|
2704
|
+
email: account.email,
|
|
2705
|
+
message: "This computer still needs a secure address. Try again; local pairing continues to work."
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
const connection = managedConnectionState?.() ?? {};
|
|
2709
|
+
if (companionIsOn()) {
|
|
2710
|
+
if (["starting", "retrying"].includes(connection.status)) {
|
|
2711
|
+
return publicState({
|
|
2712
|
+
available,
|
|
2713
|
+
status: "connecting",
|
|
2714
|
+
email: account.email,
|
|
2715
|
+
endpoint: access.endpoint,
|
|
2716
|
+
message: "The secure connection is starting. Local pairing remains available."
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
if (["unavailable", "error"].includes(connection.status)) {
|
|
2720
|
+
return publicState({
|
|
2721
|
+
available,
|
|
2722
|
+
status: "error",
|
|
2723
|
+
email: account.email,
|
|
2724
|
+
endpoint: access.endpoint,
|
|
2725
|
+
message: "The secure connection needs attention. Local pairing still works."
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
return publicState({
|
|
2730
|
+
available,
|
|
2731
|
+
status: "ready",
|
|
2732
|
+
email: account.email,
|
|
2733
|
+
endpoint: access.endpoint
|
|
2734
|
+
});
|
|
2735
|
+
};
|
|
2736
|
+
const ensureClientIdentity = async () => {
|
|
2737
|
+
const existing = ownString(credentials(), COMPANION_CLIENT_INSTANCE_FIELD);
|
|
2738
|
+
if (UUID.test(existing)) return existing;
|
|
2739
|
+
const candidate = newClientInstanceId?.();
|
|
2740
|
+
if (!UUID.test(candidate ?? "")) throw new Error("A stable computer identity could not be created");
|
|
2741
|
+
await updateCredentials((document) => {
|
|
2742
|
+
if (UUID.test(ownString(document, COMPANION_CLIENT_INSTANCE_FIELD))) return document;
|
|
2743
|
+
return { ...document, [COMPANION_CLIENT_INSTANCE_FIELD]: candidate };
|
|
2744
|
+
});
|
|
2745
|
+
return ownString(credentials(), COMPANION_CLIENT_INSTANCE_FIELD);
|
|
2746
|
+
};
|
|
2747
|
+
const markCleanupPending = async () => {
|
|
2748
|
+
await updateCredentials((document) => ({
|
|
2749
|
+
...document,
|
|
2750
|
+
[COMPANION_ACCOUNT_CLEANUP_PENDING_FIELD]: true
|
|
2751
|
+
}));
|
|
2752
|
+
};
|
|
2753
|
+
const clearAfterCleanup = async () => {
|
|
2754
|
+
await updateCredentials(withoutCompanionAccount);
|
|
2755
|
+
await stopManagedEndpoint();
|
|
2756
|
+
phase = null;
|
|
2757
|
+
};
|
|
2758
|
+
const cleanupCurrentAccount = async ({ markPending = true } = {}) => {
|
|
2759
|
+
const document = credentials();
|
|
2760
|
+
const account = storedAccount(document);
|
|
2761
|
+
if (!account) {
|
|
2762
|
+
await clearAfterCleanup();
|
|
2763
|
+
return true;
|
|
2764
|
+
}
|
|
2765
|
+
if (markPending && !companionAccountCleanupPending(document)) await markCleanupPending();
|
|
2766
|
+
await stopManagedEndpoint();
|
|
2767
|
+
if (account.installationCredential) {
|
|
2768
|
+
try {
|
|
2769
|
+
await client.deleteEndpoint(account.installationCredential);
|
|
2770
|
+
} catch {
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
if (!account.accountToken) {
|
|
2774
|
+
throw new ControlPlaneError("signed_out", 401);
|
|
2775
|
+
}
|
|
2776
|
+
const clientInstanceId = ownString(document, COMPANION_CLIENT_INSTANCE_FIELD);
|
|
2777
|
+
if (!UUID.test(clientInstanceId)) {
|
|
2778
|
+
throw new ControlPlaneError("invalid_client_identity");
|
|
2779
|
+
}
|
|
2780
|
+
if (account.installationId) {
|
|
2781
|
+
await client.revokeInstallation(account.accountToken, account.installationId).catch(() => {
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
const installations = await client.listInstallations(account.accountToken);
|
|
2785
|
+
for (const installation of installations) {
|
|
2786
|
+
if (installation.clientInstanceId !== clientInstanceId && installation.id !== account.installationId) {
|
|
2787
|
+
continue;
|
|
2788
|
+
}
|
|
2789
|
+
try {
|
|
2790
|
+
await client.revokeInstallation(account.accountToken, installation.id);
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
if (!(error instanceof ControlPlaneError) || error.status !== 404) throw error;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
try {
|
|
2796
|
+
await client.signOut(account.accountToken);
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
2799
|
+
await clearAfterCleanup();
|
|
2800
|
+
return true;
|
|
2801
|
+
};
|
|
2802
|
+
const provision = async ({ accountToken, user }) => {
|
|
2803
|
+
const clientInstanceId = await ensureClientIdentity();
|
|
2804
|
+
const before = credentials();
|
|
2805
|
+
const previous = storedAccount(before);
|
|
2806
|
+
const installation = await client.ensureInstallation({
|
|
2807
|
+
accountToken,
|
|
2808
|
+
currentCredential: previous?.userId === user.id ? previous.installationCredential : "",
|
|
2809
|
+
clientInstanceId,
|
|
2810
|
+
name: identity.name,
|
|
2811
|
+
platform: identity.platform,
|
|
2812
|
+
appVersion: identity.appVersion
|
|
2813
|
+
});
|
|
2814
|
+
const endpoint = await client.ensureEndpoint(installation.credential);
|
|
2815
|
+
try {
|
|
2816
|
+
await updateCredentials(
|
|
2817
|
+
(document) => withProvisionedAccount(document, {
|
|
2818
|
+
accountToken,
|
|
2819
|
+
user,
|
|
2820
|
+
installation,
|
|
2821
|
+
provision: endpoint
|
|
2822
|
+
})
|
|
2823
|
+
);
|
|
2824
|
+
} catch (error) {
|
|
2825
|
+
await client.deleteEndpoint(installation.credential).catch(() => {
|
|
2826
|
+
});
|
|
2827
|
+
await client.revokeInstallation(accountToken, installation.installation.id).catch(() => {
|
|
2828
|
+
});
|
|
2829
|
+
throw error;
|
|
2830
|
+
}
|
|
2831
|
+
const connection = await activatePersistedEndpoint();
|
|
2832
|
+
phase = null;
|
|
2833
|
+
if (companionIsOn() && connection && ["unavailable", "error"].includes(connection.status)) {
|
|
2834
|
+
phase = {
|
|
2835
|
+
status: "error",
|
|
2836
|
+
email: user.email,
|
|
2837
|
+
endpoint: endpoint.endpoint.url,
|
|
2838
|
+
message: "The address is ready, but this app could not start its secure connection. Local pairing still works."
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
return settledState();
|
|
2842
|
+
};
|
|
2843
|
+
const failAction = (error, { email, expiredSessionIsSignedOut = false, signedOut = false } = {}) => {
|
|
2844
|
+
const message2 = friendlyCompanionAccountError(error);
|
|
2845
|
+
phase = {
|
|
2846
|
+
status: signedOut || expiredSessionIsSignedOut && error instanceof ControlPlaneError && error.status === 401 ? "signed-out" : "error",
|
|
2847
|
+
email,
|
|
2848
|
+
message: message2
|
|
2849
|
+
};
|
|
2850
|
+
return message2;
|
|
2851
|
+
};
|
|
2852
|
+
const requestCode = (rawEmail) => serialize(async () => {
|
|
2853
|
+
if (!configured) throw new Error(FRIENDLY_MESSAGES.control_plane_unavailable);
|
|
2854
|
+
const email = normalizeAccountEmail(rawEmail);
|
|
2855
|
+
let requested;
|
|
2856
|
+
try {
|
|
2857
|
+
await requireHealthyControlPlane();
|
|
2858
|
+
requested = await client.requestOTP(email);
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
const message2 = failAction(error, { email, signedOut: true });
|
|
2861
|
+
throw new Error(message2);
|
|
2862
|
+
}
|
|
2863
|
+
phase = {
|
|
2864
|
+
status: "signed-out",
|
|
2865
|
+
email: requested.email
|
|
2866
|
+
};
|
|
2867
|
+
return settledState();
|
|
2868
|
+
});
|
|
2869
|
+
const verifyCode = (rawEmail, rawCode) => serialize(async () => {
|
|
2870
|
+
if (!configured) throw new Error(FRIENDLY_MESSAGES.control_plane_unavailable);
|
|
2871
|
+
const email = normalizeAccountEmail(rawEmail);
|
|
2872
|
+
phase = { status: "connecting", email };
|
|
2873
|
+
let verified;
|
|
2874
|
+
try {
|
|
2875
|
+
await requireHealthyControlPlane();
|
|
2876
|
+
verified = await client.verifyOTP(email, rawCode);
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
const message2 = failAction(error, { email, signedOut: true });
|
|
2879
|
+
throw new Error(message2);
|
|
2880
|
+
}
|
|
2881
|
+
const previous = storedAccount(credentials());
|
|
2882
|
+
const refreshingPendingCleanup = Boolean(
|
|
2883
|
+
previous && previous.userId === verified.user.id && companionAccountCleanupPending(credentials())
|
|
2884
|
+
);
|
|
2885
|
+
let authenticatedPersisted = false;
|
|
2886
|
+
try {
|
|
2887
|
+
if (previous && previous.userId !== verified.user.id) {
|
|
2888
|
+
await cleanupCurrentAccount();
|
|
2889
|
+
}
|
|
2890
|
+
const existingIdentity = ownString(credentials(), COMPANION_CLIENT_INSTANCE_FIELD);
|
|
2891
|
+
const clientInstanceId = UUID.test(existingIdentity) ? existingIdentity : newClientInstanceId?.();
|
|
2892
|
+
if (!UUID.test(clientInstanceId ?? "")) {
|
|
2893
|
+
throw new Error("A stable computer identity could not be created");
|
|
2894
|
+
}
|
|
2895
|
+
await updateCredentials(
|
|
2896
|
+
(document) => withAuthenticatedAccount(document, {
|
|
2897
|
+
...verified,
|
|
2898
|
+
clientInstanceId,
|
|
2899
|
+
preserveCleanupPending: refreshingPendingCleanup
|
|
2900
|
+
})
|
|
2901
|
+
);
|
|
2902
|
+
authenticatedPersisted = true;
|
|
2903
|
+
if (refreshingPendingCleanup) {
|
|
2904
|
+
await cleanupCurrentAccount({ markPending: false });
|
|
2905
|
+
return settledState();
|
|
2906
|
+
}
|
|
2907
|
+
return await provision(verified);
|
|
2908
|
+
} catch (error) {
|
|
2909
|
+
if (!authenticatedPersisted) await client.signOut(verified.accountToken).catch(() => {
|
|
2910
|
+
});
|
|
2911
|
+
failAction(error, {
|
|
2912
|
+
email: authenticatedPersisted ? verified.user.email : previous?.email ?? email,
|
|
2913
|
+
expiredSessionIsSignedOut: authenticatedPersisted
|
|
2914
|
+
});
|
|
2915
|
+
return settledState();
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
const retryWork = async () => {
|
|
2919
|
+
if (!configured) return settledState();
|
|
2920
|
+
const account = storedAccount(credentials());
|
|
2921
|
+
if (!account) {
|
|
2922
|
+
phase = null;
|
|
2923
|
+
return settledState();
|
|
2924
|
+
}
|
|
2925
|
+
if (companionAccountCleanupPending(credentials())) {
|
|
2926
|
+
phase = { status: "connecting", email: account.email };
|
|
2927
|
+
try {
|
|
2928
|
+
await requireHealthyControlPlane();
|
|
2929
|
+
await cleanupCurrentAccount({ markPending: false });
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
failAction(error, { email: account.email, expiredSessionIsSignedOut: true });
|
|
2932
|
+
}
|
|
2933
|
+
return settledState();
|
|
2934
|
+
}
|
|
2935
|
+
phase = { status: "connecting", email: account.email };
|
|
2936
|
+
try {
|
|
2937
|
+
await requireHealthyControlPlane();
|
|
2938
|
+
return await provision({
|
|
2939
|
+
accountToken: account.accountToken,
|
|
2940
|
+
user: { id: account.userId, email: account.email }
|
|
2941
|
+
});
|
|
2942
|
+
} catch (error) {
|
|
2943
|
+
failAction(error, { email: account.email, expiredSessionIsSignedOut: true });
|
|
2944
|
+
return settledState();
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
const retry = () => serialize(retryWork);
|
|
2948
|
+
const signOut = () => serialize(async () => {
|
|
2949
|
+
if (!storedAccount(credentials())) {
|
|
2950
|
+
await clearAfterCleanup();
|
|
2951
|
+
return settledState();
|
|
2952
|
+
}
|
|
2953
|
+
const email = storedAccount(credentials())?.email;
|
|
2954
|
+
phase = { status: "connecting", email };
|
|
2955
|
+
try {
|
|
2956
|
+
await cleanupCurrentAccount();
|
|
2957
|
+
healthy = true;
|
|
2958
|
+
lastHealthCheck = now();
|
|
2959
|
+
return settledState();
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
failAction(error, { email });
|
|
2962
|
+
return settledState();
|
|
2963
|
+
}
|
|
2964
|
+
});
|
|
2965
|
+
const restore = () => serialize(async () => {
|
|
2966
|
+
if (!configured) return settledState();
|
|
2967
|
+
if (!await probeControlPlane({ force: true })) return settledState();
|
|
2968
|
+
if (companionAccountCleanupPending(credentials())) return retryWork();
|
|
2969
|
+
await ensureClientIdentity();
|
|
2970
|
+
const account = storedAccount(credentials());
|
|
2971
|
+
if (account && !managedCompanionTunnelAccess(credentials())) return retryWork();
|
|
2972
|
+
phase = null;
|
|
2973
|
+
return settledState();
|
|
2974
|
+
});
|
|
2975
|
+
return Object.freeze({
|
|
2976
|
+
state: async () => {
|
|
2977
|
+
await probeControlPlane();
|
|
2978
|
+
return settledState();
|
|
2979
|
+
},
|
|
2980
|
+
requestCode,
|
|
2981
|
+
verifyCode,
|
|
2982
|
+
retry,
|
|
2983
|
+
signOut,
|
|
2984
|
+
restore
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// electron/secure-credential-state.mjs
|
|
2989
|
+
var isPlainRecord2 = (value) => {
|
|
2990
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2991
|
+
try {
|
|
2992
|
+
const constructor = value.constructor;
|
|
2993
|
+
if (constructor === void 0 || typeof constructor !== "function") return true;
|
|
2994
|
+
const prototype = constructor.prototype;
|
|
2995
|
+
return typeof prototype === "object" && prototype !== null && Object.prototype.hasOwnProperty.call(prototype, "isPrototypeOf");
|
|
2996
|
+
} catch {
|
|
2997
|
+
return false;
|
|
2998
|
+
}
|
|
2999
|
+
};
|
|
3000
|
+
var copy = (credentials) => {
|
|
3001
|
+
if (!isPlainRecord2(credentials)) {
|
|
3002
|
+
throw new TypeError("Secure credentials must be a plain record");
|
|
3003
|
+
}
|
|
3004
|
+
const document = {};
|
|
3005
|
+
for (const key of Reflect.ownKeys(credentials)) {
|
|
3006
|
+
if (!Object.prototype.propertyIsEnumerable.call(credentials, key)) continue;
|
|
3007
|
+
if (typeof key !== "string") {
|
|
3008
|
+
throw new TypeError("Secure credential keys must be strings");
|
|
3009
|
+
}
|
|
3010
|
+
if (key === "__proto__") continue;
|
|
3011
|
+
document[key] = credentials[key];
|
|
3012
|
+
}
|
|
3013
|
+
return structuredClone(document);
|
|
3014
|
+
};
|
|
3015
|
+
function createSecureCredentialState(initialCredentials, persist, { writable = true } = {}) {
|
|
3016
|
+
let current = copy(initialCredentials);
|
|
3017
|
+
let transition = Promise.resolve();
|
|
3018
|
+
const assertWritable = () => {
|
|
3019
|
+
if (writable) return;
|
|
3020
|
+
throw new Error("The credential store could not be read on this launch, so credentials cannot be saved");
|
|
3021
|
+
};
|
|
3022
|
+
const serialize = (work) => {
|
|
3023
|
+
const next = transition.then(work, work);
|
|
3024
|
+
transition = next.then(
|
|
3025
|
+
() => {
|
|
3026
|
+
},
|
|
3027
|
+
() => {
|
|
3028
|
+
}
|
|
3029
|
+
);
|
|
3030
|
+
return next;
|
|
3031
|
+
};
|
|
3032
|
+
return {
|
|
3033
|
+
read() {
|
|
3034
|
+
return copy(current);
|
|
3035
|
+
},
|
|
3036
|
+
update(derive, afterPersist) {
|
|
3037
|
+
return serialize(async () => {
|
|
3038
|
+
assertWritable();
|
|
3039
|
+
const previous = copy(current);
|
|
3040
|
+
const next = copy(await derive(copy(previous)));
|
|
3041
|
+
await persist(copy(next));
|
|
3042
|
+
try {
|
|
3043
|
+
const result = await afterPersist?.(copy(next));
|
|
3044
|
+
current = next;
|
|
3045
|
+
return result ?? copy(next);
|
|
3046
|
+
} catch (error) {
|
|
3047
|
+
await persist(copy(previous));
|
|
3048
|
+
current = previous;
|
|
3049
|
+
throw error;
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
// server/atomic.ts
|
|
3057
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3058
|
+
import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3059
|
+
function writeFileAtomic(path4, data, options = {}) {
|
|
3060
|
+
const tmp = `${path4}.${process.pid}.${randomUUID3()}.tmp`;
|
|
3061
|
+
let fd = null;
|
|
3062
|
+
try {
|
|
3063
|
+
fd = openSync(tmp, "w", options.mode);
|
|
3064
|
+
writeFileSync(fd, data);
|
|
3065
|
+
fsyncSync(fd);
|
|
3066
|
+
closeSync(fd);
|
|
3067
|
+
fd = null;
|
|
3068
|
+
renameSync(tmp, path4);
|
|
3069
|
+
} catch (e) {
|
|
3070
|
+
if (fd !== null) {
|
|
3071
|
+
try {
|
|
3072
|
+
closeSync(fd);
|
|
3073
|
+
} catch {
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
try {
|
|
3077
|
+
unlinkSync(tmp);
|
|
3078
|
+
} catch {
|
|
3079
|
+
}
|
|
3080
|
+
throw e;
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// server/tunnel.ts
|
|
3085
|
+
var HERE = dirname(fileURLToPath2(import.meta.url));
|
|
3086
|
+
var TUNNEL_CREDENTIALS_FILE = "tunnel-account.json";
|
|
3087
|
+
var TUNNEL_RUNTIME_DIR = "tunnel-runtime";
|
|
3088
|
+
function plainDocument(value) {
|
|
3089
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
3090
|
+
const document = {};
|
|
3091
|
+
for (const [key, entry] of Object.entries(value)) document[key] = entry;
|
|
3092
|
+
return document;
|
|
3093
|
+
}
|
|
3094
|
+
function openTunnelCredentials(dataDir) {
|
|
3095
|
+
const file = join2(dataDir, TUNNEL_CREDENTIALS_FILE);
|
|
3096
|
+
let initial = {};
|
|
3097
|
+
let status = "empty";
|
|
3098
|
+
if (existsSync(file)) {
|
|
3099
|
+
try {
|
|
3100
|
+
if (process.platform !== "win32" && (statSync(file).mode & 63) !== 0) chmodSync(file, 384);
|
|
3101
|
+
const parsed = plainDocument(JSON.parse(readFileSync(file, "utf8")));
|
|
3102
|
+
if (!parsed) throw new Error("not a JSON object");
|
|
3103
|
+
initial = parsed;
|
|
3104
|
+
status = "ok";
|
|
3105
|
+
} catch {
|
|
3106
|
+
status = "unavailable";
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
const persist = (next) => {
|
|
3110
|
+
mkdirSync(dataDir, { recursive: true, mode: 448 });
|
|
3111
|
+
writeFileAtomic(file, `${JSON.stringify(next, null, 2)}
|
|
3112
|
+
`, { mode: 384 });
|
|
3113
|
+
};
|
|
3114
|
+
const state = createSecureCredentialState(initial, persist, { writable: status !== "unavailable" });
|
|
3115
|
+
return {
|
|
3116
|
+
file,
|
|
3117
|
+
status,
|
|
3118
|
+
read: () => state.read(),
|
|
3119
|
+
update: (derive, afterPersist) => state.update(derive, afterPersist)
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
function stringField(document, field) {
|
|
3123
|
+
const value = document[field];
|
|
3124
|
+
return typeof value === "string" && value ? value : null;
|
|
3125
|
+
}
|
|
3126
|
+
function describeTunnelAccount(document) {
|
|
3127
|
+
return {
|
|
3128
|
+
email: stringField(document, COMPANION_ACCOUNT_EMAIL_FIELD),
|
|
3129
|
+
address: managedCompanionTunnelAccess(document)?.endpoint ?? null,
|
|
3130
|
+
installationId: stringField(document, COMPANION_INSTALLATION_ID_FIELD)
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function tunnelAccess(document) {
|
|
3134
|
+
return managedCompanionTunnelAccess(document);
|
|
3135
|
+
}
|
|
3136
|
+
function platformName(platform = process.platform) {
|
|
3137
|
+
return platform === "darwin" ? "darwin" : platform === "win32" ? "windows" : "linux";
|
|
3138
|
+
}
|
|
3139
|
+
function createTunnelAccount(options) {
|
|
3140
|
+
const env = options.env ?? process.env;
|
|
3141
|
+
const credentials = openTunnelCredentials(options.dataDir);
|
|
3142
|
+
const controlPlane = resolveCompanionControlPlaneURL({ isPackaged: true, environment: env });
|
|
3143
|
+
const client = controlPlane ? createControlPlaneClient({ baseURL: controlPlane, fetchImpl: options.fetchImpl }) : null;
|
|
3144
|
+
const service = createCompanionAccountService({
|
|
3145
|
+
client,
|
|
3146
|
+
readCredentials: () => credentials.read(),
|
|
3147
|
+
updateCredentials: (derive, afterPersist) => credentials.update(derive, afterPersist),
|
|
3148
|
+
identity: { name: options.machineName ?? hostname(), platform: platformName(), appVersion: options.version },
|
|
3149
|
+
newClientInstanceId: () => randomUUID4()
|
|
3150
|
+
});
|
|
3151
|
+
return { service, credentials, controlPlane };
|
|
3152
|
+
}
|
|
3153
|
+
function cloudflaredPath(dataDir, env = process.env) {
|
|
3154
|
+
return resolveCloudflaredBinary({ isPackaged: false, appPath: dataDir, environment: env });
|
|
3155
|
+
}
|
|
3156
|
+
function prepareEntry(here = HERE) {
|
|
3157
|
+
const bundled = join2(here, "prepare-cloudflared.js");
|
|
3158
|
+
return existsSync(bundled) ? bundled : resolve(here, "..", "scripts", "prepare-cloudflared.mjs");
|
|
3159
|
+
}
|
|
3160
|
+
function guardianEntry(here = HERE) {
|
|
3161
|
+
const bundled = join2(here, "tunnel-guardian.js");
|
|
3162
|
+
if (existsSync(bundled)) return bundled;
|
|
3163
|
+
const source = resolve(here, "..", "electron", "managed-companion-guardian.mjs");
|
|
3164
|
+
return existsSync(source) ? source : null;
|
|
3165
|
+
}
|
|
3166
|
+
async function ensureCloudflared(options) {
|
|
3167
|
+
const env = options.env ?? process.env;
|
|
3168
|
+
const found = cloudflaredPath(options.dataDir, env);
|
|
3169
|
+
if (found) return found;
|
|
3170
|
+
options.log(`downloading cloudflared once (pinned version and digest, about 40 MB) into ${options.dataDir}`);
|
|
3171
|
+
await new Promise((done, fail) => {
|
|
3172
|
+
const child = spawn3(process.execPath, [prepareEntry(options.here), "--current", "--root", options.dataDir], {
|
|
3173
|
+
env,
|
|
3174
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
3175
|
+
});
|
|
3176
|
+
child.on("error", fail);
|
|
3177
|
+
child.on("exit", (code) => code === 0 ? done() : fail(new Error(`the cloudflared download failed (exit ${code ?? "signal"})`)));
|
|
3178
|
+
});
|
|
3179
|
+
const staged = cloudflaredPath(options.dataDir, env);
|
|
3180
|
+
if (!staged) throw new Error("cloudflared was downloaded but is not where it should be; remove the data dir's dist-native folder and try again");
|
|
3181
|
+
return staged;
|
|
3182
|
+
}
|
|
3183
|
+
function createTunnelOrigin() {
|
|
3184
|
+
return createCompanionOriginEndpoint({ processId: process.pid });
|
|
3185
|
+
}
|
|
3186
|
+
function cleanupTunnelOrigin(origin) {
|
|
3187
|
+
cleanupCompanionOriginEndpoint(origin);
|
|
3188
|
+
}
|
|
3189
|
+
function startTunnel(options) {
|
|
3190
|
+
const env = options.env ?? process.env;
|
|
3191
|
+
const originPort = Number(env.OMB_TUNNEL_ORIGIN_PORT || MANAGED_COMPANION_ORIGIN_PORT);
|
|
3192
|
+
let current = { status: "stopped", ready: false };
|
|
3193
|
+
const tunnel = createManagedCompanionTunnel({
|
|
3194
|
+
binaryPath: options.binaryPath,
|
|
3195
|
+
guardianEntry: options.guardian,
|
|
3196
|
+
runtimeRoot: join2(options.dataDir, TUNNEL_RUNTIME_DIR),
|
|
3197
|
+
originPort,
|
|
3198
|
+
environment: env,
|
|
3199
|
+
fetchImpl: options.fetchImpl,
|
|
3200
|
+
onChange: (state) => {
|
|
3201
|
+
current = state;
|
|
3202
|
+
options.onState?.(state);
|
|
3203
|
+
}
|
|
3204
|
+
});
|
|
3205
|
+
const started = tunnel.start({ endpoint: options.access.endpoint, token: options.access.token, originTarget: options.originTarget });
|
|
3206
|
+
return {
|
|
3207
|
+
address: options.access.endpoint,
|
|
3208
|
+
state: () => current,
|
|
3209
|
+
started,
|
|
3210
|
+
stop: async () => {
|
|
3211
|
+
await tunnel.stop();
|
|
3212
|
+
}
|
|
3213
|
+
};
|
|
3214
|
+
}
|
|
3215
|
+
function describeTunnelState(state, address) {
|
|
3216
|
+
switch (state.status) {
|
|
3217
|
+
case "starting":
|
|
3218
|
+
return "tunnel: connecting\u2026";
|
|
3219
|
+
case "ready":
|
|
3220
|
+
return `tunnel: live at ${address}`;
|
|
3221
|
+
case "retrying":
|
|
3222
|
+
return `tunnel: ${state.error ?? "not verified yet"} retrying in ${Math.max(1, Math.round((state.retryInMs ?? 0) / 1e3))}s`;
|
|
3223
|
+
case "stopped":
|
|
3224
|
+
return "tunnel: stopped";
|
|
3225
|
+
default:
|
|
3226
|
+
return `tunnel: ${state.error ?? state.status}`;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
// server/cli.ts
|
|
3231
|
+
var HERE2 = dirname2(fileURLToPath3(import.meta.url));
|
|
3232
|
+
var COMMANDS = ["serve", "pair", "sessions", "status", "login", "logout", "help", "--help", "-h"];
|
|
3233
|
+
function parseArgs(argv, env = process.env) {
|
|
3234
|
+
const [command = "help", ...rest] = argv;
|
|
3235
|
+
if (!COMMANDS.includes(command)) {
|
|
3236
|
+
return { error: `unknown command "${command}"` };
|
|
3237
|
+
}
|
|
3238
|
+
const options = {
|
|
3239
|
+
command: command === "--help" || command === "-h" ? "help" : command,
|
|
3240
|
+
port: Number(env.OMB_PORT || 8799),
|
|
3241
|
+
dataDir: env.OMB_DATA_DIR || join3(homedir2(), ".openmausbot"),
|
|
3242
|
+
tailscale: false,
|
|
3243
|
+
tunnel: false,
|
|
3244
|
+
client: false,
|
|
3245
|
+
pair: true,
|
|
3246
|
+
json: false
|
|
3247
|
+
};
|
|
3248
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
3249
|
+
const arg = rest[i];
|
|
3250
|
+
const value = () => {
|
|
3251
|
+
const v = rest[i + 1];
|
|
3252
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`${arg} needs a value`);
|
|
3253
|
+
i += 1;
|
|
3254
|
+
return v;
|
|
3255
|
+
};
|
|
3256
|
+
try {
|
|
3257
|
+
if (arg === "--port") options.port = Number(value());
|
|
3258
|
+
else if (arg === "--data-dir") options.dataDir = resolve2(value());
|
|
3259
|
+
else if (arg === "--label") options.label = value();
|
|
3260
|
+
else if (arg === "--public-url") options.publicUrl = value().replace(/\/+$/, "");
|
|
3261
|
+
else if (arg === "--tailscale") options.tailscale = true;
|
|
3262
|
+
else if (arg === "--tunnel") options.tunnel = true;
|
|
3263
|
+
else if (arg === "--client") options.client = true;
|
|
3264
|
+
else if (arg === "--no-pair") options.pair = false;
|
|
3265
|
+
else if (arg === "--json") options.json = true;
|
|
3266
|
+
else if (arg === "--email") options.email = value();
|
|
3267
|
+
else if (options.command === "sessions" && arg === "revoke") options.revoke = value();
|
|
3268
|
+
else return { error: `unknown argument "${arg}"` };
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) return { error: "--port must be 1-65535" };
|
|
3274
|
+
if (options.publicUrl && !/^https?:\/\//.test(options.publicUrl)) return { error: "--public-url must start with http:// or https://" };
|
|
3275
|
+
if (options.tailscale && options.tunnel) return { error: "choose one of --tailscale (your tailnet) and --tunnel (a public address)" };
|
|
3276
|
+
return options;
|
|
3277
|
+
}
|
|
3278
|
+
var USAGE = `openmausbot \u2014 run the server anywhere, pair devices to it
|
|
3279
|
+
|
|
3280
|
+
openmausbot serve [--port 8799] [--data-dir DIR] [--label NAME]
|
|
3281
|
+
[--public-url https://host] [--tailscale | --tunnel] [--no-pair]
|
|
3282
|
+
openmausbot pair [--label NAME] [--client] [--public-url https://host]
|
|
3283
|
+
openmausbot sessions [revoke ID]
|
|
3284
|
+
openmausbot status
|
|
3285
|
+
openmausbot login [--email you@example.com]
|
|
3286
|
+
openmausbot logout
|
|
3287
|
+
|
|
3288
|
+
serve starts the server and prints a pairing link + QR code
|
|
3289
|
+
pair mints a pairing code against a running server (--client: chat only)
|
|
3290
|
+
sessions lists paired devices; "sessions revoke ID" signs one out
|
|
3291
|
+
status what the server says about itself
|
|
3292
|
+
login signs this machine in to an OpenMausBot account (an emailed code)
|
|
3293
|
+
and reserves its public address for --tunnel
|
|
3294
|
+
logout releases that address and signs out
|
|
3295
|
+
|
|
3296
|
+
--tailscale serve over your tailnet: Tailscale terminates HTTPS and the
|
|
3297
|
+
link uses this machine's MagicDNS name (needs Tailscale signed in
|
|
3298
|
+
and HTTPS certificates enabled for the tailnet)
|
|
3299
|
+
--tunnel serve at a public https://\u2026.openmausbot.com address through a
|
|
3300
|
+
Cloudflare tunnel: no domain, no proxy, no open port. Run
|
|
3301
|
+
\`openmausbot login\` once on this machine first.
|
|
3302
|
+
`;
|
|
3303
|
+
function defaultIo() {
|
|
3304
|
+
return {
|
|
3305
|
+
log: (line) => console.log(line),
|
|
3306
|
+
error: (line) => console.error(line),
|
|
3307
|
+
ask: async (question) => {
|
|
3308
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3309
|
+
try {
|
|
3310
|
+
return await rl.question(question);
|
|
3311
|
+
} finally {
|
|
3312
|
+
rl.close();
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
function serverVersion(here = HERE2) {
|
|
3318
|
+
try {
|
|
3319
|
+
const parsed = JSON.parse(readFileSync2(resolve2(here, "..", "package.json"), "utf8"));
|
|
3320
|
+
const version = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "version") : void 0;
|
|
3321
|
+
return typeof version === "string" && version ? version : "0.0.0";
|
|
3322
|
+
} catch {
|
|
3323
|
+
return "0.0.0";
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
3327
|
+
async function api(port, path4, init = {}) {
|
|
3328
|
+
const res = await fetch(`http://127.0.0.1:${port}${path4}`, { method: init.method, body: init.body, headers: { "content-type": "application/json" } });
|
|
3329
|
+
const body = await res.json().catch(() => ({}));
|
|
3330
|
+
return { status: res.status, body };
|
|
3331
|
+
}
|
|
3332
|
+
async function serverUp(port) {
|
|
3333
|
+
try {
|
|
3334
|
+
const { status } = await api(port, "/api/health");
|
|
3335
|
+
return status === 200;
|
|
3336
|
+
} catch {
|
|
3337
|
+
return false;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
function pairingBlock(input) {
|
|
3341
|
+
const lines = [`pairing code: ${input.code}`, `expires: ${new Date(input.expiresAt).toLocaleTimeString()} (single use)`];
|
|
3342
|
+
if (input.url) {
|
|
3343
|
+
lines.push(`open or scan: ${input.url}`);
|
|
3344
|
+
lines.push("");
|
|
3345
|
+
lines.push(qrToString(input.url));
|
|
3346
|
+
} else {
|
|
3347
|
+
lines.push(`open: /pair on the address you use for this server, and type the code`);
|
|
3348
|
+
if (input.hint) lines.push(` (${input.hint})`);
|
|
3349
|
+
}
|
|
3350
|
+
return lines.join("\n");
|
|
3351
|
+
}
|
|
3352
|
+
function qrToString(text) {
|
|
3353
|
+
let out = "";
|
|
3354
|
+
import_qrcode_terminal.default.generate(text, { small: true }, (rendered) => {
|
|
3355
|
+
out = rendered;
|
|
3356
|
+
});
|
|
3357
|
+
return out;
|
|
3358
|
+
}
|
|
3359
|
+
async function mintPairing(port, options) {
|
|
3360
|
+
const request = {};
|
|
3361
|
+
if (options.label) request.label = options.label;
|
|
3362
|
+
if (options.client) request.scopes = ["client"];
|
|
3363
|
+
const { status, body } = await api(port, "/api/auth/pairing", { method: "POST", body: JSON.stringify(request) });
|
|
3364
|
+
if (status !== 200) throw new Error(`server refused to mint a pairing code: ${typeof body?.error === "string" ? body.error : status}`);
|
|
3365
|
+
const url = options.publicUrl ? `${options.publicUrl}/pair#code=${body.code}` : typeof body.url === "string" ? body.url : null;
|
|
3366
|
+
return pairingBlock({ code: body.code, url, expiresAt: body.expiresAt, hint: typeof body.hint === "string" ? body.hint : null });
|
|
3367
|
+
}
|
|
3368
|
+
async function runPair(options) {
|
|
3369
|
+
if (!await serverUp(options.port)) {
|
|
3370
|
+
console.error(`no OpenMausBot server on http://127.0.0.1:${options.port}; start one with \`openmausbot serve\` or set OMB_PORT`);
|
|
3371
|
+
return 1;
|
|
3372
|
+
}
|
|
3373
|
+
console.log(await mintPairing(options.port, { label: options.label, client: options.client, publicUrl: options.publicUrl }));
|
|
3374
|
+
if (options.client) console.log("(client scope: chat and approvals only; cannot change settings or pair others)");
|
|
3375
|
+
return 0;
|
|
3376
|
+
}
|
|
3377
|
+
async function runSessions(options) {
|
|
3378
|
+
if (!await serverUp(options.port)) {
|
|
3379
|
+
console.error(`no OpenMausBot server on http://127.0.0.1:${options.port}`);
|
|
3380
|
+
return 1;
|
|
3381
|
+
}
|
|
3382
|
+
if (options.revoke) {
|
|
3383
|
+
const { status, body: body2 } = await api(options.port, `/api/auth/sessions/${encodeURIComponent(options.revoke)}`, { method: "DELETE" });
|
|
3384
|
+
if (status !== 200) {
|
|
3385
|
+
console.error(`could not revoke: ${typeof body2?.error === "string" ? body2.error : status}`);
|
|
3386
|
+
return 1;
|
|
3387
|
+
}
|
|
3388
|
+
console.log(`revoked ${options.revoke}: that device is signed out and its stream is closed`);
|
|
3389
|
+
return 0;
|
|
3390
|
+
}
|
|
3391
|
+
const { body } = await api(options.port, "/api/auth/sessions");
|
|
3392
|
+
const sessions = Array.isArray(body?.sessions) ? body.sessions : [];
|
|
3393
|
+
if (options.json) {
|
|
3394
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
3395
|
+
return 0;
|
|
3396
|
+
}
|
|
3397
|
+
if (!sessions.length) {
|
|
3398
|
+
console.log("no paired devices yet: run `openmausbot pair`");
|
|
3399
|
+
return 0;
|
|
3400
|
+
}
|
|
3401
|
+
console.log(formatSessions(sessions));
|
|
3402
|
+
return 0;
|
|
3403
|
+
}
|
|
3404
|
+
function formatSessions(sessions, now = Date.now()) {
|
|
3405
|
+
const age = (ms) => {
|
|
3406
|
+
const m = Math.max(0, Math.floor((now - ms) / 6e4));
|
|
3407
|
+
return m < 1 ? "just now" : m < 60 ? `${m} min ago` : m < 1440 ? `${Math.round(m / 60)} h ago` : `${Math.round(m / 1440)} d ago`;
|
|
3408
|
+
};
|
|
3409
|
+
const rows = sessions.map((s) => [s.id, s.label || "(unnamed)", s.scopes.includes("admin") ? "admin" : "client", age(s.lastSeenAt), new Date(s.expiresAt).toISOString().slice(0, 10)]);
|
|
3410
|
+
const head = ["id", "device", "scope", "last seen", "expires"];
|
|
3411
|
+
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3412
|
+
const line = (r) => r.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
3413
|
+
return [line(head), ...rows.map(line), "", "revoke one with: openmausbot sessions revoke <id>"].join("\n");
|
|
3414
|
+
}
|
|
3415
|
+
async function runStatus(options, io = defaultIo()) {
|
|
3416
|
+
let code = 0;
|
|
3417
|
+
try {
|
|
3418
|
+
const res = await fetch(`http://127.0.0.1:${options.port}/.well-known/openmausbot/environment`);
|
|
3419
|
+
const body = await res.json();
|
|
3420
|
+
io.log(options.json ? JSON.stringify(body, null, 2) : `${body.label} \xB7 OpenMausBot ${body.version} on ${body.platform} \xB7 id ${body.environmentId}`);
|
|
3421
|
+
} catch {
|
|
3422
|
+
io.error(`no OpenMausBot server on http://127.0.0.1:${options.port}`);
|
|
3423
|
+
code = 1;
|
|
3424
|
+
}
|
|
3425
|
+
if (!options.json) {
|
|
3426
|
+
const account = describeTunnelAccount(createTunnelAccount({ dataDir: options.dataDir, version: serverVersion() }).credentials.read());
|
|
3427
|
+
if (account.address) io.log(`public address: ${account.address} (signed in as ${account.email ?? "?"}; serve it with --tunnel)`);
|
|
3428
|
+
}
|
|
3429
|
+
return code;
|
|
3430
|
+
}
|
|
3431
|
+
async function runLogin(options, io = defaultIo()) {
|
|
3432
|
+
const account = createTunnelAccount({ dataDir: options.dataDir, version: serverVersion() });
|
|
3433
|
+
if (account.credentials.status === "unavailable") {
|
|
3434
|
+
io.error(`${account.credentials.file} exists but could not be read; fix or remove it, then try again`);
|
|
3435
|
+
return 1;
|
|
3436
|
+
}
|
|
3437
|
+
if (!account.controlPlane) {
|
|
3438
|
+
io.error("OMB_CONTROL_PLANE_URL is set but is not an https address");
|
|
3439
|
+
return 1;
|
|
3440
|
+
}
|
|
3441
|
+
const existing = describeTunnelAccount(account.credentials.read());
|
|
3442
|
+
if (existing.address) io.log(`already signed in as ${existing.email ?? "?"} (${existing.address}); signing in again refreshes it`);
|
|
3443
|
+
const email = (options.email ?? await io.ask("Email for your OpenMausBot account: ")).trim();
|
|
3444
|
+
if (!email) {
|
|
3445
|
+
io.error("an email address is needed: openmausbot login --email you@example.com");
|
|
3446
|
+
return 1;
|
|
3447
|
+
}
|
|
3448
|
+
try {
|
|
3449
|
+
await account.service.requestCode(email);
|
|
3450
|
+
} catch (error) {
|
|
3451
|
+
io.error(`could not send a sign-in code: ${message(error)}`);
|
|
3452
|
+
return 1;
|
|
3453
|
+
}
|
|
3454
|
+
const code = (await io.ask(`Enter the 8-digit code we emailed to ${email}: `)).trim();
|
|
3455
|
+
let state;
|
|
3456
|
+
try {
|
|
3457
|
+
state = await account.service.verifyCode(email, code);
|
|
3458
|
+
} catch (error) {
|
|
3459
|
+
io.error(`sign-in failed: ${message(error)}`);
|
|
3460
|
+
return 1;
|
|
3461
|
+
}
|
|
3462
|
+
const signedIn = describeTunnelAccount(account.credentials.read());
|
|
3463
|
+
if (!signedIn.address) {
|
|
3464
|
+
io.error(`signed in, but no public address was issued${state.message ? `: ${state.message}` : ""}`);
|
|
3465
|
+
return 1;
|
|
3466
|
+
}
|
|
3467
|
+
io.log(`Signed in as ${signedIn.email ?? email}.`);
|
|
3468
|
+
io.log(`This machine's public address: ${signedIn.address}`);
|
|
3469
|
+
io.log("Serve there with: openmausbot serve --tunnel");
|
|
3470
|
+
return 0;
|
|
3471
|
+
}
|
|
3472
|
+
async function runLogout(options, io = defaultIo()) {
|
|
3473
|
+
const account = createTunnelAccount({ dataDir: options.dataDir, version: serverVersion() });
|
|
3474
|
+
const before = describeTunnelAccount(account.credentials.read());
|
|
3475
|
+
if (!before.email) {
|
|
3476
|
+
io.log("this machine is not signed in");
|
|
3477
|
+
return 0;
|
|
3478
|
+
}
|
|
3479
|
+
let state;
|
|
3480
|
+
try {
|
|
3481
|
+
state = await account.service.signOut();
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
io.error(`sign-out failed: ${message(error)}`);
|
|
3484
|
+
return 1;
|
|
3485
|
+
}
|
|
3486
|
+
const after = describeTunnelAccount(account.credentials.read());
|
|
3487
|
+
if (after.email) {
|
|
3488
|
+
io.error(`still signed in${state.message ? `: ${state.message}` : ""}`);
|
|
3489
|
+
return 1;
|
|
3490
|
+
}
|
|
3491
|
+
io.log(`Signed out ${before.email}${before.address ? `; ${before.address} is released` : ""}.`);
|
|
3492
|
+
return 0;
|
|
3493
|
+
}
|
|
3494
|
+
function serverEntry(here = HERE2) {
|
|
3495
|
+
const bundled = join3(here, "index.js");
|
|
3496
|
+
const root = resolve2(here, "..");
|
|
3497
|
+
if (existsSync2(bundled)) {
|
|
3498
|
+
const staticDir2 = [join3(root, "dist"), join3(here, "..", "ui")].find((d) => existsSync2(join3(d, "index.html"))) ?? null;
|
|
3499
|
+
const skillsDir = existsSync2(join3(root, "skills")) ? join3(root, "skills") : null;
|
|
3500
|
+
return { command: process.execPath, args: [bundled], staticDir: staticDir2, skillsDir };
|
|
3501
|
+
}
|
|
3502
|
+
const source = join3(here, "index.ts");
|
|
3503
|
+
const staticDir = existsSync2(join3(root, "dist", "index.html")) ? join3(root, "dist") : null;
|
|
3504
|
+
return { command: process.execPath, args: ["--experimental-strip-types", source], staticDir, skillsDir: existsSync2(join3(root, "skills")) ? join3(root, "skills") : null };
|
|
3505
|
+
}
|
|
3506
|
+
async function planTunnel(options, log) {
|
|
3507
|
+
const account = createTunnelAccount({ dataDir: options.dataDir, version: serverVersion() });
|
|
3508
|
+
if (account.credentials.status === "unavailable") return { error: `${account.credentials.file} exists but could not be read; fix or remove it` };
|
|
3509
|
+
if (!describeTunnelAccount(account.credentials.read()).email) {
|
|
3510
|
+
return { error: "no account on this machine yet: run `openmausbot login` first, then `openmausbot serve --tunnel`" };
|
|
3511
|
+
}
|
|
3512
|
+
try {
|
|
3513
|
+
const state = await account.service.retry();
|
|
3514
|
+
if (state.message && !tunnelAccess(account.credentials.read())) log(`tunnel: ${state.message}`);
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
log(`tunnel: control plane not reachable right now (${message(error)}); using the saved address`);
|
|
3517
|
+
}
|
|
3518
|
+
const access = tunnelAccess(account.credentials.read());
|
|
3519
|
+
if (!access) return { error: "this machine has no public address; run `openmausbot login` again" };
|
|
3520
|
+
let binary;
|
|
3521
|
+
try {
|
|
3522
|
+
binary = await ensureCloudflared({ dataDir: options.dataDir, log });
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
return { error: `--tunnel: ${message(error)}` };
|
|
3525
|
+
}
|
|
3526
|
+
const guardian = guardianEntry();
|
|
3527
|
+
if (!guardian) return { error: "--tunnel: the connector guardian is missing from this install" };
|
|
3528
|
+
return { access, binary, guardian, origin: createTunnelOrigin() };
|
|
3529
|
+
}
|
|
3530
|
+
async function runServe(options, log = console.log) {
|
|
3531
|
+
if (await serverUp(options.port)) {
|
|
3532
|
+
console.error(`something already answers on http://127.0.0.1:${options.port}; use \`openmausbot pair\` against it, or --port for a second server`);
|
|
3533
|
+
return 1;
|
|
3534
|
+
}
|
|
3535
|
+
let publicUrl = options.publicUrl;
|
|
3536
|
+
let tailscale = null;
|
|
3537
|
+
if (options.tailscale) {
|
|
3538
|
+
const probe = await tailscaleStatus();
|
|
3539
|
+
if ("failure" in probe) {
|
|
3540
|
+
console.error(`--tailscale: ${explainTailscaleFailure(probe.failure)}`);
|
|
3541
|
+
return 1;
|
|
3542
|
+
}
|
|
3543
|
+
tailscale = probe.status;
|
|
3544
|
+
}
|
|
3545
|
+
let plan = null;
|
|
3546
|
+
if (options.tunnel) {
|
|
3547
|
+
const planned = await planTunnel(options, log);
|
|
3548
|
+
if ("error" in planned) {
|
|
3549
|
+
console.error(planned.error);
|
|
3550
|
+
return 1;
|
|
3551
|
+
}
|
|
3552
|
+
plan = planned;
|
|
3553
|
+
if (publicUrl && publicUrl !== plan.access.endpoint) log(`note: --public-url is ignored with --tunnel; the address is ${plan.access.endpoint}`);
|
|
3554
|
+
publicUrl = plan.access.endpoint;
|
|
3555
|
+
}
|
|
3556
|
+
const entry = serverEntry();
|
|
3557
|
+
if (!entry.staticDir) log("note: no built UI found next to the server; the API runs but browsers get no page (build with `pnpm exec vite build`)");
|
|
3558
|
+
const env = {
|
|
3559
|
+
...process.env,
|
|
3560
|
+
OMB_DATA_DIR: options.dataDir,
|
|
3561
|
+
OMB_PORT: String(options.port),
|
|
3562
|
+
OMB_WEBHOOK_PORT: process.env.OMB_WEBHOOK_PORT || String(options.port + 1)
|
|
3563
|
+
};
|
|
3564
|
+
if (entry.staticDir) env.OMB_STATIC_DIR = entry.staticDir;
|
|
3565
|
+
if (entry.skillsDir && !process.env.OMB_SKILLS_DIR) env.OMB_SKILLS_DIR = entry.skillsDir;
|
|
3566
|
+
if (options.label && !process.env.OMB_ENVIRONMENT_LABEL) env.OMB_ENVIRONMENT_LABEL = options.label;
|
|
3567
|
+
if (plan) env.OMB_TUNNEL_SOCKET = plan.origin.socketPath;
|
|
3568
|
+
if (tailscale) {
|
|
3569
|
+
const served = await tailscaleServe(tailscale, options.port);
|
|
3570
|
+
if ("failure" in served) {
|
|
3571
|
+
console.error(`--tailscale: ${explainTailscaleFailure(served.failure)}`);
|
|
3572
|
+
return 1;
|
|
3573
|
+
}
|
|
3574
|
+
publicUrl = served.origin;
|
|
3575
|
+
log(`tailscale: serving https://${tailscale.dnsName} \u2192 http://127.0.0.1:${options.port} (only your tailnet can reach it)`);
|
|
3576
|
+
}
|
|
3577
|
+
if (publicUrl) env.OMB_PUBLIC_URL = publicUrl;
|
|
3578
|
+
const child = spawn4(entry.command, entry.args, { env, stdio: ["ignore", "inherit", "inherit"] });
|
|
3579
|
+
let exited = null;
|
|
3580
|
+
child.on("exit", (code) => {
|
|
3581
|
+
exited = code ?? 1;
|
|
3582
|
+
});
|
|
3583
|
+
let tunnel = null;
|
|
3584
|
+
let stopping = null;
|
|
3585
|
+
const stop = () => {
|
|
3586
|
+
stopping ??= (async () => {
|
|
3587
|
+
if (tunnel) await tunnel.stop().catch(() => void 0);
|
|
3588
|
+
if (tailscale) await tailscaleServeOff(tailscale).catch(() => void 0);
|
|
3589
|
+
if (exited === null) child.kill("SIGTERM");
|
|
3590
|
+
if (plan) cleanupTunnelOrigin(plan.origin);
|
|
3591
|
+
})();
|
|
3592
|
+
return stopping;
|
|
3593
|
+
};
|
|
3594
|
+
process.on("SIGINT", () => void stop());
|
|
3595
|
+
process.on("SIGTERM", () => void stop());
|
|
3596
|
+
const deadline = Date.now() + 6e4;
|
|
3597
|
+
while (Date.now() < deadline && exited === null) {
|
|
3598
|
+
if (await serverUp(options.port)) break;
|
|
3599
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
3600
|
+
}
|
|
3601
|
+
if (exited !== null) {
|
|
3602
|
+
if (plan) cleanupTunnelOrigin(plan.origin);
|
|
3603
|
+
return exited;
|
|
3604
|
+
}
|
|
3605
|
+
if (!await serverUp(options.port)) {
|
|
3606
|
+
console.error("the server did not answer within a minute; see its output above");
|
|
3607
|
+
await stop();
|
|
3608
|
+
return 1;
|
|
3609
|
+
}
|
|
3610
|
+
if (plan && child.pid) {
|
|
3611
|
+
tunnel = startTunnel({
|
|
3612
|
+
dataDir: options.dataDir,
|
|
3613
|
+
access: plan.access,
|
|
3614
|
+
originTarget: { pid: child.pid, socketPath: plan.origin.socketPath },
|
|
3615
|
+
binaryPath: plan.binary,
|
|
3616
|
+
guardian: plan.guardian,
|
|
3617
|
+
onState: (state) => log(describeTunnelState(state, plan.access.endpoint))
|
|
3618
|
+
});
|
|
3619
|
+
tunnel.started.catch((error) => log(`tunnel: ${message(error)}`));
|
|
3620
|
+
}
|
|
3621
|
+
log("");
|
|
3622
|
+
log(`OpenMausBot is running on http://127.0.0.1:${options.port}${publicUrl ? `, reachable at ${publicUrl}` : ""}`);
|
|
3623
|
+
log(`data: ${options.dataDir}`);
|
|
3624
|
+
if (options.pair) {
|
|
3625
|
+
log("");
|
|
3626
|
+
log(await mintPairing(options.port, { label: options.label ? `${options.label} owner` : void 0, publicUrl: publicUrl ?? void 0 }));
|
|
3627
|
+
log("");
|
|
3628
|
+
log('another device later: openmausbot pair --label "Kitchen iPad"');
|
|
3629
|
+
}
|
|
3630
|
+
log("stop with Ctrl+C");
|
|
3631
|
+
return await new Promise((resolveExit) => {
|
|
3632
|
+
child.on("exit", (code) => {
|
|
3633
|
+
void stop().finally(() => resolveExit(code ?? 0));
|
|
3634
|
+
});
|
|
3635
|
+
});
|
|
3636
|
+
}
|
|
3637
|
+
async function main(argv = process.argv.slice(2)) {
|
|
3638
|
+
const options = parseArgs(argv);
|
|
3639
|
+
if ("error" in options) {
|
|
3640
|
+
console.error(`${options.error}
|
|
3641
|
+
|
|
3642
|
+
${USAGE}`);
|
|
3643
|
+
return 2;
|
|
3644
|
+
}
|
|
3645
|
+
switch (options.command) {
|
|
3646
|
+
case "serve":
|
|
3647
|
+
return runServe(options);
|
|
3648
|
+
case "pair":
|
|
3649
|
+
return runPair(options);
|
|
3650
|
+
case "sessions":
|
|
3651
|
+
return runSessions(options);
|
|
3652
|
+
case "status":
|
|
3653
|
+
return runStatus(options);
|
|
3654
|
+
case "login":
|
|
3655
|
+
return runLogin(options);
|
|
3656
|
+
case "logout":
|
|
3657
|
+
return runLogout(options);
|
|
3658
|
+
default:
|
|
3659
|
+
console.log(USAGE);
|
|
3660
|
+
return 0;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// server/pair-cli.ts
|
|
3665
|
+
main(["pair", ...process.argv.slice(2)]).then(
|
|
3666
|
+
(code) => process.exit(code),
|
|
3667
|
+
(error) => {
|
|
3668
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3669
|
+
process.exit(1);
|
|
3670
|
+
}
|
|
3671
|
+
);
|