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,349 @@
|
|
|
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 { createOrgDashboardRouter, } from '../server/org-dashboard.js';
|
|
8
|
+
import { saveConfig, DEFAULTS } from '../server/config.js';
|
|
9
|
+
let tmpDir;
|
|
10
|
+
let configPath;
|
|
11
|
+
let server;
|
|
12
|
+
let baseUrl;
|
|
13
|
+
// A workspace path we can point git remote mocks at
|
|
14
|
+
const WORKSPACE_PATH_A = '/fake/workspace/repo-a';
|
|
15
|
+
const WORKSPACE_PATH_B = '/fake/workspace/repo-b';
|
|
16
|
+
/**
|
|
17
|
+
* Creates a mock execAsync that routes calls based on the command.
|
|
18
|
+
* - `git remote get-url origin` → returns configured remote URL or throws
|
|
19
|
+
* - `gh api user ...` → returns configured user login or throws
|
|
20
|
+
* - `gh api search/issues ...` → returns configured search response or throws
|
|
21
|
+
*/
|
|
22
|
+
function makeMockExec(opts) {
|
|
23
|
+
return async (cmd, args, options) => {
|
|
24
|
+
const command = cmd;
|
|
25
|
+
const argv = args;
|
|
26
|
+
if (command === 'git' && argv[0] === 'remote') {
|
|
27
|
+
const cwd = options.cwd ?? '';
|
|
28
|
+
const remote = opts.remotes?.[cwd];
|
|
29
|
+
if (remote)
|
|
30
|
+
return { stdout: remote + '\n', stderr: '' };
|
|
31
|
+
throw Object.assign(new Error('not a git repository'), { code: 128 });
|
|
32
|
+
}
|
|
33
|
+
if (command === 'gh' && argv[0] === 'api' && argv[1] === 'user') {
|
|
34
|
+
if (opts.userError)
|
|
35
|
+
throw opts.userError;
|
|
36
|
+
const login = opts.userLogin ?? 'testuser';
|
|
37
|
+
return { stdout: login + '\n', stderr: '' };
|
|
38
|
+
}
|
|
39
|
+
if (command === 'gh' &&
|
|
40
|
+
argv[0] === 'api' &&
|
|
41
|
+
argv[1]?.startsWith('search/issues')) {
|
|
42
|
+
if (opts.searchError)
|
|
43
|
+
throw opts.searchError;
|
|
44
|
+
const items = opts.searchItems ?? [];
|
|
45
|
+
return { stdout: JSON.stringify({ items }), stderr: '' };
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Unexpected exec call: ${command} ${argv.join(' ')}`);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Builds a minimal GH search item for a given owner/repo and user.
|
|
52
|
+
*/
|
|
53
|
+
function makeSearchItem(overrides) {
|
|
54
|
+
const { ownerRepo, number = 1, title = 'Test PR', author = 'testuser', role = 'author', currentUser = 'testuser', } = overrides;
|
|
55
|
+
return {
|
|
56
|
+
number,
|
|
57
|
+
title,
|
|
58
|
+
html_url: `https://github.com/${ownerRepo}/pull/${number}`,
|
|
59
|
+
state: 'open',
|
|
60
|
+
user: { login: author },
|
|
61
|
+
pull_request: { head: { ref: 'feat/branch' }, base: { ref: 'main' } },
|
|
62
|
+
updated_at: '2026-03-21T00:00:00Z',
|
|
63
|
+
requested_reviewers: role === 'reviewer' ? [{ login: currentUser }] : [],
|
|
64
|
+
repository_url: `https://api.github.com/repos/${ownerRepo}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function startServer(execAsyncFn) {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
const app = express();
|
|
70
|
+
app.use(express.json());
|
|
71
|
+
// Cast through unknown: the mock satisfies the runtime contract but the
|
|
72
|
+
// overloaded promisify types don't align across module instances.
|
|
73
|
+
const deps = {
|
|
74
|
+
configPath,
|
|
75
|
+
execAsync: execAsyncFn,
|
|
76
|
+
};
|
|
77
|
+
app.use('/org-dashboard', createOrgDashboardRouter(deps));
|
|
78
|
+
server = app.listen(0, '127.0.0.1', () => {
|
|
79
|
+
const addr = server.address();
|
|
80
|
+
if (typeof addr === 'object' && addr) {
|
|
81
|
+
baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
82
|
+
}
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function stopServer() {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
if (server)
|
|
90
|
+
server.close(() => resolve());
|
|
91
|
+
else
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function getPrs() {
|
|
96
|
+
const res = await fetch(`${baseUrl}/org-dashboard/prs`);
|
|
97
|
+
return res.json();
|
|
98
|
+
}
|
|
99
|
+
before(() => {
|
|
100
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'org-dashboard-test-'));
|
|
101
|
+
configPath = path.join(tmpDir, 'config.json');
|
|
102
|
+
});
|
|
103
|
+
after(async () => {
|
|
104
|
+
await stopServer();
|
|
105
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
106
|
+
});
|
|
107
|
+
// Each test gets a fresh server with its own router (and thus its own cache).
|
|
108
|
+
// We stop/start the server around each test to reset the in-router cache state.
|
|
109
|
+
test('returns prs filtered to workspace repos', async () => {
|
|
110
|
+
await stopServer();
|
|
111
|
+
saveConfig(configPath, {
|
|
112
|
+
...DEFAULTS,
|
|
113
|
+
repos: [WORKSPACE_PATH_A, WORKSPACE_PATH_B],
|
|
114
|
+
});
|
|
115
|
+
const exec = makeMockExec({
|
|
116
|
+
remotes: {
|
|
117
|
+
[WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git',
|
|
118
|
+
[WORKSPACE_PATH_B]: 'git@github.com:myorg/repo-b.git',
|
|
119
|
+
},
|
|
120
|
+
userLogin: 'testuser',
|
|
121
|
+
searchItems: [
|
|
122
|
+
// Matches WORKSPACE_PATH_A
|
|
123
|
+
makeSearchItem({
|
|
124
|
+
ownerRepo: 'myorg/repo-a',
|
|
125
|
+
number: 10,
|
|
126
|
+
author: 'testuser',
|
|
127
|
+
}),
|
|
128
|
+
// Matches WORKSPACE_PATH_B
|
|
129
|
+
makeSearchItem({
|
|
130
|
+
ownerRepo: 'myorg/repo-b',
|
|
131
|
+
number: 20,
|
|
132
|
+
author: 'testuser',
|
|
133
|
+
}),
|
|
134
|
+
// Not in any workspace — should be excluded
|
|
135
|
+
makeSearchItem({
|
|
136
|
+
ownerRepo: 'myorg/other-repo',
|
|
137
|
+
number: 30,
|
|
138
|
+
author: 'testuser',
|
|
139
|
+
}),
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
await startServer(exec);
|
|
143
|
+
const data = await getPrs();
|
|
144
|
+
assert.equal(data.error, undefined, `Unexpected error: ${data.error}`);
|
|
145
|
+
assert.equal(data.prs.length, 2, 'Should return only the 2 workspace-matched PRs');
|
|
146
|
+
const numbers = data.prs.map((p) => p.number).sort((a, b) => a - b);
|
|
147
|
+
assert.deepEqual(numbers, [10, 20]);
|
|
148
|
+
// Verify repoPath is attached
|
|
149
|
+
const pr10 = data.prs.find((p) => p.number === 10);
|
|
150
|
+
assert.equal(pr10?.repoPath, WORKSPACE_PATH_A);
|
|
151
|
+
});
|
|
152
|
+
test('returns gh_not_in_path error when gh not found', async () => {
|
|
153
|
+
await stopServer();
|
|
154
|
+
saveConfig(configPath, {
|
|
155
|
+
...DEFAULTS,
|
|
156
|
+
repos: [WORKSPACE_PATH_A],
|
|
157
|
+
});
|
|
158
|
+
const notFoundError = Object.assign(new Error('spawn gh ENOENT'), {
|
|
159
|
+
code: 'ENOENT',
|
|
160
|
+
});
|
|
161
|
+
const exec = makeMockExec({
|
|
162
|
+
remotes: { [WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git' },
|
|
163
|
+
userError: notFoundError,
|
|
164
|
+
});
|
|
165
|
+
await startServer(exec);
|
|
166
|
+
const data = await getPrs();
|
|
167
|
+
assert.equal(data.error, 'gh_not_in_path');
|
|
168
|
+
assert.equal(data.prs.length, 0);
|
|
169
|
+
});
|
|
170
|
+
test('returns gh_not_authenticated error', async () => {
|
|
171
|
+
await stopServer();
|
|
172
|
+
saveConfig(configPath, {
|
|
173
|
+
...DEFAULTS,
|
|
174
|
+
repos: [WORKSPACE_PATH_A],
|
|
175
|
+
});
|
|
176
|
+
const authError = new Error('You are not logged into any GitHub hosts. Run gh auth login to authenticate.');
|
|
177
|
+
const exec = makeMockExec({
|
|
178
|
+
remotes: { [WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git' },
|
|
179
|
+
userError: authError,
|
|
180
|
+
});
|
|
181
|
+
await startServer(exec);
|
|
182
|
+
const data = await getPrs();
|
|
183
|
+
assert.equal(data.error, 'gh_not_authenticated');
|
|
184
|
+
assert.equal(data.prs.length, 0);
|
|
185
|
+
});
|
|
186
|
+
test('returns empty prs with no_workspaces error when workspaces is empty', async () => {
|
|
187
|
+
await stopServer();
|
|
188
|
+
saveConfig(configPath, {
|
|
189
|
+
...DEFAULTS,
|
|
190
|
+
repos: [],
|
|
191
|
+
});
|
|
192
|
+
// execAsync should never be called here — pass a mock that always throws to
|
|
193
|
+
// verify the early-return path triggers before any exec
|
|
194
|
+
const exec = makeMockExec({});
|
|
195
|
+
await startServer(exec);
|
|
196
|
+
const data = await getPrs();
|
|
197
|
+
assert.equal(data.error, 'no_workspaces');
|
|
198
|
+
assert.equal(data.prs.length, 0);
|
|
199
|
+
});
|
|
200
|
+
test('detects reviewer role when current user is in requested_reviewers but not the author', async () => {
|
|
201
|
+
await stopServer();
|
|
202
|
+
saveConfig(configPath, {
|
|
203
|
+
...DEFAULTS,
|
|
204
|
+
repos: [WORKSPACE_PATH_A],
|
|
205
|
+
});
|
|
206
|
+
// PR authored by someone else; testuser is a requested reviewer
|
|
207
|
+
const reviewerItem = makeSearchItem({
|
|
208
|
+
ownerRepo: 'myorg/repo-a',
|
|
209
|
+
number: 42,
|
|
210
|
+
title: 'Review me',
|
|
211
|
+
author: 'otheruser',
|
|
212
|
+
role: 'reviewer',
|
|
213
|
+
currentUser: 'testuser',
|
|
214
|
+
});
|
|
215
|
+
const exec = makeMockExec({
|
|
216
|
+
remotes: { [WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git' },
|
|
217
|
+
userLogin: 'testuser',
|
|
218
|
+
searchItems: [reviewerItem],
|
|
219
|
+
});
|
|
220
|
+
await startServer(exec);
|
|
221
|
+
const data = await getPrs();
|
|
222
|
+
assert.equal(data.error, undefined, `Unexpected error: ${data.error}`);
|
|
223
|
+
assert.equal(data.prs.length, 1, 'Should return the reviewer PR');
|
|
224
|
+
const pr = data.prs[0];
|
|
225
|
+
assert.equal(pr?.number, 42);
|
|
226
|
+
assert.equal(pr?.role, 'reviewer', 'Role should be reviewer');
|
|
227
|
+
assert.equal(pr?.author, 'otheruser', 'Author should be otheruser, not the current user');
|
|
228
|
+
});
|
|
229
|
+
test('caches results within TTL — exec called only once for two requests', async () => {
|
|
230
|
+
await stopServer();
|
|
231
|
+
saveConfig(configPath, {
|
|
232
|
+
...DEFAULTS,
|
|
233
|
+
repos: [WORKSPACE_PATH_A],
|
|
234
|
+
});
|
|
235
|
+
let searchCallCount = 0;
|
|
236
|
+
// Wrap makeMockExec with a counter on the search path
|
|
237
|
+
const baseExec = makeMockExec({
|
|
238
|
+
remotes: { [WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git' },
|
|
239
|
+
userLogin: 'testuser',
|
|
240
|
+
searchItems: [
|
|
241
|
+
makeSearchItem({
|
|
242
|
+
ownerRepo: 'myorg/repo-a',
|
|
243
|
+
number: 1,
|
|
244
|
+
author: 'testuser',
|
|
245
|
+
}),
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
const countingExec = async (...args) => {
|
|
249
|
+
const [cmd, argv] = args;
|
|
250
|
+
if (cmd === 'gh' &&
|
|
251
|
+
typeof argv[1] === 'string' &&
|
|
252
|
+
argv[1].startsWith('search/issues')) {
|
|
253
|
+
searchCallCount++;
|
|
254
|
+
}
|
|
255
|
+
return baseExec(...args);
|
|
256
|
+
};
|
|
257
|
+
await startServer(countingExec);
|
|
258
|
+
// First request — populates cache
|
|
259
|
+
const first = await getPrs();
|
|
260
|
+
assert.equal(first.error, undefined);
|
|
261
|
+
assert.equal(first.prs.length, 1);
|
|
262
|
+
// Second request — should be served from cache, no additional exec call
|
|
263
|
+
const second = await getPrs();
|
|
264
|
+
assert.equal(second.error, undefined);
|
|
265
|
+
assert.equal(second.prs.length, 1);
|
|
266
|
+
assert.equal(searchCallCount, 1, 'gh search should have been called exactly once (cache hit on second request)');
|
|
267
|
+
});
|
|
268
|
+
test('uses GraphQL path when github accessToken is in config', async () => {
|
|
269
|
+
// Use an isolated tmp dir, config, and server so this test does not
|
|
270
|
+
// interfere with the shared server used by the other tests.
|
|
271
|
+
const gqlTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'org-dashboard-gql-test-'));
|
|
272
|
+
const gqlConfigPath = path.join(gqlTmpDir, 'config.json');
|
|
273
|
+
saveConfig(gqlConfigPath, {
|
|
274
|
+
...DEFAULTS,
|
|
275
|
+
repos: [WORKSPACE_PATH_A],
|
|
276
|
+
github: { accessToken: 'ghp_test123', username: 'graphqluser' },
|
|
277
|
+
});
|
|
278
|
+
const graphqlPr = {
|
|
279
|
+
number: 99,
|
|
280
|
+
title: 'GraphQL PR',
|
|
281
|
+
url: 'https://github.com/myorg/repo-a/pull/99',
|
|
282
|
+
headRefName: 'feat/graphql',
|
|
283
|
+
baseRefName: 'main',
|
|
284
|
+
state: 'OPEN',
|
|
285
|
+
author: 'graphqluser',
|
|
286
|
+
role: 'author',
|
|
287
|
+
updatedAt: '2026-03-22T00:00:00Z',
|
|
288
|
+
additions: 5,
|
|
289
|
+
deletions: 2,
|
|
290
|
+
reviewDecision: null,
|
|
291
|
+
mergeable: null,
|
|
292
|
+
isDraft: false,
|
|
293
|
+
ciStatus: null,
|
|
294
|
+
repoName: 'repo-a',
|
|
295
|
+
repoPath: WORKSPACE_PATH_A,
|
|
296
|
+
};
|
|
297
|
+
let graphqlCallCount = 0;
|
|
298
|
+
let capturedToken;
|
|
299
|
+
let capturedRepoMap;
|
|
300
|
+
const mockFetchGraphQL = async (token, repoMap) => {
|
|
301
|
+
graphqlCallCount++;
|
|
302
|
+
capturedToken = token;
|
|
303
|
+
capturedRepoMap = repoMap;
|
|
304
|
+
return { prs: [graphqlPr], username: 'graphqluser' };
|
|
305
|
+
};
|
|
306
|
+
// exec mock that handles git remote but should NOT be called for gh user/search
|
|
307
|
+
const exec = makeMockExec({
|
|
308
|
+
remotes: { [WORKSPACE_PATH_A]: 'git@github.com:myorg/repo-a.git' },
|
|
309
|
+
});
|
|
310
|
+
let gqlServer;
|
|
311
|
+
let gqlBaseUrl;
|
|
312
|
+
await new Promise((resolve) => {
|
|
313
|
+
const app = express();
|
|
314
|
+
app.use(express.json());
|
|
315
|
+
const deps = {
|
|
316
|
+
configPath: gqlConfigPath,
|
|
317
|
+
execAsync: exec,
|
|
318
|
+
fetchGraphQL: mockFetchGraphQL,
|
|
319
|
+
};
|
|
320
|
+
app.use('/org-dashboard', createOrgDashboardRouter(deps));
|
|
321
|
+
gqlServer = app.listen(0, '127.0.0.1', () => {
|
|
322
|
+
const addr = gqlServer.address();
|
|
323
|
+
if (typeof addr === 'object' && addr) {
|
|
324
|
+
gqlBaseUrl = `http://127.0.0.1:${addr.port}`;
|
|
325
|
+
}
|
|
326
|
+
resolve();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
const res = await fetch(`${gqlBaseUrl}/org-dashboard/prs`);
|
|
331
|
+
const data = (await res.json());
|
|
332
|
+
assert.equal(data.error, undefined, `Unexpected error: ${data.error}`);
|
|
333
|
+
assert.equal(data.prs.length, 1, 'Should return the GraphQL PR');
|
|
334
|
+
assert.equal(data.prs[0]?.number, 99, 'PR number should match GraphQL data');
|
|
335
|
+
assert.equal(data.prs[0]?.title, 'GraphQL PR');
|
|
336
|
+
assert.equal(graphqlCallCount, 1, 'fetchGraphQL should have been called exactly once');
|
|
337
|
+
assert.equal(capturedToken, 'ghp_test123', 'fetchGraphQL should receive the configured access token');
|
|
338
|
+
assert.ok(capturedRepoMap instanceof Map, 'fetchGraphQL should receive the repoMap');
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
await new Promise((resolve) => {
|
|
342
|
+
if (gqlServer)
|
|
343
|
+
gqlServer.close(() => resolve());
|
|
344
|
+
else
|
|
345
|
+
resolve();
|
|
346
|
+
});
|
|
347
|
+
fs.rmSync(gqlTmpDir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ClaudeOutputParser } from '../server/output-parsers/claude-parser.js';
|
|
4
|
+
import { CodexOutputParser } from '../server/output-parsers/codex-parser.js';
|
|
5
|
+
import { OpencodeOutputParser } from '../server/output-parsers/opencode-parser.js';
|
|
6
|
+
import { NullOutputParser } from '../server/output-parsers/null-parser.js';
|
|
7
|
+
import { outputParsers } from '../server/output-parsers/index.js';
|
|
8
|
+
describe('ClaudeOutputParser', () => {
|
|
9
|
+
it('starts in initializing state', () => {
|
|
10
|
+
const parser = new ClaudeOutputParser();
|
|
11
|
+
assert.equal(parser.state, 'initializing');
|
|
12
|
+
});
|
|
13
|
+
it('transitions to waiting-for-input on > prompt', () => {
|
|
14
|
+
const parser = new ClaudeOutputParser();
|
|
15
|
+
const result = parser.onData('>\n', []);
|
|
16
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
17
|
+
});
|
|
18
|
+
it('transitions to waiting-for-input on greeting', () => {
|
|
19
|
+
const parser = new ClaudeOutputParser();
|
|
20
|
+
const result = parser.onData('How can I help you today?', []);
|
|
21
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
22
|
+
});
|
|
23
|
+
it('transitions to processing after first prompt when output arrives', () => {
|
|
24
|
+
const parser = new ClaudeOutputParser();
|
|
25
|
+
// First: see the prompt
|
|
26
|
+
parser.onData('>\n', []);
|
|
27
|
+
// Then: output starts
|
|
28
|
+
const result = parser.onData('I will help you with that task...', []);
|
|
29
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
30
|
+
});
|
|
31
|
+
it('transitions back to waiting-for-input after processing', () => {
|
|
32
|
+
const parser = new ClaudeOutputParser();
|
|
33
|
+
parser.onData('>\n', []);
|
|
34
|
+
parser.onData('Working on it...', []);
|
|
35
|
+
const result = parser.onData('>\n', []);
|
|
36
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
37
|
+
});
|
|
38
|
+
it('detects permission prompt', () => {
|
|
39
|
+
const parser = new ClaudeOutputParser();
|
|
40
|
+
parser.onData('>\n', []);
|
|
41
|
+
const result = parser.onData('Allow tool access to /usr/bin? Allow / Deny', []);
|
|
42
|
+
assert.deepEqual(result, { state: 'permission-prompt' });
|
|
43
|
+
});
|
|
44
|
+
it('detects error state', () => {
|
|
45
|
+
const parser = new ClaudeOutputParser();
|
|
46
|
+
parser.onData('>\n', []);
|
|
47
|
+
const result = parser.onData('Error: something went wrong', []);
|
|
48
|
+
assert.deepEqual(result, { state: 'error' });
|
|
49
|
+
});
|
|
50
|
+
it('ignores pure ANSI escape sequences', () => {
|
|
51
|
+
const parser = new ClaudeOutputParser();
|
|
52
|
+
const result = parser.onData('\x1b[32m\x1b[0m', []);
|
|
53
|
+
assert.equal(result, null);
|
|
54
|
+
});
|
|
55
|
+
it('returns null when state does not change', () => {
|
|
56
|
+
const parser = new ClaudeOutputParser();
|
|
57
|
+
parser.onData('>\n', []);
|
|
58
|
+
// Already in waiting-for-input, send another prompt
|
|
59
|
+
const result = parser.onData('>\n', []);
|
|
60
|
+
assert.equal(result, null);
|
|
61
|
+
});
|
|
62
|
+
it('reset returns to initializing', () => {
|
|
63
|
+
const parser = new ClaudeOutputParser();
|
|
64
|
+
parser.onData('>\n', []);
|
|
65
|
+
assert.equal(parser.state, 'waiting-for-input');
|
|
66
|
+
parser.reset();
|
|
67
|
+
assert.equal(parser.state, 'initializing');
|
|
68
|
+
});
|
|
69
|
+
it('stays initializing before first prompt', () => {
|
|
70
|
+
const parser = new ClaudeOutputParser();
|
|
71
|
+
const result = parser.onData('Loading configuration...', []);
|
|
72
|
+
// Still initializing since no prompt seen
|
|
73
|
+
assert.equal(result, null); // already in initializing, no change
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('CodexOutputParser', () => {
|
|
77
|
+
it('always returns null', () => {
|
|
78
|
+
const parser = new CodexOutputParser();
|
|
79
|
+
assert.equal(parser.onData('any output', []), null);
|
|
80
|
+
assert.equal(parser.onData('>\n', []), null);
|
|
81
|
+
assert.equal(parser.onData('Error: something', []), null);
|
|
82
|
+
});
|
|
83
|
+
it('reset is a no-op', () => {
|
|
84
|
+
const parser = new CodexOutputParser();
|
|
85
|
+
parser.reset(); // should not throw
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('outputParsers registry', () => {
|
|
89
|
+
it('creates ClaudeOutputParser for claude', () => {
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
91
|
+
const parser = outputParsers['claude']();
|
|
92
|
+
assert.ok(parser instanceof ClaudeOutputParser);
|
|
93
|
+
});
|
|
94
|
+
it('creates CodexOutputParser for codex', () => {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
96
|
+
const parser = outputParsers['codex']();
|
|
97
|
+
assert.ok(parser instanceof CodexOutputParser);
|
|
98
|
+
});
|
|
99
|
+
it('creates OpencodeOutputParser for opencode', () => {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
101
|
+
const parser = outputParsers['opencode']();
|
|
102
|
+
assert.ok(parser instanceof OpencodeOutputParser);
|
|
103
|
+
});
|
|
104
|
+
it('creates NullOutputParser for none', () => {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
106
|
+
const parser = outputParsers['none']();
|
|
107
|
+
assert.ok(parser instanceof NullOutputParser);
|
|
108
|
+
});
|
|
109
|
+
it('returns undefined for unknown keys', () => {
|
|
110
|
+
assert.equal(outputParsers['unknown-agent-xyz'], undefined);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('NullOutputParser', () => {
|
|
114
|
+
it('always returns null for any input', () => {
|
|
115
|
+
const parser = new NullOutputParser();
|
|
116
|
+
assert.equal(parser.onData('any output', []), null);
|
|
117
|
+
assert.equal(parser.onData('>\n', []), null);
|
|
118
|
+
assert.equal(parser.onData('Error: something', []), null);
|
|
119
|
+
assert.equal(parser.onData('', []), null);
|
|
120
|
+
});
|
|
121
|
+
it('reset is a no-op', () => {
|
|
122
|
+
const parser = new NullOutputParser();
|
|
123
|
+
parser.reset(); // should not throw
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('OpencodeOutputParser', () => {
|
|
127
|
+
it('starts in initializing state', () => {
|
|
128
|
+
const parser = new OpencodeOutputParser();
|
|
129
|
+
assert.equal(parser.state, 'initializing');
|
|
130
|
+
});
|
|
131
|
+
it('detects permission prompt from ! permission requested line', () => {
|
|
132
|
+
const parser = new OpencodeOutputParser();
|
|
133
|
+
const result = parser.onData('! permission requested: bash (*); allow this tool?', []);
|
|
134
|
+
assert.deepEqual(result, { state: 'permission-prompt' });
|
|
135
|
+
});
|
|
136
|
+
it('recognizes agent header and sets hasSeenFirstPrompt (no state change from initializing)', () => {
|
|
137
|
+
const parser = new OpencodeOutputParser();
|
|
138
|
+
// Parser starts in initializing; header keeps it in initializing — no state change, returns null
|
|
139
|
+
const result = parser.onData('> build . anthropic/claude-sonnet-4-5', []);
|
|
140
|
+
assert.equal(result, null);
|
|
141
|
+
assert.equal(parser.state, 'initializing');
|
|
142
|
+
});
|
|
143
|
+
it('detects bash tool icon as processing', () => {
|
|
144
|
+
const parser = new OpencodeOutputParser();
|
|
145
|
+
// Simulate having seen first prompt first
|
|
146
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
147
|
+
// Now a bash tool invocation
|
|
148
|
+
const result = parser.onData('$ npm run build', []);
|
|
149
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
150
|
+
});
|
|
151
|
+
it('detects file edit tool icon as processing', () => {
|
|
152
|
+
const parser = new OpencodeOutputParser();
|
|
153
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
154
|
+
const result = parser.onData('<- src/index.ts', []);
|
|
155
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
156
|
+
});
|
|
157
|
+
it('detects file read tool icon as processing', () => {
|
|
158
|
+
const parser = new OpencodeOutputParser();
|
|
159
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
160
|
+
const result = parser.onData('-> src/index.ts', []);
|
|
161
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
162
|
+
});
|
|
163
|
+
it('detects glob/grep tool icon as processing', () => {
|
|
164
|
+
const parser = new OpencodeOutputParser();
|
|
165
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
166
|
+
const result = parser.onData('* **/*.ts', []);
|
|
167
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
168
|
+
});
|
|
169
|
+
it('detects webfetch tool icon as processing', () => {
|
|
170
|
+
const parser = new OpencodeOutputParser();
|
|
171
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
172
|
+
const result = parser.onData('% https://example.com', []);
|
|
173
|
+
assert.deepEqual(result, { state: 'processing' });
|
|
174
|
+
});
|
|
175
|
+
it('detects waiting-for-input on > prompt without provider info', () => {
|
|
176
|
+
const parser = new OpencodeOutputParser();
|
|
177
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
178
|
+
parser.onData('$ npm install', []);
|
|
179
|
+
const result = parser.onData('>\n', []);
|
|
180
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
181
|
+
});
|
|
182
|
+
it('detects waiting-for-input on "Ready" pattern', () => {
|
|
183
|
+
const parser = new OpencodeOutputParser();
|
|
184
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
185
|
+
parser.onData('doing work...', []);
|
|
186
|
+
const result = parser.onData('Ready', []);
|
|
187
|
+
assert.deepEqual(result, { state: 'waiting-for-input' });
|
|
188
|
+
});
|
|
189
|
+
it('strips ANSI sequences before matching', () => {
|
|
190
|
+
const parser = new OpencodeOutputParser();
|
|
191
|
+
// Pure ANSI should return null
|
|
192
|
+
const result = parser.onData('\x1b[32m\x1b[0m', []);
|
|
193
|
+
assert.equal(result, null);
|
|
194
|
+
});
|
|
195
|
+
it('permission prompt has highest priority over processing', () => {
|
|
196
|
+
const parser = new OpencodeOutputParser();
|
|
197
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
198
|
+
// Even if there is tool-like content, the permission pattern wins
|
|
199
|
+
const result = parser.onData('! permission requested: bash (*); $ npm run test', []);
|
|
200
|
+
assert.deepEqual(result, { state: 'permission-prompt' });
|
|
201
|
+
});
|
|
202
|
+
it('returns null when state does not change', () => {
|
|
203
|
+
const parser = new OpencodeOutputParser();
|
|
204
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
205
|
+
// Still initializing, another header line should return null (already initializing)
|
|
206
|
+
const result = parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
207
|
+
assert.equal(result, null);
|
|
208
|
+
});
|
|
209
|
+
it('reset returns to initializing state', () => {
|
|
210
|
+
const parser = new OpencodeOutputParser();
|
|
211
|
+
parser.onData('> build . anthropic/claude-3-5-sonnet', []);
|
|
212
|
+
parser.onData('>\n', []);
|
|
213
|
+
assert.equal(parser.state, 'waiting-for-input');
|
|
214
|
+
parser.reset();
|
|
215
|
+
assert.equal(parser.state, 'initializing');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
// At runtime, __dirname resolves to dist/test/.
|
|
7
|
+
// The server runs from dist/server/, which uses path.join(__dirname, '..', '..', ...)
|
|
8
|
+
// to reach the project root. This test verifies that relationship is correct.
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
// This test file is at dist/test/, server is at dist/server/ — same depth
|
|
12
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
13
|
+
test('project root from dist/ contains frontend/ directory', () => {
|
|
14
|
+
const frontendDir = path.join(projectRoot, 'frontend');
|
|
15
|
+
assert.ok(fs.existsSync(frontendDir), `Expected frontend/ at ${frontendDir}`);
|
|
16
|
+
});
|
|
17
|
+
test('project root from dist/ contains frontend/index.html', () => {
|
|
18
|
+
const indexHtml = path.join(projectRoot, 'frontend', 'index.html');
|
|
19
|
+
assert.ok(fs.existsSync(indexHtml), `Expected frontend/index.html at ${indexHtml}`);
|
|
20
|
+
});
|
|
21
|
+
test('dist/server/ exists after compilation', () => {
|
|
22
|
+
const serverDir = path.join(projectRoot, 'dist', 'server');
|
|
23
|
+
assert.ok(fs.existsSync(serverDir), `Expected dist/server/ at ${serverDir}`);
|
|
24
|
+
});
|
|
25
|
+
test('server index.ts uses correct path depth to reach dist/frontend/', async () => {
|
|
26
|
+
// Read the source file and verify the path pattern
|
|
27
|
+
const indexSource = fs.readFileSync(path.join(projectRoot, 'server', 'index.ts'), 'utf8');
|
|
28
|
+
// Static serving must go up one level from dist/server/ to dist/, then into frontend/
|
|
29
|
+
assert.ok(indexSource.includes("path.join(__dirname, '..', 'frontend')"), 'express.static must resolve dist/frontend/ one level up from dist/server/');
|
|
30
|
+
// Config fallback must also go up two levels
|
|
31
|
+
assert.ok(indexSource.includes("path.join(__dirname, '..', '..', 'config.json')"), 'CONFIG_PATH must resolve config.json two levels up from dist/server/');
|
|
32
|
+
});
|