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,65 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const scrypt = promisify(crypto.scrypt);
|
|
4
|
+
const SCRYPT_KEYLEN = 64;
|
|
5
|
+
const MAX_ATTEMPTS = 5;
|
|
6
|
+
const LOCKOUT_DURATION_MS = 15 * 60 * 1000; // 15 minutes
|
|
7
|
+
const attemptMap = new Map();
|
|
8
|
+
export async function hashPin(pin) {
|
|
9
|
+
const salt = crypto.randomBytes(16).toString('hex');
|
|
10
|
+
const derived = (await scrypt(pin, salt, SCRYPT_KEYLEN));
|
|
11
|
+
return `scrypt:${salt}:${derived.toString('hex')}`;
|
|
12
|
+
}
|
|
13
|
+
export async function verifyPin(pin, hash) {
|
|
14
|
+
if (!hash)
|
|
15
|
+
return false;
|
|
16
|
+
if (hash.startsWith('scrypt:')) {
|
|
17
|
+
const [, salt, storedHashHex] = hash.split(':');
|
|
18
|
+
if (!salt || !storedHashHex)
|
|
19
|
+
return false;
|
|
20
|
+
try {
|
|
21
|
+
const storedBuf = Buffer.from(storedHashHex, 'hex');
|
|
22
|
+
if (storedBuf.length !== SCRYPT_KEYLEN)
|
|
23
|
+
return false;
|
|
24
|
+
const derived = (await scrypt(pin, salt, SCRYPT_KEYLEN));
|
|
25
|
+
return crypto.timingSafeEqual(storedBuf, derived);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Legacy bcrypt hashes are migrated at startup; if one reaches here, reject it
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
export function isRateLimited(ip) {
|
|
35
|
+
const entry = attemptMap.get(ip);
|
|
36
|
+
if (!entry)
|
|
37
|
+
return false;
|
|
38
|
+
if (entry.lockedUntil) {
|
|
39
|
+
if (Date.now() < entry.lockedUntil) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
attemptMap.delete(ip);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
export function recordFailedAttempt(ip) {
|
|
47
|
+
const entry = attemptMap.get(ip) ?? { count: 0, lockedUntil: null };
|
|
48
|
+
entry.count += 1;
|
|
49
|
+
if (entry.count >= MAX_ATTEMPTS) {
|
|
50
|
+
entry.lockedUntil = Date.now() + LOCKOUT_DURATION_MS;
|
|
51
|
+
}
|
|
52
|
+
attemptMap.set(ip, entry);
|
|
53
|
+
}
|
|
54
|
+
export function clearRateLimit(ip) {
|
|
55
|
+
attemptMap.delete(ip);
|
|
56
|
+
}
|
|
57
|
+
export function generateCookieToken() {
|
|
58
|
+
return crypto.randomBytes(32).toString('hex');
|
|
59
|
+
}
|
|
60
|
+
export function isLegacyHash(hash) {
|
|
61
|
+
return !!hash && !hash.startsWith('scrypt:');
|
|
62
|
+
}
|
|
63
|
+
export function _resetForTesting() {
|
|
64
|
+
attemptMap.clear();
|
|
65
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as sessions from '../sessions.js';
|
|
4
|
+
import { transitionPipeline, updatePipeline, getPipeline } from './pipeline.js';
|
|
5
|
+
import { buildBrainstormPrompt, buildPlanPrompt, buildExecutionPrompt, buildReviewPrompt, buildStuckPrompt } from './prompts.js';
|
|
6
|
+
export function buildClaudeArgs(model) {
|
|
7
|
+
return ['-p', '--dangerously-skip-permissions', '--model', model];
|
|
8
|
+
}
|
|
9
|
+
export function parseVerdictFile(content) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(content);
|
|
12
|
+
if (typeof parsed.goalName !== 'string' ||
|
|
13
|
+
typeof parsed.pass !== 'boolean' ||
|
|
14
|
+
!Array.isArray(parsed.criteriaResults) ||
|
|
15
|
+
typeof parsed.summary !== 'string') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let eventCallback = null;
|
|
25
|
+
export function onPipelineEvent(cb) {
|
|
26
|
+
eventCallback = cb;
|
|
27
|
+
}
|
|
28
|
+
function emitEvent(pipelineId, event, data) {
|
|
29
|
+
if (eventCallback)
|
|
30
|
+
eventCallback(pipelineId, event, data);
|
|
31
|
+
}
|
|
32
|
+
export async function startPipeline(pipelineId) {
|
|
33
|
+
const pipeline = getPipeline(pipelineId);
|
|
34
|
+
if (!pipeline)
|
|
35
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
36
|
+
// Move from intake to brainstorming
|
|
37
|
+
transitionPipeline(pipelineId, 'brainstorming');
|
|
38
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'brainstorming' });
|
|
39
|
+
const prompt = buildBrainstormPrompt(pipeline.task);
|
|
40
|
+
await runAgentPhase(pipelineId, prompt, pipeline.config.models.brainstorm, pipeline.config.targetRepo, (output) => {
|
|
41
|
+
// Store brainstorm output as PRD
|
|
42
|
+
updatePipeline(pipelineId, { prdContent: output });
|
|
43
|
+
transitionPipeline(pipelineId, 'prd_review');
|
|
44
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'prd_review' });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export async function approvePrd(pipelineId, editedContent) {
|
|
48
|
+
const pipeline = getPipeline(pipelineId);
|
|
49
|
+
if (!pipeline)
|
|
50
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
51
|
+
if (pipeline.state !== 'prd_review')
|
|
52
|
+
throw new Error('Pipeline not in prd_review state');
|
|
53
|
+
if (editedContent) {
|
|
54
|
+
updatePipeline(pipelineId, { prdContent: editedContent });
|
|
55
|
+
}
|
|
56
|
+
transitionPipeline(pipelineId, 'planning');
|
|
57
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'planning' });
|
|
58
|
+
const updatedPipeline = getPipeline(pipelineId);
|
|
59
|
+
const prompt = buildPlanPrompt(updatedPipeline.task, updatedPipeline.prdContent || '');
|
|
60
|
+
await runAgentPhase(pipelineId, prompt, updatedPipeline.config.models.brainstorm, updatedPipeline.config.targetRepo, (output) => {
|
|
61
|
+
updatePipeline(pipelineId, { planContent: output });
|
|
62
|
+
transitionPipeline(pipelineId, 'plan_review');
|
|
63
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'plan_review' });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function approvePlan(pipelineId, editedContent) {
|
|
67
|
+
const pipeline = getPipeline(pipelineId);
|
|
68
|
+
if (!pipeline)
|
|
69
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
70
|
+
if (pipeline.state !== 'plan_review')
|
|
71
|
+
throw new Error('Pipeline not in plan_review state');
|
|
72
|
+
if (editedContent) {
|
|
73
|
+
updatePipeline(pipelineId, { planContent: editedContent });
|
|
74
|
+
}
|
|
75
|
+
await startExecution(pipelineId);
|
|
76
|
+
}
|
|
77
|
+
export async function startExecution(pipelineId) {
|
|
78
|
+
const pipeline = getPipeline(pipelineId);
|
|
79
|
+
if (!pipeline)
|
|
80
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
81
|
+
if (pipeline.state !== 'executing') {
|
|
82
|
+
transitionPipeline(pipelineId, 'executing');
|
|
83
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'executing' });
|
|
84
|
+
}
|
|
85
|
+
const failedVerdicts = pipeline.verdicts.filter((v) => !v.pass);
|
|
86
|
+
const prompt = buildExecutionPrompt(pipeline.task, pipeline.planContent || '', failedVerdicts.length > 0 ? failedVerdicts : undefined);
|
|
87
|
+
// TODO: Create git worktree for execution if not already created
|
|
88
|
+
const cwd = pipeline.worktreePath || pipeline.config.targetRepo;
|
|
89
|
+
await runAgentPhase(pipelineId, prompt, pipeline.config.models.execute, cwd, () => {
|
|
90
|
+
startReview(pipelineId);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function startReview(pipelineId) {
|
|
94
|
+
const pipeline = getPipeline(pipelineId);
|
|
95
|
+
if (!pipeline)
|
|
96
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
97
|
+
transitionPipeline(pipelineId, 'reviewing');
|
|
98
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'reviewing' });
|
|
99
|
+
const cwd = pipeline.worktreePath || pipeline.config.targetRepo;
|
|
100
|
+
const prompt = buildReviewPrompt(pipeline.task, cwd);
|
|
101
|
+
await runAgentPhase(pipelineId, prompt, pipeline.config.models.review, cwd, () => {
|
|
102
|
+
// Try to read verdict file
|
|
103
|
+
const verdictPath = path.join(cwd, '.belayer', 'verdict.json');
|
|
104
|
+
let verdict = null;
|
|
105
|
+
try {
|
|
106
|
+
const content = fs.readFileSync(verdictPath, 'utf8');
|
|
107
|
+
verdict = parseVerdictFile(content);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// No verdict file
|
|
111
|
+
}
|
|
112
|
+
if (verdict) {
|
|
113
|
+
const updatedPipeline = getPipeline(pipelineId);
|
|
114
|
+
const verdicts = [...updatedPipeline.verdicts, verdict];
|
|
115
|
+
updatePipeline(pipelineId, { verdicts });
|
|
116
|
+
emitEvent(pipelineId, 'pipeline-verdict', { verdict: verdict });
|
|
117
|
+
if (verdict.pass) {
|
|
118
|
+
transitionPipeline(pipelineId, 'pr_created');
|
|
119
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'pr_created' });
|
|
120
|
+
// TODO: create PR
|
|
121
|
+
}
|
|
122
|
+
else if (updatedPipeline.attempts >= updatedPipeline.maxAttempts) {
|
|
123
|
+
const stuckReport = buildStuckPrompt(updatedPipeline.task, verdicts, updatedPipeline.attempts);
|
|
124
|
+
updatePipeline(pipelineId, { stuckReport });
|
|
125
|
+
transitionPipeline(pipelineId, 'stuck');
|
|
126
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'stuck' });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
transitionPipeline(pipelineId, 'retry');
|
|
130
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'retry' });
|
|
131
|
+
// Re-execute with feedback
|
|
132
|
+
startExecution(pipelineId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// No verdict — treat as failure
|
|
137
|
+
transitionPipeline(pipelineId, 'failed');
|
|
138
|
+
updatePipeline(pipelineId, { error: 'Review did not produce a verdict.json file' });
|
|
139
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'failed' });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export async function resumeFromStuck(pipelineId) {
|
|
144
|
+
const pipeline = getPipeline(pipelineId);
|
|
145
|
+
if (!pipeline)
|
|
146
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
147
|
+
if (pipeline.state !== 'stuck')
|
|
148
|
+
throw new Error('Pipeline not in stuck state');
|
|
149
|
+
transitionPipeline(pipelineId, 'executing');
|
|
150
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'executing' });
|
|
151
|
+
await startExecution(pipelineId);
|
|
152
|
+
}
|
|
153
|
+
export function abortPipeline(pipelineId) {
|
|
154
|
+
const pipeline = getPipeline(pipelineId);
|
|
155
|
+
if (!pipeline)
|
|
156
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
157
|
+
if (pipeline.state === 'done' || pipeline.state === 'failed') {
|
|
158
|
+
throw new Error('Pipeline is already ' + pipeline.state);
|
|
159
|
+
}
|
|
160
|
+
// Kill active session if any
|
|
161
|
+
if (pipeline.activeSessionId) {
|
|
162
|
+
try {
|
|
163
|
+
sessions.kill(pipeline.activeSessionId);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Session may already be gone
|
|
167
|
+
}
|
|
168
|
+
updatePipeline(pipelineId, { activeSessionId: undefined });
|
|
169
|
+
}
|
|
170
|
+
transitionPipeline(pipelineId, 'failed');
|
|
171
|
+
updatePipeline(pipelineId, { error: 'Aborted by user' });
|
|
172
|
+
emitEvent(pipelineId, 'pipeline-state-changed', { state: 'failed' });
|
|
173
|
+
}
|
|
174
|
+
async function runAgentPhase(pipelineId, prompt, model, cwd, onComplete) {
|
|
175
|
+
const args = [...buildClaudeArgs(model), prompt];
|
|
176
|
+
let output = '';
|
|
177
|
+
const session = sessions.create({
|
|
178
|
+
type: 'repo',
|
|
179
|
+
repoPath: cwd,
|
|
180
|
+
cwd,
|
|
181
|
+
command: 'claude',
|
|
182
|
+
args,
|
|
183
|
+
displayName: `belayer-${pipelineId.slice(0, 8)}`,
|
|
184
|
+
});
|
|
185
|
+
updatePipeline(pipelineId, { activeSessionId: session.id });
|
|
186
|
+
// Monitor for completion via PTY exit
|
|
187
|
+
const ptySession = sessions.get(session.id);
|
|
188
|
+
if (ptySession) {
|
|
189
|
+
// Collect output
|
|
190
|
+
const dataDisposable = ptySession.pty.onData((data) => {
|
|
191
|
+
output += data;
|
|
192
|
+
emitEvent(pipelineId, 'pipeline-output', { chunk: data });
|
|
193
|
+
});
|
|
194
|
+
ptySession.pty.onExit(() => {
|
|
195
|
+
dataDisposable.dispose();
|
|
196
|
+
updatePipeline(pipelineId, { activeSessionId: undefined });
|
|
197
|
+
onComplete(output);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class TextSource {
|
|
2
|
+
name = 'text';
|
|
3
|
+
canHandle(input) {
|
|
4
|
+
return input.trim().length > 0;
|
|
5
|
+
}
|
|
6
|
+
async fetch(input) {
|
|
7
|
+
const lines = input.trim().split('\n').map((l) => l.trim());
|
|
8
|
+
const title = lines[0] || '';
|
|
9
|
+
const description = lines.length > 1 ? lines.slice(1).join('\n') : title;
|
|
10
|
+
return {
|
|
11
|
+
source: 'text',
|
|
12
|
+
title,
|
|
13
|
+
description,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const sources = [new TextSource()];
|
|
18
|
+
export function resolveTaskSource(input) {
|
|
19
|
+
if (!input.trim()) {
|
|
20
|
+
throw new Error('No task source can handle empty input');
|
|
21
|
+
}
|
|
22
|
+
for (const source of sources) {
|
|
23
|
+
if (source.canHandle(input))
|
|
24
|
+
return source;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('No task source can handle this input');
|
|
27
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { VALID_TRANSITIONS } from './types.js';
|
|
6
|
+
const pipelines = new Map();
|
|
7
|
+
let pipelinesDir = path.join(os.homedir(), '.config', 'claude-remote-cli', 'pipelines');
|
|
8
|
+
export function setPipelinesDir(dir) {
|
|
9
|
+
pipelinesDir = dir;
|
|
10
|
+
}
|
|
11
|
+
function ensureDir() {
|
|
12
|
+
if (!fs.existsSync(pipelinesDir)) {
|
|
13
|
+
fs.mkdirSync(pipelinesDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function persist(pipeline) {
|
|
17
|
+
ensureDir();
|
|
18
|
+
const filePath = path.join(pipelinesDir, pipeline.id + '.json');
|
|
19
|
+
fs.writeFileSync(filePath, JSON.stringify(pipeline, null, 2), 'utf8');
|
|
20
|
+
}
|
|
21
|
+
export function createPipeline(task, config) {
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
const pipeline = {
|
|
24
|
+
id: crypto.randomBytes(8).toString('hex'),
|
|
25
|
+
state: 'intake',
|
|
26
|
+
task,
|
|
27
|
+
config,
|
|
28
|
+
createdAt: now,
|
|
29
|
+
updatedAt: now,
|
|
30
|
+
attempts: 0,
|
|
31
|
+
maxAttempts: config.maxAttempts,
|
|
32
|
+
verdicts: [],
|
|
33
|
+
};
|
|
34
|
+
pipelines.set(pipeline.id, pipeline);
|
|
35
|
+
persist(pipeline);
|
|
36
|
+
return pipeline;
|
|
37
|
+
}
|
|
38
|
+
export function getPipeline(id) {
|
|
39
|
+
return pipelines.get(id) ?? null;
|
|
40
|
+
}
|
|
41
|
+
export function listPipelines() {
|
|
42
|
+
return Array.from(pipelines.values()).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
43
|
+
}
|
|
44
|
+
export function transitionPipeline(id, newState) {
|
|
45
|
+
const pipeline = pipelines.get(id);
|
|
46
|
+
if (!pipeline)
|
|
47
|
+
throw new Error('Pipeline not found: ' + id);
|
|
48
|
+
const allowed = VALID_TRANSITIONS[pipeline.state];
|
|
49
|
+
if (!allowed || !allowed.includes(newState)) {
|
|
50
|
+
throw new Error(`Invalid transition from ${pipeline.state} to ${newState}`);
|
|
51
|
+
}
|
|
52
|
+
pipeline.state = newState;
|
|
53
|
+
pipeline.updatedAt = new Date().toISOString();
|
|
54
|
+
if (newState === 'retry') {
|
|
55
|
+
pipeline.attempts += 1;
|
|
56
|
+
}
|
|
57
|
+
pipelines.set(id, pipeline);
|
|
58
|
+
persist(pipeline);
|
|
59
|
+
return pipeline;
|
|
60
|
+
}
|
|
61
|
+
export function updatePipeline(id, updates) {
|
|
62
|
+
const pipeline = pipelines.get(id);
|
|
63
|
+
if (!pipeline)
|
|
64
|
+
throw new Error('Pipeline not found: ' + id);
|
|
65
|
+
Object.assign(pipeline, updates);
|
|
66
|
+
pipeline.updatedAt = new Date().toISOString();
|
|
67
|
+
pipelines.set(id, pipeline);
|
|
68
|
+
persist(pipeline);
|
|
69
|
+
return pipeline;
|
|
70
|
+
}
|
|
71
|
+
export function deletePipeline(id) {
|
|
72
|
+
pipelines.delete(id);
|
|
73
|
+
const filePath = path.join(pipelinesDir, id + '.json');
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(filePath);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// File may not exist
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function clearPipelines() {
|
|
82
|
+
pipelines.clear();
|
|
83
|
+
}
|
|
84
|
+
export function loadAllPipelines() {
|
|
85
|
+
ensureDir();
|
|
86
|
+
const files = fs.readdirSync(pipelinesDir).filter((f) => f.endsWith('.json'));
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
try {
|
|
89
|
+
const raw = fs.readFileSync(path.join(pipelinesDir, file), 'utf8');
|
|
90
|
+
const pipeline = JSON.parse(raw);
|
|
91
|
+
pipelines.set(pipeline.id, pipeline);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Skip malformed files
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { updatePipeline, getPipeline } from './pipeline.js';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function buildPrTitle(task) {
|
|
6
|
+
return `feat: ${task.title}`;
|
|
7
|
+
}
|
|
8
|
+
export function buildPrBody(task, prdContent, attempts) {
|
|
9
|
+
return `## ${task.title}
|
|
10
|
+
|
|
11
|
+
${task.description}
|
|
12
|
+
|
|
13
|
+
**Automated by Belayer** — completed in ${attempts} attempt${attempts !== 1 ? 's' : ''}.
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary>PRD</summary>
|
|
17
|
+
|
|
18
|
+
${prdContent}
|
|
19
|
+
|
|
20
|
+
</details>
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
export async function createPullRequest(pipelineId) {
|
|
24
|
+
const pipeline = getPipeline(pipelineId);
|
|
25
|
+
if (!pipeline)
|
|
26
|
+
throw new Error('Pipeline not found: ' + pipelineId);
|
|
27
|
+
const cwd = pipeline.worktreePath || pipeline.config.targetRepo;
|
|
28
|
+
const title = buildPrTitle(pipeline.task);
|
|
29
|
+
const body = buildPrBody(pipeline.task, pipeline.prdContent || '', pipeline.attempts);
|
|
30
|
+
// Push branch
|
|
31
|
+
if (pipeline.config.autoPush && pipeline.branchName) {
|
|
32
|
+
await execFileAsync('git', ['push', '-u', 'origin', pipeline.branchName], { cwd });
|
|
33
|
+
}
|
|
34
|
+
// Create PR via gh CLI
|
|
35
|
+
const { stdout } = await execFileAsync('gh', [
|
|
36
|
+
'pr', 'create',
|
|
37
|
+
'--title', title,
|
|
38
|
+
'--body', body,
|
|
39
|
+
'--base', pipeline.config.baseBranch,
|
|
40
|
+
'--json', 'number,url',
|
|
41
|
+
], { cwd });
|
|
42
|
+
const parsed = JSON.parse(stdout);
|
|
43
|
+
const result = { prNumber: parsed.number, prUrl: parsed.url };
|
|
44
|
+
updatePipeline(pipelineId, result);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export async function checkCiStatus(pipelineId) {
|
|
48
|
+
const pipeline = getPipeline(pipelineId);
|
|
49
|
+
if (!pipeline || !pipeline.prNumber)
|
|
50
|
+
throw new Error('Pipeline has no PR');
|
|
51
|
+
const cwd = pipeline.worktreePath || pipeline.config.targetRepo;
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execFileAsync('gh', [
|
|
54
|
+
'pr', 'checks', String(pipeline.prNumber),
|
|
55
|
+
'--json', 'name,bucket',
|
|
56
|
+
], { cwd });
|
|
57
|
+
const checks = JSON.parse(stdout);
|
|
58
|
+
if (checks.length === 0)
|
|
59
|
+
return 'success'; // No CI configured
|
|
60
|
+
if (checks.every((c) => c.bucket === 'pass'))
|
|
61
|
+
return 'success';
|
|
62
|
+
if (checks.some((c) => c.bucket === 'fail'))
|
|
63
|
+
return 'failure';
|
|
64
|
+
return 'pending';
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 'pending';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export function buildBrainstormPrompt(task) {
|
|
2
|
+
let prompt = `You are a product engineer. Your goal is to brainstorm a design for the following task and produce a PRD (Product Requirements Document).
|
|
3
|
+
|
|
4
|
+
## Task
|
|
5
|
+
|
|
6
|
+
**Title:** ${task.title}
|
|
7
|
+
|
|
8
|
+
**Description:**
|
|
9
|
+
${task.description}
|
|
10
|
+
`;
|
|
11
|
+
if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
12
|
+
prompt += `\n**Acceptance Criteria:**\n${task.acceptanceCriteria.map((c) => '- ' + c).join('\n')}\n`;
|
|
13
|
+
}
|
|
14
|
+
prompt += `
|
|
15
|
+
## Instructions
|
|
16
|
+
|
|
17
|
+
1. Analyze the task requirements
|
|
18
|
+
2. Brainstorm the design approach — consider trade-offs, alternatives, and edge cases
|
|
19
|
+
3. Produce a PRD with:
|
|
20
|
+
- **Goal** (one sentence)
|
|
21
|
+
- **Background** (context and motivation)
|
|
22
|
+
- **Requirements** (numbered list of specific requirements)
|
|
23
|
+
- **Acceptance Criteria** (testable criteria for each requirement)
|
|
24
|
+
- **Technical Approach** (high-level architecture decisions)
|
|
25
|
+
- **Non-Goals** (what this does NOT include)
|
|
26
|
+
|
|
27
|
+
Output the PRD as a markdown document.`;
|
|
28
|
+
return prompt;
|
|
29
|
+
}
|
|
30
|
+
export function buildPlanPrompt(task, prdContent) {
|
|
31
|
+
return `You are a senior engineer creating an implementation plan. You have a PRD to implement.
|
|
32
|
+
|
|
33
|
+
## Task
|
|
34
|
+
|
|
35
|
+
**Title:** ${task.title}
|
|
36
|
+
|
|
37
|
+
## PRD
|
|
38
|
+
|
|
39
|
+
${prdContent}
|
|
40
|
+
|
|
41
|
+
## Instructions
|
|
42
|
+
|
|
43
|
+
Create a detailed implementation plan with bite-sized tasks. For each task:
|
|
44
|
+
1. List exact files to create or modify
|
|
45
|
+
2. Describe the changes in detail
|
|
46
|
+
3. Include test requirements
|
|
47
|
+
4. Keep each task small enough to complete in one commit
|
|
48
|
+
|
|
49
|
+
Use the \`/harness:plan\` format: numbered tasks with file paths, step-by-step instructions, and commit messages.
|
|
50
|
+
|
|
51
|
+
Output the plan as a markdown document.`;
|
|
52
|
+
}
|
|
53
|
+
export function buildExecutionPrompt(task, planContent, previousVerdicts) {
|
|
54
|
+
let prompt = `You are implementing a feature according to a plan. Follow the plan exactly, task by task. Use TDD where applicable. Commit after each task.
|
|
55
|
+
|
|
56
|
+
## Task
|
|
57
|
+
|
|
58
|
+
**Title:** ${task.title}
|
|
59
|
+
|
|
60
|
+
## Plan
|
|
61
|
+
|
|
62
|
+
${planContent}
|
|
63
|
+
`;
|
|
64
|
+
if (previousVerdicts && previousVerdicts.length > 0) {
|
|
65
|
+
prompt += `\n## PREVIOUS REVIEW FEEDBACK\n\nThe following review(s) found issues that must be fixed:\n\n`;
|
|
66
|
+
for (const verdict of previousVerdicts) {
|
|
67
|
+
prompt += `### Review: ${verdict.goalName}\n`;
|
|
68
|
+
prompt += `**Result:** ${verdict.pass ? 'PASS' : 'FAIL'}\n`;
|
|
69
|
+
prompt += `**Summary:** ${verdict.summary}\n`;
|
|
70
|
+
for (const cr of verdict.criteriaResults) {
|
|
71
|
+
if (!cr.met) {
|
|
72
|
+
prompt += `- **FAILED:** ${cr.criterion}${cr.reason ? ' — ' + cr.reason : ''}\n`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (verdict.suggestions && verdict.suggestions.length > 0) {
|
|
76
|
+
prompt += `**Suggestions:** ${verdict.suggestions.join('; ')}\n`;
|
|
77
|
+
}
|
|
78
|
+
prompt += '\n';
|
|
79
|
+
}
|
|
80
|
+
prompt += `Fix ALL failed criteria before marking the work as complete.\n`;
|
|
81
|
+
}
|
|
82
|
+
prompt += `\n## Completion
|
|
83
|
+
|
|
84
|
+
When all tasks are done:
|
|
85
|
+
1. Run all tests and ensure they pass
|
|
86
|
+
2. Create a file \`.belayer/COMPLETE\` containing "done"
|
|
87
|
+
`;
|
|
88
|
+
return prompt;
|
|
89
|
+
}
|
|
90
|
+
export function buildReviewPrompt(task, worktreePath) {
|
|
91
|
+
let prompt = `You are an independent code reviewer. Review the implementation in this worktree against the task requirements.
|
|
92
|
+
|
|
93
|
+
## Task
|
|
94
|
+
|
|
95
|
+
**Title:** ${task.title}
|
|
96
|
+
**Description:** ${task.description}
|
|
97
|
+
`;
|
|
98
|
+
if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
99
|
+
prompt += `\n**Acceptance Criteria:**\n${task.acceptanceCriteria.map((c) => '- ' + c).join('\n')}\n`;
|
|
100
|
+
}
|
|
101
|
+
prompt += `
|
|
102
|
+
## Instructions
|
|
103
|
+
|
|
104
|
+
1. Read the code changes (check git diff against the base branch)
|
|
105
|
+
2. Run the tests
|
|
106
|
+
3. Evaluate each acceptance criterion
|
|
107
|
+
4. Write a structured verdict
|
|
108
|
+
|
|
109
|
+
## Output
|
|
110
|
+
|
|
111
|
+
Write the verdict to \`.belayer/verdict.json\` with this exact shape:
|
|
112
|
+
|
|
113
|
+
\`\`\`json
|
|
114
|
+
{
|
|
115
|
+
"goalName": "${task.title}",
|
|
116
|
+
"pass": true or false,
|
|
117
|
+
"criteriaResults": [
|
|
118
|
+
{ "criterion": "description", "met": true or false, "reason": "explanation" }
|
|
119
|
+
],
|
|
120
|
+
"summary": "overall assessment",
|
|
121
|
+
"suggestions": ["optional improvement suggestions"],
|
|
122
|
+
"timestamp": "ISO 8601 timestamp"
|
|
123
|
+
}
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
Be thorough but fair. Only fail criteria that are clearly not met.`;
|
|
127
|
+
return prompt;
|
|
128
|
+
}
|
|
129
|
+
export function buildStuckPrompt(task, verdicts, attempts) {
|
|
130
|
+
let prompt = `## STUCK REPORT
|
|
131
|
+
|
|
132
|
+
The pipeline for "${task.title}" is stuck after ${attempts} failed attempt(s).
|
|
133
|
+
|
|
134
|
+
## Verdict History
|
|
135
|
+
|
|
136
|
+
`;
|
|
137
|
+
for (let i = 0; i < verdicts.length; i++) {
|
|
138
|
+
const v = verdicts[i];
|
|
139
|
+
prompt += `### Attempt ${i + 1}: ${v.pass ? 'PASS' : 'FAIL'}\n`;
|
|
140
|
+
prompt += `${v.summary}\n`;
|
|
141
|
+
for (const cr of v.criteriaResults) {
|
|
142
|
+
prompt += `- ${cr.met ? '\u2713' : '\u2717'} ${cr.criterion}${cr.reason ? ': ' + cr.reason : ''}\n`;
|
|
143
|
+
}
|
|
144
|
+
prompt += '\n';
|
|
145
|
+
}
|
|
146
|
+
prompt += `## Action Required
|
|
147
|
+
|
|
148
|
+
A human must review and decide:
|
|
149
|
+
1. **Fix and resume** — address the issues manually, then resume the pipeline
|
|
150
|
+
2. **Skip review** — force the pipeline to proceed to PR creation
|
|
151
|
+
3. **Abort** — cancel this pipeline
|
|
152
|
+
`;
|
|
153
|
+
return prompt;
|
|
154
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const DEFAULT_PIPELINE_CONFIG = {
|
|
2
|
+
models: { brainstorm: 'opus', execute: 'opus', review: 'sonnet' },
|
|
3
|
+
maxAttempts: 3,
|
|
4
|
+
autoPush: true,
|
|
5
|
+
targetRepo: '',
|
|
6
|
+
baseBranch: 'main',
|
|
7
|
+
};
|
|
8
|
+
export const VALID_TRANSITIONS = {
|
|
9
|
+
intake: ['brainstorming', 'failed'],
|
|
10
|
+
brainstorming: ['prd_review', 'failed'],
|
|
11
|
+
prd_review: ['planning', 'brainstorming', 'failed'],
|
|
12
|
+
planning: ['plan_review', 'failed'],
|
|
13
|
+
plan_review: ['executing', 'planning', 'failed'],
|
|
14
|
+
executing: ['reviewing', 'failed'],
|
|
15
|
+
reviewing: ['pr_created', 'retry', 'stuck', 'failed'],
|
|
16
|
+
retry: ['executing', 'stuck', 'failed'],
|
|
17
|
+
stuck: ['executing', 'failed'],
|
|
18
|
+
pr_created: ['ci_monitoring', 'done', 'failed'],
|
|
19
|
+
ci_monitoring: ['ready_for_review', 'failed'],
|
|
20
|
+
ready_for_review: ['done', 'failed'],
|
|
21
|
+
done: [],
|
|
22
|
+
failed: ['intake'],
|
|
23
|
+
};
|