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,694 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { createLogger } from './logger.js';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
const logger = createLogger('git');
|
|
6
|
+
function normalizeBranchNames(stdout) {
|
|
7
|
+
const branches = stdout
|
|
8
|
+
.split('\n')
|
|
9
|
+
.map((branch) => branch.trim())
|
|
10
|
+
.filter((branch) => branch && !branch.includes('HEAD'))
|
|
11
|
+
.map((branch) => branch.replace(/^origin\//, ''));
|
|
12
|
+
return [...new Set(branches)].sort();
|
|
13
|
+
}
|
|
14
|
+
async function listBranches(repoPath, options = {}) {
|
|
15
|
+
const run = options.exec || execFileAsync;
|
|
16
|
+
if (options.refresh) {
|
|
17
|
+
try {
|
|
18
|
+
await run('git', ['fetch', '--all', '--prune'], { cwd: repoPath });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Best effort — still return the locally-known refs below.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const { stdout } = await run('git', ['branch', '-a', '--format=%(refname:short)'], { cwd: repoPath });
|
|
26
|
+
return normalizeBranchNames(stdout);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function getCurrentBranch(repoPath, options = {}) {
|
|
33
|
+
const run = options.exec || execFileAsync;
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
36
|
+
cwd: repoPath,
|
|
37
|
+
});
|
|
38
|
+
return stdout.trim() || null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function getActivityFeed(repoPath, options = {}) {
|
|
45
|
+
const run = options.exec || execFileAsync;
|
|
46
|
+
try {
|
|
47
|
+
const { stdout } = await run('git', [
|
|
48
|
+
'log',
|
|
49
|
+
'--all',
|
|
50
|
+
'--since=24 hours ago',
|
|
51
|
+
'--oneline',
|
|
52
|
+
'--max-count=50',
|
|
53
|
+
'--format=%H|%h|%s|%an|%ar|%D',
|
|
54
|
+
], { cwd: repoPath, timeout: 5000 });
|
|
55
|
+
const lines = stdout.split('\n').filter((line) => line.trim());
|
|
56
|
+
const entries = [];
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
try {
|
|
59
|
+
// Split into exactly 6 parts by the first 5 pipe characters
|
|
60
|
+
const parts = [];
|
|
61
|
+
let remaining = line;
|
|
62
|
+
for (let i = 0; i < 5; i++) {
|
|
63
|
+
const idx = remaining.indexOf('|');
|
|
64
|
+
if (idx === -1)
|
|
65
|
+
break;
|
|
66
|
+
parts.push(remaining.slice(0, idx));
|
|
67
|
+
remaining = remaining.slice(idx + 1);
|
|
68
|
+
}
|
|
69
|
+
parts.push(remaining);
|
|
70
|
+
if (parts.length < 5)
|
|
71
|
+
continue;
|
|
72
|
+
const hash = parts[0] ?? '';
|
|
73
|
+
const shortHash = parts[1] ?? '';
|
|
74
|
+
const message = parts[2] ?? '';
|
|
75
|
+
const author = parts[3] ?? '';
|
|
76
|
+
const timeAgo = parts[4] ?? '';
|
|
77
|
+
const decorations = parts[5] ?? '';
|
|
78
|
+
if (!hash || !shortHash)
|
|
79
|
+
continue;
|
|
80
|
+
const branches = decorations
|
|
81
|
+
.split(',')
|
|
82
|
+
.map((d) => d.trim())
|
|
83
|
+
.filter((d) => d && !d.startsWith('tag:') && d !== 'HEAD')
|
|
84
|
+
.map((d) => d.replace(/^HEAD -> /, '').replace(/^origin\//, ''));
|
|
85
|
+
entries.push({
|
|
86
|
+
hash: hash.trim(),
|
|
87
|
+
shortHash: shortHash.trim(),
|
|
88
|
+
message: message.trim(),
|
|
89
|
+
author: author.trim(),
|
|
90
|
+
timeAgo: timeAgo.trim(),
|
|
91
|
+
branches: [...new Set(branches)],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Skip malformed lines
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return entries;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function switchBranch(repoPath, branch, options = {}) {
|
|
106
|
+
const run = options.exec || execFileAsync;
|
|
107
|
+
try {
|
|
108
|
+
await run('git', ['checkout', branch], { cwd: repoPath, timeout: 5000 });
|
|
109
|
+
return { success: true };
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err && typeof err === 'object') {
|
|
113
|
+
const errObj = err;
|
|
114
|
+
const errorText = errObj.stderr ?? errObj.message ?? 'Unknown error';
|
|
115
|
+
return { success: false, error: errorText.trim() };
|
|
116
|
+
}
|
|
117
|
+
return { success: false, error: 'Unknown error' };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function getCommitsAhead(repoPath, branch, baseBranch, options = {}) {
|
|
121
|
+
const run = options.exec || execFileAsync;
|
|
122
|
+
try {
|
|
123
|
+
const { stdout } = await run('git', ['rev-list', '--count', `${baseBranch}..${branch}`], { cwd: repoPath, timeout: 5000 });
|
|
124
|
+
const count = parseInt(stdout.trim(), 10);
|
|
125
|
+
return Number.isFinite(count) ? count : 0;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function getWorkingTreeDiff(repoPath, exec = execFileAsync) {
|
|
132
|
+
try {
|
|
133
|
+
const { stdout } = await exec('git', ['diff', '--shortstat'], {
|
|
134
|
+
cwd: repoPath,
|
|
135
|
+
timeout: 5000,
|
|
136
|
+
});
|
|
137
|
+
// Output like: " 3 files changed, 55 insertions(+), 12 deletions(-)"
|
|
138
|
+
const insertions = stdout.match(/(\d+) insertion/);
|
|
139
|
+
const deletions = stdout.match(/(\d+) deletion/);
|
|
140
|
+
return {
|
|
141
|
+
additions: insertions?.[1] ? parseInt(insertions[1], 10) : 0,
|
|
142
|
+
deletions: deletions?.[1] ? parseInt(deletions[1], 10) : 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return { additions: 0, deletions: 0 };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Convert a git branch name to a human-readable display name.
|
|
151
|
+
* "fix-mobile-scroll-bug" → "Fix mobile scroll bug"
|
|
152
|
+
* "feature/add-auth" → "Add auth"
|
|
153
|
+
*/
|
|
154
|
+
function branchToDisplayName(branch) {
|
|
155
|
+
const stripped = branch.replace(/^(feature|fix|chore|refactor|docs|test|ci|build)\//i, '');
|
|
156
|
+
const words = stripped.replace(/[-_]/g, ' ').trim();
|
|
157
|
+
if (!words)
|
|
158
|
+
return branch;
|
|
159
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
160
|
+
}
|
|
161
|
+
async function isBranchStale(repoPath, branch, options = {}) {
|
|
162
|
+
const run = options.exec || execFileAsync;
|
|
163
|
+
try {
|
|
164
|
+
for (const base of ['main', 'master']) {
|
|
165
|
+
try {
|
|
166
|
+
const { stdout } = await run('git', ['rev-list', '--count', `${base}..${branch}`], { cwd: repoPath, timeout: 5000 });
|
|
167
|
+
const count = parseInt(stdout.trim(), 10);
|
|
168
|
+
if (count === 0)
|
|
169
|
+
return true;
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Extracts "owner/repo" from a git remote URL.
|
|
184
|
+
* Handles both SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git) forms.
|
|
185
|
+
*/
|
|
186
|
+
function extractOwnerRepo(remoteUrl) {
|
|
187
|
+
// SSH: git@github.com:owner/repo.git
|
|
188
|
+
const sshMatch = remoteUrl.match(/git@[^:]+:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
189
|
+
if (sshMatch)
|
|
190
|
+
return sshMatch[1] ?? null;
|
|
191
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
192
|
+
const httpsMatch = remoteUrl.match(/https?:\/\/[^/]+\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
193
|
+
if (httpsMatch)
|
|
194
|
+
return httpsMatch[1] ?? null;
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns a map of "owner/repo" → workspace path for all git workspaces.
|
|
199
|
+
* Workspaces that are not git repos or have no remote are omitted.
|
|
200
|
+
*/
|
|
201
|
+
async function buildRepoMap(workspacePaths, exec) {
|
|
202
|
+
const map = new Map();
|
|
203
|
+
await Promise.all(workspacePaths.map(async (wsPath) => {
|
|
204
|
+
try {
|
|
205
|
+
const { stdout } = await exec('git', ['remote', 'get-url', 'origin'], {
|
|
206
|
+
cwd: wsPath,
|
|
207
|
+
timeout: 10_000,
|
|
208
|
+
});
|
|
209
|
+
const ownerRepo = extractOwnerRepo(stdout.trim());
|
|
210
|
+
if (ownerRepo) {
|
|
211
|
+
map.set(ownerRepo.toLowerCase(), wsPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Not a git repo or no remote — skip
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
return map;
|
|
219
|
+
}
|
|
220
|
+
async function listBranchesEnriched(repoPath, options = {}) {
|
|
221
|
+
const run = options.exec || execFileAsync;
|
|
222
|
+
if (options.refresh) {
|
|
223
|
+
try {
|
|
224
|
+
await run('git', ['fetch', '--all', '--prune'], { cwd: repoPath });
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Best effort — still return the locally-known refs below.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Get local branches
|
|
231
|
+
let localBranches = [];
|
|
232
|
+
try {
|
|
233
|
+
const { stdout } = await run('git', ['branch', '--format=%(refname:short)'], { cwd: repoPath });
|
|
234
|
+
localBranches = stdout
|
|
235
|
+
.split('\n')
|
|
236
|
+
.map((b) => b.trim())
|
|
237
|
+
.filter((b) => b.length > 0);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// continue with empty list
|
|
241
|
+
}
|
|
242
|
+
// Get remote branches (strip origin/ prefix, skip HEAD)
|
|
243
|
+
let remoteBranches = [];
|
|
244
|
+
try {
|
|
245
|
+
const { stdout } = await run('git', ['branch', '-r', '--format=%(refname:short)'], { cwd: repoPath });
|
|
246
|
+
remoteBranches = stdout
|
|
247
|
+
.split('\n')
|
|
248
|
+
.map((b) => b.trim())
|
|
249
|
+
.filter((b) => b.length > 0 && !b.includes('HEAD') && b.startsWith('origin/'))
|
|
250
|
+
.map((b) => b.replace(/^origin\//, ''));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// continue with empty list
|
|
254
|
+
}
|
|
255
|
+
// Get worktree → branch mapping via porcelain output
|
|
256
|
+
const worktreeBranchMap = new Map(); // worktreePath → branchName
|
|
257
|
+
try {
|
|
258
|
+
const { stdout } = await run('git', ['worktree', 'list', '--porcelain'], {
|
|
259
|
+
cwd: repoPath,
|
|
260
|
+
});
|
|
261
|
+
const blocks = stdout.split(/\n\n+/);
|
|
262
|
+
for (const block of blocks) {
|
|
263
|
+
const lines = block
|
|
264
|
+
.split('\n')
|
|
265
|
+
.map((l) => l.trim())
|
|
266
|
+
.filter((l) => l.length > 0);
|
|
267
|
+
let worktreePath = null;
|
|
268
|
+
let branchName = null;
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
if (line.startsWith('worktree ')) {
|
|
271
|
+
worktreePath = line.slice('worktree '.length);
|
|
272
|
+
}
|
|
273
|
+
else if (line.startsWith('branch ')) {
|
|
274
|
+
// "branch refs/heads/branchname"
|
|
275
|
+
branchName = line
|
|
276
|
+
.slice('branch '.length)
|
|
277
|
+
.replace(/^refs\/heads\//, '');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (worktreePath && branchName) {
|
|
281
|
+
worktreeBranchMap.set(worktreePath, branchName);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// continue without worktree data
|
|
287
|
+
}
|
|
288
|
+
// Build reverse map: branchName → worktree info
|
|
289
|
+
const branchWorktreeMap = new Map();
|
|
290
|
+
for (const [wtPath, branchName] of worktreeBranchMap) {
|
|
291
|
+
const worktreeName = wtPath.split('/').at(-1) ?? wtPath;
|
|
292
|
+
const matchingSession = (options.sessions ?? []).find((s) => s.worktreePath === wtPath);
|
|
293
|
+
const entry = {
|
|
294
|
+
worktreePath: wtPath,
|
|
295
|
+
worktreeName,
|
|
296
|
+
};
|
|
297
|
+
if (matchingSession?.id !== undefined) {
|
|
298
|
+
entry.sessionId = matchingSession.id;
|
|
299
|
+
}
|
|
300
|
+
branchWorktreeMap.set(branchName, entry);
|
|
301
|
+
}
|
|
302
|
+
// Deduplicate by name across local + remote
|
|
303
|
+
const allNames = new Set([...localBranches, ...remoteBranches]);
|
|
304
|
+
const localSet = new Set(localBranches);
|
|
305
|
+
const remoteSet = new Set(remoteBranches);
|
|
306
|
+
const result = [...allNames].sort().map((name) => {
|
|
307
|
+
const checkedOutIn = branchWorktreeMap.get(name);
|
|
308
|
+
return {
|
|
309
|
+
name,
|
|
310
|
+
isLocal: localSet.has(name),
|
|
311
|
+
isRemote: remoteSet.has(name),
|
|
312
|
+
...(checkedOutIn ? { checkedOutIn } : {}),
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
async function renameBranch(repoPath, newName, options = {}) {
|
|
318
|
+
const run = options.exec || execFileAsync;
|
|
319
|
+
try {
|
|
320
|
+
// Get current branch name first
|
|
321
|
+
const { stdout: currentStdout } = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoPath });
|
|
322
|
+
const oldName = currentStdout.trim();
|
|
323
|
+
if (!oldName)
|
|
324
|
+
return { success: false, error: 'Could not determine current branch' };
|
|
325
|
+
if (oldName === 'HEAD')
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: 'Cannot rename: not on a branch (detached HEAD)',
|
|
329
|
+
};
|
|
330
|
+
await run('git', ['branch', '-m', '--', newName], {
|
|
331
|
+
cwd: repoPath,
|
|
332
|
+
timeout: 5000,
|
|
333
|
+
});
|
|
334
|
+
return { success: true, oldName, newName };
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const errObj = err;
|
|
338
|
+
return {
|
|
339
|
+
success: false,
|
|
340
|
+
error: (errObj.stderr ?? errObj.message ?? 'Unknown error').trim(),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function createBranch(repoPath, branchName, options = {}) {
|
|
345
|
+
const run = options.exec || execFileAsync;
|
|
346
|
+
try {
|
|
347
|
+
await run('git', ['checkout', '-b', '--', branchName], {
|
|
348
|
+
cwd: repoPath,
|
|
349
|
+
timeout: 5000,
|
|
350
|
+
});
|
|
351
|
+
return { success: true, branch: branchName };
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
const errObj = err;
|
|
355
|
+
return {
|
|
356
|
+
success: false,
|
|
357
|
+
error: (errObj.stderr ?? errObj.message ?? 'Unknown error').trim(),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function pushBranch(repoPath, branch, deleteOldBranch, options = {}) {
|
|
362
|
+
const run = options.exec || execFileAsync;
|
|
363
|
+
try {
|
|
364
|
+
await run('git', ['push', 'origin', branch], {
|
|
365
|
+
cwd: repoPath,
|
|
366
|
+
timeout: 30000,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
const errObj = err;
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: (errObj.stderr ?? errObj.message ?? 'Unknown error').trim(),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (deleteOldBranch) {
|
|
377
|
+
try {
|
|
378
|
+
await run('git', ['push', 'origin', '--delete', deleteOldBranch], {
|
|
379
|
+
cwd: repoPath,
|
|
380
|
+
timeout: 10000,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
const errObj = err;
|
|
385
|
+
return {
|
|
386
|
+
success: true,
|
|
387
|
+
deleteError: (errObj.stderr ??
|
|
388
|
+
errObj.message ??
|
|
389
|
+
'Failed to delete old branch').trim(),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return { success: true };
|
|
394
|
+
}
|
|
395
|
+
function parseStatus(code) {
|
|
396
|
+
switch (code.trim()) {
|
|
397
|
+
case 'M':
|
|
398
|
+
return 'modified';
|
|
399
|
+
case 'A':
|
|
400
|
+
return 'added';
|
|
401
|
+
case 'D':
|
|
402
|
+
return 'deleted';
|
|
403
|
+
case '??':
|
|
404
|
+
return 'untracked';
|
|
405
|
+
default:
|
|
406
|
+
if (code.startsWith('R'))
|
|
407
|
+
return 'renamed';
|
|
408
|
+
return 'modified';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function fileDirectory(filePath) {
|
|
412
|
+
const dir = filePath.lastIndexOf('/');
|
|
413
|
+
return dir === -1 ? '.' : filePath.slice(0, dir);
|
|
414
|
+
}
|
|
415
|
+
function normalizeNumstatPath(filePath) {
|
|
416
|
+
if (!filePath.includes(' => '))
|
|
417
|
+
return filePath;
|
|
418
|
+
const arrow = ' => ';
|
|
419
|
+
// Handle brace-style paths like "{old/dir => new/dir}/file.ts"
|
|
420
|
+
if (filePath.startsWith('{')) {
|
|
421
|
+
const braceMatch = filePath.match(/^\{([^{}]+)\}(.*)$/);
|
|
422
|
+
if (braceMatch) {
|
|
423
|
+
const inner = braceMatch[1];
|
|
424
|
+
const suffix = braceMatch[2] ?? '';
|
|
425
|
+
const idx = inner.lastIndexOf(arrow);
|
|
426
|
+
if (idx !== -1) {
|
|
427
|
+
const newPart = inner.slice(idx + arrow.length) || inner.slice(0, idx);
|
|
428
|
+
return newPart + suffix;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Simple "old => new" form
|
|
433
|
+
const idx = filePath.lastIndexOf(arrow);
|
|
434
|
+
if (idx !== -1)
|
|
435
|
+
return filePath.slice(idx + arrow.length);
|
|
436
|
+
return filePath;
|
|
437
|
+
}
|
|
438
|
+
async function getChangedFiles(repoPath, base, exec = execFileAsync) {
|
|
439
|
+
let statusEntries;
|
|
440
|
+
if (base === 'cached') {
|
|
441
|
+
// Staged files
|
|
442
|
+
const { stdout } = await exec('git', ['diff', '--cached', '--name-status', '--find-renames'], { cwd: repoPath, timeout: 10000 });
|
|
443
|
+
statusEntries = stdout
|
|
444
|
+
.split('\n')
|
|
445
|
+
.filter(Boolean)
|
|
446
|
+
.map((line) => {
|
|
447
|
+
const parts = line.split('\t');
|
|
448
|
+
const code = parts[0] ?? '';
|
|
449
|
+
if (code.startsWith('R')) {
|
|
450
|
+
return {
|
|
451
|
+
path: parts[2] ?? '',
|
|
452
|
+
oldPath: parts[1] ?? '',
|
|
453
|
+
status: 'renamed',
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return { path: parts[1] ?? '', status: parseStatus(code) };
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else if (base) {
|
|
460
|
+
// Branch comparison
|
|
461
|
+
const { stdout } = await exec('git', ['diff', '--name-status', '--find-renames', `${base}...HEAD`], { cwd: repoPath, timeout: 10000 });
|
|
462
|
+
statusEntries = stdout
|
|
463
|
+
.split('\n')
|
|
464
|
+
.filter(Boolean)
|
|
465
|
+
.map((line) => {
|
|
466
|
+
const parts = line.split('\t');
|
|
467
|
+
const code = parts[0] ?? '';
|
|
468
|
+
if (code.startsWith('R')) {
|
|
469
|
+
return {
|
|
470
|
+
path: parts[2] ?? '',
|
|
471
|
+
oldPath: parts[1] ?? '',
|
|
472
|
+
status: 'renamed',
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
return { path: parts[1] ?? '', status: parseStatus(code) };
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Working tree: git status --porcelain=v1 -z
|
|
480
|
+
const { stdout } = await exec('git', ['status', '--porcelain=v1', '-z'], {
|
|
481
|
+
cwd: repoPath,
|
|
482
|
+
timeout: 10000,
|
|
483
|
+
});
|
|
484
|
+
statusEntries = [];
|
|
485
|
+
const parts = stdout.split('\0').filter(Boolean);
|
|
486
|
+
for (let i = 0; i < parts.length; i++) {
|
|
487
|
+
const entry = parts[i];
|
|
488
|
+
const code = entry.slice(0, 2);
|
|
489
|
+
const filePath = entry.slice(3);
|
|
490
|
+
if (code.startsWith('R')) {
|
|
491
|
+
// porcelain=v1 -z: rename record is XY <newname>\0<origname>\0
|
|
492
|
+
// filePath (entry.slice(3)) is the new name, parts[i+1] is the old name
|
|
493
|
+
const oldPath = parts[++i] ?? filePath;
|
|
494
|
+
statusEntries.push({ path: filePath, oldPath, status: 'renamed' });
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
statusEntries.push({ path: filePath, status: parseStatus(code) });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (statusEntries.length === 0)
|
|
502
|
+
return [];
|
|
503
|
+
// Get per-file stats via numstat
|
|
504
|
+
const numstatArgs = base
|
|
505
|
+
? ['diff', '--numstat', '--find-renames', `${base}...HEAD`]
|
|
506
|
+
: ['diff', '--numstat', '--find-renames', 'HEAD'];
|
|
507
|
+
const numstatMap = new Map();
|
|
508
|
+
try {
|
|
509
|
+
const { stdout: numstat } = await exec('git', numstatArgs, {
|
|
510
|
+
cwd: repoPath,
|
|
511
|
+
timeout: 10000,
|
|
512
|
+
});
|
|
513
|
+
for (const line of numstat.split('\n').filter(Boolean)) {
|
|
514
|
+
const [add, del, ...pathParts] = line.split('\t');
|
|
515
|
+
const filePath = pathParts.join('\t');
|
|
516
|
+
const actualPath = normalizeNumstatPath(filePath);
|
|
517
|
+
numstatMap.set(actualPath, {
|
|
518
|
+
additions: add === '-' ? 0 : parseInt(add ?? '0', 10),
|
|
519
|
+
deletions: del === '-' ? 0 : parseInt(del ?? '0', 10),
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
logger.warn('[git] numstat failed for', repoPath, err instanceof Error ? err.message : String(err));
|
|
525
|
+
}
|
|
526
|
+
const files = [];
|
|
527
|
+
for (const entry of statusEntries) {
|
|
528
|
+
if (!entry.path)
|
|
529
|
+
continue;
|
|
530
|
+
const stats = numstatMap.get(entry.path);
|
|
531
|
+
let additions = stats?.additions ?? 0;
|
|
532
|
+
let deletions = stats?.deletions ?? 0;
|
|
533
|
+
if (entry.status === 'untracked' && additions === 0) {
|
|
534
|
+
try {
|
|
535
|
+
const { stdout: wcOut } = await exec('wc', ['-l', '--', entry.path], {
|
|
536
|
+
cwd: repoPath,
|
|
537
|
+
timeout: 5000,
|
|
538
|
+
});
|
|
539
|
+
const match = wcOut.trim().match(/^\s*(\d+)/);
|
|
540
|
+
if (match)
|
|
541
|
+
additions = parseInt(match[1], 10);
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// best effort
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
files.push({
|
|
548
|
+
path: entry.path,
|
|
549
|
+
status: entry.status,
|
|
550
|
+
additions,
|
|
551
|
+
deletions,
|
|
552
|
+
directory: fileDirectory(entry.path),
|
|
553
|
+
...(entry.oldPath ? { oldPath: entry.oldPath } : {}),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return files;
|
|
557
|
+
}
|
|
558
|
+
async function getFileDiff(repoPath, filePath, base, exec = execFileAsync) {
|
|
559
|
+
let args;
|
|
560
|
+
if (!base) {
|
|
561
|
+
args = ['diff', '--unified=3', '--find-renames', '--', filePath];
|
|
562
|
+
}
|
|
563
|
+
else if (base === 'cached') {
|
|
564
|
+
args = ['diff', '--cached', '--unified=3', '--', filePath];
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
args = [
|
|
568
|
+
'diff',
|
|
569
|
+
`${base}...HEAD`,
|
|
570
|
+
'--unified=3',
|
|
571
|
+
'--find-renames',
|
|
572
|
+
'--',
|
|
573
|
+
filePath,
|
|
574
|
+
];
|
|
575
|
+
}
|
|
576
|
+
const { stdout } = await exec('git', args, { cwd: repoPath, timeout: 10000 });
|
|
577
|
+
// If empty (no changes or untracked), try --no-index for new files
|
|
578
|
+
if (!stdout.trim()) {
|
|
579
|
+
try {
|
|
580
|
+
const { stdout: noIndexOut } = await exec('git', ['diff', '--no-index', '--', '/dev/null', filePath], { cwd: repoPath, timeout: 10000 });
|
|
581
|
+
return noIndexOut;
|
|
582
|
+
}
|
|
583
|
+
catch (err) {
|
|
584
|
+
// git diff --no-index exits with code 1 when there ARE differences
|
|
585
|
+
const e = err;
|
|
586
|
+
if (e.stdout)
|
|
587
|
+
return e.stdout;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return stdout;
|
|
591
|
+
}
|
|
592
|
+
async function getDefaultBranch(repoPath, exec = execFileAsync) {
|
|
593
|
+
// Try symbolic-ref first (most repos have this set)
|
|
594
|
+
try {
|
|
595
|
+
const { stdout } = await exec('git', ['symbolic-ref', 'refs/remotes/origin/HEAD'], { cwd: repoPath, timeout: 5000 });
|
|
596
|
+
const ref = stdout.trim();
|
|
597
|
+
const prefix = 'refs/remotes/origin/';
|
|
598
|
+
if (ref.startsWith(prefix))
|
|
599
|
+
return ref.slice(prefix.length);
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
// Not set — fall through to heuristic
|
|
603
|
+
}
|
|
604
|
+
// Check if main or master exists locally
|
|
605
|
+
for (const candidate of ['main', 'master']) {
|
|
606
|
+
try {
|
|
607
|
+
await exec('git', ['rev-parse', '--verify', `refs/heads/${candidate}`], {
|
|
608
|
+
cwd: repoPath,
|
|
609
|
+
timeout: 5000,
|
|
610
|
+
});
|
|
611
|
+
return candidate;
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
// Not found — try next
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return 'main'; // ultimate fallback
|
|
618
|
+
}
|
|
619
|
+
function isGitRefNotFoundError(error) {
|
|
620
|
+
if (!error || typeof error !== 'object')
|
|
621
|
+
return false;
|
|
622
|
+
const stderr = error.stderr ?? '';
|
|
623
|
+
const message = error.message ?? '';
|
|
624
|
+
const text = stderr || message;
|
|
625
|
+
return (text.includes('Needed a single revision') ||
|
|
626
|
+
text.includes('unknown revision or path not in the working tree') ||
|
|
627
|
+
text.includes('ambiguous argument') ||
|
|
628
|
+
text.includes("couldn't find remote ref") ||
|
|
629
|
+
text.includes('could not find remote ref') ||
|
|
630
|
+
text.includes('not something we can merge') ||
|
|
631
|
+
text.includes('fatal: bad revision'));
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Ensure a branch ref exists locally. If not, fetch it from origin.
|
|
635
|
+
* Returns { found: true } if the branch is now available locally,
|
|
636
|
+
* { found: false, reason: 'not_found' } if it doesn't exist anywhere,
|
|
637
|
+
* or { found: false, reason: 'fetch_failed' } if the fetch failed unexpectedly.
|
|
638
|
+
* Rethrows non-git errors (permissions, timeouts, corrupt repo) so callers
|
|
639
|
+
* can distinguish "branch doesn't exist" from "git is broken".
|
|
640
|
+
*/
|
|
641
|
+
async function ensureBranchLocal(repoPath, branch, options = {}) {
|
|
642
|
+
const run = options.exec || execFileAsync;
|
|
643
|
+
// Check if branch exists locally
|
|
644
|
+
try {
|
|
645
|
+
await run('git', ['rev-parse', '--verify', '--', branch], {
|
|
646
|
+
cwd: repoPath,
|
|
647
|
+
timeout: 5000,
|
|
648
|
+
});
|
|
649
|
+
return { found: true };
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (!isGitRefNotFoundError(error)) {
|
|
653
|
+
throw error; // permissions, timeout, corrupt repo — surface to caller
|
|
654
|
+
}
|
|
655
|
+
// Not found locally — try fetching
|
|
656
|
+
}
|
|
657
|
+
// Fetch from origin
|
|
658
|
+
try {
|
|
659
|
+
await run('git', ['fetch', 'origin', '--', `${branch}:${branch}`], {
|
|
660
|
+
cwd: repoPath,
|
|
661
|
+
timeout: 30000,
|
|
662
|
+
});
|
|
663
|
+
return { found: true };
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
if (isGitRefNotFoundError(error)) {
|
|
667
|
+
return { found: false, reason: 'not_found' };
|
|
668
|
+
}
|
|
669
|
+
// Network failures, auth errors, DNS issues — distinct from "branch doesn't exist"
|
|
670
|
+
return { found: false, reason: 'fetch_failed' };
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/** Check if a PR is in MERGED state (immediate check, no 24h delay like isStalePr). */
|
|
674
|
+
function isPrMerged(pr) {
|
|
675
|
+
return pr.state === 'MERGED';
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Compute branch lifecycle state from authoritative sources.
|
|
679
|
+
* Main branch can be active/stale but never merged.
|
|
680
|
+
*/
|
|
681
|
+
function computeBranchLifecycleState(input) {
|
|
682
|
+
const { pr, isBranchStale: stale, hasActiveSessions, isMainBranch } = input;
|
|
683
|
+
// Merged: PR is merged AND not the main branch
|
|
684
|
+
if (pr && isPrMerged(pr) && !isMainBranch) {
|
|
685
|
+
return { state: 'merged', prNumber: pr.number, prTitle: pr.title };
|
|
686
|
+
}
|
|
687
|
+
// Active: has sessions OR branch is not stale
|
|
688
|
+
if (hasActiveSessions || !stale) {
|
|
689
|
+
return { state: 'active' };
|
|
690
|
+
}
|
|
691
|
+
// Stale: no sessions AND branch is stale (0 commits ahead of main)
|
|
692
|
+
return { state: 'stale' };
|
|
693
|
+
}
|
|
694
|
+
export { listBranches, listBranchesEnriched, normalizeBranchNames, getActivityFeed, switchBranch, getCommitsAhead, getCurrentBranch, getWorkingTreeDiff, branchToDisplayName, isBranchStale, extractOwnerRepo, buildRepoMap, renameBranch, createBranch, pushBranch, getChangedFiles, getFileDiff, getDefaultBranch, ensureBranchLocal, isPrMerged, computeBranchLifecycleState, };
|