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,137 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
import { loadConfig } from './config.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const GIT_TIMEOUT_MS = 10_000;
|
|
8
|
+
const CACHE_TTL_MS = 60_000;
|
|
9
|
+
let cache = null;
|
|
10
|
+
/** Clears the branch linker cache (call when sessions are created or ended). */
|
|
11
|
+
export function invalidateBranchLinkerCache() {
|
|
12
|
+
cache = null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extracts all ticket IDs from a branch name.
|
|
16
|
+
* Returns an array of normalized ticket IDs (e.g. "PROJ-123", "GH-456").
|
|
17
|
+
*/
|
|
18
|
+
function extractTicketIds(branchName) {
|
|
19
|
+
const ids = [];
|
|
20
|
+
// Jira style: PROJECT-123 (2+ uppercase letters, dash, digits)
|
|
21
|
+
// Skip "GH" prefix — that's our GitHub Issues namespace, handled separately below.
|
|
22
|
+
const jiraRegex = /([A-Z]{2,}-\d+)/gi;
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = jiraRegex.exec(branchName)) !== null) {
|
|
25
|
+
if (match[1] && match[1].toUpperCase().split('-')[0] !== 'GH') {
|
|
26
|
+
ids.push(match[1].toUpperCase());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// GitHub Issues: gh-123 at word boundaries (start/end or preceded/followed by dash or slash)
|
|
30
|
+
const ghRegex = /(?:^|[-/])gh-(\d+)(?:[-/]|$)/gi;
|
|
31
|
+
while ((match = ghRegex.exec(branchName)) !== null) {
|
|
32
|
+
ids.push(`GH-${match[1]}`);
|
|
33
|
+
}
|
|
34
|
+
return ids;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates and returns an Express Router that handles all /branch-linker routes.
|
|
38
|
+
*
|
|
39
|
+
* Caller is responsible for mounting and applying auth middleware:
|
|
40
|
+
* app.use('/branch-linker', requireAuth, createBranchLinkerRouter({ configPath }));
|
|
41
|
+
*/
|
|
42
|
+
export function createBranchLinkerRouter(deps) {
|
|
43
|
+
const { configPath } = deps;
|
|
44
|
+
const exec = deps.execAsync ?? execFileAsync;
|
|
45
|
+
const getActiveBranchNames = deps.getActiveBranchNames ?? (() => new Map());
|
|
46
|
+
const router = Router();
|
|
47
|
+
function getConfig() {
|
|
48
|
+
return loadConfig(configPath);
|
|
49
|
+
}
|
|
50
|
+
/** Core link-building logic, usable both from the HTTP handler and internal callers. */
|
|
51
|
+
async function fetchLinks() {
|
|
52
|
+
const config = getConfig();
|
|
53
|
+
const workspacePaths = config.repos ?? [];
|
|
54
|
+
if (workspacePaths.length === 0) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
// Return cached result if still fresh
|
|
59
|
+
if (cache && now - cache.fetchedAt < CACHE_TTL_MS) {
|
|
60
|
+
return cache.links;
|
|
61
|
+
}
|
|
62
|
+
// Get active branch names per repo from sessions
|
|
63
|
+
const activeBranchNames = getActiveBranchNames();
|
|
64
|
+
// Fetch branches per workspace using Promise.allSettled (partial failures are non-fatal)
|
|
65
|
+
const results = await Promise.allSettled(workspacePaths.map(async (wsPath) => {
|
|
66
|
+
let stdout;
|
|
67
|
+
try {
|
|
68
|
+
({ stdout } = await exec('git', ['branch', '--format=%(refname:short)'], { cwd: wsPath, timeout: GIT_TIMEOUT_MS }));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Not a git repo or git not available — non-fatal
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const repoName = path.basename(wsPath);
|
|
75
|
+
const activeInRepo = activeBranchNames.get(wsPath) ?? new Set();
|
|
76
|
+
const branchNames = stdout
|
|
77
|
+
.split('\n')
|
|
78
|
+
.map((b) => b.trim())
|
|
79
|
+
.filter(Boolean);
|
|
80
|
+
const links = [];
|
|
81
|
+
for (const branchName of branchNames) {
|
|
82
|
+
const ticketIds = extractTicketIds(branchName);
|
|
83
|
+
for (const ticketId of ticketIds) {
|
|
84
|
+
// Infer ticket source from ID pattern
|
|
85
|
+
let source;
|
|
86
|
+
if (ticketId.startsWith('GH-')) {
|
|
87
|
+
source = 'github';
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Non-GH ticket IDs (e.g., PROJ-123) assumed to be Jira
|
|
91
|
+
source = 'jira';
|
|
92
|
+
}
|
|
93
|
+
links.push({
|
|
94
|
+
ticketId,
|
|
95
|
+
link: {
|
|
96
|
+
repoPath: wsPath,
|
|
97
|
+
repoName,
|
|
98
|
+
branchName,
|
|
99
|
+
hasActiveSession: activeInRepo.has(branchName),
|
|
100
|
+
source,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return links;
|
|
106
|
+
}));
|
|
107
|
+
// Build the ticket -> BranchLink[] map
|
|
108
|
+
const linksMap = new Map();
|
|
109
|
+
for (const result of results) {
|
|
110
|
+
if (result.status === 'fulfilled') {
|
|
111
|
+
for (const { ticketId, link } of result.value) {
|
|
112
|
+
const existing = linksMap.get(ticketId);
|
|
113
|
+
if (existing) {
|
|
114
|
+
existing.push(link);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
linksMap.set(ticketId, [link]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Convert Map to plain object for JSON serialization
|
|
123
|
+
const response = {};
|
|
124
|
+
for (const [ticketId, links] of linksMap) {
|
|
125
|
+
response[ticketId] = links;
|
|
126
|
+
}
|
|
127
|
+
// Update module-level cache
|
|
128
|
+
cache = { links: response, fetchedAt: now };
|
|
129
|
+
return response;
|
|
130
|
+
}
|
|
131
|
+
// GET /branch-linker/links — map of ticketId -> BranchLink[]
|
|
132
|
+
router.get('/links', async (_req, res) => {
|
|
133
|
+
const response = await fetchLinks();
|
|
134
|
+
res.json(response);
|
|
135
|
+
});
|
|
136
|
+
return Object.assign(router, { fetchLinks });
|
|
137
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
const tokenStore = new Map();
|
|
6
|
+
const pathToToken = new Map();
|
|
7
|
+
// ── Scoped auth token ──
|
|
8
|
+
let scopedToken = '';
|
|
9
|
+
export function generateScopedToken() {
|
|
10
|
+
scopedToken = crypto.randomBytes(32).toString('hex');
|
|
11
|
+
return scopedToken;
|
|
12
|
+
}
|
|
13
|
+
export function validateScopedToken(token) {
|
|
14
|
+
return token.length > 0 && token === scopedToken;
|
|
15
|
+
}
|
|
16
|
+
// ── Content tokens ──
|
|
17
|
+
export function createBrowserToken(filePath) {
|
|
18
|
+
const existing = pathToToken.get(filePath);
|
|
19
|
+
if (existing && tokenStore.has(existing))
|
|
20
|
+
return existing;
|
|
21
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
22
|
+
const baseDir = path.dirname(filePath);
|
|
23
|
+
tokenStore.set(token, { baseDir, filePath, createdAt: Date.now() });
|
|
24
|
+
pathToToken.set(filePath, token);
|
|
25
|
+
return token;
|
|
26
|
+
}
|
|
27
|
+
export function validateToken(token) {
|
|
28
|
+
const entry = tokenStore.get(token);
|
|
29
|
+
return entry ? { baseDir: entry.baseDir, filePath: entry.filePath } : null;
|
|
30
|
+
}
|
|
31
|
+
export function resolveTokenPath(token, relativePath) {
|
|
32
|
+
const entry = tokenStore.get(token);
|
|
33
|
+
if (!entry)
|
|
34
|
+
return null;
|
|
35
|
+
if (path.isAbsolute(relativePath))
|
|
36
|
+
return null;
|
|
37
|
+
const resolved = path.resolve(entry.baseDir, relativePath);
|
|
38
|
+
if (!resolved.startsWith(entry.baseDir + path.sep) &&
|
|
39
|
+
resolved !== entry.baseDir)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
const realBaseDir = fs.realpathSync(entry.baseDir);
|
|
43
|
+
const realResolved = fs.realpathSync(resolved);
|
|
44
|
+
if (realResolved !== realBaseDir &&
|
|
45
|
+
!realResolved.startsWith(realBaseDir + path.sep))
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return resolved;
|
|
52
|
+
}
|
|
53
|
+
export function getTokenForPath(filePath) {
|
|
54
|
+
return pathToToken.get(filePath) ?? null;
|
|
55
|
+
}
|
|
56
|
+
export function cleanExpiredTokens(ttlMs) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
for (const [token, entry] of tokenStore) {
|
|
59
|
+
if (now - entry.createdAt >= ttlMs) {
|
|
60
|
+
tokenStore.delete(token);
|
|
61
|
+
pathToToken.delete(entry.filePath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function _resetForTesting() {
|
|
66
|
+
tokenStore.clear();
|
|
67
|
+
pathToToken.clear();
|
|
68
|
+
scopedToken = '';
|
|
69
|
+
}
|
|
70
|
+
// ── Express router ──
|
|
71
|
+
export function createBrowserContentRouter(broadcastEvent) {
|
|
72
|
+
const router = Router();
|
|
73
|
+
router.post('/browser-tabs', (req, res) => {
|
|
74
|
+
const authHeader = req.headers.authorization;
|
|
75
|
+
const bearerToken = authHeader?.startsWith('Bearer ')
|
|
76
|
+
? authHeader.slice(7)
|
|
77
|
+
: '';
|
|
78
|
+
if (!validateScopedToken(bearerToken)) {
|
|
79
|
+
res.status(401).json({ error: 'Invalid browser token' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const { path: filePath } = req.body;
|
|
83
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
84
|
+
res.status(400).json({ error: 'path is required' });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const resolved = path.resolve(filePath);
|
|
88
|
+
try {
|
|
89
|
+
const stat = fs.statSync(resolved);
|
|
90
|
+
if (!stat.isFile()) {
|
|
91
|
+
res
|
|
92
|
+
.status(400)
|
|
93
|
+
.json({ error: 'path must be a file, not a directory' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
res.status(404).json({ error: 'file not found' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const existingToken = getTokenForPath(resolved);
|
|
102
|
+
if (existingToken) {
|
|
103
|
+
// Always emit browser-tab-opened (not just refreshed) so reconnecting clients,
|
|
104
|
+
// second devices, or users who closed the tab get it re-opened with the token.
|
|
105
|
+
// openHtmlTab on the frontend is idempotent — it focuses existing tabs.
|
|
106
|
+
broadcastEvent('browser-tab-opened', {
|
|
107
|
+
filePath: resolved,
|
|
108
|
+
token: existingToken,
|
|
109
|
+
});
|
|
110
|
+
broadcastEvent('browser-tab-refreshed', { filePath: resolved });
|
|
111
|
+
res.json({ token: existingToken, refreshed: true });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const token = createBrowserToken(resolved);
|
|
115
|
+
broadcastEvent('browser-tab-opened', { filePath: resolved, token });
|
|
116
|
+
res.json({ token, refreshed: false });
|
|
117
|
+
});
|
|
118
|
+
router.get('/browser-content/:token/*', (req, res) => {
|
|
119
|
+
const { token } = req.params;
|
|
120
|
+
const relativePath = req.params[0] ?? '';
|
|
121
|
+
if (!relativePath) {
|
|
122
|
+
res.status(400).send('Missing file path');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const resolved = resolveTokenPath(token, relativePath);
|
|
126
|
+
if (!resolved) {
|
|
127
|
+
res.status(403).send('Forbidden');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let stat;
|
|
131
|
+
try {
|
|
132
|
+
stat = fs.statSync(resolved);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
res.status(404).send('Not found');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!stat.isFile()) {
|
|
139
|
+
res.status(400).send('Not a file');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
res.sendFile(resolved);
|
|
143
|
+
});
|
|
144
|
+
return router;
|
|
145
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
const SUPPORTED_MIME = {
|
|
5
|
+
'image/png': { ext: '.png', osascriptClass: '«class PNGf»' },
|
|
6
|
+
'image/jpeg': { ext: '.jpg', osascriptClass: '«class JPEG»' },
|
|
7
|
+
'image/gif': { ext: '.gif', osascriptClass: '«class GIFf»' },
|
|
8
|
+
'image/webp': { ext: '.webp', osascriptClass: '«class PNGf»' },
|
|
9
|
+
};
|
|
10
|
+
let cachedTool;
|
|
11
|
+
export function detectClipboardTool() {
|
|
12
|
+
if (cachedTool !== undefined)
|
|
13
|
+
return cachedTool;
|
|
14
|
+
if (process.platform === 'darwin') {
|
|
15
|
+
cachedTool = 'osascript';
|
|
16
|
+
return cachedTool;
|
|
17
|
+
}
|
|
18
|
+
if (process.env['DISPLAY'] || process.env['WAYLAND_DISPLAY']) {
|
|
19
|
+
try {
|
|
20
|
+
execFileSync('which', ['xclip'], { stdio: 'ignore' });
|
|
21
|
+
cachedTool = 'xclip';
|
|
22
|
+
return cachedTool;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// xclip not found
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
cachedTool = null;
|
|
29
|
+
return cachedTool;
|
|
30
|
+
}
|
|
31
|
+
function mimeInfo(mimeType) {
|
|
32
|
+
const info = SUPPORTED_MIME[mimeType];
|
|
33
|
+
if (!info)
|
|
34
|
+
throw new Error(`Unsupported MIME type: ${mimeType}`);
|
|
35
|
+
return info;
|
|
36
|
+
}
|
|
37
|
+
export function extensionForMime(mimeType) {
|
|
38
|
+
return mimeInfo(mimeType).ext;
|
|
39
|
+
}
|
|
40
|
+
export async function setClipboardImage(filePath, mimeType) {
|
|
41
|
+
const tool = detectClipboardTool();
|
|
42
|
+
const info = mimeInfo(mimeType); // throws if unsupported
|
|
43
|
+
if (tool === 'osascript') {
|
|
44
|
+
const script = `set the clipboard to (read (POSIX file "${filePath}") as ${info.osascriptClass})`;
|
|
45
|
+
await execFileAsync('osascript', ['-e', script]);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (tool === 'xclip') {
|
|
49
|
+
await execFileAsync('xclip', [
|
|
50
|
+
'-selection',
|
|
51
|
+
'clipboard',
|
|
52
|
+
'-t',
|
|
53
|
+
mimeType,
|
|
54
|
+
'-i',
|
|
55
|
+
filePath,
|
|
56
|
+
]);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
export function _resetForTesting() {
|
|
62
|
+
cachedTool = undefined;
|
|
63
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
const logger = createLogger('codex-hooks-adapter');
|
|
6
|
+
// Codex hook event names that we relay
|
|
7
|
+
const CODEX_EVENTS = [
|
|
8
|
+
'SessionStart',
|
|
9
|
+
'Stop',
|
|
10
|
+
'UserPromptSubmit',
|
|
11
|
+
'PreToolUse',
|
|
12
|
+
'PostToolUse',
|
|
13
|
+
];
|
|
14
|
+
// Map codex events to our canonical event types
|
|
15
|
+
const EVENT_MAP = {
|
|
16
|
+
SessionStart: 'session.started',
|
|
17
|
+
Stop: 'session.ended',
|
|
18
|
+
UserPromptSubmit: 'prompt.submitted',
|
|
19
|
+
PreToolUse: 'tool.started',
|
|
20
|
+
PostToolUse: 'tool.finished',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Write codex hooks adapter to a temp directory.
|
|
24
|
+
* Returns the directory path (set as CODEX_CONFIG_DIR).
|
|
25
|
+
*/
|
|
26
|
+
export function writeCodexHooksAdapter(sessionId, port, hookToken, _configDir) {
|
|
27
|
+
const tmpDir = path.join(os.tmpdir(), 'relay-ide', `codex-hooks-${sessionId}`);
|
|
28
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
29
|
+
// Write session config for the relay script to read
|
|
30
|
+
const configPath = path.join(tmpDir, 'session.json');
|
|
31
|
+
fs.writeFileSync(configPath, JSON.stringify({ sessionId, port, hookToken }), 'utf-8');
|
|
32
|
+
logger.debug('Wrote Codex session config', configPath);
|
|
33
|
+
// Write relay script — reads config from file to avoid shell injection
|
|
34
|
+
// Parse session.json once (single node invocation) to extract all values at once for performance.
|
|
35
|
+
const relayScript = `#!/usr/bin/env bash
|
|
36
|
+
set -u
|
|
37
|
+
INPUT=$(cat)
|
|
38
|
+
EVENT="\${HOOK_EVENT_NAME:-unknown}"
|
|
39
|
+
CANONICAL_EVENT=""
|
|
40
|
+
case "$EVENT" in
|
|
41
|
+
SessionStart) CANONICAL_EVENT="session.started" ;;
|
|
42
|
+
Stop) CANONICAL_EVENT="session.ended" ;;
|
|
43
|
+
UserPromptSubmit) CANONICAL_EVENT="prompt.submitted" ;;
|
|
44
|
+
PreToolUse) CANONICAL_EVENT="tool.started" ;;
|
|
45
|
+
PostToolUse) CANONICAL_EVENT="tool.finished" ;;
|
|
46
|
+
*) CANONICAL_EVENT="$EVENT" ;;
|
|
47
|
+
esac
|
|
48
|
+
CONFIG_FILE="${configPath}"
|
|
49
|
+
read -r SESSION_ID PORT TOKEN < <(node -e "const fs=require('node:fs');const{sessionId,port,hookToken}=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write([sessionId,port,hookToken].join(' ')+'\\n');" "$CONFIG_FILE")
|
|
50
|
+
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
51
|
+
PAYLOAD=$(printf '{"sessionId":"%s","token":"%s","eventType":"%s","data":%s,"timestamp":"%s"}' "$SESSION_ID" "$TOKEN" "$CANONICAL_EVENT" "$INPUT" "$TIMESTAMP")
|
|
52
|
+
curl -s -X POST "http://127.0.0.1:$PORT/hooks/agent-event" \\
|
|
53
|
+
-H "Content-Type: application/json" \\
|
|
54
|
+
-d "$PAYLOAD" \\
|
|
55
|
+
> /dev/null 2>&1 || true
|
|
56
|
+
`;
|
|
57
|
+
const relayPath = path.join(tmpDir, 'relay.sh');
|
|
58
|
+
fs.writeFileSync(relayPath, relayScript, { mode: 0o755 });
|
|
59
|
+
// Read existing user hooks if present
|
|
60
|
+
let existingHooks = {};
|
|
61
|
+
const userHooksPath = path.join(os.homedir(), '.codex', 'hooks.json');
|
|
62
|
+
try {
|
|
63
|
+
existingHooks = JSON.parse(fs.readFileSync(userHooksPath, 'utf-8'));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* no existing hooks */
|
|
67
|
+
}
|
|
68
|
+
// Build merged hooks: append our relay to each event
|
|
69
|
+
const relayHook = { type: 'command', command: relayPath };
|
|
70
|
+
const mergedHooks = { ...existingHooks };
|
|
71
|
+
for (const event of CODEX_EVENTS) {
|
|
72
|
+
const existing = mergedHooks[event] ?? [];
|
|
73
|
+
mergedHooks[event] = [...existing, relayHook];
|
|
74
|
+
}
|
|
75
|
+
fs.writeFileSync(path.join(tmpDir, 'hooks.json'), JSON.stringify(mergedHooks, null, 2));
|
|
76
|
+
return tmpDir;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove the codex hooks adapter temp directory for a session.
|
|
80
|
+
* Called when the session ends to avoid leaking temp files.
|
|
81
|
+
*/
|
|
82
|
+
export function cleanupCodexHooksAdapter(sessionId) {
|
|
83
|
+
const tmpDir = path.join(os.tmpdir(), 'relay-ide', `codex-hooks-${sessionId}`);
|
|
84
|
+
try {
|
|
85
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
86
|
+
logger.debug('Cleaned up Codex hooks adapter', tmpDir);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Non-fatal — temp dir may not exist or already be cleaned up
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Exported for testing */
|
|
93
|
+
export { CODEX_EVENTS, EVENT_MAP };
|