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,231 @@
|
|
|
1
|
+
import { test, before, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import { createBranchLinkerRouter, invalidateBranchLinkerCache, } from '../server/branch-linker.js';
|
|
8
|
+
import { saveConfig, DEFAULTS } from '../server/config.js';
|
|
9
|
+
let tmpDir;
|
|
10
|
+
let configPath;
|
|
11
|
+
let server;
|
|
12
|
+
let baseUrl;
|
|
13
|
+
const WORKSPACE_PATH_A = '/fake/workspace/repo-a';
|
|
14
|
+
const WORKSPACE_PATH_B = '/fake/workspace/repo-b';
|
|
15
|
+
/**
|
|
16
|
+
* Creates a mock execAsync that returns configured branch lists per cwd.
|
|
17
|
+
* - `git branch -a` → returns newline-joined branch names or throws
|
|
18
|
+
*/
|
|
19
|
+
function makeMockExec(opts) {
|
|
20
|
+
return async (cmd, args, options) => {
|
|
21
|
+
const command = cmd;
|
|
22
|
+
const argv = args;
|
|
23
|
+
const cwd = options.cwd ?? '';
|
|
24
|
+
if (command === 'git' && argv[0] === 'branch') {
|
|
25
|
+
if (opts.errorByPath?.[cwd])
|
|
26
|
+
throw opts.errorByPath[cwd];
|
|
27
|
+
const branches = opts.branchesByPath?.[cwd] ?? [];
|
|
28
|
+
return { stdout: branches.join('\n') + '\n', stderr: '' };
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Unexpected exec call: ${command} ${argv.join(' ')}`);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function startServer(execAsyncFn, getActiveBranchNames) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const app = express();
|
|
36
|
+
app.use(express.json());
|
|
37
|
+
const deps = {
|
|
38
|
+
configPath,
|
|
39
|
+
execAsync: execAsyncFn,
|
|
40
|
+
...(getActiveBranchNames ? { getActiveBranchNames } : {}),
|
|
41
|
+
};
|
|
42
|
+
app.use('/branch-linker', createBranchLinkerRouter(deps));
|
|
43
|
+
server = app.listen(0, '127.0.0.1', () => {
|
|
44
|
+
const addr = server.address();
|
|
45
|
+
if (typeof addr === 'object' && addr) {
|
|
46
|
+
baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
47
|
+
}
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function stopServer() {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
if (server)
|
|
55
|
+
server.close(() => resolve());
|
|
56
|
+
else
|
|
57
|
+
resolve();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function getLinks() {
|
|
61
|
+
const res = await fetch(`${baseUrl}/branch-linker/links`);
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
before(() => {
|
|
65
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'branch-linker-test-'));
|
|
66
|
+
configPath = path.join(tmpDir, 'config.json');
|
|
67
|
+
// Clear any module-level cache before test suite runs
|
|
68
|
+
invalidateBranchLinkerCache();
|
|
69
|
+
});
|
|
70
|
+
after(async () => {
|
|
71
|
+
await stopServer();
|
|
72
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
73
|
+
});
|
|
74
|
+
test('extracts Jira ticket IDs from branch names', async () => {
|
|
75
|
+
await stopServer();
|
|
76
|
+
invalidateBranchLinkerCache();
|
|
77
|
+
saveConfig(configPath, {
|
|
78
|
+
...DEFAULTS,
|
|
79
|
+
workspaces: [WORKSPACE_PATH_A],
|
|
80
|
+
});
|
|
81
|
+
const exec = makeMockExec({
|
|
82
|
+
branchesByPath: {
|
|
83
|
+
[WORKSPACE_PATH_A]: ['dy/fix/ACME-123-auth', 'main'],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
await startServer(exec);
|
|
87
|
+
const data = await getLinks();
|
|
88
|
+
assert.ok('ACME-123' in data, 'Should extract ACME-123 from branch name');
|
|
89
|
+
const links = data['ACME-123'];
|
|
90
|
+
assert.equal(links.length, 1);
|
|
91
|
+
assert.equal(links[0].branchName, 'dy/fix/ACME-123-auth');
|
|
92
|
+
assert.equal(links[0].repoPath, WORKSPACE_PATH_A);
|
|
93
|
+
assert.equal(links[0].repoName, 'repo-a');
|
|
94
|
+
});
|
|
95
|
+
test('extracts GH issue IDs from gh-N branches', async () => {
|
|
96
|
+
await stopServer();
|
|
97
|
+
invalidateBranchLinkerCache();
|
|
98
|
+
saveConfig(configPath, {
|
|
99
|
+
...DEFAULTS,
|
|
100
|
+
workspaces: [WORKSPACE_PATH_A],
|
|
101
|
+
});
|
|
102
|
+
const exec = makeMockExec({
|
|
103
|
+
branchesByPath: {
|
|
104
|
+
// Use a branch that only the GH regex matches (no embedded uppercase-only letters)
|
|
105
|
+
// to get a single clean GH-42 link. The Jira regex (/[A-Z]{2,}-\d+/gi) would also
|
|
106
|
+
// match 'gh-42' since the flag is case-insensitive, so we use a prefix that isolates
|
|
107
|
+
// the GH regex match by starting with 'gh-' at the very start of the branch name.
|
|
108
|
+
[WORKSPACE_PATH_A]: ['gh-42-login-fix'],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
await startServer(exec);
|
|
112
|
+
const data = await getLinks();
|
|
113
|
+
assert.ok('GH-42' in data, 'Should extract GH-42 from branch name');
|
|
114
|
+
const links = data['GH-42'];
|
|
115
|
+
// Only the GH regex matches this branch — the Jira regex explicitly excludes 'GH'
|
|
116
|
+
// to avoid double-matching. Verify all links point to the correct branch and repo.
|
|
117
|
+
assert.ok(links.length >= 1, 'Should have at least one GH-42 link');
|
|
118
|
+
assert.ok(links.every((l) => l.branchName === 'gh-42-login-fix'), 'All links should reference the correct branch');
|
|
119
|
+
assert.ok(links.every((l) => l.repoPath === WORKSPACE_PATH_A), 'All links should reference the correct repo');
|
|
120
|
+
});
|
|
121
|
+
test('same ticket in two repos yields array of 2 BranchLinks', async () => {
|
|
122
|
+
await stopServer();
|
|
123
|
+
invalidateBranchLinkerCache();
|
|
124
|
+
saveConfig(configPath, {
|
|
125
|
+
...DEFAULTS,
|
|
126
|
+
workspaces: [WORKSPACE_PATH_A, WORKSPACE_PATH_B],
|
|
127
|
+
});
|
|
128
|
+
const exec = makeMockExec({
|
|
129
|
+
branchesByPath: {
|
|
130
|
+
[WORKSPACE_PATH_A]: ['feature/PROJ-99-payment'],
|
|
131
|
+
[WORKSPACE_PATH_B]: ['bugfix/PROJ-99-payment-fix'],
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
await startServer(exec);
|
|
135
|
+
const data = await getLinks();
|
|
136
|
+
assert.ok('PROJ-99' in data, 'Should have PROJ-99 key');
|
|
137
|
+
const links = data['PROJ-99'];
|
|
138
|
+
assert.equal(links.length, 2, 'Should have 2 BranchLinks for the same ticket across 2 repos');
|
|
139
|
+
const repoPaths = links.map((l) => l.repoPath).sort();
|
|
140
|
+
assert.deepEqual(repoPaths, [WORKSPACE_PATH_A, WORKSPACE_PATH_B].sort());
|
|
141
|
+
});
|
|
142
|
+
test('ignores branches without ticket IDs', async () => {
|
|
143
|
+
await stopServer();
|
|
144
|
+
invalidateBranchLinkerCache();
|
|
145
|
+
saveConfig(configPath, {
|
|
146
|
+
...DEFAULTS,
|
|
147
|
+
workspaces: [WORKSPACE_PATH_A],
|
|
148
|
+
});
|
|
149
|
+
const exec = makeMockExec({
|
|
150
|
+
branchesByPath: {
|
|
151
|
+
[WORKSPACE_PATH_A]: ['main', 'develop', 'chore/cleanup', 'feature/new-ui'],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
await startServer(exec);
|
|
155
|
+
const data = await getLinks();
|
|
156
|
+
assert.equal(Object.keys(data).length, 0, 'Plain branches should produce no ticket links');
|
|
157
|
+
});
|
|
158
|
+
test('hasActiveSession true when branch is in active set', async () => {
|
|
159
|
+
await stopServer();
|
|
160
|
+
invalidateBranchLinkerCache();
|
|
161
|
+
saveConfig(configPath, {
|
|
162
|
+
...DEFAULTS,
|
|
163
|
+
workspaces: [WORKSPACE_PATH_A],
|
|
164
|
+
});
|
|
165
|
+
const activeBranch = 'feature/ACTIVE-1-work';
|
|
166
|
+
const exec = makeMockExec({
|
|
167
|
+
branchesByPath: {
|
|
168
|
+
[WORKSPACE_PATH_A]: [activeBranch, 'feature/INACTIVE-2-other'],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const getActiveBranchNames = () => {
|
|
172
|
+
return new Map([[WORKSPACE_PATH_A, new Set([activeBranch])]]);
|
|
173
|
+
};
|
|
174
|
+
await startServer(exec, getActiveBranchNames);
|
|
175
|
+
const data = await getLinks();
|
|
176
|
+
const activeLinks = data['ACTIVE-1'];
|
|
177
|
+
assert.ok(activeLinks, 'Should have ACTIVE-1 ticket');
|
|
178
|
+
assert.equal(activeLinks.length, 1);
|
|
179
|
+
assert.equal(activeLinks[0].hasActiveSession, true, 'Active branch should have hasActiveSession true');
|
|
180
|
+
const inactiveLinks = data['INACTIVE-2'];
|
|
181
|
+
assert.ok(inactiveLinks, 'Should have INACTIVE-2 ticket');
|
|
182
|
+
assert.equal(inactiveLinks[0].hasActiveSession, false, 'Inactive branch should have hasActiveSession false');
|
|
183
|
+
});
|
|
184
|
+
test('invalidateBranchLinkerCache forces fresh scan', async () => {
|
|
185
|
+
await stopServer();
|
|
186
|
+
invalidateBranchLinkerCache();
|
|
187
|
+
saveConfig(configPath, {
|
|
188
|
+
...DEFAULTS,
|
|
189
|
+
workspaces: [WORKSPACE_PATH_A],
|
|
190
|
+
});
|
|
191
|
+
let gitCallCount = 0;
|
|
192
|
+
const baseExec = makeMockExec({
|
|
193
|
+
branchesByPath: {
|
|
194
|
+
[WORKSPACE_PATH_A]: ['feature/SCAN-1-fresh'],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const countingExec = async (...args) => {
|
|
198
|
+
const [cmd] = args;
|
|
199
|
+
if (cmd === 'git')
|
|
200
|
+
gitCallCount++;
|
|
201
|
+
return baseExec(...args);
|
|
202
|
+
};
|
|
203
|
+
await startServer(countingExec);
|
|
204
|
+
// First request — populates module-level cache
|
|
205
|
+
const first = await getLinks();
|
|
206
|
+
assert.ok('SCAN-1' in first, 'Should have SCAN-1 after first request');
|
|
207
|
+
assert.equal(gitCallCount, 1, 'git should be called once on first request');
|
|
208
|
+
// Second request — served from cache
|
|
209
|
+
const second = await getLinks();
|
|
210
|
+
assert.ok('SCAN-1' in second);
|
|
211
|
+
assert.equal(gitCallCount, 1, 'git should not be called again within TTL');
|
|
212
|
+
// Invalidate cache
|
|
213
|
+
invalidateBranchLinkerCache();
|
|
214
|
+
// Third request — cache is cleared, should fetch fresh
|
|
215
|
+
const third = await getLinks();
|
|
216
|
+
assert.ok('SCAN-1' in third);
|
|
217
|
+
assert.equal(gitCallCount, 2, 'git should be called again after cache invalidation');
|
|
218
|
+
});
|
|
219
|
+
test('returns empty object when no workspaces', async () => {
|
|
220
|
+
await stopServer();
|
|
221
|
+
invalidateBranchLinkerCache();
|
|
222
|
+
saveConfig(configPath, {
|
|
223
|
+
...DEFAULTS,
|
|
224
|
+
workspaces: [],
|
|
225
|
+
});
|
|
226
|
+
// execAsync should never be called here
|
|
227
|
+
const exec = makeMockExec({});
|
|
228
|
+
await startServer(exec);
|
|
229
|
+
const data = await getLinks();
|
|
230
|
+
assert.equal(Object.keys(data).length, 0, 'Should return empty object when no workspaces configured');
|
|
231
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { MOUNTAIN_NAMES } from '../server/types.js';
|
|
4
|
+
import { branchToDisplayName } from '../server/git.js';
|
|
5
|
+
describe('MOUNTAIN_NAMES', () => {
|
|
6
|
+
test('contains 30 mountain names', () => {
|
|
7
|
+
assert.equal(MOUNTAIN_NAMES.length, 30);
|
|
8
|
+
});
|
|
9
|
+
test('all names are lowercase kebab-case', () => {
|
|
10
|
+
for (const name of MOUNTAIN_NAMES) {
|
|
11
|
+
assert.match(name, /^[a-z][a-z0-9-]*$/, `Mountain name "${name}" is not kebab-case`);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
test('no duplicate names', () => {
|
|
15
|
+
const unique = new Set(MOUNTAIN_NAMES);
|
|
16
|
+
assert.equal(unique.size, MOUNTAIN_NAMES.length);
|
|
17
|
+
});
|
|
18
|
+
test('cycling wraps around at array length', () => {
|
|
19
|
+
let idx = 28;
|
|
20
|
+
const name1 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
|
|
21
|
+
idx++;
|
|
22
|
+
const name2 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
|
|
23
|
+
idx++;
|
|
24
|
+
const name3 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
|
|
25
|
+
assert.equal(name1, 'whitney');
|
|
26
|
+
assert.equal(name2, 'hood');
|
|
27
|
+
assert.equal(name3, 'everest'); // wraps back to start
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('branchToDisplayName', () => {
|
|
31
|
+
test('converts kebab-case to sentence case', () => {
|
|
32
|
+
assert.equal(branchToDisplayName('fix-mobile-scroll-bug'), 'Fix mobile scroll bug');
|
|
33
|
+
});
|
|
34
|
+
test('strips common branch prefixes', () => {
|
|
35
|
+
assert.equal(branchToDisplayName('feature/add-auth'), 'Add auth');
|
|
36
|
+
assert.equal(branchToDisplayName('fix/api-timeout'), 'Api timeout');
|
|
37
|
+
assert.equal(branchToDisplayName('chore/update-deps'), 'Update deps');
|
|
38
|
+
});
|
|
39
|
+
test('handles simple names', () => {
|
|
40
|
+
assert.equal(branchToDisplayName('lhotse'), 'Lhotse');
|
|
41
|
+
});
|
|
42
|
+
test('handles underscores', () => {
|
|
43
|
+
assert.equal(branchToDisplayName('fix_the_thing'), 'Fix the thing');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { detectClipboardTool, setClipboardImage } from '../server/clipboard.js';
|
|
4
|
+
describe('clipboard', () => {
|
|
5
|
+
it('detectClipboardTool returns a string or null', () => {
|
|
6
|
+
const result = detectClipboardTool();
|
|
7
|
+
assert.ok(result === null || typeof result === 'string');
|
|
8
|
+
});
|
|
9
|
+
it('setClipboardImage rejects unsupported mime types', async () => {
|
|
10
|
+
await assert.rejects(() => setClipboardImage('/tmp/test.txt', 'text/plain'), /Unsupported/);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { test, before, after, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import { DEFAULTS, loadConfig, saveConfig, ensureMetaDir, readMeta, writeMeta, deleteMeta, resolveSessionSettings, deleteWorkspaceSettingKeys } from '../server/config.js';
|
|
7
|
+
let tmpDir;
|
|
8
|
+
before(() => {
|
|
9
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-remote-cli-config-test-'));
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
for (const entry of fs.readdirSync(tmpDir, { withFileTypes: true })) {
|
|
13
|
+
const fullPath = path.join(tmpDir, entry.name);
|
|
14
|
+
if (entry.isDirectory()) {
|
|
15
|
+
fs.rmSync(fullPath, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
fs.unlinkSync(fullPath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
after(() => {
|
|
23
|
+
fs.rmdirSync(tmpDir);
|
|
24
|
+
});
|
|
25
|
+
test('loadConfig loads a JSON config file', () => {
|
|
26
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
27
|
+
const data = { port: 4000, host: '127.0.0.1' };
|
|
28
|
+
fs.writeFileSync(configPath, JSON.stringify(data), 'utf8');
|
|
29
|
+
const config = loadConfig(configPath);
|
|
30
|
+
assert.equal(config.port, 4000);
|
|
31
|
+
assert.equal(config.host, '127.0.0.1');
|
|
32
|
+
});
|
|
33
|
+
test('loadConfig merges with defaults for missing fields', () => {
|
|
34
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
35
|
+
fs.writeFileSync(configPath, JSON.stringify({ port: 9000 }), 'utf8');
|
|
36
|
+
const config = loadConfig(configPath);
|
|
37
|
+
assert.equal(config.port, 9000);
|
|
38
|
+
assert.equal(config.host, DEFAULTS.host);
|
|
39
|
+
assert.equal(config.cookieTTL, DEFAULTS.cookieTTL);
|
|
40
|
+
assert.deepEqual(config.repos, DEFAULTS.repos);
|
|
41
|
+
assert.equal(config.claudeCommand, DEFAULTS.claudeCommand);
|
|
42
|
+
assert.deepEqual(config.claudeArgs, DEFAULTS.claudeArgs);
|
|
43
|
+
assert.equal(config.defaultAgent, DEFAULTS.defaultAgent);
|
|
44
|
+
});
|
|
45
|
+
test('loadConfig throws if config file not found', () => {
|
|
46
|
+
const configPath = path.join(tmpDir, 'nonexistent.json');
|
|
47
|
+
assert.throws(() => loadConfig(configPath), /Config file not found/);
|
|
48
|
+
});
|
|
49
|
+
test('saveConfig writes JSON with 2-space indent', () => {
|
|
50
|
+
const configPath = path.join(tmpDir, 'output.json');
|
|
51
|
+
const config = { port: 3456, host: '0.0.0.0' };
|
|
52
|
+
saveConfig(configPath, config);
|
|
53
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
54
|
+
assert.equal(raw, JSON.stringify(config, null, 2));
|
|
55
|
+
});
|
|
56
|
+
test('DEFAULTS has expected keys and values', () => {
|
|
57
|
+
assert.equal(DEFAULTS.host, '0.0.0.0');
|
|
58
|
+
assert.equal(DEFAULTS.port, 3456);
|
|
59
|
+
assert.equal(DEFAULTS.cookieTTL, '24h');
|
|
60
|
+
assert.deepEqual(DEFAULTS.repos, []);
|
|
61
|
+
assert.equal(DEFAULTS.claudeCommand, 'claude');
|
|
62
|
+
assert.deepEqual(DEFAULTS.claudeArgs, []);
|
|
63
|
+
assert.equal(DEFAULTS.defaultAgent, 'claude');
|
|
64
|
+
assert.equal(DEFAULTS.defaultContinue, true);
|
|
65
|
+
assert.equal(DEFAULTS.defaultYolo, false);
|
|
66
|
+
assert.equal(DEFAULTS.launchInTmux, false);
|
|
67
|
+
});
|
|
68
|
+
test('loadConfig returns correct defaults for defaultContinue, defaultYolo, and launchInTmux', () => {
|
|
69
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
70
|
+
fs.writeFileSync(configPath, JSON.stringify({ port: 3456 }), 'utf8');
|
|
71
|
+
const config = loadConfig(configPath);
|
|
72
|
+
assert.equal(config.defaultContinue, true);
|
|
73
|
+
assert.equal(config.defaultYolo, false);
|
|
74
|
+
assert.equal(config.launchInTmux, false);
|
|
75
|
+
});
|
|
76
|
+
test('ensureMetaDir creates worktree-meta directory', () => {
|
|
77
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
78
|
+
ensureMetaDir(configPath);
|
|
79
|
+
const metaPath = path.join(tmpDir, 'worktree-meta');
|
|
80
|
+
assert.ok(fs.existsSync(metaPath));
|
|
81
|
+
});
|
|
82
|
+
test('writeMeta creates and readMeta reads metadata file', () => {
|
|
83
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
84
|
+
const meta = { worktreePath: '/tmp/test-worktree', displayName: 'My Feature', lastActivity: '2026-02-22T00:00:00.000Z' };
|
|
85
|
+
writeMeta(configPath, meta);
|
|
86
|
+
const read = readMeta(configPath, '/tmp/test-worktree');
|
|
87
|
+
assert.deepEqual(read, meta);
|
|
88
|
+
});
|
|
89
|
+
test('readMeta returns null for non-existent metadata', () => {
|
|
90
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
91
|
+
const result = readMeta(configPath, '/no/such/worktree');
|
|
92
|
+
assert.equal(result, null);
|
|
93
|
+
});
|
|
94
|
+
test('writeMeta overwrites existing metadata', () => {
|
|
95
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
96
|
+
writeMeta(configPath, { worktreePath: '/tmp/wt', displayName: 'Old Name', lastActivity: '2026-01-01T00:00:00.000Z' });
|
|
97
|
+
writeMeta(configPath, { worktreePath: '/tmp/wt', displayName: 'New Name', lastActivity: '2026-02-22T00:00:00.000Z' });
|
|
98
|
+
const read = readMeta(configPath, '/tmp/wt');
|
|
99
|
+
assert.equal(read.displayName, 'New Name');
|
|
100
|
+
assert.equal(read.lastActivity, '2026-02-22T00:00:00.000Z');
|
|
101
|
+
});
|
|
102
|
+
test('deleteMeta removes metadata file', () => {
|
|
103
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
104
|
+
writeMeta(configPath, { worktreePath: '/tmp/del-test', displayName: 'To Delete', lastActivity: '2026-02-22T00:00:00.000Z' });
|
|
105
|
+
assert.ok(readMeta(configPath, '/tmp/del-test'));
|
|
106
|
+
deleteMeta(configPath, '/tmp/del-test');
|
|
107
|
+
assert.equal(readMeta(configPath, '/tmp/del-test'), null);
|
|
108
|
+
});
|
|
109
|
+
test('deleteMeta is a no-op for non-existent metadata', () => {
|
|
110
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
111
|
+
assert.doesNotThrow(() => deleteMeta(configPath, '/no/such/path'));
|
|
112
|
+
});
|
|
113
|
+
test('resolveSessionSettings returns global defaults when no workspace or overrides', () => {
|
|
114
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
115
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
116
|
+
defaultAgent: 'claude',
|
|
117
|
+
defaultContinue: true,
|
|
118
|
+
defaultYolo: false,
|
|
119
|
+
launchInTmux: false,
|
|
120
|
+
claudeArgs: [],
|
|
121
|
+
}), 'utf8');
|
|
122
|
+
const config = loadConfig(configPath);
|
|
123
|
+
const result = resolveSessionSettings(config, '/some/repo', {});
|
|
124
|
+
assert.equal(result.agent, 'claude');
|
|
125
|
+
assert.equal(result.yolo, false);
|
|
126
|
+
assert.equal(result.continue, true);
|
|
127
|
+
assert.equal(result.useTmux, false);
|
|
128
|
+
assert.deepEqual(result.claudeArgs, []);
|
|
129
|
+
});
|
|
130
|
+
test('resolveSessionSettings applies workspace overrides over globals', () => {
|
|
131
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
132
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
133
|
+
defaultAgent: 'claude',
|
|
134
|
+
defaultYolo: false,
|
|
135
|
+
defaultContinue: true,
|
|
136
|
+
launchInTmux: false,
|
|
137
|
+
claudeArgs: [],
|
|
138
|
+
workspaceSettings: {
|
|
139
|
+
'/my/repo': { defaultYolo: true, defaultAgent: 'codex' },
|
|
140
|
+
},
|
|
141
|
+
}), 'utf8');
|
|
142
|
+
const config = loadConfig(configPath);
|
|
143
|
+
const result = resolveSessionSettings(config, '/my/repo', {});
|
|
144
|
+
assert.equal(result.agent, 'codex');
|
|
145
|
+
assert.equal(result.yolo, true);
|
|
146
|
+
assert.equal(result.continue, true);
|
|
147
|
+
});
|
|
148
|
+
test('resolveSessionSettings explicit overrides beat workspace settings', () => {
|
|
149
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
150
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
151
|
+
defaultAgent: 'claude',
|
|
152
|
+
defaultYolo: true,
|
|
153
|
+
defaultContinue: true,
|
|
154
|
+
launchInTmux: false,
|
|
155
|
+
claudeArgs: [],
|
|
156
|
+
workspaceSettings: {
|
|
157
|
+
'/my/repo': { defaultYolo: true },
|
|
158
|
+
},
|
|
159
|
+
}), 'utf8');
|
|
160
|
+
const config = loadConfig(configPath);
|
|
161
|
+
const result = resolveSessionSettings(config, '/my/repo', { yolo: false });
|
|
162
|
+
assert.equal(result.yolo, false);
|
|
163
|
+
});
|
|
164
|
+
test('resolveSessionSettings uses override claudeArgs, not global', () => {
|
|
165
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
166
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
167
|
+
defaultAgent: 'claude',
|
|
168
|
+
defaultYolo: false,
|
|
169
|
+
defaultContinue: true,
|
|
170
|
+
launchInTmux: false,
|
|
171
|
+
claudeArgs: ['--global-arg'],
|
|
172
|
+
}), 'utf8');
|
|
173
|
+
const config = loadConfig(configPath);
|
|
174
|
+
const result = resolveSessionSettings(config, '/some/repo', { claudeArgs: ['--custom'] });
|
|
175
|
+
assert.deepEqual(result.claudeArgs, ['--custom']);
|
|
176
|
+
});
|
|
177
|
+
test('resolveSessionSettings falls through to globals when no workspace exists', () => {
|
|
178
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
179
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
180
|
+
defaultAgent: 'codex',
|
|
181
|
+
defaultYolo: true,
|
|
182
|
+
defaultContinue: false,
|
|
183
|
+
launchInTmux: true,
|
|
184
|
+
claudeArgs: ['--verbose'],
|
|
185
|
+
}), 'utf8');
|
|
186
|
+
const config = loadConfig(configPath);
|
|
187
|
+
const result = resolveSessionSettings(config, '/nonexistent/repo', {});
|
|
188
|
+
assert.equal(result.agent, 'codex');
|
|
189
|
+
assert.equal(result.yolo, true);
|
|
190
|
+
assert.equal(result.continue, false);
|
|
191
|
+
assert.equal(result.useTmux, true);
|
|
192
|
+
assert.deepEqual(result.claudeArgs, ['--verbose']);
|
|
193
|
+
});
|
|
194
|
+
test('deleteWorkspaceSettingKeys removes specified keys', () => {
|
|
195
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
196
|
+
const config = {
|
|
197
|
+
...DEFAULTS,
|
|
198
|
+
workspaceSettings: {
|
|
199
|
+
'/my/repo': { defaultYolo: true, defaultAgent: 'codex', branchPrefix: 'dy/' },
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
203
|
+
deleteWorkspaceSettingKeys(configPath, config, '/my/repo', ['defaultYolo', 'defaultAgent']);
|
|
204
|
+
assert.equal(config.workspaceSettings['/my/repo'].defaultYolo, undefined);
|
|
205
|
+
assert.equal(config.workspaceSettings['/my/repo'].defaultAgent, undefined);
|
|
206
|
+
assert.equal(config.workspaceSettings['/my/repo'].branchPrefix, 'dy/');
|
|
207
|
+
});
|
|
208
|
+
test('deleteWorkspaceSettingKeys removes entire workspace entry when empty', () => {
|
|
209
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
210
|
+
const config = {
|
|
211
|
+
...DEFAULTS,
|
|
212
|
+
workspaceSettings: {
|
|
213
|
+
'/my/repo': { defaultYolo: true },
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
217
|
+
deleteWorkspaceSettingKeys(configPath, config, '/my/repo', ['defaultYolo']);
|
|
218
|
+
assert.equal(config.workspaceSettings['/my/repo'], undefined);
|
|
219
|
+
});
|
|
220
|
+
test('deleteWorkspaceSettingKeys is no-op for nonexistent workspace', () => {
|
|
221
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
222
|
+
const config = { ...DEFAULTS };
|
|
223
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
224
|
+
assert.doesNotThrow(() => deleteWorkspaceSettingKeys(configPath, config, '/no/such/repo', ['defaultYolo']));
|
|
225
|
+
});
|
|
226
|
+
test('workspaceGroups with valid paths loads cleanly', () => {
|
|
227
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
228
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
229
|
+
workspaces: ['/a/repo', '/b/repo'],
|
|
230
|
+
workspaceGroups: {
|
|
231
|
+
'Group A': ['/a/repo'],
|
|
232
|
+
'Group B': ['/b/repo'],
|
|
233
|
+
},
|
|
234
|
+
}), 'utf8');
|
|
235
|
+
const config = loadConfig(configPath);
|
|
236
|
+
assert.deepEqual(config.workspaceGroups['Group A'], ['/a/repo']);
|
|
237
|
+
assert.deepEqual(config.workspaceGroups['Group B'], ['/b/repo']);
|
|
238
|
+
});
|
|
239
|
+
test('workspaceGroups with invalid path filters it out', () => {
|
|
240
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
241
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
242
|
+
workspaces: ['/valid/repo'],
|
|
243
|
+
workspaceGroups: {
|
|
244
|
+
'My Group': ['/valid/repo', '/not/in/workspaces'],
|
|
245
|
+
},
|
|
246
|
+
}), 'utf8');
|
|
247
|
+
const config = loadConfig(configPath);
|
|
248
|
+
assert.deepEqual(config.workspaceGroups['My Group'], ['/valid/repo']);
|
|
249
|
+
});
|
|
250
|
+
test('workspaceGroups with duplicate path keeps first-group winner', () => {
|
|
251
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
252
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
253
|
+
workspaces: ['/shared/repo'],
|
|
254
|
+
workspaceGroups: {
|
|
255
|
+
'First': ['/shared/repo'],
|
|
256
|
+
'Second': ['/shared/repo'],
|
|
257
|
+
},
|
|
258
|
+
}), 'utf8');
|
|
259
|
+
const config = loadConfig(configPath);
|
|
260
|
+
assert.deepEqual(config.workspaceGroups['First'], ['/shared/repo']);
|
|
261
|
+
assert.equal(config.workspaceGroups['Second'], undefined);
|
|
262
|
+
});
|
|
263
|
+
test('workspaceGroups undefined produces no errors', () => {
|
|
264
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
265
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
266
|
+
workspaces: ['/some/repo'],
|
|
267
|
+
}), 'utf8');
|
|
268
|
+
const config = loadConfig(configPath);
|
|
269
|
+
assert.equal(config.workspaceGroups, undefined);
|
|
270
|
+
});
|
|
271
|
+
test('workspaceGroups with all-invalid paths removes empty group', () => {
|
|
272
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
273
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
274
|
+
workspaces: ['/valid/repo'],
|
|
275
|
+
workspaceGroups: {
|
|
276
|
+
'Ghost Group': ['/not/here', '/also/not/here'],
|
|
277
|
+
},
|
|
278
|
+
}), 'utf8');
|
|
279
|
+
const config = loadConfig(configPath);
|
|
280
|
+
assert.equal(config.workspaceGroups['Ghost Group'], undefined);
|
|
281
|
+
});
|