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
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# relay-ide
|
|
2
|
+
|
|
3
|
+
Control Claude Code from your phone or any browser — manage multiple terminal sessions across repos and worktrees with a mobile-friendly web UI.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
| Dependency | Why |
|
|
8
|
+
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
9
|
+
| **[Node.js 24+](https://nodejs.org/)** | Runtime for the server |
|
|
10
|
+
| **[Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)** | Default coding agent — must be in your `PATH` |
|
|
11
|
+
| **[Codex CLI](https://github.com/openai/codex)** | _Optional_ — alternative coding agent. Install if you want to use Codex sessions |
|
|
12
|
+
| **[GitHub CLI (`gh`)](https://cli.github.com/)** | _Optional_ — required for the **PRs tab**. Run `gh auth login` after installing. |
|
|
13
|
+
|
|
14
|
+
## Getting Started
|
|
15
|
+
|
|
16
|
+
### 1. Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g relay-ide
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Start the server
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
relay-ide --bg
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This installs a persistent background service (launchd on macOS, systemd on Linux) that starts on login and restarts on crash. See [Background Service](#background-service) for more options.
|
|
29
|
+
|
|
30
|
+
Or run in the foreground:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
relay-ide
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Set your PIN
|
|
37
|
+
|
|
38
|
+
Open `http://localhost:3456` in your browser. On first visit you'll be prompted to create a PIN that protects access to your Claude sessions.
|
|
39
|
+
|
|
40
|
+
If you started the server in the foreground, you can set the PIN in the terminal instead.
|
|
41
|
+
|
|
42
|
+
### 4. Add your project directories
|
|
43
|
+
|
|
44
|
+
Click **Settings** in the app to add root directories — these are parent folders that contain your git repos (scanned one level deep).
|
|
45
|
+
|
|
46
|
+
You can also edit `~/.config/relay-ide/config.json` directly:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"rootDirs": ["/home/you/projects", "/home/you/work"]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 5. Access from your phone
|
|
55
|
+
|
|
56
|
+
Relay IDE binds to `0.0.0.0` by default, but you should **not** expose it to the public internet. Use [Tailscale](https://tailscale.com/) for a private encrypted connection between your devices — see [Remote Access](#remote-access) below.
|
|
57
|
+
|
|
58
|
+
## Remote Access
|
|
59
|
+
|
|
60
|
+
The recommended way to access Relay IDE from another device (phone, tablet, laptop) is [Tailscale](https://tailscale.com/), which creates a private encrypted network using WireGuard.
|
|
61
|
+
|
|
62
|
+
1. **Install Tailscale** on your computer and on your phone/tablet
|
|
63
|
+
- macOS: `brew install tailscale` or download from [tailscale.com/download](https://tailscale.com/download)
|
|
64
|
+
- Linux: follow the [install guide](https://tailscale.com/download/linux)
|
|
65
|
+
- iOS/Android: install the Tailscale app from your app store
|
|
66
|
+
|
|
67
|
+
2. **Sign in** to the same Tailscale account on both devices
|
|
68
|
+
|
|
69
|
+
3. **Find your computer's Tailscale IP** — run `tailscale ip` or check the admin console (looks like `100.x.y.z`)
|
|
70
|
+
|
|
71
|
+
4. **Open the app** on your phone at `http://100.x.y.z:3456`
|
|
72
|
+
|
|
73
|
+
Your traffic is encrypted end-to-end, no ports are exposed to the internet, and only devices on your Tailscale network can reach the server.
|
|
74
|
+
|
|
75
|
+
> **Alternatives:** [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) or [ngrok](https://ngrok.com/) also work, but they expose your server to the public internet and rely on the PIN as your only defense.
|
|
76
|
+
|
|
77
|
+
## Platform Support
|
|
78
|
+
|
|
79
|
+
Tested on **macOS** and **Linux**. Windows is not currently tested — file watching and PTY spawning may behave differently.
|
|
80
|
+
|
|
81
|
+
## CLI Usage
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Usage: relay-ide [options]
|
|
85
|
+
relay-ide <command>
|
|
86
|
+
|
|
87
|
+
Commands:
|
|
88
|
+
update Update to the latest version from npm
|
|
89
|
+
install Install as a background service (survives reboot)
|
|
90
|
+
uninstall Stop and remove the background service
|
|
91
|
+
status Show whether the service is running
|
|
92
|
+
worktree Manage git worktrees (wraps git worktree)
|
|
93
|
+
add [path] [-b branch] [--yolo] Create worktree and launch Claude
|
|
94
|
+
remove <path> Forward to git worktree remove
|
|
95
|
+
list Forward to git worktree list
|
|
96
|
+
pin Manage authentication PIN
|
|
97
|
+
reset Reset the PIN (interactive, requires TTY)
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--bg Shortcut: install and start as background service
|
|
101
|
+
--port <port> Override server port (default: 3456)
|
|
102
|
+
--host <host> Override bind address (default: 0.0.0.0)
|
|
103
|
+
--config <path> Path to config.json (default: ~/.config/relay-ide/config.json)
|
|
104
|
+
--yolo With 'worktree add': pass --dangerously-skip-permissions to Claude
|
|
105
|
+
--version, -v Show version
|
|
106
|
+
--help, -h Show this help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Background Service
|
|
110
|
+
|
|
111
|
+
Run as a persistent service that starts on login and restarts on crash:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
relay-ide --bg
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or with custom options:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
relay-ide install --port 4000
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Manage the service:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
relay-ide status # Check if running
|
|
127
|
+
relay-ide uninstall # Stop and remove
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- **macOS**: Uses launchd (`~/Library/LaunchAgents/`)
|
|
131
|
+
- **Linux**: Uses systemd user units (`~/.config/systemd/user/`)
|
|
132
|
+
- **Logs (macOS)**: `~/.config/relay-ide/logs/`
|
|
133
|
+
- **Logs (Linux)**: `journalctl --user -u relay-ide -f`
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
Config is stored at `~/.config/relay-ide/config.json` (created on first run).
|
|
138
|
+
|
|
139
|
+
When running from source, it uses `./config.json` in the project root instead.
|
|
140
|
+
|
|
141
|
+
| Field | Default | Description |
|
|
142
|
+
| --------------- | --------- | -------------------------------------------------------------- |
|
|
143
|
+
| `host` | `0.0.0.0` | Bind address |
|
|
144
|
+
| `port` | `3456` | Server port |
|
|
145
|
+
| `cookieTTL` | `24h` | Auth cookie lifetime (e.g. `30m`, `12h`, `7d`) |
|
|
146
|
+
| `rootDirs` | `[]` | Directories containing your git repos (scanned one level deep) |
|
|
147
|
+
| `claudeCommand` | `claude` | Path to the Claude Code CLI binary |
|
|
148
|
+
| `claudeArgs` | `[]` | Extra arguments passed to every session |
|
|
149
|
+
| `defaultAgent` | `claude` | Default coding agent CLI (`claude` or `codex`) |
|
|
150
|
+
|
|
151
|
+
Root directories can also be managed from the **Settings** button in the app.
|
|
152
|
+
|
|
153
|
+
### PIN Management
|
|
154
|
+
|
|
155
|
+
The PIN hash is stored in config under `pinHash`.
|
|
156
|
+
|
|
157
|
+
**Reset via CLI** (recommended):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
relay-ide pin reset
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
This requires an interactive terminal. You'll be asked to verify your current PIN (if set), then enter a new one.
|
|
164
|
+
|
|
165
|
+
**Reset manually:**
|
|
166
|
+
|
|
167
|
+
1. Delete the `pinHash` field from `~/.config/relay-ide/config.json`
|
|
168
|
+
2. Restart the server (`relay-ide uninstall && relay-ide --bg`)
|
|
169
|
+
3. Open the web UI and set a new PIN
|
|
170
|
+
|
|
171
|
+
## Features
|
|
172
|
+
|
|
173
|
+
### Session Management
|
|
174
|
+
|
|
175
|
+
- **Multi-agent support** — choose between Claude Code and Codex as the coding agent per session, with a configurable default in Settings
|
|
176
|
+
- **Repo sessions** — click any idle repo to instantly open Claude with `--continue` (no dialog), or start fresh from the new-session dialog
|
|
177
|
+
- **Branch-aware worktrees** — create worktrees from new or existing branches with a type-to-search branch picker
|
|
178
|
+
- **Worktree isolation** — each worktree session runs in its own git worktree under `.worktrees/`
|
|
179
|
+
- **Resume sessions** — click inactive worktrees to reconnect with `--continue`
|
|
180
|
+
- **Persistent session names** — display names, branch names, and timestamps survive server restarts
|
|
181
|
+
- **Scrollback buffer** — reconnect to a session and see prior output
|
|
182
|
+
- **Yolo mode** — skip permission prompts with `--dangerously-skip-permissions` (per-session pill button)
|
|
183
|
+
- **Worktree cleanup** — delete inactive worktrees via the trash pill button (removes worktree, prunes refs, deletes branch)
|
|
184
|
+
|
|
185
|
+
### Pull Requests
|
|
186
|
+
|
|
187
|
+
- **Pull requests tab** — view your open PRs (authored and review-requested) via `gh` CLI, organized in collapsible per-repo groups with count badges, Author/Reviewer filter, and one-click session creation from any PR branch
|
|
188
|
+
|
|
189
|
+
### GitHub Webhooks (real-time PR / CI updates)
|
|
190
|
+
|
|
191
|
+
By default the app polls GitHub every 30 seconds for PR and CI status. Connect a webhook for instant updates instead:
|
|
192
|
+
|
|
193
|
+
1. **Connect GitHub** — open **Settings → Integrations → GitHub** and authorise the OAuth App. This requests the `repo` and `admin:repo_hook` scopes so the app can manage webhooks on your behalf.
|
|
194
|
+
2. **Set up webhooks** — open **Settings → Integrations → Webhooks**. Click **Setup Webhook** next to any repo. The app creates a GitHub webhook pointing at a [smee.io](https://smee.io/) proxy channel and starts a local smee client to relay events.
|
|
195
|
+
3. **Verify** — the webhook panel shows a health indicator (last event timestamp). Once connected, polling stops for that repo and updates arrive in real time.
|
|
196
|
+
|
|
197
|
+
> No public server is required. The smee.io proxy forwards GitHub webhook payloads to your local instance over a persistent SSE connection.
|
|
198
|
+
|
|
199
|
+
### UI
|
|
200
|
+
|
|
201
|
+
- **Tabbed sidebar** — switch between Repos, Worktrees, and PRs views with shared filters and item counts
|
|
202
|
+
- **Sidebar filters** — filter by root directory, repo, or text search
|
|
203
|
+
- **Inline actions** — pill buttons on session cards for rename, YOLO, worktree creation, and delete (hover on desktop, long-press on mobile)
|
|
204
|
+
- **Resizable sidebar** — drag the sidebar edge to resize; collapse/expand with a button (persisted to localStorage)
|
|
205
|
+
- **Responsive layout** — works on desktop and mobile with slide-out sidebar
|
|
206
|
+
- **Touch toolbar** — mobile-friendly buttons for special keys (hidden on desktop)
|
|
207
|
+
- **Clipboard image paste** — paste screenshots directly into remote terminal sessions (macOS clipboard + xclip on Linux)
|
|
208
|
+
|
|
209
|
+
### Settings
|
|
210
|
+
|
|
211
|
+
- **Full-screen Settings dialog** — redesigned as a scrollable full-screen modal with a table-of-contents drawer for quick section navigation
|
|
212
|
+
- **GitHub integration** — connect via OAuth App (Device Flow) for PR data, CI status, and webhook management
|
|
213
|
+
- **Webhook management** — self-service webhook CRUD per repo with smee.io proxy, health state, and auto-provision backfill
|
|
214
|
+
- **Jira integration** — connect Jira and configure project mappings for the org dashboard tickets panel
|
|
215
|
+
|
|
216
|
+
### Operations
|
|
217
|
+
|
|
218
|
+
- **PIN-protected access** with rate limiting
|
|
219
|
+
- **Real-time updates** — worktree changes on disk are pushed to the browser instantly via WebSocket
|
|
220
|
+
- **Smart polling** — falls back to 30-second polling for repos without webhooks; switches off automatically once a webhook is active
|
|
221
|
+
- **Update notifications** — toast notification when a new version is available, with one-click update
|
|
222
|
+
- **CLI self-update** — `relay-ide update` to update from npm
|
|
223
|
+
|
|
224
|
+
## Architecture
|
|
225
|
+
|
|
226
|
+
TypeScript + ESM backend (Express + node-pty + WebSocket) compiled to `dist/`. Svelte 5 frontend (runes + Vite) compiled to `dist/frontend/`.
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
relay-ide/
|
|
230
|
+
├── bin/
|
|
231
|
+
│ └── relay-ide.ts # CLI entry point
|
|
232
|
+
├── server/
|
|
233
|
+
│ ├── index.ts # Express server, REST API routes
|
|
234
|
+
│ ├── sessions.ts # PTY session manager (node-pty)
|
|
235
|
+
│ ├── ws.ts # WebSocket relay (PTY ↔ browser)
|
|
236
|
+
│ ├── watcher.ts # File watcher for .worktrees/ changes
|
|
237
|
+
│ ├── auth.ts # PIN hashing, verification, rate limiting
|
|
238
|
+
│ ├── config.ts # Config loading/saving, worktree metadata
|
|
239
|
+
│ ├── clipboard.ts # System clipboard operations (image paste)
|
|
240
|
+
│ ├── service.ts # Background service management (launchd/systemd)
|
|
241
|
+
│ └── types.ts # Shared TypeScript interfaces
|
|
242
|
+
├── frontend/
|
|
243
|
+
│ └── src/
|
|
244
|
+
│ ├── components/ # Svelte 5 components (Sidebar, Terminal, SessionList, etc.)
|
|
245
|
+
│ ├── lib/state/ # Reactive state modules (.svelte.ts)
|
|
246
|
+
│ ├── lib/api.ts # REST API client
|
|
247
|
+
│ ├── lib/ws.ts # WebSocket connection management
|
|
248
|
+
│ ├── lib/types.ts # Frontend TypeScript interfaces
|
|
249
|
+
│ ├── lib/utils.ts # Shared utilities (path display, time formatting, device detection)
|
|
250
|
+
│ └── lib/actions.ts # Svelte actions (scroll-on-hover, longpress-click)
|
|
251
|
+
├── test/ # Unit tests (node:test)
|
|
252
|
+
├── dist/ # Compiled output (gitignored)
|
|
253
|
+
├── config.example.json
|
|
254
|
+
└── package.json
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console -- CLI entry point, user-facing stdout/stderr output */
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { execFile, spawn } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import * as service from '../server/service.js';
|
|
9
|
+
import { DEFAULTS } from '../server/config.js';
|
|
10
|
+
import { createLogger } from '../server/logger.js';
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const logger = createLogger('cli');
|
|
14
|
+
function execErrorMessage(err, fallback) {
|
|
15
|
+
const e = err;
|
|
16
|
+
return (e.stderr || e.message || fallback).trimEnd();
|
|
17
|
+
}
|
|
18
|
+
// Parse CLI flags
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
21
|
+
logger.info(`Usage: claude-remote-cli [options]
|
|
22
|
+
claude-remote-cli <command>
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
update Update to the latest version from npm
|
|
26
|
+
install Install as a background service (survives reboot)
|
|
27
|
+
uninstall Stop and remove the background service
|
|
28
|
+
status Show whether the service is running
|
|
29
|
+
worktree Manage git worktrees (wraps git worktree)
|
|
30
|
+
add [path] [-b branch] [--yolo] Create worktree and launch Claude
|
|
31
|
+
remove <path> Forward to git worktree remove
|
|
32
|
+
list Forward to git worktree list
|
|
33
|
+
browser Open an HTML file in the remote viewer
|
|
34
|
+
<path> Path to HTML file
|
|
35
|
+
pin Manage authentication PIN
|
|
36
|
+
reset Reset the PIN (interactive, requires TTY)
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--bg Shortcut: install and start as background service
|
|
40
|
+
--port <port> Override server port (default: 3456)
|
|
41
|
+
--host <host> Override bind address (default: 0.0.0.0)
|
|
42
|
+
--config <path> Path to config.json (default: ~/.config/claude-remote-cli/config.json)
|
|
43
|
+
--debug-log Enable SDK event debug logging to ~/.config/claude-remote-cli/debug/
|
|
44
|
+
--yolo With 'worktree add': pass --dangerously-skip-permissions to Claude
|
|
45
|
+
--version, -v Show version
|
|
46
|
+
--help, -h Show this help`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
51
|
+
console.log(pkg.version);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
function getArg(flag) {
|
|
55
|
+
const idx = args.indexOf(flag);
|
|
56
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
57
|
+
return undefined;
|
|
58
|
+
return args[idx + 1];
|
|
59
|
+
}
|
|
60
|
+
function resolveConfigPath() {
|
|
61
|
+
const explicit = getArg('--config');
|
|
62
|
+
if (explicit)
|
|
63
|
+
return explicit;
|
|
64
|
+
return path.join(service.CONFIG_DIR, 'config.json');
|
|
65
|
+
}
|
|
66
|
+
function runServiceCommand(fn) {
|
|
67
|
+
try {
|
|
68
|
+
fn();
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
logger.error(e.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
const command = args[0];
|
|
77
|
+
if (command === 'update') {
|
|
78
|
+
try {
|
|
79
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
80
|
+
logger.info(`Current version: ${pkg.version}`);
|
|
81
|
+
const configPath = resolveConfigPath();
|
|
82
|
+
let channel = 'stable';
|
|
83
|
+
if (fs.existsSync(configPath)) {
|
|
84
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
85
|
+
if (config.updateChannel === 'nightly' ||
|
|
86
|
+
config.updateChannel === 'stable') {
|
|
87
|
+
channel = config.updateChannel;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const tag = channel === 'nightly' ? 'nightly' : 'latest';
|
|
91
|
+
logger.info(`Updating claude-remote-cli from ${channel} channel...`);
|
|
92
|
+
await execFileAsync('npm', ['install', '-g', `claude-remote-cli@${tag}`]);
|
|
93
|
+
const updatedPkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
94
|
+
if (updatedPkg.version === pkg.version) {
|
|
95
|
+
logger.info(`Already on the latest version (${pkg.version}).`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
logger.info(`Updated to ${updatedPkg.version}.`);
|
|
99
|
+
if (service.isInstalled()) {
|
|
100
|
+
logger.info('Background service detected — restarting...');
|
|
101
|
+
service.uninstall();
|
|
102
|
+
service.install({
|
|
103
|
+
configPath: resolveConfigPath(),
|
|
104
|
+
port: getArg('--port') ?? String(DEFAULTS.port),
|
|
105
|
+
host: getArg('--host') ?? DEFAULTS.host,
|
|
106
|
+
});
|
|
107
|
+
logger.info('Service restarted.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
logger.error(`Update failed: ${e.message}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
if (command === 'worktree') {
|
|
118
|
+
const wtArgs = args.slice(1);
|
|
119
|
+
const subCommand = wtArgs[0];
|
|
120
|
+
if (!subCommand) {
|
|
121
|
+
logger.error('Usage: claude-remote-cli worktree <add|remove|list> [options]');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (subCommand !== 'add') {
|
|
125
|
+
try {
|
|
126
|
+
const result = await execFileAsync('git', ['worktree', ...wtArgs]);
|
|
127
|
+
if (result.stdout)
|
|
128
|
+
console.log(result.stdout.trimEnd());
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
logger.error(execErrorMessage(err, 'git worktree failed'));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
// Handle 'add' -- strip --yolo, determine path, forward to git, then launch claude
|
|
137
|
+
const hasYolo = wtArgs.includes('--yolo');
|
|
138
|
+
const gitWtArgs = wtArgs.filter(function (a) {
|
|
139
|
+
return a !== '--yolo';
|
|
140
|
+
});
|
|
141
|
+
const addSubArgs = gitWtArgs.slice(1);
|
|
142
|
+
let targetDir;
|
|
143
|
+
const bIdx = gitWtArgs.indexOf('-b');
|
|
144
|
+
const branchForDefault = bIdx !== -1 && bIdx + 1 < gitWtArgs.length
|
|
145
|
+
? gitWtArgs[bIdx + 1]
|
|
146
|
+
: undefined;
|
|
147
|
+
if (addSubArgs.length === 0 || addSubArgs[0].startsWith('-')) {
|
|
148
|
+
let repoRoot;
|
|
149
|
+
try {
|
|
150
|
+
const result = await execFileAsync('git', [
|
|
151
|
+
'rev-parse',
|
|
152
|
+
'--show-toplevel',
|
|
153
|
+
]);
|
|
154
|
+
repoRoot = result.stdout.trim();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
logger.error('Not inside a git repository.');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const dirName = branchForDefault
|
|
161
|
+
? branchForDefault.replace(/\//g, '-')
|
|
162
|
+
: 'worktree-' + Date.now().toString(36);
|
|
163
|
+
targetDir = path.join(repoRoot, '.worktrees', dirName);
|
|
164
|
+
gitWtArgs.splice(1, 0, targetDir);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
targetDir = path.resolve(addSubArgs[0]);
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const result = await execFileAsync('git', ['worktree', ...gitWtArgs]);
|
|
171
|
+
if (result.stdout)
|
|
172
|
+
console.log(result.stdout.trimEnd());
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
logger.error(execErrorMessage(err, 'git worktree add failed'));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
logger.info(`Worktree created at ${targetDir}`);
|
|
179
|
+
const claudeArgs = [];
|
|
180
|
+
if (hasYolo)
|
|
181
|
+
claudeArgs.push('--dangerously-skip-permissions');
|
|
182
|
+
logger.info(`Launching claude${hasYolo ? ' (yolo mode)' : ''} in ${targetDir}...`);
|
|
183
|
+
const child = spawn('claude', claudeArgs, {
|
|
184
|
+
cwd: targetDir,
|
|
185
|
+
stdio: 'inherit',
|
|
186
|
+
env: { ...process.env, CLAUDECODE: undefined },
|
|
187
|
+
});
|
|
188
|
+
child.on('exit', (code) => {
|
|
189
|
+
process.exit(code ?? 0);
|
|
190
|
+
});
|
|
191
|
+
// Block until child exits via the handler above
|
|
192
|
+
await new Promise(() => { });
|
|
193
|
+
}
|
|
194
|
+
if (command === 'pin') {
|
|
195
|
+
const subCommand = args[1];
|
|
196
|
+
if (subCommand !== 'reset') {
|
|
197
|
+
logger.error('Usage: claude-remote-cli pin reset');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
if (!process.stdin.isTTY) {
|
|
201
|
+
logger.error('PIN reset requires an interactive terminal.');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
const configPath = resolveConfigPath();
|
|
205
|
+
if (!fs.existsSync(configPath)) {
|
|
206
|
+
logger.error('No config file found. Run claude-remote-cli first to create one.');
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
const { loadConfig: loadCfg, saveConfig: saveCfg } = await import('../server/config.js');
|
|
210
|
+
const { hashPin, verifyPin } = await import('../server/auth.js');
|
|
211
|
+
const config = loadCfg(configPath);
|
|
212
|
+
const readline = await import('node:readline');
|
|
213
|
+
function prompt(query, hidden = false) {
|
|
214
|
+
return new Promise((resolve) => {
|
|
215
|
+
if (hidden) {
|
|
216
|
+
process.stdout.write(query);
|
|
217
|
+
const stdin = process.stdin;
|
|
218
|
+
const wasRaw = stdin.isRaw;
|
|
219
|
+
if (stdin.setRawMode)
|
|
220
|
+
stdin.setRawMode(true);
|
|
221
|
+
let value = '';
|
|
222
|
+
const onData = (ch) => {
|
|
223
|
+
const c = ch.toString('utf8');
|
|
224
|
+
if (c === '\n' || c === '\r') {
|
|
225
|
+
if (stdin.setRawMode)
|
|
226
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
227
|
+
stdin.removeListener('data', onData);
|
|
228
|
+
process.stdout.write('\n');
|
|
229
|
+
resolve(value);
|
|
230
|
+
}
|
|
231
|
+
else if (c === '\u007f' || c === '\b') {
|
|
232
|
+
if (value.length > 0) {
|
|
233
|
+
value = value.slice(0, -1);
|
|
234
|
+
process.stdout.write('\b \b');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (c >= ' ') {
|
|
238
|
+
value += c;
|
|
239
|
+
process.stdout.write('*');
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
stdin.on('data', onData);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const rl = readline.createInterface({
|
|
246
|
+
input: process.stdin,
|
|
247
|
+
output: process.stdout,
|
|
248
|
+
});
|
|
249
|
+
rl.question(query, (answer) => {
|
|
250
|
+
rl.close();
|
|
251
|
+
resolve(answer);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// If PIN exists, optionally verify current PIN.
|
|
257
|
+
// Skipping is intentional: local shell access is proof of ownership
|
|
258
|
+
// (the user could edit the config file directly to delete pinHash).
|
|
259
|
+
if (config.pinHash) {
|
|
260
|
+
const current = await prompt('Current PIN (press Enter to skip): ', true);
|
|
261
|
+
if (current) {
|
|
262
|
+
const valid = await verifyPin(current, config.pinHash);
|
|
263
|
+
if (!valid) {
|
|
264
|
+
logger.error('Current PIN is incorrect.');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const newPin = await prompt('New PIN: ', true);
|
|
270
|
+
if (!newPin || newPin.length < 4) {
|
|
271
|
+
logger.error('PIN must be at least 4 characters.');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
const confirmPin = await prompt('Confirm new PIN: ', true);
|
|
275
|
+
if (newPin !== confirmPin) {
|
|
276
|
+
logger.error('PINs do not match.');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
config.pinHash = await hashPin(newPin);
|
|
280
|
+
saveCfg(configPath, config);
|
|
281
|
+
logger.info('PIN updated successfully. All existing sessions will need to re-authenticate.');
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
if (command === 'install' ||
|
|
285
|
+
command === 'uninstall' ||
|
|
286
|
+
command === 'status' ||
|
|
287
|
+
args.includes('--bg')) {
|
|
288
|
+
if (command === 'uninstall') {
|
|
289
|
+
runServiceCommand(() => {
|
|
290
|
+
service.uninstall();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else if (command === 'status') {
|
|
294
|
+
runServiceCommand(() => {
|
|
295
|
+
const st = service.status();
|
|
296
|
+
if (!st.installed) {
|
|
297
|
+
logger.info('Service is not installed.');
|
|
298
|
+
}
|
|
299
|
+
else if (st.running) {
|
|
300
|
+
logger.info('Service is installed and running.');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
logger.info('Service is installed but not running.');
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
runServiceCommand(() => {
|
|
309
|
+
service.install({
|
|
310
|
+
configPath: resolveConfigPath(),
|
|
311
|
+
port: getArg('--port') ?? String(DEFAULTS.port),
|
|
312
|
+
host: getArg('--host') ?? DEFAULTS.host,
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (command === 'browser') {
|
|
318
|
+
const browserArgs = args.slice(1);
|
|
319
|
+
if (browserArgs.includes('--help') ||
|
|
320
|
+
browserArgs.includes('-h') ||
|
|
321
|
+
browserArgs.length === 0) {
|
|
322
|
+
logger.error(`Usage: claude-remote-cli browser <path>
|
|
323
|
+
|
|
324
|
+
Opens an HTML file in the remote browser viewer tab.
|
|
325
|
+
|
|
326
|
+
Arguments:
|
|
327
|
+
<path> Path to HTML file (absolute or relative)
|
|
328
|
+
|
|
329
|
+
Environment:
|
|
330
|
+
CLAUDE_REMOTE_PORT Server port (default: 3456)
|
|
331
|
+
CLAUDE_REMOTE_BROWSER_TOKEN Auth token for browser tab API`);
|
|
332
|
+
process.exit(browserArgs.includes('--help') || browserArgs.includes('-h') ? 0 : 1);
|
|
333
|
+
}
|
|
334
|
+
const filePath = path.resolve(browserArgs[0]);
|
|
335
|
+
if (!fs.existsSync(filePath)) {
|
|
336
|
+
logger.error(`Error: file not found: ${filePath}`);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
const port = process.env['CLAUDE_REMOTE_PORT'] ?? String(DEFAULTS.port);
|
|
340
|
+
const token = process.env['CLAUDE_REMOTE_BROWSER_TOKEN'] ?? '';
|
|
341
|
+
if (!token) {
|
|
342
|
+
logger.error('Error: CLAUDE_REMOTE_BROWSER_TOKEN not set. Are you running inside a claude-remote-cli session?');
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const res = await fetch(`http://127.0.0.1:${port}/browser-tabs`, {
|
|
347
|
+
method: 'POST',
|
|
348
|
+
headers: {
|
|
349
|
+
'Content-Type': 'application/json',
|
|
350
|
+
Authorization: `Bearer ${token}`,
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify({ path: filePath }),
|
|
353
|
+
});
|
|
354
|
+
if (!res.ok) {
|
|
355
|
+
const body = await res.text();
|
|
356
|
+
logger.error(`Error: server returned ${res.status}: ${body}`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const data = (await res.json());
|
|
360
|
+
if (data.refreshed) {
|
|
361
|
+
logger.info(`Refreshed: ${filePath}`);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
logger.info(`Opened: ${filePath}`);
|
|
365
|
+
}
|
|
366
|
+
process.exit(0);
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
370
|
+
logger.error(`Error: could not connect to server on port ${port}: ${msg}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const configPath = resolveConfigPath();
|
|
375
|
+
const configDir = path.dirname(configPath);
|
|
376
|
+
// Ensure config directory exists
|
|
377
|
+
if (!fs.existsSync(configDir)) {
|
|
378
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
379
|
+
}
|
|
380
|
+
// Pass config path and CLI overrides to the server
|
|
381
|
+
process.env['CLAUDE_REMOTE_CONFIG'] = configPath;
|
|
382
|
+
const portArg = getArg('--port');
|
|
383
|
+
if (portArg !== undefined)
|
|
384
|
+
process.env['CLAUDE_REMOTE_PORT'] = portArg;
|
|
385
|
+
const hostArg = getArg('--host');
|
|
386
|
+
if (hostArg !== undefined)
|
|
387
|
+
process.env['CLAUDE_REMOTE_HOST'] = hostArg;
|
|
388
|
+
if (args.includes('--debug-log'))
|
|
389
|
+
process.env['CLAUDE_REMOTE_DEBUG_LOG'] = '1';
|
|
390
|
+
await import('../server/index.js');
|