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,96 @@
|
|
|
1
|
+
export const RETRY_MAX_ATTEMPTS = 3;
|
|
2
|
+
/** Backoff schedule before attempt N (N is 1-based over retries): 1s / 3s / 8s. */
|
|
3
|
+
export const BACKOFF_BASE_MS = [1_000, 3_000, 8_000];
|
|
4
|
+
const TRANSIENT_PATTERNS = [
|
|
5
|
+
{ pattern: /\b(?:429|rate.?limit|too many requests)\b/i, reason: "rate_limited" },
|
|
6
|
+
{ pattern: /\boverloaded\b|\bcapacity\b/i, reason: "overloaded" },
|
|
7
|
+
{ pattern: /\b5\d{2}\b|\binternal server error\b|\bbad gateway\b|\bservice unavailable\b/i, reason: "server_error" },
|
|
8
|
+
{
|
|
9
|
+
pattern: /\b(?:econnreset|econnrefused|epipe|etimedout|eai_again|connection reset|connection refused|socket hang up|network error|fetch failed)\b/i,
|
|
10
|
+
reason: "connection_reset",
|
|
11
|
+
},
|
|
12
|
+
{ pattern: /\btimeout(ed)?\b|\btimed? out\b/i, reason: "timeout" },
|
|
13
|
+
];
|
|
14
|
+
const TERMINAL_PATTERNS = [
|
|
15
|
+
{
|
|
16
|
+
pattern: /\b(?:40[13]|unauthorized|forbidden|invalid api key|missing bearer|authentication required|not logged in|logged out)\b/i,
|
|
17
|
+
reason: "auth",
|
|
18
|
+
},
|
|
19
|
+
{ pattern: /\b402\b|\bquota\b|\bbilling\b|\bsubscription\b/i, reason: "quota" },
|
|
20
|
+
{ pattern: /\bmodel not found\b|\bunknown model\b|\bdoes not exist for model\b|\bunsupported model\b/i, reason: "unknown_model" },
|
|
21
|
+
{ pattern: /\b400\b|\b422\b|\binvalid request\b|\bmalformed\b|\bunexpected status\b/i, reason: "invalid_request" },
|
|
22
|
+
{ pattern: /\b404\b|\bno such thread\b|\bthread gone\b/i, reason: "not_found" },
|
|
23
|
+
// interrupt/cancel vocabulary from the drivers' own stop paths — a turn the
|
|
24
|
+
// user stopped must never come back as an auto-retry
|
|
25
|
+
{ pattern: /\b(?:interrupted|cancelled by user)\b/i, reason: "interrupted" },
|
|
26
|
+
];
|
|
27
|
+
const messageOf = (err) => {
|
|
28
|
+
if (!err)
|
|
29
|
+
return "";
|
|
30
|
+
if (err instanceof Error)
|
|
31
|
+
return `${err.message}${err.cause ? ` ${String(err.cause)}` : ""}`;
|
|
32
|
+
if ("text" in err)
|
|
33
|
+
return err.text;
|
|
34
|
+
return [err.stderr ?? "", ""].join(" ").trim();
|
|
35
|
+
};
|
|
36
|
+
/** Classify a thrown error or a CLI exit into retry-worthy vs terminal.
|
|
37
|
+
*
|
|
38
|
+
* Exit-report shape: a nonzero exit with no error text is treated as
|
|
39
|
+
* terminal (`terminal_exit`) — drivers only reach it after the CLI already
|
|
40
|
+
* reported its own protocol-level failure. A signal kill (negative code) is
|
|
41
|
+
* never retried either.
|
|
42
|
+
*/
|
|
43
|
+
export function classifyError(err) {
|
|
44
|
+
const text = messageOf(err);
|
|
45
|
+
if (err && "exitCode" in err) {
|
|
46
|
+
const { exitCode: code } = err;
|
|
47
|
+
if (code !== null && code < 0)
|
|
48
|
+
return { transient: false, reason: "interrupted" };
|
|
49
|
+
for (const { pattern, reason } of TRANSIENT_PATTERNS) {
|
|
50
|
+
if (pattern.test(text))
|
|
51
|
+
return { transient: true, reason };
|
|
52
|
+
}
|
|
53
|
+
for (const { pattern, reason } of TERMINAL_PATTERNS) {
|
|
54
|
+
if (pattern.test(text))
|
|
55
|
+
return { transient: false, reason };
|
|
56
|
+
}
|
|
57
|
+
return { transient: false, reason: "terminal_exit" };
|
|
58
|
+
}
|
|
59
|
+
for (const { pattern, reason } of TERMINAL_PATTERNS) {
|
|
60
|
+
if (pattern.test(text))
|
|
61
|
+
return { transient: false, reason };
|
|
62
|
+
}
|
|
63
|
+
for (const { pattern, reason } of TRANSIENT_PATTERNS) {
|
|
64
|
+
if (pattern.test(text))
|
|
65
|
+
return { transient: true, reason };
|
|
66
|
+
}
|
|
67
|
+
return { transient: false, reason: "unknown" };
|
|
68
|
+
}
|
|
69
|
+
/** Capped exponential delay with jitter, in milliseconds. Attempt 0 (the
|
|
70
|
+
* first retry) waits ~1s, then ~3s, then ~8s; beyond that the cap holds.
|
|
71
|
+
* Jitter stays within ±25% so tests can bound it and a thundering herd of
|
|
72
|
+
* bots doesn't re-sync on the same tick. */
|
|
73
|
+
export function computeBackoff(attempt, random = Math.random) {
|
|
74
|
+
const base = BACKOFF_BASE_MS[Math.min(Math.max(attempt, 0), BACKOFF_BASE_MS.length - 1)];
|
|
75
|
+
const jitter = base * 0.25;
|
|
76
|
+
return Math.round(base - jitter + random() * jitter * 2);
|
|
77
|
+
}
|
|
78
|
+
export function interruptibleDelay(ms, signal) {
|
|
79
|
+
let timer = null;
|
|
80
|
+
let onCancel = null;
|
|
81
|
+
const promise = new Promise((resolve) => {
|
|
82
|
+
timer = setTimeout(() => resolve("elapsed"), Math.max(1, ms));
|
|
83
|
+
timer.unref?.();
|
|
84
|
+
if (signal?.aborted)
|
|
85
|
+
return resolve("cancelled");
|
|
86
|
+
onCancel = () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
resolve("cancelled");
|
|
89
|
+
};
|
|
90
|
+
signal?.addEventListener("abort", onCancel, { once: true });
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
promise,
|
|
94
|
+
cancel: () => onCancel?.(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
// Neutral hook point for the source-available enterprise layer.
|
|
10
|
+
//
|
|
11
|
+
// The layer lives in ../enterprise (its own LICENSE) and is loaded only if that
|
|
12
|
+
// folder is present: delete it and this file still compiles, the server still
|
|
13
|
+
// starts, and /api/edition reports the open-source edition. Core never imports
|
|
14
|
+
// the layer statically, so the bundle and the packaged app carry no trace of it.
|
|
15
|
+
// The layer's only obligation is `register()`, which turns OMB_LICENSE_KEY into
|
|
16
|
+
// entitlements; core keeps the resulting status and answers `entitled()`.
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { SERVER_ROOT } from "./proxy-paths.js";
|
|
22
|
+
/** What the enterprise layer's register() must return; validated because it is external code. */
|
|
23
|
+
const layerSchema = z.object({
|
|
24
|
+
customer: z.string().min(1),
|
|
25
|
+
features: z.array(z.string().min(1)),
|
|
26
|
+
expiresAt: z.string().nullable(),
|
|
27
|
+
});
|
|
28
|
+
const DEFAULT_DIR = join(SERVER_ROOT, "..", "enterprise");
|
|
29
|
+
let current = { edition: "oss", features: [] };
|
|
30
|
+
function oss(notice) {
|
|
31
|
+
current = notice ? { edition: "oss", features: [], notice } : { edition: "oss", features: [] };
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
34
|
+
/** Resolve the edition once at startup. Never throws: a broken layer or key
|
|
35
|
+
* degrades to the open-source edition with a notice that says what to fix. */
|
|
36
|
+
export async function loadEnterpriseLayer(options = {}) {
|
|
37
|
+
const dir = options.dir ?? process.env.OMB_ENTERPRISE_DIR ?? DEFAULT_DIR;
|
|
38
|
+
const licenseKey = options.licenseKey ?? process.env.OMB_LICENSE_KEY;
|
|
39
|
+
// Source in a checkout, a compiled bundle in an image: same convention as proxy-paths.ts.
|
|
40
|
+
const entry = [join(dir, "server", "index.ts"), join(dir, "server", "index.js")].find((candidate) => existsSync(candidate));
|
|
41
|
+
if (!entry) {
|
|
42
|
+
return oss(licenseKey ? `OMB_LICENSE_KEY is set but no enterprise layer exists at ${dir}` : undefined);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(entry).href));
|
|
46
|
+
const register = Reflect.get(Object(loaded), "register");
|
|
47
|
+
if (typeof register !== "function")
|
|
48
|
+
throw new Error(`${entry} does not export register()`);
|
|
49
|
+
if (!licenseKey)
|
|
50
|
+
return oss("enterprise layer present but OMB_LICENSE_KEY is not set");
|
|
51
|
+
const registered = await register({ licenseKey });
|
|
52
|
+
const layer = layerSchema.parse(registered);
|
|
53
|
+
current = {
|
|
54
|
+
edition: "enterprise",
|
|
55
|
+
customer: layer.customer,
|
|
56
|
+
features: [...new Set(layer.features)].sort(),
|
|
57
|
+
expiresAt: layer.expiresAt,
|
|
58
|
+
};
|
|
59
|
+
return current;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return oss(`enterprise layer disabled: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** A license that expires while the server keeps running stops granting
|
|
66
|
+
* features at that moment, not at the next restart. */
|
|
67
|
+
function expired(now) {
|
|
68
|
+
if (current.edition !== "enterprise" || !current.expiresAt)
|
|
69
|
+
return false;
|
|
70
|
+
return now >= new Date(current.expiresAt).getTime();
|
|
71
|
+
}
|
|
72
|
+
export function editionStatus(now = Date.now()) {
|
|
73
|
+
if (!expired(now))
|
|
74
|
+
return current;
|
|
75
|
+
return {
|
|
76
|
+
edition: "oss",
|
|
77
|
+
features: [],
|
|
78
|
+
notice: `enterprise layer disabled: OMB_LICENSE_KEY expired on ${current.expiresAt}; renew it to keep enterprise features`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Feature gates in core ask this and nothing else. Unknown ids are simply not
|
|
82
|
+
* granted, and the open-source edition always carries an empty feature list. */
|
|
83
|
+
export function entitled(feature, now = Date.now()) {
|
|
84
|
+
return !expired(now) && current.features.includes(feature);
|
|
85
|
+
}
|
|
86
|
+
/** One line for the startup log. */
|
|
87
|
+
export function describeEdition(status) {
|
|
88
|
+
if (status.edition === "enterprise") {
|
|
89
|
+
const until = status.expiresAt ? ` until ${status.expiresAt}` : "";
|
|
90
|
+
return `openmausbot enterprise edition for ${status.customer}${until}: ${status.features.join(", ") || "no features"}`;
|
|
91
|
+
}
|
|
92
|
+
return `openmausbot open-source edition${status.notice ? ` (${status.notice})` : ""}`;
|
|
93
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// PATH augmentation for GUI launches — the fix for "CLI not found" when
|
|
2
|
+
// the app is opened from Finder (issues #8, #12).
|
|
3
|
+
//
|
|
4
|
+
// A macOS app launched from Finder inherits a bare PATH
|
|
5
|
+
// (/usr/bin:/bin:...): no ~/.local/bin (the claude installer default),
|
|
6
|
+
// no /opt/homebrew/bin, and no nvm/volta/asdf shims — those only exist
|
|
7
|
+
// in interactive shells. The terminal sees the CLIs; the packaged app
|
|
8
|
+
// doesn't. So every spawn of an agent CLI goes through augmentedPath():
|
|
9
|
+
// the inherited PATH, plus the well-known install locations that exist
|
|
10
|
+
// on this machine, plus (async, best-effort) whatever PATH the user's
|
|
11
|
+
// real login shell reports.
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, readdirSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { basename, delimiter, dirname, extname, join } from "node:path";
|
|
16
|
+
/** nvm keeps every node version's bin dir separately; newest first so a
|
|
17
|
+
* CLI installed under the latest node wins. */
|
|
18
|
+
function nvmBinDirs() {
|
|
19
|
+
try {
|
|
20
|
+
const base = join(homedir(), ".nvm", "versions", "node");
|
|
21
|
+
return readdirSync(base)
|
|
22
|
+
.filter((v) => v.startsWith("v"))
|
|
23
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
|
|
24
|
+
.map((v) => join(base, v, "bin"));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function knownDirs() {
|
|
31
|
+
const home = homedir();
|
|
32
|
+
return [
|
|
33
|
+
join(home, ".local", "bin"), // claude installer default
|
|
34
|
+
join(home, ".npm-global", "bin"), // npm prefix ~/.npm-global (claude, opencode)
|
|
35
|
+
join(home, ".kimi-code", "bin"), // kimi-code installer
|
|
36
|
+
join(home, ".grok", "bin"), // x.ai installer
|
|
37
|
+
join(home, ".opencode", "bin"), // opencode installer
|
|
38
|
+
join(home, ".claude", "local"), // claude "local install"
|
|
39
|
+
"/opt/homebrew/bin", // brew, Apple silicon
|
|
40
|
+
"/usr/local/bin", // brew Intel / classic installs
|
|
41
|
+
join(home, ".volta", "bin"),
|
|
42
|
+
join(home, ".bun", "bin"),
|
|
43
|
+
join(home, ".asdf", "shims"),
|
|
44
|
+
join(home, ".deno", "bin"),
|
|
45
|
+
join(home, "bin"),
|
|
46
|
+
...nvmBinDirs(),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/** Windows equivalents of knownDirs. A GUI app inherits the user PATH at
|
|
50
|
+
* launch, but only at launch: a CLI installed while the app is running is
|
|
51
|
+
* invisible until it restarts, because Windows never pushes PATH changes
|
|
52
|
+
* into a live process. Scanning the standard install locations recovers
|
|
53
|
+
* those without a restart — `~/.grok/bin` (the x.ai installer) and
|
|
54
|
+
* `%APPDATA%\npm` (global npm shims), plus `%LOCALAPPDATA%\agy\bin`, cover
|
|
55
|
+
* every engine we ship an install command for. */
|
|
56
|
+
function windowsKnownDirs() {
|
|
57
|
+
const home = homedir();
|
|
58
|
+
const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
59
|
+
const localAppData = process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
|
|
60
|
+
return [
|
|
61
|
+
join(appData, "npm"), // npm -g shims: claude, codex
|
|
62
|
+
join(home, ".grok", "bin"), // x.ai installer
|
|
63
|
+
join(localAppData, "agy", "bin"), // Antigravity installer
|
|
64
|
+
join(home, ".local", "bin"), // claude native installer
|
|
65
|
+
join(home, ".claude", "local"),
|
|
66
|
+
join(home, "bin"), // Factory droid installer (%USERPROFILE%\bin)
|
|
67
|
+
join(home, ".bun", "bin"),
|
|
68
|
+
join(home, ".deno", "bin"),
|
|
69
|
+
join(home, "go", "bin"),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
let cached = null;
|
|
73
|
+
let probed = false;
|
|
74
|
+
let loginShellPath = null;
|
|
75
|
+
/** Drop the memoized PATH so the next augmentedPath() rescans. Called when
|
|
76
|
+
* the app re-probes engines, so "check again" can find something installed
|
|
77
|
+
* since launch instead of answering from the PATH we booted with. `probed`
|
|
78
|
+
* must reset too: the login-shell probe merges rc-file PATH entries (e.g.
|
|
79
|
+
* ~/.kimi-code/bin exported from .zshrc) into the cache asynchronously,
|
|
80
|
+
* and without resetting it a rescan would rebuild the cache without those
|
|
81
|
+
* entries and never re-probe — "check again" would permanently lose
|
|
82
|
+
* anything only the login shell's rc file knows about. */
|
|
83
|
+
export function resetPathCache() {
|
|
84
|
+
cached = null;
|
|
85
|
+
probed = false;
|
|
86
|
+
}
|
|
87
|
+
/** Current best PATH, synchronously. Cheap after the first call. */
|
|
88
|
+
export function augmentedPath() {
|
|
89
|
+
if (cached === null) {
|
|
90
|
+
cached = mergePaths([
|
|
91
|
+
...(process.env.OMB_EXTRA_PATH ? process.env.OMB_EXTRA_PATH.split(delimiter) : []),
|
|
92
|
+
...(process.env.PATH ? process.env.PATH.split(delimiter) : []),
|
|
93
|
+
// Keep the last successful login-shell result while a rescan starts a
|
|
94
|
+
// fresh asynchronous probe. Otherwise resetPathCache() would make
|
|
95
|
+
// rc-only CLIs disappear again for the response that triggered it.
|
|
96
|
+
...(loginShellPath ? loginShellPath.split(delimiter) : []),
|
|
97
|
+
// Both platforms scan their standard install locations; only the
|
|
98
|
+
// login-shell probe below stays unix-only, since Windows has no
|
|
99
|
+
// equivalent rc file to source.
|
|
100
|
+
...(process.platform === "win32" ? windowsKnownDirs() : knownDirs()).filter((d) => existsSync(d)),
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
// belt-and-braces: fold in the login shell's PATH once, in the
|
|
104
|
+
// background — catches anything the known-dirs list doesn't (custom
|
|
105
|
+
// rc exports). Never blocks a spawn; the next one benefits.
|
|
106
|
+
if (!probed && !process.env.VITEST && process.platform !== "win32") {
|
|
107
|
+
probed = true;
|
|
108
|
+
probeLoginShellPath();
|
|
109
|
+
}
|
|
110
|
+
return cached;
|
|
111
|
+
}
|
|
112
|
+
function mergePaths(parts) {
|
|
113
|
+
return [...new Set(parts.filter(Boolean))].join(delimiter);
|
|
114
|
+
}
|
|
115
|
+
function probeLoginShellPath() {
|
|
116
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
117
|
+
// -l -i: nvm and friends live in .zshrc/.bashrc, which only interactive
|
|
118
|
+
// shells read. A marker isolates $PATH from any rc-file noise.
|
|
119
|
+
execFile(shell, ["-l", "-i", "-c", 'printf "__OMB_PATH__%s" "$PATH"'], { timeout: 5000 }, (err, stdout) => {
|
|
120
|
+
if (err || !stdout)
|
|
121
|
+
return;
|
|
122
|
+
const m = /__OMB_PATH__([^\n]*)/.exec(stdout);
|
|
123
|
+
if (!m || !m[1])
|
|
124
|
+
return;
|
|
125
|
+
loginShellPath = m[1];
|
|
126
|
+
cached = mergePaths([...(cached ?? "").split(delimiter), ...m[1].split(delimiter)]);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Test hook — the cache is process-wide otherwise. */
|
|
130
|
+
export function resetPathCacheForTests() {
|
|
131
|
+
cached = null;
|
|
132
|
+
probed = false;
|
|
133
|
+
loginShellPath = null;
|
|
134
|
+
}
|
|
135
|
+
/** Every `name` binary on the augmented PATH as absolute paths, in PATH
|
|
136
|
+
* order (first = what a bare name would run). Used by the Engines panel's
|
|
137
|
+
* "detected" dropdown and the /api/cli-candidates endpoint. A path-ish
|
|
138
|
+
* name is echoed back as-is — it already IS a location. */
|
|
139
|
+
export function findCliCandidates(name) {
|
|
140
|
+
if (!name || /[\n\r]/.test(name))
|
|
141
|
+
return [];
|
|
142
|
+
if (/[/\\]/.test(name) || /^[a-zA-Z]:/.test(name))
|
|
143
|
+
return [name];
|
|
144
|
+
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
145
|
+
const out = [];
|
|
146
|
+
for (const dir of augmentedPath().split(delimiter)) {
|
|
147
|
+
if (!dir)
|
|
148
|
+
continue;
|
|
149
|
+
for (const ext of exts) {
|
|
150
|
+
const p = join(dir, name + ext);
|
|
151
|
+
if (existsSync(p)) {
|
|
152
|
+
out.push(p);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
/** Split a `cli` string into [command, ...fixedArgs] on unquoted whitespace —
|
|
160
|
+
* a mini tokenizer, never a shell. Quotes group segments (paths with spaces,
|
|
161
|
+
* fixed args with spaces); no escapes, no substitution, nothing evaluated. */
|
|
162
|
+
export function splitCliString(cli) {
|
|
163
|
+
const out = [];
|
|
164
|
+
let cur = "";
|
|
165
|
+
let quote = null;
|
|
166
|
+
for (const ch of cli.trim()) {
|
|
167
|
+
if (quote) {
|
|
168
|
+
if (ch === quote)
|
|
169
|
+
quote = null;
|
|
170
|
+
else
|
|
171
|
+
cur += ch;
|
|
172
|
+
}
|
|
173
|
+
else if (ch === '"' || ch === "'") {
|
|
174
|
+
quote = ch;
|
|
175
|
+
}
|
|
176
|
+
else if (/\s/.test(ch)) {
|
|
177
|
+
if (cur)
|
|
178
|
+
out.push(cur);
|
|
179
|
+
cur = "";
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
cur += ch;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (cur)
|
|
186
|
+
out.push(cur);
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
function isFile(p) {
|
|
190
|
+
try {
|
|
191
|
+
return statSync(p, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** PATHEXT-aware `which`. A path-ish cli is probed where it points. */
|
|
198
|
+
function whichWin(cli) {
|
|
199
|
+
const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
200
|
+
// an extensionless name is not runnable on Windows, so PATHEXT wins over
|
|
201
|
+
// the bare file — npm installs both `claude` (a sh script) and `claude.cmd`
|
|
202
|
+
const probe = (base) => {
|
|
203
|
+
const order = extname(base) ? [base, ...exts.map((e) => base + e)] : [...exts.map((e) => base + e), base];
|
|
204
|
+
return order.find(isFile) ?? null;
|
|
205
|
+
};
|
|
206
|
+
if (/[\\/]/.test(cli) || /^[a-zA-Z]:/.test(cli))
|
|
207
|
+
return probe(cli);
|
|
208
|
+
for (const dir of augmentedPath().split(delimiter)) {
|
|
209
|
+
if (!dir)
|
|
210
|
+
continue;
|
|
211
|
+
const hit = probe(join(dir, cli));
|
|
212
|
+
if (hit)
|
|
213
|
+
return hit;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/** node.exe to run a script with: the one npm's shim would pick, else PATH,
|
|
218
|
+
* else this executable only when it really is Node. In a packaged app,
|
|
219
|
+
* process.execPath is Electron and must never be mistaken for node.exe. */
|
|
220
|
+
function nodeExe(near) {
|
|
221
|
+
const local = join(near, "node.exe");
|
|
222
|
+
if (isFile(local))
|
|
223
|
+
return local;
|
|
224
|
+
// Ask for the executable explicitly so a custom PATHEXT ordering cannot
|
|
225
|
+
// make a stray node.cmd hide the real node.exe beside it.
|
|
226
|
+
const onPath = whichWin("node.exe");
|
|
227
|
+
if (onPath && extname(onPath).toLowerCase() === ".exe")
|
|
228
|
+
return onPath;
|
|
229
|
+
return process.versions.electron ? null : process.execPath;
|
|
230
|
+
}
|
|
231
|
+
/** npm/pnpm .cmd shims all spell their target as "%dp0%\..." (or
|
|
232
|
+
* "%~dp0\..."). Whatever of those exists on disk is what the shim runs. */
|
|
233
|
+
function parseCmdShim(shim) {
|
|
234
|
+
let text;
|
|
235
|
+
try {
|
|
236
|
+
text = readFileSync(shim, "utf8");
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const dir = dirname(shim);
|
|
242
|
+
const targets = [...text.matchAll(/"%~?dp0%?\\?([^"]+)"/g)]
|
|
243
|
+
.map((m) => join(dir, m[1]))
|
|
244
|
+
.filter((p) => isFile(p) && basename(p).toLowerCase() !== "node.exe");
|
|
245
|
+
const script = targets.find((p) => /\.[cm]?js$/i.test(p));
|
|
246
|
+
if (script) {
|
|
247
|
+
const node = nodeExe(dir);
|
|
248
|
+
if (node)
|
|
249
|
+
return { command: node, args: [script] };
|
|
250
|
+
}
|
|
251
|
+
const exe = targets.find((p) => extname(p).toLowerCase() === ".exe");
|
|
252
|
+
return exe ? { command: exe, args: [] } : null;
|
|
253
|
+
}
|
|
254
|
+
/** `#!/usr/bin/env node` → `node <script>`. Only node: nothing else has a
|
|
255
|
+
* meaningful Windows equivalent worth guessing at. */
|
|
256
|
+
function parseNodeShebang(file) {
|
|
257
|
+
let head = "";
|
|
258
|
+
let fd = null;
|
|
259
|
+
try {
|
|
260
|
+
fd = openSync(file, "r");
|
|
261
|
+
const buf = Buffer.alloc(128);
|
|
262
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
263
|
+
head = buf.subarray(0, n).toString("utf8").split("\n", 1)[0];
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
if (fd !== null) {
|
|
270
|
+
try {
|
|
271
|
+
closeSync(fd);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
/* best-effort descriptor cleanup */
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!/^#!.*\bnode(\.exe)?\b/.test(head))
|
|
279
|
+
return null;
|
|
280
|
+
const node = nodeExe(dirname(file));
|
|
281
|
+
return node ? { command: node, args: [file] } : null;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* How to actually spawn `cli` with `args` on this platform. Identity
|
|
285
|
+
* everywhere but win32 — POSIX already resolves PATH and #! itself.
|
|
286
|
+
*/
|
|
287
|
+
/** Resolve a single command word (no tokenizer) — the platform spawn rules. */
|
|
288
|
+
function resolveWord(cli, args) {
|
|
289
|
+
if (process.platform !== "win32")
|
|
290
|
+
return { command: cli, args };
|
|
291
|
+
const file = whichWin(cli);
|
|
292
|
+
// not found: hand back the name so spawn reports its own ENOENT
|
|
293
|
+
if (!file)
|
|
294
|
+
return { command: cli, args };
|
|
295
|
+
const ext = extname(file).toLowerCase();
|
|
296
|
+
if (ext === ".cmd" || ext === ".bat") {
|
|
297
|
+
const direct = parseCmdShim(file);
|
|
298
|
+
return direct ? { command: direct.command, args: [...direct.args, ...args] } : { command: file, args };
|
|
299
|
+
}
|
|
300
|
+
if (ext === ".exe" || ext === ".com")
|
|
301
|
+
return { command: file, args };
|
|
302
|
+
const viaNode = parseNodeShebang(file);
|
|
303
|
+
return viaNode ? { command: viaNode.command, args: [...viaNode.args, ...args] } : { command: file, args };
|
|
304
|
+
}
|
|
305
|
+
export function resolveCliSpawn(cli, args) {
|
|
306
|
+
// An EXISTING FILE wins over the tokenizer: paths with spaces come from
|
|
307
|
+
// our own candidate list unquoted ("/Applications/My Tools/claude"), and
|
|
308
|
+
// splitting those would shred them. Only a bare word (no spaces) or an
|
|
309
|
+
// explicitly-quoted/wrapper string reaches the split below.
|
|
310
|
+
const trimmed = cli.trim();
|
|
311
|
+
if (trimmed.includes(" ") && existsSync(trimmed))
|
|
312
|
+
return resolveWord(trimmed, args);
|
|
313
|
+
// A `cli` value may carry fixed leading arguments — wrapper scripts like
|
|
314
|
+
// `/usr/local/bin/ag claude agp` are one string in the Engines panel. ONE
|
|
315
|
+
// tokenizer pass, then resolve the head directly (never re-tokenized: a
|
|
316
|
+
// quoted token may itself contain spaces, so feeding it back through the
|
|
317
|
+
// splitter would shred it). Fixed args go BEFORE invocation args — a
|
|
318
|
+
// wrapper's subcommand must lead (`ag claude agp --help`), or the wrapper
|
|
319
|
+
// swallows the flag itself.
|
|
320
|
+
const split = splitCliString(cli);
|
|
321
|
+
if (split.length > 1 || split[0] !== trimmed) {
|
|
322
|
+
const [head, ...fixed] = split;
|
|
323
|
+
// empty input → hand the raw string to spawn so IT reports the ENOENT
|
|
324
|
+
if (!head)
|
|
325
|
+
return { command: cli, args };
|
|
326
|
+
return resolveWord(head, [...fixed, ...args]);
|
|
327
|
+
}
|
|
328
|
+
return resolveWord(cli, args);
|
|
329
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// What a client learns about this server before it authenticates: a stable
|
|
2
|
+
// identity, a label, the version, and what it can do. Served without auth at
|
|
3
|
+
// /.well-known/openmausbot/environment so a saved connection can check it is
|
|
4
|
+
// still talking to the same server, and so version skew is visible.
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { closeSync, fsyncSync, linkSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
7
|
+
import { hostname } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { SERVER_ROOT } from "./proxy-paths.js";
|
|
10
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11
|
+
function hasCode(error, code) {
|
|
12
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
13
|
+
}
|
|
14
|
+
function readEnvironmentId(file) {
|
|
15
|
+
let contents;
|
|
16
|
+
try {
|
|
17
|
+
contents = readFileSync(file, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (hasCode(error, "ENOENT"))
|
|
21
|
+
return undefined;
|
|
22
|
+
throw new Error(`Cannot read the existing environment identity at ${file}; refusing to replace it.`, {
|
|
23
|
+
cause: error,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const id = contents.trim();
|
|
27
|
+
if (!UUID_PATTERN.test(id)) {
|
|
28
|
+
throw new Error(`The existing environment identity at ${file} is not a valid UUID; refusing to replace it.`);
|
|
29
|
+
}
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
/** Read or create the id. Written once, never rotated by the server itself. */
|
|
33
|
+
export function loadEnvironmentId(dataDir) {
|
|
34
|
+
const file = join(dataDir, "environment-id");
|
|
35
|
+
const existing = readEnvironmentId(file);
|
|
36
|
+
if (existing)
|
|
37
|
+
return existing;
|
|
38
|
+
const directory = dirname(file);
|
|
39
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
40
|
+
const id = randomUUID();
|
|
41
|
+
const temporaryFile = join(directory, `.environment-id.${process.pid}.${randomUUID()}.tmp`);
|
|
42
|
+
let descriptor;
|
|
43
|
+
try {
|
|
44
|
+
descriptor = openSync(temporaryFile, "wx", 0o600);
|
|
45
|
+
writeFileSync(descriptor, id + "\n", "utf8");
|
|
46
|
+
fsyncSync(descriptor);
|
|
47
|
+
closeSync(descriptor);
|
|
48
|
+
descriptor = undefined;
|
|
49
|
+
try {
|
|
50
|
+
// The temporary file is complete before it becomes visible at the final
|
|
51
|
+
// path. A hard link is an atomic, no-replace publish on every supported
|
|
52
|
+
// desktop platform, unlike rename (which overwrites on POSIX).
|
|
53
|
+
linkSync(temporaryFile, file);
|
|
54
|
+
return id;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!hasCode(error, "EEXIST"))
|
|
58
|
+
throw error;
|
|
59
|
+
const winner = readEnvironmentId(file);
|
|
60
|
+
if (winner)
|
|
61
|
+
return winner;
|
|
62
|
+
throw new Error(`The environment identity at ${file} disappeared while it was being created.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (descriptor !== undefined)
|
|
67
|
+
closeSync(descriptor);
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(temporaryFile);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A leftover private temporary file is harmless and must not make a
|
|
73
|
+
// successfully published identity unusable.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** The desktop app passes its own version; a checkout reads package.json;
|
|
78
|
+
* an image sets OMB_APP_VERSION at build time. */
|
|
79
|
+
export function serverVersion() {
|
|
80
|
+
const fromEnv = process.env.OMB_APP_VERSION?.trim();
|
|
81
|
+
if (fromEnv)
|
|
82
|
+
return fromEnv;
|
|
83
|
+
try {
|
|
84
|
+
const pkg = JSON.parse(readFileSync(join(SERVER_ROOT, "..", "package.json"), "utf8"));
|
|
85
|
+
const version = Reflect.get(Object(pkg), "version");
|
|
86
|
+
if (typeof version === "string" && version)
|
|
87
|
+
return version;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* no package.json next to the bundle */
|
|
91
|
+
}
|
|
92
|
+
return "unknown";
|
|
93
|
+
}
|
|
94
|
+
export function environmentDescriptor(input) {
|
|
95
|
+
return {
|
|
96
|
+
environmentId: input.environmentId,
|
|
97
|
+
label: process.env.OMB_ENVIRONMENT_LABEL?.trim() || hostname(),
|
|
98
|
+
platform: process.platform,
|
|
99
|
+
version: serverVersion(),
|
|
100
|
+
capabilities: {
|
|
101
|
+
remoteSessions: true,
|
|
102
|
+
selfUpdate: input.desktopManaged ? "desktop-managed" : "operator",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IMAGE_MAX_BYTES } from "./attachments.js";
|
|
2
|
+
const MAX_BASE64_CHARS = Math.ceil(IMAGE_MAX_BYTES / 3) * 4 + 8;
|
|
3
|
+
function sniffRaster(bytes) {
|
|
4
|
+
if (bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
5
|
+
return "image/png";
|
|
6
|
+
}
|
|
7
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
8
|
+
return "image/jpeg";
|
|
9
|
+
}
|
|
10
|
+
const head = bytes.subarray(0, 6).toString("ascii");
|
|
11
|
+
if (head === "GIF87a" || head === "GIF89a")
|
|
12
|
+
return "image/gif";
|
|
13
|
+
if (bytes.length >= 12 &&
|
|
14
|
+
bytes.subarray(0, 4).toString("ascii") === "RIFF" &&
|
|
15
|
+
bytes.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
16
|
+
return "image/webp";
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** Decode an untrusted provider image without trusting its claimed MIME.
|
|
21
|
+
* Codex normally sends raw base64, but accepting a raster data URL makes the
|
|
22
|
+
* boundary resilient to app-server shape changes without widening formats. */
|
|
23
|
+
export function decodeGeneratedImage(input) {
|
|
24
|
+
const trimmed = input.trim();
|
|
25
|
+
const comma = trimmed.startsWith("data:") ? trimmed.indexOf(",") : -1;
|
|
26
|
+
const encoded = comma >= 0 ? trimmed.slice(comma + 1) : trimmed;
|
|
27
|
+
if (!encoded || encoded.length > MAX_BASE64_CHARS || !/^[A-Za-z0-9+/\s]*={0,2}$/.test(encoded)) {
|
|
28
|
+
throw new Error("generated image payload is invalid or too large");
|
|
29
|
+
}
|
|
30
|
+
const compact = encoded.replace(/\s/g, "");
|
|
31
|
+
const bytes = Buffer.from(compact, "base64");
|
|
32
|
+
if (bytes.length === 0 || bytes.length > IMAGE_MAX_BYTES) {
|
|
33
|
+
throw new Error(`generated image exceeds ${IMAGE_MAX_BYTES} bytes`);
|
|
34
|
+
}
|
|
35
|
+
const mime = sniffRaster(bytes);
|
|
36
|
+
if (!mime)
|
|
37
|
+
throw new Error("generated image is not a supported raster format");
|
|
38
|
+
return { bytes, mime };
|
|
39
|
+
}
|