relay-ide 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -0
- package/dist/bin/claude-remote-cli.js +390 -0
- package/dist/bin/relay-ide.js +390 -0
- package/dist/frontend/assets/abap-BdImnpbu.js +1 -0
- package/dist/frontend/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/frontend/assets/ada-bCR0ucgS.js +1 -0
- package/dist/frontend/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/frontend/assets/angular-html-DA-rfuFy.js +1 -0
- package/dist/frontend/assets/angular-ts-BrjP3tb8.js +1 -0
- package/dist/frontend/assets/apache-Pmp26Uib.js +1 -0
- package/dist/frontend/assets/apex-D8_7TLub.js +1 -0
- package/dist/frontend/assets/apl-CORt7UWP.js +1 -0
- package/dist/frontend/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/frontend/assets/ara-BRHolxvo.js +1 -0
- package/dist/frontend/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/frontend/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/frontend/assets/astro-HNnZUWAn.js +1 -0
- package/dist/frontend/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/frontend/assets/awk-DMzUqQB5.js +1 -0
- package/dist/frontend/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/frontend/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/frontend/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/frontend/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/frontend/assets/bat-BkioyH1T.js +1 -0
- package/dist/frontend/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/frontend/assets/berry-uYugtg8r.js +1 -0
- package/dist/frontend/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/frontend/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/frontend/assets/bird2-BIv1doCn.js +1 -0
- package/dist/frontend/assets/blade-BjGOyj-B.js +1 -0
- package/dist/frontend/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/frontend/assets/c-BIGW1oBm.js +1 -0
- package/dist/frontend/assets/c3-eo99z4R2.js +1 -0
- package/dist/frontend/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/frontend/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/frontend/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/frontend/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/frontend/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/frontend/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/frontend/assets/clarity-D53aC0YG.js +1 -0
- package/dist/frontend/assets/clojure-P80f7IUj.js +1 -0
- package/dist/frontend/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/frontend/assets/cobol-nBiQ_Alo.js +1 -0
- package/dist/frontend/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/frontend/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/frontend/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/frontend/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/frontend/assets/coq-DkFqJrB1.js +1 -0
- package/dist/frontend/assets/cpp-CofmeUqb.js +1 -0
- package/dist/frontend/assets/crystal-DNxU26gB.js +1 -0
- package/dist/frontend/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/frontend/assets/css-CLj8gQPS.js +1 -0
- package/dist/frontend/assets/csv-fuZLfV_i.js +1 -0
- package/dist/frontend/assets/cue-D82EKSYY.js +1 -0
- package/dist/frontend/assets/cypher-COkxafJQ.js +1 -0
- package/dist/frontend/assets/d-85-TOEBH.js +1 -0
- package/dist/frontend/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/frontend/assets/dart-bE4Kk8sk.js +1 -0
- package/dist/frontend/assets/dax-CEL-wOlO.js +1 -0
- package/dist/frontend/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/frontend/assets/diff-D97Zzqfu.js +1 -0
- package/dist/frontend/assets/docker-BcOcwvcX.js +1 -0
- package/dist/frontend/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/frontend/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/frontend/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/frontend/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/frontend/assets/edge-FbVlp4U3.js +1 -0
- package/dist/frontend/assets/elixir-CkH2-t6x.js +1 -0
- package/dist/frontend/assets/elm-DbKCFpqz.js +1 -0
- package/dist/frontend/assets/emacs-lisp-CXvaQtF9.js +1 -0
- package/dist/frontend/assets/erb-BYCe7drp.js +1 -0
- package/dist/frontend/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/frontend/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/frontend/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/frontend/assets/fennel-BYunw83y.js +1 -0
- package/dist/frontend/assets/fish-BvzEVeQv.js +1 -0
- package/dist/frontend/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/frontend/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/frontend/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/frontend/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/frontend/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/frontend/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/frontend/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/frontend/assets/genie-D0YGMca9.js +1 -0
- package/dist/frontend/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/frontend/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/frontend/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/frontend/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/frontend/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/frontend/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/frontend/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/frontend/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/frontend/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/frontend/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/frontend/assets/gleam-BspZqrRM.js +1 -0
- package/dist/frontend/assets/glimmer-js-ByusRIyA.js +1 -0
- package/dist/frontend/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/dist/frontend/assets/glsl-DplSGwfg.js +1 -0
- package/dist/frontend/assets/gn-n2N0HUVH.js +1 -0
- package/dist/frontend/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/frontend/assets/go-C27-OAKa.js +1 -0
- package/dist/frontend/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/frontend/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/frontend/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/frontend/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/frontend/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/frontend/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/frontend/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/frontend/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/frontend/assets/hack-i7_Ulhet.js +1 -0
- package/dist/frontend/assets/haml-D5jkg6IW.js +1 -0
- package/dist/frontend/assets/handlebars-BpdQsYii.js +1 -0
- package/dist/frontend/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/frontend/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/frontend/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/frontend/assets/hjson-D5-asLiD.js +1 -0
- package/dist/frontend/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/frontend/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/frontend/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/dist/frontend/assets/houston-DnULxvSX.js +1 -0
- package/dist/frontend/assets/html-derivative-DlHx6ybY.js +1 -0
- package/dist/frontend/assets/html-pp8916En.js +1 -0
- package/dist/frontend/assets/http-jrhK8wxY.js +1 -0
- package/dist/frontend/assets/hurl-irOxFIW8.js +1 -0
- package/dist/frontend/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/frontend/assets/hy-DFXneXwc.js +1 -0
- package/dist/frontend/assets/imba-DGztddWO.js +1 -0
- package/dist/frontend/assets/ini-BEwlwnbL.js +1 -0
- package/dist/frontend/assets/java-CylS5w8V.js +1 -0
- package/dist/frontend/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/frontend/assets/jinja-f2NsQr07.js +1 -0
- package/dist/frontend/assets/jison-wvAkD_A8.js +1 -0
- package/dist/frontend/assets/json-Cp-IABpG.js +1 -0
- package/dist/frontend/assets/json5-C9tS-k6U.js +1 -0
- package/dist/frontend/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/frontend/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/frontend/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/frontend/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/frontend/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/frontend/assets/julia-CxzCAyBv.js +1 -0
- package/dist/frontend/assets/just-VxiPbLrw.js +1 -0
- package/dist/frontend/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/frontend/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/frontend/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/frontend/assets/kdl-DV7GczEv.js +1 -0
- package/dist/frontend/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/frontend/assets/kusto-wEQ09or8.js +1 -0
- package/dist/frontend/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/frontend/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/frontend/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/frontend/assets/less-B1dDrJ26.js +1 -0
- package/dist/frontend/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/frontend/assets/liquid-C0sCDyMI.js +1 -0
- package/dist/frontend/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/frontend/assets/log-2UxHyX5q.js +1 -0
- package/dist/frontend/assets/logo-BtOb2qkB.js +1 -0
- package/dist/frontend/assets/lua-BaeVxFsk.js +1 -0
- package/dist/frontend/assets/luau-C-HG3fhB.js +1 -0
- package/dist/frontend/assets/main-CL5_Wlhv.css +32 -0
- package/dist/frontend/assets/main-Czet4Z1x.js +371 -0
- package/dist/frontend/assets/make-CHLpvVh8.js +1 -0
- package/dist/frontend/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/frontend/assets/marko-DjSrsDqO.js +1 -0
- package/dist/frontend/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/frontend/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/frontend/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/frontend/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/frontend/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/frontend/assets/matlab-D7o27uSR.js +1 -0
- package/dist/frontend/assets/mdc-DTYItulj.js +1 -0
- package/dist/frontend/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/frontend/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/frontend/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/frontend/assets/min-light-CTRr51gU.js +1 -0
- package/dist/frontend/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/frontend/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/frontend/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/frontend/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/frontend/assets/move-IF9eRakj.js +1 -0
- package/dist/frontend/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/frontend/assets/nextflow-C-mBbutL.js +1 -0
- package/dist/frontend/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/dist/frontend/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/frontend/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/frontend/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/frontend/assets/nim-BIad80T-.js +1 -0
- package/dist/frontend/assets/nix-CwoSXNpI.js +1 -0
- package/dist/frontend/assets/nord-Ddv68eIx.js +1 -0
- package/dist/frontend/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/frontend/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/frontend/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/frontend/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/frontend/assets/odin-BBf5iR-q.js +1 -0
- package/dist/frontend/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/frontend/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/frontend/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/frontend/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/frontend/assets/perl-NvoQZIq0.js +1 -0
- package/dist/frontend/assets/php-R6g_5hLQ.js +1 -0
- package/dist/frontend/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/frontend/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/frontend/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/frontend/assets/po-BTJTHyun.js +1 -0
- package/dist/frontend/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/frontend/assets/polar-C0HS_06l.js +1 -0
- package/dist/frontend/assets/postcss-CXtECtnM.js +1 -0
- package/dist/frontend/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/frontend/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/frontend/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/frontend/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/frontend/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/frontend/assets/pug-DKIMFp6K.js +1 -0
- package/dist/frontend/assets/puppet-BMWR74SV.js +1 -0
- package/dist/frontend/assets/purescript-CklMAg4u.js +1 -0
- package/dist/frontend/assets/python-B6aJPvgy.js +1 -0
- package/dist/frontend/assets/qml-3beO22l8.js +1 -0
- package/dist/frontend/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/frontend/assets/qss-IeuSbFQv.js +1 -0
- package/dist/frontend/assets/r-Dspwwk_N.js +1 -0
- package/dist/frontend/assets/racket-BqYA7rlc.js +1 -0
- package/dist/frontend/assets/raku-DXvB9xmW.js +1 -0
- package/dist/frontend/assets/razor-BDqjjVU7.js +1 -0
- package/dist/frontend/assets/red-bN70gL4F.js +1 -0
- package/dist/frontend/assets/reg-C-SQnVFl.js +1 -0
- package/dist/frontend/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/frontend/assets/rel-C3B-1QV4.js +1 -0
- package/dist/frontend/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/frontend/assets/ron-D8l8udqQ.js +1 -0
- package/dist/frontend/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/frontend/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/frontend/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/frontend/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/frontend/assets/rst-CRjBmOyv.js +1 -0
- package/dist/frontend/assets/ruby-Wjq7vjNf.js +1 -0
- package/dist/frontend/assets/rust-B1yitclQ.js +1 -0
- package/dist/frontend/assets/sas-cz2c8ADy.js +1 -0
- package/dist/frontend/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/frontend/assets/scala-C151Ov-r.js +1 -0
- package/dist/frontend/assets/scheme-C98Dy4si.js +1 -0
- package/dist/frontend/assets/scss-D5BDwBP9.js +1 -0
- package/dist/frontend/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/frontend/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/frontend/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/frontend/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/frontend/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/frontend/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/frontend/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/frontend/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/frontend/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/frontend/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/frontend/assets/solidity-rGO070M0.js +1 -0
- package/dist/frontend/assets/soy-8wufbnw4.js +1 -0
- package/dist/frontend/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/frontend/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/frontend/assets/sql-BLtJtn59.js +1 -0
- package/dist/frontend/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/frontend/assets/stata-BH5u7GGu.js +1 -0
- package/dist/frontend/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/frontend/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/frontend/assets/svelte-Cy7k_4gC.js +1 -0
- package/dist/frontend/assets/swift-D82vCrfD.js +1 -0
- package/dist/frontend/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/frontend/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/frontend/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/frontend/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/frontend/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/frontend/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/frontend/assets/templ-DhtptRzy.js +1 -0
- package/dist/frontend/assets/terraform-BETggiCN.js +1 -0
- package/dist/frontend/assets/tex-idrVyKtj.js +1 -0
- package/dist/frontend/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/frontend/assets/toml-vGWfd6FD.js +1 -0
- package/dist/frontend/assets/ts-tags-DQrlYJgV.js +1 -0
- package/dist/frontend/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/frontend/assets/tsx-COt5Ahok.js +1 -0
- package/dist/frontend/assets/turtle-BsS91CYL.js +1 -0
- package/dist/frontend/assets/twig-xg9kU7Mw.js +1 -0
- package/dist/frontend/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/frontend/assets/typespec-CAFt9gP4.js +1 -0
- package/dist/frontend/assets/typst-DHCkPAjA.js +1 -0
- package/dist/frontend/assets/v-BcVCzyr7.js +1 -0
- package/dist/frontend/assets/vala-CsfeWuGM.js +1 -0
- package/dist/frontend/assets/vb-D17OF-Vu.js +1 -0
- package/dist/frontend/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/frontend/assets/vesper-DU1UobuO.js +1 -0
- package/dist/frontend/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/frontend/assets/viml-CJc9bBzg.js +1 -0
- package/dist/frontend/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/frontend/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/frontend/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/frontend/assets/vue-D2xRrEX4.js +1 -0
- package/dist/frontend/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/frontend/assets/vue-vine-BoDAl6tE.js +1 -0
- package/dist/frontend/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/frontend/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/frontend/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/frontend/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/frontend/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/frontend/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/frontend/assets/wit-5i3qLPDT.js +1 -0
- package/dist/frontend/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/frontend/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/frontend/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/frontend/assets/yaml-Buea-lGh.js +1 -0
- package/dist/frontend/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/frontend/assets/zig-VOosw3JB.js +1 -0
- package/dist/frontend/icon-192.png +0 -0
- package/dist/frontend/icon-512.png +0 -0
- package/dist/frontend/icon.svg +8 -0
- package/dist/frontend/index.html +30 -0
- package/dist/frontend/manifest.json +25 -0
- package/dist/frontend/sw.js +66 -0
- package/dist/server/agent-events.js +39 -0
- package/dist/server/analytics.js +885 -0
- package/dist/server/auth.js +65 -0
- package/dist/server/belayer/executor.js +200 -0
- package/dist/server/belayer/intake.js +27 -0
- package/dist/server/belayer/pipeline.js +97 -0
- package/dist/server/belayer/pr-lifecycle.js +69 -0
- package/dist/server/belayer/prompts.js +154 -0
- package/dist/server/belayer/types.js +23 -0
- package/dist/server/branch-linker.js +137 -0
- package/dist/server/browser-content.js +145 -0
- package/dist/server/clipboard.js +63 -0
- package/dist/server/codex-hooks-adapter.js +93 -0
- package/dist/server/config.js +325 -0
- package/dist/server/gh-routes.js +163 -0
- package/dist/server/gh.js +276 -0
- package/dist/server/git-routes.js +154 -0
- package/dist/server/git.js +694 -0
- package/dist/server/github-app.js +218 -0
- package/dist/server/github-graphql.js +178 -0
- package/dist/server/hooks.js +373 -0
- package/dist/server/index.js +1549 -0
- package/dist/server/integration-github.js +137 -0
- package/dist/server/integration-jira.js +210 -0
- package/dist/server/integration-linear.js +176 -0
- package/dist/server/logger.js +18 -0
- package/dist/server/mobile-input-pipeline.js +129 -0
- package/dist/server/opencode-relay.js +53 -0
- package/dist/server/org-dashboard.js +241 -0
- package/dist/server/output-parsers/claude-parser.js +56 -0
- package/dist/server/output-parsers/codex-parser.js +13 -0
- package/dist/server/output-parsers/index.js +14 -0
- package/dist/server/output-parsers/null-parser.js +12 -0
- package/dist/server/output-parsers/opencode-parser.js +77 -0
- package/dist/server/pty-handler.js +586 -0
- package/dist/server/push.js +84 -0
- package/dist/server/review-poller.js +237 -0
- package/dist/server/sdk-handler.js +539 -0
- package/dist/server/service.js +189 -0
- package/dist/server/sessions.js +638 -0
- package/dist/server/telemetry.js +236 -0
- package/dist/server/ticket-transitions.js +166 -0
- package/dist/server/types.js +146 -0
- package/dist/server/utils.js +23 -0
- package/dist/server/watcher.js +661 -0
- package/dist/server/webhook-manager.js +547 -0
- package/dist/server/webhooks.js +73 -0
- package/dist/server/workspace-groups.js +363 -0
- package/dist/server/workspaces.js +1207 -0
- package/dist/server/ws.js +192 -0
- package/dist/test/EmptyState.spec.js +51 -0
- package/dist/test/action-coverage.test.js +139 -0
- package/dist/test/actions/registry.test.js +59 -0
- package/dist/test/actions/shortcuts.test.js +79 -0
- package/dist/test/agent-events.test.js +151 -0
- package/dist/test/analytics.test.js +158 -0
- package/dist/test/attention.test.js +91 -0
- package/dist/test/auth.test.js +105 -0
- package/dist/test/backend-state.test.js +47 -0
- package/dist/test/belayer-executor.test.js +33 -0
- package/dist/test/belayer-intake.test.js +44 -0
- package/dist/test/belayer-pipeline.test.js +113 -0
- package/dist/test/belayer-pr-lifecycle.test.js +26 -0
- package/dist/test/belayer-prompts.test.js +60 -0
- package/dist/test/belayer-types.test.js +69 -0
- package/dist/test/bin/claude-remote-cli.js +214 -0
- package/dist/test/boot-state.test.js +133 -0
- package/dist/test/branch-lifecycle.test.js +75 -0
- package/dist/test/branch-linker.test.js +236 -0
- package/dist/test/branch-rename.test.js +45 -0
- package/dist/test/branch-watcher.test.js +115 -0
- package/dist/test/browser-cli.test.js +91 -0
- package/dist/test/browser-content.test.js +93 -0
- package/dist/test/browser-tabs-ui.test.js +39 -0
- package/dist/test/changed-files-api.test.js +140 -0
- package/dist/test/clipboard.test.js +12 -0
- package/dist/test/codex-hooks-adapter.test.js +237 -0
- package/dist/test/components/EmptyState.spec.js +51 -0
- package/dist/test/components/ErrorToast.spec.js +65 -0
- package/dist/test/components/TuiCheckbox.spec.js +120 -0
- package/dist/test/components/TuiInput.spec.js +186 -0
- package/dist/test/components/leaf-component-migration.spec.js +104 -0
- package/dist/test/config-freshness.test.js +63 -0
- package/dist/test/config.test.js +813 -0
- package/dist/test/diff-summary.test.js +98 -0
- package/dist/test/display-state.test.js +179 -0
- package/dist/test/event-message-types.test.js +32 -0
- package/dist/test/file-tree-utils.test.js +167 -0
- package/dist/test/framework-types.test.js +183 -0
- package/dist/test/frameworks-api.test.js +93 -0
- package/dist/test/frontend/src/lib/pr-state.js +114 -0
- package/dist/test/fs-browse.test.js +246 -0
- package/dist/test/fuzzy-scorer.test.js +145 -0
- package/dist/test/gh-routes.test.js +156 -0
- package/dist/test/git-changed-files.test.js +152 -0
- package/dist/test/git-routes.test.js +146 -0
- package/dist/test/git-utils.test.js +68 -0
- package/dist/test/git-watcher.test.js +110 -0
- package/dist/test/git.test.js +140 -0
- package/dist/test/github-app.test.js +455 -0
- package/dist/test/github-graphql.test.js +301 -0
- package/dist/test/greetings.test.js +83 -0
- package/dist/test/hooks-agent-event.test.js +412 -0
- package/dist/test/hooks.test.js +149 -0
- package/dist/test/integration-github.test.js +220 -0
- package/dist/test/integration-jira.test.js +238 -0
- package/dist/test/integration-linear.test.js +293 -0
- package/dist/test/mobile-input.test.js +235 -0
- package/dist/test/opencode-relay.test.js +107 -0
- package/dist/test/org-dashboard.test.js +349 -0
- package/dist/test/output-parser.test.js +217 -0
- package/dist/test/paths.test.js +32 -0
- package/dist/test/pr-state.test.js +407 -0
- package/dist/test/pr-status.test.js +82 -0
- package/dist/test/presets.test.js +242 -0
- package/dist/test/pty-handler-multi-agent.test.js +149 -0
- package/dist/test/pty-handler.test.js +146 -0
- package/dist/test/pull-requests.test.js +78 -0
- package/dist/test/review-poller.test.js +349 -0
- package/dist/test/server/analytics.js +121 -0
- package/dist/test/server/auth.js +63 -0
- package/dist/test/server/branch-linker.js +124 -0
- package/dist/test/server/clipboard.js +56 -0
- package/dist/test/server/config.js +137 -0
- package/dist/test/server/git.js +308 -0
- package/dist/test/server/hooks.js +196 -0
- package/dist/test/server/index.js +1124 -0
- package/dist/test/server/integration-github.js +117 -0
- package/dist/test/server/integration-jira.js +164 -0
- package/dist/test/server/integration-linear.js +176 -0
- package/dist/test/server/mobile-input-pipeline.js +123 -0
- package/dist/test/server/org-dashboard.js +184 -0
- package/dist/test/server/output-parsers/claude-parser.js +54 -0
- package/dist/test/server/output-parsers/codex-parser.js +13 -0
- package/dist/test/server/output-parsers/index.js +7 -0
- package/dist/test/server/pty-handler.js +310 -0
- package/dist/test/server/push.js +80 -0
- package/dist/test/server/review-poller.js +218 -0
- package/dist/test/server/service.js +169 -0
- package/dist/test/server/sessions.js +434 -0
- package/dist/test/server/ticket-transitions.js +216 -0
- package/dist/test/server/types.js +20 -0
- package/dist/test/server/utils.js +22 -0
- package/dist/test/server/watcher.js +139 -0
- package/dist/test/server/workspaces.js +657 -0
- package/dist/test/server/ws.js +152 -0
- package/dist/test/server-startup.test.js +62 -0
- package/dist/test/service.test.js +43 -0
- package/dist/test/session-analytics-api.test.js +123 -0
- package/dist/test/session-analytics.test.js +425 -0
- package/dist/test/session-intent.test.js +249 -0
- package/dist/test/sessions.test.js +1152 -0
- package/dist/test/sidebar-items.test.js +164 -0
- package/dist/test/stores/boot-state-store.test.js +165 -0
- package/dist/test/stores/sessions-logic.test.js +191 -0
- package/dist/test/stores/toasts-store.test.js +66 -0
- package/dist/test/stores/ui-store.test.js +203 -0
- package/dist/test/stores/unread-store.test.js +97 -0
- package/dist/test/telemetry-api.test.js +54 -0
- package/dist/test/telemetry-sync.test.js +68 -0
- package/dist/test/telemetry.test.js +295 -0
- package/dist/test/terminal-zoom.test.js +102 -0
- package/dist/test/test/analytics.test.js +152 -0
- package/dist/test/test/auth.test.js +95 -0
- package/dist/test/test/branch-linker.test.js +231 -0
- package/dist/test/test/branch-rename.test.js +45 -0
- package/dist/test/test/clipboard.test.js +12 -0
- package/dist/test/test/config.test.js +281 -0
- package/dist/test/test/fs-browse.test.js +202 -0
- package/dist/test/test/git.test.js +67 -0
- package/dist/test/test/hooks.test.js +139 -0
- package/dist/test/test/integration-github.test.js +203 -0
- package/dist/test/test/integration-jira.test.js +294 -0
- package/dist/test/test/integration-linear.test.js +293 -0
- package/dist/test/test/mobile-input.test.js +193 -0
- package/dist/test/test/org-dashboard.test.js +240 -0
- package/dist/test/test/output-parser.test.js +95 -0
- package/dist/test/test/paths.test.js +32 -0
- package/dist/test/test/pr-state.test.js +220 -0
- package/dist/test/test/pull-requests.test.js +67 -0
- package/dist/test/test/review-poller.test.js +235 -0
- package/dist/test/test/service.test.js +43 -0
- package/dist/test/test/sessions.test.js +750 -0
- package/dist/test/test/ticket-transitions.test.js +130 -0
- package/dist/test/test/version.test.js +34 -0
- package/dist/test/test/worktrees.test.js +256 -0
- package/dist/test/ticket-transitions.test.js +312 -0
- package/dist/test/unread.test.js +23 -0
- package/dist/test/version.test.js +34 -0
- package/dist/test/webhook-manager.test.js +484 -0
- package/dist/test/webhooks.test.js +208 -0
- package/dist/test/workspace-groups.test.js +377 -0
- package/dist/test/worktrees.test.js +531 -0
- package/package.json +88 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS } from './types.js';
|
|
7
|
+
import { createPtySession } from './pty-handler.js';
|
|
8
|
+
import { getPrForBranch, getWorkingTreeDiff } from './git.js';
|
|
9
|
+
import { trackEvent } from './analytics.js';
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
// In-memory registry: id -> Session
|
|
13
|
+
const sessions = new Map();
|
|
14
|
+
// Session metadata cache: session ID or worktree path -> SessionMeta
|
|
15
|
+
const metaCache = new Map();
|
|
16
|
+
// Module-level defaults for hooks injection (set via configure())
|
|
17
|
+
let defaultPort;
|
|
18
|
+
let defaultForceOutputParser;
|
|
19
|
+
function configure(opts) {
|
|
20
|
+
defaultPort = opts.port;
|
|
21
|
+
defaultForceOutputParser = opts.forceOutputParser;
|
|
22
|
+
}
|
|
23
|
+
let terminalCounter = 0;
|
|
24
|
+
let agentCounter = 0;
|
|
25
|
+
const idleChangeCallbacks = [];
|
|
26
|
+
function onIdleChange(cb) {
|
|
27
|
+
idleChangeCallbacks.push(cb);
|
|
28
|
+
}
|
|
29
|
+
const stateChangeCallbacks = [];
|
|
30
|
+
function onStateChange(cb) {
|
|
31
|
+
stateChangeCallbacks.push(cb);
|
|
32
|
+
}
|
|
33
|
+
const sessionCreateCallbacks = [];
|
|
34
|
+
function onSessionCreate(cb) {
|
|
35
|
+
sessionCreateCallbacks.push(cb);
|
|
36
|
+
}
|
|
37
|
+
function fireSessionCreate(sessionId, repoPath, branchName) {
|
|
38
|
+
for (const cb of sessionCreateCallbacks) {
|
|
39
|
+
try {
|
|
40
|
+
cb(sessionId, repoPath, branchName);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error('[sessions] sessionCreate callback error:', err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const sessionEndCallbacks = [];
|
|
48
|
+
function onSessionEnd(cb) {
|
|
49
|
+
sessionEndCallbacks.push(cb);
|
|
50
|
+
}
|
|
51
|
+
function fireSessionEnd(sessionId, repoPath, branchName) {
|
|
52
|
+
for (const cb of sessionEndCallbacks) {
|
|
53
|
+
try {
|
|
54
|
+
cb(sessionId, repoPath, branchName);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error('[sessions] sessionEnd callback error:', err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function fireStateChange(sessionId, state) {
|
|
62
|
+
for (const cb of [...stateChangeCallbacks])
|
|
63
|
+
cb(sessionId, state);
|
|
64
|
+
}
|
|
65
|
+
function create({ id: providedId, needsBranchRename, branchRenamePrompt, initialPrompt, agent = 'claude', cols = 80, rows = 24, args = [], port, forceOutputParser, ...rest }) {
|
|
66
|
+
const id = providedId || crypto.randomBytes(8).toString('hex');
|
|
67
|
+
const ptyParams = {
|
|
68
|
+
...rest,
|
|
69
|
+
id,
|
|
70
|
+
agent,
|
|
71
|
+
cols,
|
|
72
|
+
rows,
|
|
73
|
+
args,
|
|
74
|
+
port: port ?? defaultPort,
|
|
75
|
+
forceOutputParser: forceOutputParser ?? defaultForceOutputParser,
|
|
76
|
+
};
|
|
77
|
+
const { session: ptySession, result } = createPtySession(ptyParams, sessions, idleChangeCallbacks, stateChangeCallbacks, sessionEndCallbacks);
|
|
78
|
+
trackEvent({
|
|
79
|
+
category: 'session',
|
|
80
|
+
action: 'created',
|
|
81
|
+
target: id,
|
|
82
|
+
properties: {
|
|
83
|
+
agent,
|
|
84
|
+
type: rest.type ?? 'worktree',
|
|
85
|
+
workspace: rest.root ?? rest.repoPath,
|
|
86
|
+
mode: rest.command ? 'terminal' : 'agent',
|
|
87
|
+
},
|
|
88
|
+
session_id: id,
|
|
89
|
+
});
|
|
90
|
+
if (needsBranchRename) {
|
|
91
|
+
ptySession.needsBranchRename = true;
|
|
92
|
+
}
|
|
93
|
+
if (branchRenamePrompt) {
|
|
94
|
+
ptySession.branchRenamePrompt = branchRenamePrompt;
|
|
95
|
+
}
|
|
96
|
+
if (initialPrompt) {
|
|
97
|
+
ptySession.initialPrompt = initialPrompt;
|
|
98
|
+
}
|
|
99
|
+
fireSessionCreate(id, ptySession.repoPath, ptySession.branchName);
|
|
100
|
+
if (initialPrompt) {
|
|
101
|
+
const promptHandler = (changedId, state) => {
|
|
102
|
+
if (changedId === id && state === 'waiting-for-input' && ptySession.initialPrompt) {
|
|
103
|
+
const prompt = ptySession.initialPrompt;
|
|
104
|
+
ptySession.initialPrompt = undefined; // one-shot
|
|
105
|
+
// Small delay to ensure the agent's input handler is ready
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
try {
|
|
108
|
+
ptySession.pty.write(prompt + '\n');
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error('[sessions] Failed to inject initial prompt:', err);
|
|
112
|
+
}
|
|
113
|
+
}, 500);
|
|
114
|
+
// Remove this handler after firing
|
|
115
|
+
const idx = stateChangeCallbacks.indexOf(promptHandler);
|
|
116
|
+
if (idx !== -1)
|
|
117
|
+
stateChangeCallbacks.splice(idx, 1);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
stateChangeCallbacks.push(promptHandler);
|
|
121
|
+
}
|
|
122
|
+
return { ...result, needsBranchRename: !!ptySession.needsBranchRename };
|
|
123
|
+
}
|
|
124
|
+
function get(id) {
|
|
125
|
+
return sessions.get(id);
|
|
126
|
+
}
|
|
127
|
+
function list() {
|
|
128
|
+
return Array.from(sessions.values())
|
|
129
|
+
.map((s) => ({
|
|
130
|
+
id: s.id,
|
|
131
|
+
type: s.type,
|
|
132
|
+
agent: s.agent,
|
|
133
|
+
mode: s.mode,
|
|
134
|
+
root: s.root,
|
|
135
|
+
repoName: s.repoName,
|
|
136
|
+
repoPath: s.repoPath,
|
|
137
|
+
worktreeName: s.worktreeName,
|
|
138
|
+
branchName: s.branchName,
|
|
139
|
+
displayName: s.displayName,
|
|
140
|
+
createdAt: s.createdAt,
|
|
141
|
+
lastActivity: s.lastActivity,
|
|
142
|
+
idle: s.idle,
|
|
143
|
+
cwd: s.cwd,
|
|
144
|
+
customCommand: s.customCommand,
|
|
145
|
+
useTmux: s.useTmux,
|
|
146
|
+
tmuxSessionName: s.tmuxSessionName,
|
|
147
|
+
status: s.status,
|
|
148
|
+
needsBranchRename: !!s.needsBranchRename,
|
|
149
|
+
agentState: s.agentState,
|
|
150
|
+
currentActivity: s.currentActivity,
|
|
151
|
+
}))
|
|
152
|
+
.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
|
153
|
+
}
|
|
154
|
+
function updateDisplayName(id, displayName) {
|
|
155
|
+
const session = sessions.get(id);
|
|
156
|
+
if (!session)
|
|
157
|
+
throw new Error('Session not found: ' + id);
|
|
158
|
+
session.displayName = displayName;
|
|
159
|
+
return { id, displayName };
|
|
160
|
+
}
|
|
161
|
+
function kill(id) {
|
|
162
|
+
const session = sessions.get(id);
|
|
163
|
+
if (!session) {
|
|
164
|
+
throw new Error(`Session not found: ${id}`);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
session.pty.kill('SIGTERM');
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// PTY may already be dead (e.g. disconnected sessions) — still delete from registry
|
|
171
|
+
}
|
|
172
|
+
if (session.tmuxSessionName) {
|
|
173
|
+
execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
|
|
174
|
+
}
|
|
175
|
+
const durationS = Math.round((Date.now() - new Date(session.createdAt).getTime()) / 1000);
|
|
176
|
+
trackEvent({
|
|
177
|
+
category: 'session',
|
|
178
|
+
action: 'ended',
|
|
179
|
+
target: id,
|
|
180
|
+
properties: {
|
|
181
|
+
agent: session.agent,
|
|
182
|
+
type: session.type,
|
|
183
|
+
workspace: session.root || session.repoPath,
|
|
184
|
+
duration_s: durationS,
|
|
185
|
+
},
|
|
186
|
+
session_id: id,
|
|
187
|
+
});
|
|
188
|
+
fireSessionEnd(id, session.repoPath, session.branchName);
|
|
189
|
+
sessions.delete(id);
|
|
190
|
+
}
|
|
191
|
+
function killAllTmuxSessions() {
|
|
192
|
+
for (const session of sessions.values()) {
|
|
193
|
+
if (session.tmuxSessionName) {
|
|
194
|
+
execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function resize(id, cols, rows) {
|
|
199
|
+
const session = sessions.get(id);
|
|
200
|
+
if (!session) {
|
|
201
|
+
throw new Error(`Session not found: ${id}`);
|
|
202
|
+
}
|
|
203
|
+
session.pty.resize(cols, rows);
|
|
204
|
+
}
|
|
205
|
+
function write(id, data) {
|
|
206
|
+
const session = sessions.get(id);
|
|
207
|
+
if (!session) {
|
|
208
|
+
throw new Error(`Session not found: ${id}`);
|
|
209
|
+
}
|
|
210
|
+
session.pty.write(data);
|
|
211
|
+
}
|
|
212
|
+
function findRepoSession(repoPath) {
|
|
213
|
+
return list().find((s) => s.type === 'repo' && s.repoPath === repoPath);
|
|
214
|
+
}
|
|
215
|
+
function nextTerminalName() {
|
|
216
|
+
return `Terminal ${++terminalCounter}`;
|
|
217
|
+
}
|
|
218
|
+
function nextAgentName() {
|
|
219
|
+
return `Agent ${++agentCounter}`;
|
|
220
|
+
}
|
|
221
|
+
function serializeAll(configDir) {
|
|
222
|
+
const scrollbackDirPath = path.join(configDir, 'scrollback');
|
|
223
|
+
fs.mkdirSync(scrollbackDirPath, { recursive: true });
|
|
224
|
+
const serializedPty = [];
|
|
225
|
+
for (const session of sessions.values()) {
|
|
226
|
+
// Write scrollback to disk
|
|
227
|
+
const scrollbackPath = path.join(scrollbackDirPath, session.id + '.buf');
|
|
228
|
+
fs.writeFileSync(scrollbackPath, session.scrollback.join(''), 'utf-8');
|
|
229
|
+
serializedPty.push({
|
|
230
|
+
id: session.id,
|
|
231
|
+
type: session.type,
|
|
232
|
+
agent: session.agent,
|
|
233
|
+
root: session.root,
|
|
234
|
+
repoName: session.repoName,
|
|
235
|
+
repoPath: session.repoPath,
|
|
236
|
+
worktreeName: session.worktreeName,
|
|
237
|
+
branchName: session.branchName,
|
|
238
|
+
displayName: session.displayName,
|
|
239
|
+
createdAt: session.createdAt,
|
|
240
|
+
lastActivity: session.lastActivity,
|
|
241
|
+
useTmux: session.useTmux,
|
|
242
|
+
tmuxSessionName: session.tmuxSessionName,
|
|
243
|
+
customCommand: session.customCommand,
|
|
244
|
+
cwd: session.cwd,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const pending = {
|
|
248
|
+
version: 1,
|
|
249
|
+
timestamp: new Date().toISOString(),
|
|
250
|
+
sessions: serializedPty,
|
|
251
|
+
};
|
|
252
|
+
fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending, null, 2), 'utf-8');
|
|
253
|
+
}
|
|
254
|
+
async function restoreFromDisk(configDir) {
|
|
255
|
+
const pendingPath = path.join(configDir, 'pending-sessions.json');
|
|
256
|
+
if (!fs.existsSync(pendingPath))
|
|
257
|
+
return 0;
|
|
258
|
+
let pending;
|
|
259
|
+
try {
|
|
260
|
+
pending = JSON.parse(fs.readFileSync(pendingPath, 'utf-8'));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
fs.unlinkSync(pendingPath);
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
// Ignore stale files (>5 minutes old)
|
|
267
|
+
if (Date.now() - new Date(pending.timestamp).getTime() > STALE_THRESHOLD_MS) {
|
|
268
|
+
fs.unlinkSync(pendingPath);
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
const scrollbackDirPath = path.join(configDir, 'scrollback');
|
|
272
|
+
let restored = 0;
|
|
273
|
+
// Restore PTY sessions
|
|
274
|
+
for (const s of pending.sessions) {
|
|
275
|
+
// Load scrollback from disk
|
|
276
|
+
let initialScrollback;
|
|
277
|
+
const scrollbackPath = path.join(scrollbackDirPath, s.id + '.buf');
|
|
278
|
+
try {
|
|
279
|
+
const data = fs.readFileSync(scrollbackPath, 'utf-8');
|
|
280
|
+
if (data.length > 0)
|
|
281
|
+
initialScrollback = [data];
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Missing scrollback is non-fatal
|
|
285
|
+
}
|
|
286
|
+
// Determine spawn command and args
|
|
287
|
+
let command;
|
|
288
|
+
let args = [];
|
|
289
|
+
if (s.customCommand) {
|
|
290
|
+
// Terminal session — respawn the shell
|
|
291
|
+
command = s.customCommand;
|
|
292
|
+
}
|
|
293
|
+
else if (s.useTmux && s.tmuxSessionName) {
|
|
294
|
+
// Tmux session — check if tmux session is still alive
|
|
295
|
+
let tmuxAlive = false;
|
|
296
|
+
try {
|
|
297
|
+
await execFileAsync('tmux', ['has-session', '-t', s.tmuxSessionName]);
|
|
298
|
+
tmuxAlive = true;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// tmux session is gone
|
|
302
|
+
}
|
|
303
|
+
if (tmuxAlive) {
|
|
304
|
+
// Attach to surviving tmux session
|
|
305
|
+
command = 'tmux';
|
|
306
|
+
args = ['-u', 'attach-session', '-t', s.tmuxSessionName];
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// Tmux session died — fall back to agent with continue args
|
|
310
|
+
args = [...AGENT_CONTINUE_ARGS[s.agent]];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// Non-tmux agent session — respawn with continue args
|
|
315
|
+
args = [...AGENT_CONTINUE_ARGS[s.agent]];
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const createParams = {
|
|
319
|
+
id: s.id,
|
|
320
|
+
type: s.type,
|
|
321
|
+
agent: s.agent,
|
|
322
|
+
repoName: s.repoName,
|
|
323
|
+
repoPath: s.repoPath,
|
|
324
|
+
cwd: s.cwd,
|
|
325
|
+
root: s.root,
|
|
326
|
+
worktreeName: s.worktreeName,
|
|
327
|
+
branchName: s.branchName,
|
|
328
|
+
displayName: s.displayName,
|
|
329
|
+
args,
|
|
330
|
+
useTmux: false, // Don't re-wrap in tmux — either attaching to existing or using plain agent
|
|
331
|
+
tmuxSessionName: s.tmuxSessionName,
|
|
332
|
+
restored: true,
|
|
333
|
+
};
|
|
334
|
+
if (command)
|
|
335
|
+
createParams.command = command;
|
|
336
|
+
if (initialScrollback)
|
|
337
|
+
createParams.initialScrollback = initialScrollback;
|
|
338
|
+
create(createParams);
|
|
339
|
+
restored++;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
console.error(`Failed to restore session ${s.id} (${s.displayName})`);
|
|
343
|
+
}
|
|
344
|
+
// Clean up scrollback file
|
|
345
|
+
try {
|
|
346
|
+
fs.unlinkSync(scrollbackPath);
|
|
347
|
+
}
|
|
348
|
+
catch { /* ignore */ }
|
|
349
|
+
}
|
|
350
|
+
// Clean up
|
|
351
|
+
try {
|
|
352
|
+
fs.unlinkSync(pendingPath);
|
|
353
|
+
}
|
|
354
|
+
catch { /* ignore */ }
|
|
355
|
+
try {
|
|
356
|
+
fs.rmdirSync(path.join(configDir, 'scrollback'));
|
|
357
|
+
}
|
|
358
|
+
catch { /* ignore — may not be empty */ }
|
|
359
|
+
// Sync counters to avoid duplicate display names after restore
|
|
360
|
+
for (const s of sessions.values()) {
|
|
361
|
+
const agentMatch = s.displayName?.match(/^Agent (\d+)$/);
|
|
362
|
+
if (agentMatch)
|
|
363
|
+
agentCounter = Math.max(agentCounter, parseInt(agentMatch[1], 10));
|
|
364
|
+
const termMatch = s.displayName?.match(/^Terminal (\d+)$/);
|
|
365
|
+
if (termMatch)
|
|
366
|
+
terminalCounter = Math.max(terminalCounter, parseInt(termMatch[1], 10));
|
|
367
|
+
}
|
|
368
|
+
return restored;
|
|
369
|
+
}
|
|
370
|
+
/** Returns the set of tmux session names currently owned by restored sessions */
|
|
371
|
+
function activeTmuxSessionNames() {
|
|
372
|
+
const names = new Set();
|
|
373
|
+
for (const session of sessions.values()) {
|
|
374
|
+
if (session.tmuxSessionName)
|
|
375
|
+
names.add(session.tmuxSessionName);
|
|
376
|
+
}
|
|
377
|
+
return names;
|
|
378
|
+
}
|
|
379
|
+
async function fetchMetaForSession(session) {
|
|
380
|
+
const repoPath = session.repoPath;
|
|
381
|
+
const branch = session.branchName;
|
|
382
|
+
let prNumber = null;
|
|
383
|
+
let additions = 0;
|
|
384
|
+
let deletions = 0;
|
|
385
|
+
if (branch) {
|
|
386
|
+
try {
|
|
387
|
+
const pr = await getPrForBranch(repoPath, branch);
|
|
388
|
+
if (pr) {
|
|
389
|
+
prNumber = pr.number;
|
|
390
|
+
additions = pr.additions;
|
|
391
|
+
deletions = pr.deletions;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch { /* gh CLI unavailable */ }
|
|
395
|
+
}
|
|
396
|
+
// Fallback to working tree diff if no PR data
|
|
397
|
+
if (additions === 0 && deletions === 0) {
|
|
398
|
+
const diff = await getWorkingTreeDiff(repoPath);
|
|
399
|
+
additions = diff.additions;
|
|
400
|
+
deletions = diff.deletions;
|
|
401
|
+
}
|
|
402
|
+
return { prNumber, additions, deletions, fetchedAt: new Date().toISOString() };
|
|
403
|
+
}
|
|
404
|
+
async function getSessionMeta(id, refresh = false) {
|
|
405
|
+
if (!refresh && metaCache.has(id))
|
|
406
|
+
return metaCache.get(id);
|
|
407
|
+
const session = sessions.get(id);
|
|
408
|
+
if (!session)
|
|
409
|
+
return metaCache.get(id) ?? null;
|
|
410
|
+
const summary = list().find(s => s.id === id);
|
|
411
|
+
if (!summary)
|
|
412
|
+
return null;
|
|
413
|
+
const meta = await fetchMetaForSession(summary);
|
|
414
|
+
metaCache.set(id, meta);
|
|
415
|
+
return meta;
|
|
416
|
+
}
|
|
417
|
+
function getAllSessionMeta() {
|
|
418
|
+
const result = {};
|
|
419
|
+
for (const [key, meta] of metaCache) {
|
|
420
|
+
result[key] = meta;
|
|
421
|
+
}
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
// Populate cache for all active sessions (called on startup or refresh)
|
|
425
|
+
async function populateMetaCache() {
|
|
426
|
+
const allSessions = list();
|
|
427
|
+
await Promise.allSettled(allSessions.map(async (s) => {
|
|
428
|
+
if (!metaCache.has(s.id)) {
|
|
429
|
+
const meta = await fetchMetaForSession(s);
|
|
430
|
+
metaCache.set(s.id, meta);
|
|
431
|
+
}
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
export { configure, create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, onIdleChange, onStateChange, onSessionCreate, onSessionEnd, findRepoSession, nextTerminalName, nextAgentName, serializeAll, restoreFromDisk, activeTmuxSessionNames, getSessionMeta, getAllSessionMeta, populateMetaCache, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { Router } from 'express';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const GH_TIMEOUT_MS = 10_000;
|
|
7
|
+
function ghIssueNumber(ticketId) {
|
|
8
|
+
const match = ticketId.match(/^GH-(\d+)$/i);
|
|
9
|
+
return match ? match[1] : null;
|
|
10
|
+
}
|
|
11
|
+
async function addLabel(exec, repoPath, issueNumber, label) {
|
|
12
|
+
try {
|
|
13
|
+
await exec('gh', ['issue', 'edit', issueNumber, '--add-label', label], {
|
|
14
|
+
cwd: repoPath,
|
|
15
|
+
timeout: GH_TIMEOUT_MS,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error(`[ticket-transitions] Failed to add label "${label}" to #${issueNumber}:`, err);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function removeLabel(exec, repoPath, issueNumber, label) {
|
|
23
|
+
try {
|
|
24
|
+
await exec('gh', ['issue', 'edit', issueNumber, '--remove-label', label], {
|
|
25
|
+
cwd: repoPath,
|
|
26
|
+
timeout: GH_TIMEOUT_MS,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Label may not exist — non-fatal
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Call a Jira transition by ID. Non-fatal on error. */
|
|
34
|
+
async function jiraTransition(ticketId, transitionId) {
|
|
35
|
+
const baseUrl = process.env.JIRA_BASE_URL;
|
|
36
|
+
const email = process.env.JIRA_EMAIL;
|
|
37
|
+
const token = process.env.JIRA_API_TOKEN;
|
|
38
|
+
if (!baseUrl || !email || !token)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`${baseUrl}/rest/api/3/issue/${encodeURIComponent(ticketId)}/transitions`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Authorization': `Basic ${Buffer.from(email + ':' + token).toString('base64')}`,
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({ transition: { id: transitionId } }),
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
console.error(`[ticket-transitions] Jira transition returned ${res.status} for ${ticketId}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`[ticket-transitions] Jira transition failed for ${ticketId}:`, err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Update a Linear issue state. Non-fatal on error. */
|
|
58
|
+
async function linearStateUpdate(ticketIdentifier, stateId) {
|
|
59
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
60
|
+
if (!apiKey)
|
|
61
|
+
return;
|
|
62
|
+
// Linear mutations need the issue ID, but we only have the identifier (e.g. "TEAM-123").
|
|
63
|
+
// Resolve the issue ID by identifier, then update state.
|
|
64
|
+
try {
|
|
65
|
+
const searchRes = await fetch('https://api.linear.app/graphql', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
query: `query($filter: IssueFilter) { issues(filter: $filter, first: 1) { nodes { id } } }`,
|
|
70
|
+
variables: { filter: { identifier: { eq: ticketIdentifier } } },
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
if (!searchRes.ok) {
|
|
74
|
+
console.error(`[ticket-transitions] Linear issue lookup returned ${searchRes.status} for ${ticketIdentifier}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const searchData = (await searchRes.json());
|
|
78
|
+
const issueId = searchData.data?.issues?.nodes?.[0]?.id;
|
|
79
|
+
if (!issueId)
|
|
80
|
+
return;
|
|
81
|
+
const updateRes = await fetch('https://api.linear.app/graphql', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
query: `mutation($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success } }`,
|
|
86
|
+
variables: { id: issueId, stateId },
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
if (!updateRes.ok) {
|
|
90
|
+
console.error(`[ticket-transitions] Linear state update returned ${updateRes.status} for ${ticketIdentifier}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error(`[ticket-transitions] Linear state update failed for ${ticketIdentifier}:`, err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Best-effort source detection from a ticket ID pattern.
|
|
99
|
+
* Known limitation: when both Jira and Linear are configured, non-GH tickets
|
|
100
|
+
* are matched by whichever env var is present. This is imperfect — a future
|
|
101
|
+
* improvement would persist the source alongside branch links.
|
|
102
|
+
*/
|
|
103
|
+
function detectTicketSource(ticketId) {
|
|
104
|
+
if (ticketId.startsWith('GH-'))
|
|
105
|
+
return 'github';
|
|
106
|
+
// Prefer Jira for PROJECT-style keys (>= 3 uppercase letters before dash)
|
|
107
|
+
// since Jira project keys are typically longer than Linear team keys (2-3 chars).
|
|
108
|
+
const prefix = ticketId.split('-')[0] ?? '';
|
|
109
|
+
if (prefix.length >= 3 && process.env.JIRA_API_TOKEN)
|
|
110
|
+
return 'jira';
|
|
111
|
+
if (process.env.LINEAR_API_KEY)
|
|
112
|
+
return 'linear';
|
|
113
|
+
if (process.env.JIRA_API_TOKEN)
|
|
114
|
+
return 'jira';
|
|
115
|
+
return 'github'; // fallback
|
|
116
|
+
}
|
|
117
|
+
export function createTicketTransitionsRouter(deps) {
|
|
118
|
+
// In-memory idempotency guard: ticketId -> last transitioned state
|
|
119
|
+
const transitionMap = new Map();
|
|
120
|
+
const exec = deps.execAsync ?? execFileAsync;
|
|
121
|
+
const { configPath } = deps;
|
|
122
|
+
const router = Router();
|
|
123
|
+
/** Get status mapping for a transition state from config */
|
|
124
|
+
function getStatusMapping(source, state) {
|
|
125
|
+
const config = loadConfig(configPath);
|
|
126
|
+
if (source === 'jira')
|
|
127
|
+
return config.integrations?.jira?.statusMappings?.[state];
|
|
128
|
+
return config.integrations?.linear?.statusMappings?.[state];
|
|
129
|
+
}
|
|
130
|
+
async function transitionOnSessionCreate(ctx) {
|
|
131
|
+
const current = transitionMap.get(ctx.ticketId);
|
|
132
|
+
if (current && current !== 'none')
|
|
133
|
+
return;
|
|
134
|
+
if (ctx.source === 'github') {
|
|
135
|
+
const issueNum = ghIssueNumber(ctx.ticketId);
|
|
136
|
+
if (!issueNum)
|
|
137
|
+
return;
|
|
138
|
+
transitionMap.set(ctx.ticketId, 'in-progress');
|
|
139
|
+
await addLabel(exec, ctx.repoPath, issueNum, 'in-progress');
|
|
140
|
+
}
|
|
141
|
+
else if (ctx.source === 'jira') {
|
|
142
|
+
transitionMap.set(ctx.ticketId, 'in-progress');
|
|
143
|
+
const transitionId = getStatusMapping('jira', 'in-progress');
|
|
144
|
+
if (transitionId)
|
|
145
|
+
await jiraTransition(ctx.ticketId, transitionId);
|
|
146
|
+
}
|
|
147
|
+
else if (ctx.source === 'linear') {
|
|
148
|
+
transitionMap.set(ctx.ticketId, 'in-progress');
|
|
149
|
+
const stateId = getStatusMapping('linear', 'in-progress');
|
|
150
|
+
if (stateId)
|
|
151
|
+
await linearStateUpdate(ctx.ticketId, stateId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function checkPrTransitions(prs, branchLinks) {
|
|
155
|
+
for (const pr of prs) {
|
|
156
|
+
for (const [ticketId, links] of Object.entries(branchLinks)) {
|
|
157
|
+
const linked = links.some((l) => l.branchName === pr.headRefName);
|
|
158
|
+
if (!linked)
|
|
159
|
+
continue;
|
|
160
|
+
const current = transitionMap.get(ticketId);
|
|
161
|
+
const source = detectTicketSource(ticketId);
|
|
162
|
+
if (pr.state === 'OPEN' && current !== 'code-review' && current !== 'ready-for-qa') {
|
|
163
|
+
if (source === 'github') {
|
|
164
|
+
const issueNum = ghIssueNumber(ticketId);
|
|
165
|
+
if (!issueNum)
|
|
166
|
+
continue;
|
|
167
|
+
const repoPath = links[0]?.repoPath;
|
|
168
|
+
if (!repoPath)
|
|
169
|
+
continue;
|
|
170
|
+
transitionMap.set(ticketId, 'code-review');
|
|
171
|
+
await removeLabel(exec, repoPath, issueNum, 'in-progress');
|
|
172
|
+
await addLabel(exec, repoPath, issueNum, 'code-review');
|
|
173
|
+
}
|
|
174
|
+
else if (source === 'jira') {
|
|
175
|
+
transitionMap.set(ticketId, 'code-review');
|
|
176
|
+
const transitionId = getStatusMapping('jira', 'code-review');
|
|
177
|
+
if (transitionId)
|
|
178
|
+
await jiraTransition(ticketId, transitionId);
|
|
179
|
+
}
|
|
180
|
+
else if (source === 'linear') {
|
|
181
|
+
transitionMap.set(ticketId, 'code-review');
|
|
182
|
+
const stateId = getStatusMapping('linear', 'code-review');
|
|
183
|
+
if (stateId)
|
|
184
|
+
await linearStateUpdate(ticketId, stateId);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (pr.state === 'MERGED' && current !== 'ready-for-qa') {
|
|
188
|
+
if (source === 'github') {
|
|
189
|
+
const issueNum = ghIssueNumber(ticketId);
|
|
190
|
+
if (!issueNum)
|
|
191
|
+
continue;
|
|
192
|
+
const repoPath = links[0]?.repoPath;
|
|
193
|
+
if (!repoPath)
|
|
194
|
+
continue;
|
|
195
|
+
transitionMap.set(ticketId, 'ready-for-qa');
|
|
196
|
+
await removeLabel(exec, repoPath, issueNum, 'code-review');
|
|
197
|
+
await addLabel(exec, repoPath, issueNum, 'ready-for-qa');
|
|
198
|
+
}
|
|
199
|
+
else if (source === 'jira') {
|
|
200
|
+
transitionMap.set(ticketId, 'ready-for-qa');
|
|
201
|
+
const transitionId = getStatusMapping('jira', 'ready-for-qa');
|
|
202
|
+
if (transitionId)
|
|
203
|
+
await jiraTransition(ticketId, transitionId);
|
|
204
|
+
}
|
|
205
|
+
else if (source === 'linear') {
|
|
206
|
+
transitionMap.set(ticketId, 'ready-for-qa');
|
|
207
|
+
const stateId = getStatusMapping('linear', 'ready-for-qa');
|
|
208
|
+
if (stateId)
|
|
209
|
+
await linearStateUpdate(ticketId, stateId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { router, transitionOnSessionCreate, checkPrTransitions };
|
|
216
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Agent command records
|
|
2
|
+
export const AGENT_COMMANDS = {
|
|
3
|
+
claude: 'claude',
|
|
4
|
+
codex: 'codex',
|
|
5
|
+
};
|
|
6
|
+
export const AGENT_CONTINUE_ARGS = {
|
|
7
|
+
claude: ['--continue'],
|
|
8
|
+
codex: ['resume', '--last'],
|
|
9
|
+
};
|
|
10
|
+
export const AGENT_YOLO_ARGS = {
|
|
11
|
+
claude: ['--dangerously-skip-permissions'],
|
|
12
|
+
codex: ['--full-auto'],
|
|
13
|
+
};
|
|
14
|
+
export const MOUNTAIN_NAMES = [
|
|
15
|
+
'everest', 'kilimanjaro', 'denali', 'fuji', 'rainier', 'matterhorn',
|
|
16
|
+
'elbrus', 'aconcagua', 'kangchenjunga', 'lhotse', 'makalu', 'cho-oyu',
|
|
17
|
+
'dhaulagiri', 'manaslu', 'annapurna', 'nanga-parbat', 'olympus',
|
|
18
|
+
'mont-blanc', 'k2', 'vinson', 'erebus', 'logan', 'puncak-jaya',
|
|
19
|
+
'wilhelm', 'cook', 'ararat', 'etna', 'shasta', 'whitney', 'hood',
|
|
20
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Strip ANSI escape sequences (CSI, OSC, charset, mode sequences)
|
|
2
|
+
export const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b\[\?[0-9;]*[hlm]|\x1b\[[0-9]*[ABCDJKH]/g;
|
|
3
|
+
export function stripAnsi(text) {
|
|
4
|
+
return text.replace(ANSI_RE, '');
|
|
5
|
+
}
|
|
6
|
+
export function semverLessThan(a, b) {
|
|
7
|
+
const parse = (v) => (v.split('-').at(0) ?? v).split('.').map(Number);
|
|
8
|
+
const pa = parse(a);
|
|
9
|
+
const pb = parse(b);
|
|
10
|
+
const aMaj = pa[0] ?? 0, aMin = pa[1] ?? 0, aPat = pa[2] ?? 0;
|
|
11
|
+
const bMaj = pb[0] ?? 0, bMin = pb[1] ?? 0, bPat = pb[2] ?? 0;
|
|
12
|
+
if (aMaj !== bMaj)
|
|
13
|
+
return aMaj < bMaj;
|
|
14
|
+
if (aMin !== bMin)
|
|
15
|
+
return aMin < bMin;
|
|
16
|
+
return aPat < bPat;
|
|
17
|
+
}
|
|
18
|
+
export function cleanEnv() {
|
|
19
|
+
const env = Object.assign({}, process.env);
|
|
20
|
+
delete env.CLAUDECODE;
|
|
21
|
+
return env;
|
|
22
|
+
}
|