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,586 @@
|
|
|
1
|
+
import pty from 'node-pty';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { AGENT_COMMANDS, AGENT_CONTINUE_ARGS, resolveFramework, } from './types.js';
|
|
7
|
+
import { readMeta, writeMeta } from './config.js';
|
|
8
|
+
import { cleanEnv } from './utils.js';
|
|
9
|
+
import { outputParsers } from './output-parsers/index.js';
|
|
10
|
+
import { installOpencodeRelayPlugin } from './opencode-relay.js';
|
|
11
|
+
import { writeCodexHooksAdapter } from './codex-hooks-adapter.js';
|
|
12
|
+
import { createLogger } from './logger.js';
|
|
13
|
+
const IDLE_TIMEOUT_MS = 5000;
|
|
14
|
+
const MAX_SCROLLBACK = 256 * 1024; // 256KB max
|
|
15
|
+
const logger = createLogger('pty');
|
|
16
|
+
export function getTmuxPrefix() {
|
|
17
|
+
return process.env.NO_PIN === '1' ? 'crcd-' : 'crc-';
|
|
18
|
+
}
|
|
19
|
+
export function generateTmuxSessionName(displayName, id) {
|
|
20
|
+
const sanitized = displayName
|
|
21
|
+
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
22
|
+
.replace(/-+/g, '-')
|
|
23
|
+
.slice(0, 30);
|
|
24
|
+
return `${getTmuxPrefix()}${sanitized}-${id.slice(0, 8)}`;
|
|
25
|
+
}
|
|
26
|
+
export function resolveTmuxSpawn(command, args, tmuxSessionName) {
|
|
27
|
+
return {
|
|
28
|
+
command: 'tmux',
|
|
29
|
+
args: [
|
|
30
|
+
'-u',
|
|
31
|
+
'new-session',
|
|
32
|
+
'-s',
|
|
33
|
+
tmuxSessionName,
|
|
34
|
+
'--',
|
|
35
|
+
command,
|
|
36
|
+
...args,
|
|
37
|
+
';',
|
|
38
|
+
'set',
|
|
39
|
+
'set-clipboard',
|
|
40
|
+
'on',
|
|
41
|
+
';',
|
|
42
|
+
'set',
|
|
43
|
+
'allow-passthrough',
|
|
44
|
+
'on',
|
|
45
|
+
';',
|
|
46
|
+
'set',
|
|
47
|
+
'mode-keys',
|
|
48
|
+
'vi',
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function shellQuote(value) {
|
|
53
|
+
return "'" + value.replace(/'/g, "'\"'\"'") + "'";
|
|
54
|
+
}
|
|
55
|
+
function readGlobalStatusLineCommand() {
|
|
56
|
+
try {
|
|
57
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
58
|
+
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
return typeof parsed.statusLine?.command === 'string'
|
|
61
|
+
? parsed.statusLine.command
|
|
62
|
+
: '';
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function buildStatusLineRelayScript(sessionId, configDir, globalCmd) {
|
|
69
|
+
const telemetryDir = path.join(configDir, 'telemetry');
|
|
70
|
+
const telemetryPath = path.join(telemetryDir, `${sessionId}.json`);
|
|
71
|
+
const tempPattern = `${telemetryPath}.tmp.XXXXXX`;
|
|
72
|
+
return `#!/usr/bin/env bash
|
|
73
|
+
set -u
|
|
74
|
+
mkdir -p ${shellQuote(telemetryDir)}
|
|
75
|
+
tmp_file=$(mktemp ${shellQuote(tempPattern)})
|
|
76
|
+
cleanup() {
|
|
77
|
+
rm -f "$tmp_file"
|
|
78
|
+
}
|
|
79
|
+
trap cleanup EXIT
|
|
80
|
+
GLOBAL_CMD=${shellQuote(globalCmd)}
|
|
81
|
+
if [ -n "$GLOBAL_CMD" ] && [ -x "$GLOBAL_CMD" ]; then
|
|
82
|
+
tee "$tmp_file" | "$GLOBAL_CMD"
|
|
83
|
+
pipeline_statuses=("\${PIPESTATUS[@]}")
|
|
84
|
+
else
|
|
85
|
+
tee "$tmp_file" | node -e 'let raw="";process.stdin.setEncoding("utf8");process.stdin.on("data", (chunk) => raw += chunk);process.stdin.on("end", () => { try { const data = JSON.parse(raw); const model = data?.model?.display_name ?? "Claude"; const remaining = data?.context_window?.remaining_percentage ?? "?"; process.stdout.write(model + " | " + remaining + "% ctx\n"); } catch { process.stdout.write("Claude | ?% ctx\n"); } });'
|
|
86
|
+
pipeline_statuses=("\${PIPESTATUS[@]}")
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
pipeline_status=0
|
|
90
|
+
for status in "\${pipeline_statuses[@]}"; do
|
|
91
|
+
if [ "$status" -ne 0 ]; then
|
|
92
|
+
pipeline_status=$status
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
if [ "\${pipeline_statuses[0]}" -eq 0 ]; then
|
|
97
|
+
mv "$tmp_file" ${shellQuote(telemetryPath)}
|
|
98
|
+
trap - EXIT
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
exit "$pipeline_status"
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
function writeStatusLineScript(sessionId, dir, configDir) {
|
|
105
|
+
const scriptPath = path.join(dir, 'relay-statusline.sh');
|
|
106
|
+
const script = buildStatusLineRelayScript(sessionId, configDir, readGlobalStatusLineCommand());
|
|
107
|
+
fs.writeFileSync(scriptPath, script, 'utf-8');
|
|
108
|
+
fs.chmodSync(scriptPath, 0o755);
|
|
109
|
+
return scriptPath;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Upgrade an existing hooks-settings.json to include statusLine if missing.
|
|
113
|
+
* Called on session restore to ensure sessions created before telemetry support
|
|
114
|
+
* get the relay script written to disk. The running Claude process will pick this
|
|
115
|
+
* up on its next statusLine poll cycle (Claude re-reads the settings file).
|
|
116
|
+
*/
|
|
117
|
+
export function upgradeHooksSettings(sessionId, configDir) {
|
|
118
|
+
const dir = path.join(os.tmpdir(), 'relay-ide', sessionId);
|
|
119
|
+
const filePath = path.join(dir, 'hooks-settings.json');
|
|
120
|
+
try {
|
|
121
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
122
|
+
const settings = JSON.parse(raw);
|
|
123
|
+
if (settings.statusLine)
|
|
124
|
+
return false; // already has statusLine
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false; // file doesn't exist or is malformed
|
|
128
|
+
}
|
|
129
|
+
// Write the relay script and patch the settings file
|
|
130
|
+
try {
|
|
131
|
+
const statusLinePath = writeStatusLineScript(sessionId, dir, configDir);
|
|
132
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
133
|
+
const settings = JSON.parse(raw);
|
|
134
|
+
settings.statusLine = { type: 'command', command: statusLinePath };
|
|
135
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.warn(`Failed to upgrade hooks settings for session ${sessionId}:`, err);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function writeHooksSettingsFile(sessionId, port, token, configDir) {
|
|
144
|
+
const dir = path.join(os.tmpdir(), 'relay-ide', sessionId);
|
|
145
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
146
|
+
const filePath = path.join(dir, 'hooks-settings.json');
|
|
147
|
+
const statusLinePath = writeStatusLineScript(sessionId, dir, configDir);
|
|
148
|
+
const base = `http://127.0.0.1:${port}`;
|
|
149
|
+
const q = `sessionId=${sessionId}&token=${token}`;
|
|
150
|
+
const settings = {
|
|
151
|
+
hooks: {
|
|
152
|
+
Stop: [
|
|
153
|
+
{
|
|
154
|
+
hooks: [{ type: 'http', url: `${base}/hooks/stop?${q}`, timeout: 5 }],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
Notification: [
|
|
158
|
+
{
|
|
159
|
+
matcher: 'permission_prompt',
|
|
160
|
+
hooks: [
|
|
161
|
+
{
|
|
162
|
+
type: 'http',
|
|
163
|
+
url: `${base}/hooks/notification?${q}&type=permission_prompt`,
|
|
164
|
+
timeout: 5,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
matcher: 'idle_prompt',
|
|
170
|
+
hooks: [
|
|
171
|
+
{
|
|
172
|
+
type: 'http',
|
|
173
|
+
url: `${base}/hooks/notification?${q}&type=idle_prompt`,
|
|
174
|
+
timeout: 5,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
UserPromptSubmit: [
|
|
180
|
+
{
|
|
181
|
+
hooks: [
|
|
182
|
+
{
|
|
183
|
+
type: 'http',
|
|
184
|
+
url: `${base}/hooks/prompt-submit?${q}`,
|
|
185
|
+
timeout: 5,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
SessionEnd: [
|
|
191
|
+
{
|
|
192
|
+
hooks: [
|
|
193
|
+
{ type: 'http', url: `${base}/hooks/session-end?${q}`, timeout: 5 },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
PreToolUse: [
|
|
198
|
+
{
|
|
199
|
+
hooks: [
|
|
200
|
+
{ type: 'http', url: `${base}/hooks/tool-use?${q}`, timeout: 5 },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
PostToolUse: [
|
|
205
|
+
{
|
|
206
|
+
hooks: [
|
|
207
|
+
{ type: 'http', url: `${base}/hooks/tool-result?${q}`, timeout: 5 },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
statusLine: {
|
|
213
|
+
type: 'command',
|
|
214
|
+
command: statusLinePath,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
218
|
+
fs.chmodSync(filePath, 0o600);
|
|
219
|
+
return filePath;
|
|
220
|
+
}
|
|
221
|
+
export function createPtySession(params, sessionsMap, stateChangeCallbacks = [], sessionEndCallbacks = [], fireBackendStateIfChanged) {
|
|
222
|
+
const { id, type, agent = 'claude', repoName, repoPath, worktreePath = null, cwd, branchName, displayName, command, args: rawArgs = [], cols = 80, rows = 24, configPath, configDir, useTmux: paramUseTmux, tmuxSessionName: paramTmuxSessionName, initialScrollback, restored: paramRestored, port, forceOutputParser, yolo: paramYolo, claudeArgs: paramClaudeArgs, hookToken: paramHookToken, hooksActive: paramHooksActive, frameworks, } = params;
|
|
223
|
+
let args = rawArgs;
|
|
224
|
+
const createdAt = new Date().toISOString();
|
|
225
|
+
// Resolve the agent framework (builtin or future config-driven custom)
|
|
226
|
+
// Falls back to a minimal stub using deprecated aliases when agent is not a known framework.
|
|
227
|
+
let framework;
|
|
228
|
+
try {
|
|
229
|
+
framework = resolveFramework(frameworks ? { frameworks } : {}, agent);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Unknown agent — synthesize a minimal framework from deprecated aliases for backward compat
|
|
233
|
+
framework = {
|
|
234
|
+
id: agent,
|
|
235
|
+
displayName: agent,
|
|
236
|
+
command: AGENT_COMMANDS[agent] ?? agent,
|
|
237
|
+
continueArgs: AGENT_CONTINUE_ARGS[agent] ?? [],
|
|
238
|
+
yoloArgs: [],
|
|
239
|
+
parserType: 'none',
|
|
240
|
+
eventSource: 'parser',
|
|
241
|
+
capabilities: {
|
|
242
|
+
supportsHooks: false,
|
|
243
|
+
supportsContinue: false,
|
|
244
|
+
supportsYolo: false,
|
|
245
|
+
supportsTelemetry: false,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const resolvedCommand = command || framework.commandOverride || framework.command;
|
|
250
|
+
const env = cleanEnv();
|
|
251
|
+
// Inject Claude --settings hooks file for frameworks that use HTTP hook callbacks.
|
|
252
|
+
// This is the Claude-specific hook mechanism; codex uses hooks.json (Task 7) and
|
|
253
|
+
// opencode uses a TS relay plugin (Task 6) — each with its own injection path.
|
|
254
|
+
// For restored sessions whose tmux died, reuse the preserved token but re-create the settings
|
|
255
|
+
// file on disk — the new Claude process needs --settings even though the token is the same.
|
|
256
|
+
// For surviving tmux sessions (command='tmux'), shouldInjectHooks is false — the old Claude
|
|
257
|
+
// instance already has its settings file, and we just need the token for server-side validation.
|
|
258
|
+
let hookToken = paramHookToken ?? '';
|
|
259
|
+
let hooksActive = paramHooksActive ?? false;
|
|
260
|
+
let settingsPath = '';
|
|
261
|
+
const effectiveEventSource = forceOutputParser
|
|
262
|
+
? 'parser'
|
|
263
|
+
: framework.eventSource;
|
|
264
|
+
if (paramYolo && framework.yoloEnv) {
|
|
265
|
+
Object.assign(env, framework.yoloEnv);
|
|
266
|
+
}
|
|
267
|
+
if (effectiveEventSource === 'plugin' && port !== undefined) {
|
|
268
|
+
if (!hookToken) {
|
|
269
|
+
hookToken = crypto.randomBytes(32).toString('hex');
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
installOpencodeRelayPlugin();
|
|
273
|
+
env.CRC_RELAY_URL = `http://127.0.0.1:${port}`;
|
|
274
|
+
env.CRC_SESSION_ID = id;
|
|
275
|
+
env.CRC_RELAY_TOKEN = hookToken;
|
|
276
|
+
hooksActive = true;
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
logger.warn(`Failed to install opencode relay plugin for session ${id}:`, err);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (framework.id === 'codex' && port !== undefined) {
|
|
283
|
+
if (!hookToken) {
|
|
284
|
+
hookToken = crypto.randomBytes(32).toString('hex');
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const codexConfigDir = writeCodexHooksAdapter(id, port, hookToken, configDir ?? process.cwd());
|
|
288
|
+
env.CODEX_CONFIG_DIR = codexConfigDir;
|
|
289
|
+
hooksActive = true;
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
logger.warn(`Failed to write codex hooks adapter for session ${id}:`, err);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Only inject --settings for claude; codex and opencode have their own hook injection paths
|
|
296
|
+
const shouldInjectHooks = framework.id === 'claude' &&
|
|
297
|
+
framework.capabilities.supportsHooks &&
|
|
298
|
+
effectiveEventSource === 'hooks' &&
|
|
299
|
+
!command &&
|
|
300
|
+
port !== undefined;
|
|
301
|
+
if (shouldInjectHooks) {
|
|
302
|
+
if (!hookToken) {
|
|
303
|
+
hookToken = crypto.randomBytes(32).toString('hex');
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
settingsPath = writeHooksSettingsFile(id, port, hookToken, configDir ?? process.cwd());
|
|
307
|
+
args = ['--settings', settingsPath, ...args];
|
|
308
|
+
hooksActive = true;
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
logger.warn(`Failed to generate hooks settings for session ${id}:`, err);
|
|
312
|
+
hooksActive = false;
|
|
313
|
+
hookToken = '';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const useTmux = !command && !!paramUseTmux;
|
|
317
|
+
let spawnCommand = resolvedCommand;
|
|
318
|
+
let spawnArgs = args;
|
|
319
|
+
const tmuxSessionName = paramTmuxSessionName ||
|
|
320
|
+
(useTmux
|
|
321
|
+
? generateTmuxSessionName(params.tmuxDisplayName ||
|
|
322
|
+
displayName ||
|
|
323
|
+
repoName ||
|
|
324
|
+
path.basename(cwd) ||
|
|
325
|
+
'session', id)
|
|
326
|
+
: '');
|
|
327
|
+
if (useTmux) {
|
|
328
|
+
const tmux = resolveTmuxSpawn(resolvedCommand, args, tmuxSessionName);
|
|
329
|
+
spawnCommand = tmux.command;
|
|
330
|
+
spawnArgs = tmux.args;
|
|
331
|
+
}
|
|
332
|
+
const ptyProcess = pty.spawn(spawnCommand, spawnArgs, {
|
|
333
|
+
name: 'xterm-256color',
|
|
334
|
+
cols,
|
|
335
|
+
rows,
|
|
336
|
+
cwd,
|
|
337
|
+
env,
|
|
338
|
+
});
|
|
339
|
+
// Scrollback buffer: stores all PTY output so we can replay on WebSocket (re)connect
|
|
340
|
+
const scrollback = initialScrollback ? [...initialScrollback] : [];
|
|
341
|
+
let scrollbackBytes = initialScrollback
|
|
342
|
+
? initialScrollback.reduce((sum, s) => sum + s.length, 0)
|
|
343
|
+
: 0;
|
|
344
|
+
// Instantiate vendor-specific output parser dispatched by framework.parserType
|
|
345
|
+
// Falls back to 'none' parser for unknown parserType values
|
|
346
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
347
|
+
const parserFactory = outputParsers[framework.parserType] ?? outputParsers['none'];
|
|
348
|
+
const parser = parserFactory();
|
|
349
|
+
const session = {
|
|
350
|
+
id,
|
|
351
|
+
type: type || 'agent',
|
|
352
|
+
agent,
|
|
353
|
+
mode: 'pty',
|
|
354
|
+
repoPath: repoPath || '',
|
|
355
|
+
worktreePath: worktreePath ?? null,
|
|
356
|
+
repoName: repoName || '',
|
|
357
|
+
branchName: branchName || '',
|
|
358
|
+
displayName: displayName || repoName || path.basename(cwd) || '',
|
|
359
|
+
pty: ptyProcess,
|
|
360
|
+
createdAt,
|
|
361
|
+
lastActivity: createdAt,
|
|
362
|
+
scrollback,
|
|
363
|
+
idle: false,
|
|
364
|
+
cwd,
|
|
365
|
+
customCommand: command || null,
|
|
366
|
+
useTmux,
|
|
367
|
+
tmuxSessionName,
|
|
368
|
+
onPtyReplacedCallbacks: [],
|
|
369
|
+
status: 'active',
|
|
370
|
+
restored: paramRestored || false,
|
|
371
|
+
needsBranchRename: false,
|
|
372
|
+
agentState: 'initializing',
|
|
373
|
+
outputParser: parser,
|
|
374
|
+
hookToken,
|
|
375
|
+
hooksActive,
|
|
376
|
+
cleanedUp: false,
|
|
377
|
+
yolo: paramYolo ?? false,
|
|
378
|
+
sessionArgs: paramClaudeArgs ?? [],
|
|
379
|
+
claudeArgs: paramClaudeArgs ?? [], // keep for backward compat
|
|
380
|
+
continuePolicy: params.continuePolicy ?? 'never',
|
|
381
|
+
// Derive dataQuality from what actually got enabled, not just the configured framework eventSource.
|
|
382
|
+
// If hook/plugin injection failed, hooksActive is false → downgrade to 'parser' for accuracy.
|
|
383
|
+
dataQuality: hooksActive ? effectiveEventSource : 'parser',
|
|
384
|
+
_lastHookTime: undefined,
|
|
385
|
+
};
|
|
386
|
+
sessionsMap.set(id, session);
|
|
387
|
+
// Load existing metadata to preserve a previously-set displayName
|
|
388
|
+
if (configPath && worktreePath) {
|
|
389
|
+
const existing = readMeta(configPath, worktreePath);
|
|
390
|
+
if (existing && existing.displayName) {
|
|
391
|
+
session.displayName = existing.displayName;
|
|
392
|
+
}
|
|
393
|
+
writeMeta(configPath, {
|
|
394
|
+
worktreePath,
|
|
395
|
+
displayName: session.displayName,
|
|
396
|
+
lastActivity: createdAt,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
let metaFlushTimer = null;
|
|
400
|
+
let idleTimer = null;
|
|
401
|
+
function resetIdleTimer() {
|
|
402
|
+
if (session.idle) {
|
|
403
|
+
session.idle = false;
|
|
404
|
+
fireBackendStateIfChanged?.(session);
|
|
405
|
+
}
|
|
406
|
+
if (idleTimer)
|
|
407
|
+
clearTimeout(idleTimer);
|
|
408
|
+
idleTimer = setTimeout(() => {
|
|
409
|
+
if (!session.idle) {
|
|
410
|
+
session.idle = true;
|
|
411
|
+
fireBackendStateIfChanged?.(session);
|
|
412
|
+
}
|
|
413
|
+
}, IDLE_TIMEOUT_MS);
|
|
414
|
+
}
|
|
415
|
+
const continueArgs = framework.continueArgs;
|
|
416
|
+
function attachHandlers(proc, canRetry) {
|
|
417
|
+
const spawnTime = Date.now();
|
|
418
|
+
// Clear restored flag after 3s of running — means the PTY is healthy
|
|
419
|
+
const restoredClearTimer = session.restored
|
|
420
|
+
? setTimeout(() => {
|
|
421
|
+
session.restored = false;
|
|
422
|
+
}, 3000)
|
|
423
|
+
: null;
|
|
424
|
+
proc.onData((data) => {
|
|
425
|
+
session.lastActivity = new Date().toISOString();
|
|
426
|
+
resetIdleTimer();
|
|
427
|
+
scrollback.push(data);
|
|
428
|
+
scrollbackBytes += data.length;
|
|
429
|
+
// Trim oldest entries if over limit
|
|
430
|
+
while (scrollbackBytes > MAX_SCROLLBACK && scrollback.length > 1) {
|
|
431
|
+
scrollbackBytes -= scrollback.shift().length;
|
|
432
|
+
}
|
|
433
|
+
if (configPath && worktreePath && !metaFlushTimer) {
|
|
434
|
+
metaFlushTimer = setTimeout(() => {
|
|
435
|
+
metaFlushTimer = null;
|
|
436
|
+
writeMeta(configPath, {
|
|
437
|
+
worktreePath,
|
|
438
|
+
displayName: session.displayName,
|
|
439
|
+
lastActivity: session.lastActivity,
|
|
440
|
+
});
|
|
441
|
+
}, 5000);
|
|
442
|
+
}
|
|
443
|
+
// Vendor-specific output parsing for semantic state detection
|
|
444
|
+
const parseResult = session.outputParser.onData(data, scrollback.slice(-20));
|
|
445
|
+
if (parseResult && parseResult.state !== session.agentState) {
|
|
446
|
+
if (session.hooksActive) {
|
|
447
|
+
// Hooks are authoritative — check 30s reconciliation timeout
|
|
448
|
+
const lastHook = session._lastHookTime;
|
|
449
|
+
const sessionAge = Date.now() - new Date(session.createdAt).getTime();
|
|
450
|
+
if (lastHook && Date.now() - lastHook > 30000) {
|
|
451
|
+
// No hook for 30s and parser disagrees — parser overrides
|
|
452
|
+
session.agentState = parseResult.state;
|
|
453
|
+
for (const cb of stateChangeCallbacks)
|
|
454
|
+
cb(session.id, parseResult.state);
|
|
455
|
+
fireBackendStateIfChanged?.(session);
|
|
456
|
+
}
|
|
457
|
+
else if (!lastHook && sessionAge > 30000) {
|
|
458
|
+
// Hooks active but never fired in 30s — allow parser to override to prevent permanent suppression
|
|
459
|
+
session.agentState = parseResult.state;
|
|
460
|
+
for (const cb of stateChangeCallbacks)
|
|
461
|
+
cb(session.id, parseResult.state);
|
|
462
|
+
fireBackendStateIfChanged?.(session);
|
|
463
|
+
}
|
|
464
|
+
// else: suppress parser — hooks are still fresh
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// No hooks — parser is primary (current behavior)
|
|
468
|
+
session.agentState = parseResult.state;
|
|
469
|
+
for (const cb of stateChangeCallbacks)
|
|
470
|
+
cb(session.id, parseResult.state);
|
|
471
|
+
fireBackendStateIfChanged?.(session);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
proc.onExit(() => {
|
|
476
|
+
if (canRetry && Date.now() - spawnTime < 3000) {
|
|
477
|
+
let retryArgs = rawArgs.filter((a) => !continueArgs.includes(a));
|
|
478
|
+
// Re-inject hooks settings if active (settingsPath captured from outer scope)
|
|
479
|
+
if (session.hooksActive && settingsPath) {
|
|
480
|
+
retryArgs = ['--settings', settingsPath, ...retryArgs];
|
|
481
|
+
}
|
|
482
|
+
const retryNotice = '\r\n[relay-ide] --continue not available; starting new session...\r\n';
|
|
483
|
+
scrollback.length = 0;
|
|
484
|
+
scrollbackBytes = 0;
|
|
485
|
+
scrollback.push(retryNotice);
|
|
486
|
+
scrollbackBytes = retryNotice.length;
|
|
487
|
+
let retryCommand = resolvedCommand;
|
|
488
|
+
let retrySpawnArgs = retryArgs;
|
|
489
|
+
if (useTmux && tmuxSessionName) {
|
|
490
|
+
const retryTmuxName = tmuxSessionName + '-retry';
|
|
491
|
+
session.tmuxSessionName = retryTmuxName;
|
|
492
|
+
const tmux = resolveTmuxSpawn(resolvedCommand, retryArgs, retryTmuxName);
|
|
493
|
+
retryCommand = tmux.command;
|
|
494
|
+
retrySpawnArgs = tmux.args;
|
|
495
|
+
}
|
|
496
|
+
let retryPty;
|
|
497
|
+
try {
|
|
498
|
+
retryPty = pty.spawn(retryCommand, retrySpawnArgs, {
|
|
499
|
+
name: 'xterm-256color',
|
|
500
|
+
cols,
|
|
501
|
+
rows,
|
|
502
|
+
cwd,
|
|
503
|
+
env,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Retry spawn failed — fall through to normal exit cleanup
|
|
508
|
+
if (restoredClearTimer)
|
|
509
|
+
clearTimeout(restoredClearTimer);
|
|
510
|
+
if (idleTimer)
|
|
511
|
+
clearTimeout(idleTimer);
|
|
512
|
+
if (metaFlushTimer)
|
|
513
|
+
clearTimeout(metaFlushTimer);
|
|
514
|
+
sessionsMap.delete(id);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
session.pty = retryPty;
|
|
518
|
+
for (const cb of session.onPtyReplacedCallbacks)
|
|
519
|
+
cb(retryPty);
|
|
520
|
+
attachHandlers(retryPty, false);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (session.cleanedUp)
|
|
524
|
+
return; // Dedup: SessionEnd hook already cleaned up
|
|
525
|
+
session.cleanedUp = true;
|
|
526
|
+
if (restoredClearTimer)
|
|
527
|
+
clearTimeout(restoredClearTimer);
|
|
528
|
+
// If PTY exited and this is a restored session, mark disconnected rather than delete
|
|
529
|
+
if (session.restored) {
|
|
530
|
+
session.status = 'disconnected';
|
|
531
|
+
session.restored = false; // clear so user-initiated kills can delete normally
|
|
532
|
+
if (idleTimer)
|
|
533
|
+
clearTimeout(idleTimer);
|
|
534
|
+
if (metaFlushTimer)
|
|
535
|
+
clearTimeout(metaFlushTimer);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (idleTimer)
|
|
539
|
+
clearTimeout(idleTimer);
|
|
540
|
+
if (metaFlushTimer)
|
|
541
|
+
clearTimeout(metaFlushTimer);
|
|
542
|
+
if (configPath && worktreePath) {
|
|
543
|
+
writeMeta(configPath, {
|
|
544
|
+
worktreePath,
|
|
545
|
+
displayName: session.displayName,
|
|
546
|
+
lastActivity: session.lastActivity,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
for (const cb of sessionEndCallbacks) {
|
|
550
|
+
try {
|
|
551
|
+
cb(id, cwd, session.branchName);
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
logger.error('sessionEnd callback error:', err);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
sessionsMap.delete(id);
|
|
558
|
+
const tmpDir = path.join(os.tmpdir(), 'relay-ide', id);
|
|
559
|
+
fs.rm(tmpDir, { recursive: true, force: true }, () => { });
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
attachHandlers(ptyProcess, continueArgs.some((a) => args.includes(a)));
|
|
563
|
+
const result = {
|
|
564
|
+
id,
|
|
565
|
+
type: session.type,
|
|
566
|
+
agent: session.agent,
|
|
567
|
+
mode: 'pty',
|
|
568
|
+
repoPath: session.repoPath,
|
|
569
|
+
worktreePath: session.worktreePath,
|
|
570
|
+
repoName: session.repoName,
|
|
571
|
+
branchName: session.branchName,
|
|
572
|
+
displayName: session.displayName,
|
|
573
|
+
pid: ptyProcess.pid,
|
|
574
|
+
createdAt,
|
|
575
|
+
lastActivity: createdAt,
|
|
576
|
+
idle: false,
|
|
577
|
+
cwd,
|
|
578
|
+
customCommand: command || null,
|
|
579
|
+
useTmux,
|
|
580
|
+
tmuxSessionName,
|
|
581
|
+
status: 'active',
|
|
582
|
+
needsBranchRename: false,
|
|
583
|
+
agentState: 'initializing',
|
|
584
|
+
};
|
|
585
|
+
return { session, result };
|
|
586
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import webpush from 'web-push';
|
|
2
|
+
let vapidPublicKey = null;
|
|
3
|
+
const subscriptions = new Map();
|
|
4
|
+
const MAX_PAYLOAD_SIZE = 4 * 1024; // 4KB
|
|
5
|
+
export function ensureVapidKeys(config, configPath, save) {
|
|
6
|
+
if (config.vapidPublicKey && config.vapidPrivateKey) {
|
|
7
|
+
vapidPublicKey = config.vapidPublicKey;
|
|
8
|
+
webpush.setVapidDetails('mailto:noreply@relay-ide.local', config.vapidPublicKey, config.vapidPrivateKey);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const keys = webpush.generateVAPIDKeys();
|
|
13
|
+
config.vapidPublicKey = keys.publicKey;
|
|
14
|
+
config.vapidPrivateKey = keys.privateKey;
|
|
15
|
+
save(configPath, config);
|
|
16
|
+
vapidPublicKey = keys.publicKey;
|
|
17
|
+
webpush.setVapidDetails('mailto:noreply@relay-ide.local', keys.publicKey, keys.privateKey);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// VAPID key generation failed — push will be unavailable
|
|
21
|
+
vapidPublicKey = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function getVapidPublicKey() {
|
|
25
|
+
return vapidPublicKey;
|
|
26
|
+
}
|
|
27
|
+
export function subscribe(subscription, sessionIds) {
|
|
28
|
+
// Replace the full session list for this endpoint — the client sends
|
|
29
|
+
// the complete set of sessions it wants notifications for.
|
|
30
|
+
subscriptions.set(subscription.endpoint, {
|
|
31
|
+
subscription,
|
|
32
|
+
sessionIds: new Set(sessionIds),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function unsubscribe(endpoint) {
|
|
36
|
+
subscriptions.delete(endpoint);
|
|
37
|
+
}
|
|
38
|
+
export function removeSession(sessionId) {
|
|
39
|
+
for (const entry of subscriptions.values()) {
|
|
40
|
+
entry.sessionIds.delete(sessionId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function truncatePayload(payload) {
|
|
44
|
+
if (payload.length <= MAX_PAYLOAD_SIZE)
|
|
45
|
+
return payload;
|
|
46
|
+
// Try to parse, truncate text fields, and re-serialize
|
|
47
|
+
try {
|
|
48
|
+
const obj = JSON.parse(payload);
|
|
49
|
+
if (typeof obj.enrichedMessage === 'string' &&
|
|
50
|
+
obj.enrichedMessage.length > 100) {
|
|
51
|
+
obj.enrichedMessage =
|
|
52
|
+
obj.enrichedMessage.slice(0, 100) + '...';
|
|
53
|
+
}
|
|
54
|
+
const truncated = JSON.stringify(obj);
|
|
55
|
+
if (truncated.length <= MAX_PAYLOAD_SIZE)
|
|
56
|
+
return truncated;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// fall through
|
|
60
|
+
}
|
|
61
|
+
return payload.slice(0, MAX_PAYLOAD_SIZE);
|
|
62
|
+
}
|
|
63
|
+
export function notifySessionAttention(sessionId, session) {
|
|
64
|
+
if (!vapidPublicKey)
|
|
65
|
+
return;
|
|
66
|
+
const payloadObj = {
|
|
67
|
+
type: 'session-attention',
|
|
68
|
+
sessionId,
|
|
69
|
+
displayName: session.displayName,
|
|
70
|
+
sessionType: session.type,
|
|
71
|
+
};
|
|
72
|
+
const payload = truncatePayload(JSON.stringify(payloadObj));
|
|
73
|
+
for (const [endpoint, entry] of subscriptions) {
|
|
74
|
+
if (!entry.sessionIds.has(sessionId))
|
|
75
|
+
continue;
|
|
76
|
+
webpush
|
|
77
|
+
.sendNotification(entry.subscription, payload)
|
|
78
|
+
.catch((err) => {
|
|
79
|
+
if (err.statusCode === 410 || err.statusCode === 404) {
|
|
80
|
+
subscriptions.delete(endpoint);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|