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.
Files changed (506) hide show
  1. package/README.md +259 -0
  2. package/dist/bin/claude-remote-cli.js +390 -0
  3. package/dist/bin/relay-ide.js +390 -0
  4. package/dist/frontend/assets/abap-BdImnpbu.js +1 -0
  5. package/dist/frontend/assets/actionscript-3-CoDkCxhg.js +1 -0
  6. package/dist/frontend/assets/ada-bCR0ucgS.js +1 -0
  7. package/dist/frontend/assets/andromeeda-C4gqWexZ.js +1 -0
  8. package/dist/frontend/assets/angular-html-DA-rfuFy.js +1 -0
  9. package/dist/frontend/assets/angular-ts-BrjP3tb8.js +1 -0
  10. package/dist/frontend/assets/apache-Pmp26Uib.js +1 -0
  11. package/dist/frontend/assets/apex-D8_7TLub.js +1 -0
  12. package/dist/frontend/assets/apl-CORt7UWP.js +1 -0
  13. package/dist/frontend/assets/applescript-Co6uUVPk.js +1 -0
  14. package/dist/frontend/assets/ara-BRHolxvo.js +1 -0
  15. package/dist/frontend/assets/asciidoc-Ve4PFQV2.js +1 -0
  16. package/dist/frontend/assets/asm-D_Q5rh1f.js +1 -0
  17. package/dist/frontend/assets/astro-HNnZUWAn.js +1 -0
  18. package/dist/frontend/assets/aurora-x-D-2ljcwZ.js +1 -0
  19. package/dist/frontend/assets/awk-DMzUqQB5.js +1 -0
  20. package/dist/frontend/assets/ayu-dark-DYE7WIF3.js +1 -0
  21. package/dist/frontend/assets/ayu-light-BA47KaF1.js +1 -0
  22. package/dist/frontend/assets/ayu-mirage-32ctXXKs.js +1 -0
  23. package/dist/frontend/assets/ballerina-BFfxhgS-.js +1 -0
  24. package/dist/frontend/assets/bat-BkioyH1T.js +1 -0
  25. package/dist/frontend/assets/beancount-k_qm7-4y.js +1 -0
  26. package/dist/frontend/assets/berry-uYugtg8r.js +1 -0
  27. package/dist/frontend/assets/bibtex-CHM0blh-.js +1 -0
  28. package/dist/frontend/assets/bicep-Bmn6On1c.js +1 -0
  29. package/dist/frontend/assets/bird2-BIv1doCn.js +1 -0
  30. package/dist/frontend/assets/blade-BjGOyj-B.js +1 -0
  31. package/dist/frontend/assets/bsl-BO_Y6i37.js +1 -0
  32. package/dist/frontend/assets/c-BIGW1oBm.js +1 -0
  33. package/dist/frontend/assets/c3-eo99z4R2.js +1 -0
  34. package/dist/frontend/assets/cadence-Bv_4Rxtq.js +1 -0
  35. package/dist/frontend/assets/cairo-KRGpt6FW.js +1 -0
  36. package/dist/frontend/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  37. package/dist/frontend/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  38. package/dist/frontend/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  39. package/dist/frontend/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  40. package/dist/frontend/assets/clarity-D53aC0YG.js +1 -0
  41. package/dist/frontend/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/frontend/assets/cmake-D1j8_8rp.js +1 -0
  43. package/dist/frontend/assets/cobol-nBiQ_Alo.js +1 -0
  44. package/dist/frontend/assets/codeowners-Bp6g37R7.js +1 -0
  45. package/dist/frontend/assets/codeql-DsOJ9woJ.js +1 -0
  46. package/dist/frontend/assets/coffee-Ch7k5sss.js +1 -0
  47. package/dist/frontend/assets/common-lisp-Cg-RD9OK.js +1 -0
  48. package/dist/frontend/assets/coq-DkFqJrB1.js +1 -0
  49. package/dist/frontend/assets/cpp-CofmeUqb.js +1 -0
  50. package/dist/frontend/assets/crystal-DNxU26gB.js +1 -0
  51. package/dist/frontend/assets/csharp-COcwbKMJ.js +1 -0
  52. package/dist/frontend/assets/css-CLj8gQPS.js +1 -0
  53. package/dist/frontend/assets/csv-fuZLfV_i.js +1 -0
  54. package/dist/frontend/assets/cue-D82EKSYY.js +1 -0
  55. package/dist/frontend/assets/cypher-COkxafJQ.js +1 -0
  56. package/dist/frontend/assets/d-85-TOEBH.js +1 -0
  57. package/dist/frontend/assets/dark-plus-C3mMm8J8.js +1 -0
  58. package/dist/frontend/assets/dart-bE4Kk8sk.js +1 -0
  59. package/dist/frontend/assets/dax-CEL-wOlO.js +1 -0
  60. package/dist/frontend/assets/desktop-BmXAJ9_W.js +1 -0
  61. package/dist/frontend/assets/diff-D97Zzqfu.js +1 -0
  62. package/dist/frontend/assets/docker-BcOcwvcX.js +1 -0
  63. package/dist/frontend/assets/dotenv-Da5cRb03.js +1 -0
  64. package/dist/frontend/assets/dracula-BzJJZx-M.js +1 -0
  65. package/dist/frontend/assets/dracula-soft-BXkSAIEj.js +1 -0
  66. package/dist/frontend/assets/dream-maker-BtqSS_iP.js +1 -0
  67. package/dist/frontend/assets/edge-FbVlp4U3.js +1 -0
  68. package/dist/frontend/assets/elixir-CkH2-t6x.js +1 -0
  69. package/dist/frontend/assets/elm-DbKCFpqz.js +1 -0
  70. package/dist/frontend/assets/emacs-lisp-CXvaQtF9.js +1 -0
  71. package/dist/frontend/assets/erb-BYCe7drp.js +1 -0
  72. package/dist/frontend/assets/erlang-DsQrWhSR.js +1 -0
  73. package/dist/frontend/assets/everforest-dark-BgDCqdQA.js +1 -0
  74. package/dist/frontend/assets/everforest-light-C8M2exoo.js +1 -0
  75. package/dist/frontend/assets/fennel-BYunw83y.js +1 -0
  76. package/dist/frontend/assets/fish-BvzEVeQv.js +1 -0
  77. package/dist/frontend/assets/fluent-C4IJs8-o.js +1 -0
  78. package/dist/frontend/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  79. package/dist/frontend/assets/fortran-free-form-BxgE0vQu.js +1 -0
  80. package/dist/frontend/assets/fsharp-CXgrBDvD.js +1 -0
  81. package/dist/frontend/assets/gdresource-BOOCDP_w.js +1 -0
  82. package/dist/frontend/assets/gdscript-C5YyOfLZ.js +1 -0
  83. package/dist/frontend/assets/gdshader-DkwncUOv.js +1 -0
  84. package/dist/frontend/assets/genie-D0YGMca9.js +1 -0
  85. package/dist/frontend/assets/gherkin-DyxjwDmM.js +1 -0
  86. package/dist/frontend/assets/git-commit-F4YmCXRG.js +1 -0
  87. package/dist/frontend/assets/git-rebase-r7XF79zn.js +1 -0
  88. package/dist/frontend/assets/github-dark-DHJKELXO.js +1 -0
  89. package/dist/frontend/assets/github-dark-default-Cuk6v7N8.js +1 -0
  90. package/dist/frontend/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  91. package/dist/frontend/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  92. package/dist/frontend/assets/github-light-DAi9KRSo.js +1 -0
  93. package/dist/frontend/assets/github-light-default-D7oLnXFd.js +1 -0
  94. package/dist/frontend/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  95. package/dist/frontend/assets/gleam-BspZqrRM.js +1 -0
  96. package/dist/frontend/assets/glimmer-js-ByusRIyA.js +1 -0
  97. package/dist/frontend/assets/glimmer-ts-BfAWNZQY.js +1 -0
  98. package/dist/frontend/assets/glsl-DplSGwfg.js +1 -0
  99. package/dist/frontend/assets/gn-n2N0HUVH.js +1 -0
  100. package/dist/frontend/assets/gnuplot-DdkO51Og.js +1 -0
  101. package/dist/frontend/assets/go-C27-OAKa.js +1 -0
  102. package/dist/frontend/assets/graphql-ChdNCCLP.js +1 -0
  103. package/dist/frontend/assets/groovy-gcz8RCvz.js +1 -0
  104. package/dist/frontend/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  105. package/dist/frontend/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  106. package/dist/frontend/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  107. package/dist/frontend/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  108. package/dist/frontend/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  109. package/dist/frontend/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  110. package/dist/frontend/assets/hack-i7_Ulhet.js +1 -0
  111. package/dist/frontend/assets/haml-D5jkg6IW.js +1 -0
  112. package/dist/frontend/assets/handlebars-BpdQsYii.js +1 -0
  113. package/dist/frontend/assets/haskell-Df6bDoY_.js +1 -0
  114. package/dist/frontend/assets/haxe-CzTSHFRz.js +1 -0
  115. package/dist/frontend/assets/hcl-BWvSN4gD.js +1 -0
  116. package/dist/frontend/assets/hjson-D5-asLiD.js +1 -0
  117. package/dist/frontend/assets/hlsl-D3lLCCz7.js +1 -0
  118. package/dist/frontend/assets/horizon-BUw7H-hv.js +1 -0
  119. package/dist/frontend/assets/horizon-bright-CUuTKBJd.js +1 -0
  120. package/dist/frontend/assets/houston-DnULxvSX.js +1 -0
  121. package/dist/frontend/assets/html-derivative-DlHx6ybY.js +1 -0
  122. package/dist/frontend/assets/html-pp8916En.js +1 -0
  123. package/dist/frontend/assets/http-jrhK8wxY.js +1 -0
  124. package/dist/frontend/assets/hurl-irOxFIW8.js +1 -0
  125. package/dist/frontend/assets/hxml-Bvhsp5Yf.js +1 -0
  126. package/dist/frontend/assets/hy-DFXneXwc.js +1 -0
  127. package/dist/frontend/assets/imba-DGztddWO.js +1 -0
  128. package/dist/frontend/assets/ini-BEwlwnbL.js +1 -0
  129. package/dist/frontend/assets/java-CylS5w8V.js +1 -0
  130. package/dist/frontend/assets/javascript-wDzz0qaB.js +1 -0
  131. package/dist/frontend/assets/jinja-f2NsQr07.js +1 -0
  132. package/dist/frontend/assets/jison-wvAkD_A8.js +1 -0
  133. package/dist/frontend/assets/json-Cp-IABpG.js +1 -0
  134. package/dist/frontend/assets/json5-C9tS-k6U.js +1 -0
  135. package/dist/frontend/assets/jsonc-Des-eS-w.js +1 -0
  136. package/dist/frontend/assets/jsonl-DcaNXYhu.js +1 -0
  137. package/dist/frontend/assets/jsonnet-DFQXde-d.js +1 -0
  138. package/dist/frontend/assets/jssm-C2t-YnRu.js +1 -0
  139. package/dist/frontend/assets/jsx-g9-lgVsj.js +1 -0
  140. package/dist/frontend/assets/julia-CxzCAyBv.js +1 -0
  141. package/dist/frontend/assets/just-VxiPbLrw.js +1 -0
  142. package/dist/frontend/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  143. package/dist/frontend/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  144. package/dist/frontend/assets/kanagawa-wave-DWedfzmr.js +1 -0
  145. package/dist/frontend/assets/kdl-DV7GczEv.js +1 -0
  146. package/dist/frontend/assets/kotlin-BdnUsdx6.js +1 -0
  147. package/dist/frontend/assets/kusto-wEQ09or8.js +1 -0
  148. package/dist/frontend/assets/laserwave-DUszq2jm.js +1 -0
  149. package/dist/frontend/assets/latex-CWtU0Tv5.js +1 -0
  150. package/dist/frontend/assets/lean-BZvkOJ9d.js +1 -0
  151. package/dist/frontend/assets/less-B1dDrJ26.js +1 -0
  152. package/dist/frontend/assets/light-plus-B7mTdjB0.js +1 -0
  153. package/dist/frontend/assets/liquid-C0sCDyMI.js +1 -0
  154. package/dist/frontend/assets/llvm-DjAJT7YJ.js +1 -0
  155. package/dist/frontend/assets/log-2UxHyX5q.js +1 -0
  156. package/dist/frontend/assets/logo-BtOb2qkB.js +1 -0
  157. package/dist/frontend/assets/lua-BaeVxFsk.js +1 -0
  158. package/dist/frontend/assets/luau-C-HG3fhB.js +1 -0
  159. package/dist/frontend/assets/main-CL5_Wlhv.css +32 -0
  160. package/dist/frontend/assets/main-Czet4Z1x.js +371 -0
  161. package/dist/frontend/assets/make-CHLpvVh8.js +1 -0
  162. package/dist/frontend/assets/markdown-Cvjx9yec.js +1 -0
  163. package/dist/frontend/assets/marko-DjSrsDqO.js +1 -0
  164. package/dist/frontend/assets/material-theme-D5KoaKCx.js +1 -0
  165. package/dist/frontend/assets/material-theme-darker-BfHTSMKl.js +1 -0
  166. package/dist/frontend/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  167. package/dist/frontend/assets/material-theme-ocean-CyktbL80.js +1 -0
  168. package/dist/frontend/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  169. package/dist/frontend/assets/matlab-D7o27uSR.js +1 -0
  170. package/dist/frontend/assets/mdc-DTYItulj.js +1 -0
  171. package/dist/frontend/assets/mdx-Cmh6b_Ma.js +1 -0
  172. package/dist/frontend/assets/mermaid-mWjccvbQ.js +1 -0
  173. package/dist/frontend/assets/min-dark-CafNBF8u.js +1 -0
  174. package/dist/frontend/assets/min-light-CTRr51gU.js +1 -0
  175. package/dist/frontend/assets/mipsasm-CKIfxQSi.js +1 -0
  176. package/dist/frontend/assets/mojo-rZm6bMo-.js +1 -0
  177. package/dist/frontend/assets/monokai-D4h5O-jR.js +1 -0
  178. package/dist/frontend/assets/moonbit-_H4v1dQx.js +1 -0
  179. package/dist/frontend/assets/move-IF9eRakj.js +1 -0
  180. package/dist/frontend/assets/narrat-DRg8JJMk.js +1 -0
  181. package/dist/frontend/assets/nextflow-C-mBbutL.js +1 -0
  182. package/dist/frontend/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  183. package/dist/frontend/assets/nginx-BpAMiNFr.js +1 -0
  184. package/dist/frontend/assets/night-owl-C39BiMTA.js +1 -0
  185. package/dist/frontend/assets/night-owl-light-CMTm3GFP.js +1 -0
  186. package/dist/frontend/assets/nim-BIad80T-.js +1 -0
  187. package/dist/frontend/assets/nix-CwoSXNpI.js +1 -0
  188. package/dist/frontend/assets/nord-Ddv68eIx.js +1 -0
  189. package/dist/frontend/assets/nushell-Cz2AlsmD.js +1 -0
  190. package/dist/frontend/assets/objective-c-DXmwc3jG.js +1 -0
  191. package/dist/frontend/assets/objective-cpp-CLxacb5B.js +1 -0
  192. package/dist/frontend/assets/ocaml-C0hk2d4L.js +1 -0
  193. package/dist/frontend/assets/odin-BBf5iR-q.js +1 -0
  194. package/dist/frontend/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  195. package/dist/frontend/assets/one-light-C3Wv6jpd.js +1 -0
  196. package/dist/frontend/assets/openscad-C4EeE6gA.js +1 -0
  197. package/dist/frontend/assets/pascal-D93ZcfNL.js +1 -0
  198. package/dist/frontend/assets/perl-NvoQZIq0.js +1 -0
  199. package/dist/frontend/assets/php-R6g_5hLQ.js +1 -0
  200. package/dist/frontend/assets/pkl-u5AG7uiY.js +1 -0
  201. package/dist/frontend/assets/plastic-3e1v2bzS.js +1 -0
  202. package/dist/frontend/assets/plsql-ChMvpjG-.js +1 -0
  203. package/dist/frontend/assets/po-BTJTHyun.js +1 -0
  204. package/dist/frontend/assets/poimandres-CS3Unz2-.js +1 -0
  205. package/dist/frontend/assets/polar-C0HS_06l.js +1 -0
  206. package/dist/frontend/assets/postcss-CXtECtnM.js +1 -0
  207. package/dist/frontend/assets/powerquery-CEu0bR-o.js +1 -0
  208. package/dist/frontend/assets/powershell-Dpen1YoG.js +1 -0
  209. package/dist/frontend/assets/prisma-Dd19v3D-.js +1 -0
  210. package/dist/frontend/assets/prolog-CbFg5uaA.js +1 -0
  211. package/dist/frontend/assets/proto-C7zT0LnQ.js +1 -0
  212. package/dist/frontend/assets/pug-DKIMFp6K.js +1 -0
  213. package/dist/frontend/assets/puppet-BMWR74SV.js +1 -0
  214. package/dist/frontend/assets/purescript-CklMAg4u.js +1 -0
  215. package/dist/frontend/assets/python-B6aJPvgy.js +1 -0
  216. package/dist/frontend/assets/qml-3beO22l8.js +1 -0
  217. package/dist/frontend/assets/qmldir-C8lEn-DE.js +1 -0
  218. package/dist/frontend/assets/qss-IeuSbFQv.js +1 -0
  219. package/dist/frontend/assets/r-Dspwwk_N.js +1 -0
  220. package/dist/frontend/assets/racket-BqYA7rlc.js +1 -0
  221. package/dist/frontend/assets/raku-DXvB9xmW.js +1 -0
  222. package/dist/frontend/assets/razor-BDqjjVU7.js +1 -0
  223. package/dist/frontend/assets/red-bN70gL4F.js +1 -0
  224. package/dist/frontend/assets/reg-C-SQnVFl.js +1 -0
  225. package/dist/frontend/assets/regexp-CDVJQ6XC.js +1 -0
  226. package/dist/frontend/assets/rel-C3B-1QV4.js +1 -0
  227. package/dist/frontend/assets/riscv-BM1_JUlF.js +1 -0
  228. package/dist/frontend/assets/ron-D8l8udqQ.js +1 -0
  229. package/dist/frontend/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  230. package/dist/frontend/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  231. package/dist/frontend/assets/rose-pine-qdsjHGoJ.js +1 -0
  232. package/dist/frontend/assets/rosmsg-BJDFO7_C.js +1 -0
  233. package/dist/frontend/assets/rst-CRjBmOyv.js +1 -0
  234. package/dist/frontend/assets/ruby-Wjq7vjNf.js +1 -0
  235. package/dist/frontend/assets/rust-B1yitclQ.js +1 -0
  236. package/dist/frontend/assets/sas-cz2c8ADy.js +1 -0
  237. package/dist/frontend/assets/sass-Cj5Yp3dK.js +1 -0
  238. package/dist/frontend/assets/scala-C151Ov-r.js +1 -0
  239. package/dist/frontend/assets/scheme-C98Dy4si.js +1 -0
  240. package/dist/frontend/assets/scss-D5BDwBP9.js +1 -0
  241. package/dist/frontend/assets/sdbl-DVxCFoDh.js +1 -0
  242. package/dist/frontend/assets/shaderlab-Dg9Lc6iA.js +1 -0
  243. package/dist/frontend/assets/shellscript-Yzrsuije.js +1 -0
  244. package/dist/frontend/assets/shellsession-BADoaaVG.js +1 -0
  245. package/dist/frontend/assets/slack-dark-BthQWCQV.js +1 -0
  246. package/dist/frontend/assets/slack-ochin-DqwNpetd.js +1 -0
  247. package/dist/frontend/assets/smalltalk-BERRCDM3.js +1 -0
  248. package/dist/frontend/assets/snazzy-light-Bw305WKR.js +1 -0
  249. package/dist/frontend/assets/solarized-dark-DXbdFlpD.js +1 -0
  250. package/dist/frontend/assets/solarized-light-L9t79GZl.js +1 -0
  251. package/dist/frontend/assets/solidity-rGO070M0.js +1 -0
  252. package/dist/frontend/assets/soy-8wufbnw4.js +1 -0
  253. package/dist/frontend/assets/sparql-rVzFXLq3.js +1 -0
  254. package/dist/frontend/assets/splunk-BtCnVYZw.js +1 -0
  255. package/dist/frontend/assets/sql-BLtJtn59.js +1 -0
  256. package/dist/frontend/assets/ssh-config-_ykCGR6B.js +1 -0
  257. package/dist/frontend/assets/stata-BH5u7GGu.js +1 -0
  258. package/dist/frontend/assets/stylus-BEDo0Tqx.js +1 -0
  259. package/dist/frontend/assets/surrealql-Bq5Q-fJD.js +1 -0
  260. package/dist/frontend/assets/svelte-Cy7k_4gC.js +1 -0
  261. package/dist/frontend/assets/swift-D82vCrfD.js +1 -0
  262. package/dist/frontend/assets/synthwave-84-CbfX1IO0.js +1 -0
  263. package/dist/frontend/assets/system-verilog-CnnmHF94.js +1 -0
  264. package/dist/frontend/assets/systemd-4A_iFExJ.js +1 -0
  265. package/dist/frontend/assets/talonscript-CkByrt1z.js +1 -0
  266. package/dist/frontend/assets/tasl-QIJgUcNo.js +1 -0
  267. package/dist/frontend/assets/tcl-dwOrl1Do.js +1 -0
  268. package/dist/frontend/assets/templ-DhtptRzy.js +1 -0
  269. package/dist/frontend/assets/terraform-BETggiCN.js +1 -0
  270. package/dist/frontend/assets/tex-idrVyKtj.js +1 -0
  271. package/dist/frontend/assets/tokyo-night-hegEt444.js +1 -0
  272. package/dist/frontend/assets/toml-vGWfd6FD.js +1 -0
  273. package/dist/frontend/assets/ts-tags-DQrlYJgV.js +1 -0
  274. package/dist/frontend/assets/tsv-B_m7g4N7.js +1 -0
  275. package/dist/frontend/assets/tsx-COt5Ahok.js +1 -0
  276. package/dist/frontend/assets/turtle-BsS91CYL.js +1 -0
  277. package/dist/frontend/assets/twig-xg9kU7Mw.js +1 -0
  278. package/dist/frontend/assets/typescript-BPQ3VLAy.js +1 -0
  279. package/dist/frontend/assets/typespec-CAFt9gP4.js +1 -0
  280. package/dist/frontend/assets/typst-DHCkPAjA.js +1 -0
  281. package/dist/frontend/assets/v-BcVCzyr7.js +1 -0
  282. package/dist/frontend/assets/vala-CsfeWuGM.js +1 -0
  283. package/dist/frontend/assets/vb-D17OF-Vu.js +1 -0
  284. package/dist/frontend/assets/verilog-BQ8w6xss.js +1 -0
  285. package/dist/frontend/assets/vesper-DU1UobuO.js +1 -0
  286. package/dist/frontend/assets/vhdl-CeAyd5Ju.js +1 -0
  287. package/dist/frontend/assets/viml-CJc9bBzg.js +1 -0
  288. package/dist/frontend/assets/vitesse-black-Bkuqu6BP.js +1 -0
  289. package/dist/frontend/assets/vitesse-dark-D0r3Knsf.js +1 -0
  290. package/dist/frontend/assets/vitesse-light-CVO1_9PV.js +1 -0
  291. package/dist/frontend/assets/vue-D2xRrEX4.js +1 -0
  292. package/dist/frontend/assets/vue-html-AaS7Mt5G.js +1 -0
  293. package/dist/frontend/assets/vue-vine-BoDAl6tE.js +1 -0
  294. package/dist/frontend/assets/vyper-CDx5xZoG.js +1 -0
  295. package/dist/frontend/assets/wasm-CG6Dc4jp.js +1 -0
  296. package/dist/frontend/assets/wasm-MzD3tlZU.js +1 -0
  297. package/dist/frontend/assets/wenyan-BV7otONQ.js +1 -0
  298. package/dist/frontend/assets/wgsl-Dx-B1_4e.js +1 -0
  299. package/dist/frontend/assets/wikitext-BhOHFoWU.js +1 -0
  300. package/dist/frontend/assets/wit-5i3qLPDT.js +1 -0
  301. package/dist/frontend/assets/wolfram-lXgVvXCa.js +1 -0
  302. package/dist/frontend/assets/xml-sdJ4AIDG.js +1 -0
  303. package/dist/frontend/assets/xsl-CtQFsRM5.js +1 -0
  304. package/dist/frontend/assets/yaml-Buea-lGh.js +1 -0
  305. package/dist/frontend/assets/zenscript-DVFEvuxE.js +1 -0
  306. package/dist/frontend/assets/zig-VOosw3JB.js +1 -0
  307. package/dist/frontend/icon-192.png +0 -0
  308. package/dist/frontend/icon-512.png +0 -0
  309. package/dist/frontend/icon.svg +8 -0
  310. package/dist/frontend/index.html +30 -0
  311. package/dist/frontend/manifest.json +25 -0
  312. package/dist/frontend/sw.js +66 -0
  313. package/dist/server/agent-events.js +39 -0
  314. package/dist/server/analytics.js +885 -0
  315. package/dist/server/auth.js +65 -0
  316. package/dist/server/belayer/executor.js +200 -0
  317. package/dist/server/belayer/intake.js +27 -0
  318. package/dist/server/belayer/pipeline.js +97 -0
  319. package/dist/server/belayer/pr-lifecycle.js +69 -0
  320. package/dist/server/belayer/prompts.js +154 -0
  321. package/dist/server/belayer/types.js +23 -0
  322. package/dist/server/branch-linker.js +137 -0
  323. package/dist/server/browser-content.js +145 -0
  324. package/dist/server/clipboard.js +63 -0
  325. package/dist/server/codex-hooks-adapter.js +93 -0
  326. package/dist/server/config.js +325 -0
  327. package/dist/server/gh-routes.js +163 -0
  328. package/dist/server/gh.js +276 -0
  329. package/dist/server/git-routes.js +154 -0
  330. package/dist/server/git.js +694 -0
  331. package/dist/server/github-app.js +218 -0
  332. package/dist/server/github-graphql.js +178 -0
  333. package/dist/server/hooks.js +373 -0
  334. package/dist/server/index.js +1549 -0
  335. package/dist/server/integration-github.js +137 -0
  336. package/dist/server/integration-jira.js +210 -0
  337. package/dist/server/integration-linear.js +176 -0
  338. package/dist/server/logger.js +18 -0
  339. package/dist/server/mobile-input-pipeline.js +129 -0
  340. package/dist/server/opencode-relay.js +53 -0
  341. package/dist/server/org-dashboard.js +241 -0
  342. package/dist/server/output-parsers/claude-parser.js +56 -0
  343. package/dist/server/output-parsers/codex-parser.js +13 -0
  344. package/dist/server/output-parsers/index.js +14 -0
  345. package/dist/server/output-parsers/null-parser.js +12 -0
  346. package/dist/server/output-parsers/opencode-parser.js +77 -0
  347. package/dist/server/pty-handler.js +586 -0
  348. package/dist/server/push.js +84 -0
  349. package/dist/server/review-poller.js +237 -0
  350. package/dist/server/sdk-handler.js +539 -0
  351. package/dist/server/service.js +189 -0
  352. package/dist/server/sessions.js +638 -0
  353. package/dist/server/telemetry.js +236 -0
  354. package/dist/server/ticket-transitions.js +166 -0
  355. package/dist/server/types.js +146 -0
  356. package/dist/server/utils.js +23 -0
  357. package/dist/server/watcher.js +661 -0
  358. package/dist/server/webhook-manager.js +547 -0
  359. package/dist/server/webhooks.js +73 -0
  360. package/dist/server/workspace-groups.js +363 -0
  361. package/dist/server/workspaces.js +1207 -0
  362. package/dist/server/ws.js +192 -0
  363. package/dist/test/EmptyState.spec.js +51 -0
  364. package/dist/test/action-coverage.test.js +139 -0
  365. package/dist/test/actions/registry.test.js +59 -0
  366. package/dist/test/actions/shortcuts.test.js +79 -0
  367. package/dist/test/agent-events.test.js +151 -0
  368. package/dist/test/analytics.test.js +158 -0
  369. package/dist/test/attention.test.js +91 -0
  370. package/dist/test/auth.test.js +105 -0
  371. package/dist/test/backend-state.test.js +47 -0
  372. package/dist/test/belayer-executor.test.js +33 -0
  373. package/dist/test/belayer-intake.test.js +44 -0
  374. package/dist/test/belayer-pipeline.test.js +113 -0
  375. package/dist/test/belayer-pr-lifecycle.test.js +26 -0
  376. package/dist/test/belayer-prompts.test.js +60 -0
  377. package/dist/test/belayer-types.test.js +69 -0
  378. package/dist/test/bin/claude-remote-cli.js +214 -0
  379. package/dist/test/boot-state.test.js +133 -0
  380. package/dist/test/branch-lifecycle.test.js +75 -0
  381. package/dist/test/branch-linker.test.js +236 -0
  382. package/dist/test/branch-rename.test.js +45 -0
  383. package/dist/test/branch-watcher.test.js +115 -0
  384. package/dist/test/browser-cli.test.js +91 -0
  385. package/dist/test/browser-content.test.js +93 -0
  386. package/dist/test/browser-tabs-ui.test.js +39 -0
  387. package/dist/test/changed-files-api.test.js +140 -0
  388. package/dist/test/clipboard.test.js +12 -0
  389. package/dist/test/codex-hooks-adapter.test.js +237 -0
  390. package/dist/test/components/EmptyState.spec.js +51 -0
  391. package/dist/test/components/ErrorToast.spec.js +65 -0
  392. package/dist/test/components/TuiCheckbox.spec.js +120 -0
  393. package/dist/test/components/TuiInput.spec.js +186 -0
  394. package/dist/test/components/leaf-component-migration.spec.js +104 -0
  395. package/dist/test/config-freshness.test.js +63 -0
  396. package/dist/test/config.test.js +813 -0
  397. package/dist/test/diff-summary.test.js +98 -0
  398. package/dist/test/display-state.test.js +179 -0
  399. package/dist/test/event-message-types.test.js +32 -0
  400. package/dist/test/file-tree-utils.test.js +167 -0
  401. package/dist/test/framework-types.test.js +183 -0
  402. package/dist/test/frameworks-api.test.js +93 -0
  403. package/dist/test/frontend/src/lib/pr-state.js +114 -0
  404. package/dist/test/fs-browse.test.js +246 -0
  405. package/dist/test/fuzzy-scorer.test.js +145 -0
  406. package/dist/test/gh-routes.test.js +156 -0
  407. package/dist/test/git-changed-files.test.js +152 -0
  408. package/dist/test/git-routes.test.js +146 -0
  409. package/dist/test/git-utils.test.js +68 -0
  410. package/dist/test/git-watcher.test.js +110 -0
  411. package/dist/test/git.test.js +140 -0
  412. package/dist/test/github-app.test.js +455 -0
  413. package/dist/test/github-graphql.test.js +301 -0
  414. package/dist/test/greetings.test.js +83 -0
  415. package/dist/test/hooks-agent-event.test.js +412 -0
  416. package/dist/test/hooks.test.js +149 -0
  417. package/dist/test/integration-github.test.js +220 -0
  418. package/dist/test/integration-jira.test.js +238 -0
  419. package/dist/test/integration-linear.test.js +293 -0
  420. package/dist/test/mobile-input.test.js +235 -0
  421. package/dist/test/opencode-relay.test.js +107 -0
  422. package/dist/test/org-dashboard.test.js +349 -0
  423. package/dist/test/output-parser.test.js +217 -0
  424. package/dist/test/paths.test.js +32 -0
  425. package/dist/test/pr-state.test.js +407 -0
  426. package/dist/test/pr-status.test.js +82 -0
  427. package/dist/test/presets.test.js +242 -0
  428. package/dist/test/pty-handler-multi-agent.test.js +149 -0
  429. package/dist/test/pty-handler.test.js +146 -0
  430. package/dist/test/pull-requests.test.js +78 -0
  431. package/dist/test/review-poller.test.js +349 -0
  432. package/dist/test/server/analytics.js +121 -0
  433. package/dist/test/server/auth.js +63 -0
  434. package/dist/test/server/branch-linker.js +124 -0
  435. package/dist/test/server/clipboard.js +56 -0
  436. package/dist/test/server/config.js +137 -0
  437. package/dist/test/server/git.js +308 -0
  438. package/dist/test/server/hooks.js +196 -0
  439. package/dist/test/server/index.js +1124 -0
  440. package/dist/test/server/integration-github.js +117 -0
  441. package/dist/test/server/integration-jira.js +164 -0
  442. package/dist/test/server/integration-linear.js +176 -0
  443. package/dist/test/server/mobile-input-pipeline.js +123 -0
  444. package/dist/test/server/org-dashboard.js +184 -0
  445. package/dist/test/server/output-parsers/claude-parser.js +54 -0
  446. package/dist/test/server/output-parsers/codex-parser.js +13 -0
  447. package/dist/test/server/output-parsers/index.js +7 -0
  448. package/dist/test/server/pty-handler.js +310 -0
  449. package/dist/test/server/push.js +80 -0
  450. package/dist/test/server/review-poller.js +218 -0
  451. package/dist/test/server/service.js +169 -0
  452. package/dist/test/server/sessions.js +434 -0
  453. package/dist/test/server/ticket-transitions.js +216 -0
  454. package/dist/test/server/types.js +20 -0
  455. package/dist/test/server/utils.js +22 -0
  456. package/dist/test/server/watcher.js +139 -0
  457. package/dist/test/server/workspaces.js +657 -0
  458. package/dist/test/server/ws.js +152 -0
  459. package/dist/test/server-startup.test.js +62 -0
  460. package/dist/test/service.test.js +43 -0
  461. package/dist/test/session-analytics-api.test.js +123 -0
  462. package/dist/test/session-analytics.test.js +425 -0
  463. package/dist/test/session-intent.test.js +249 -0
  464. package/dist/test/sessions.test.js +1152 -0
  465. package/dist/test/sidebar-items.test.js +164 -0
  466. package/dist/test/stores/boot-state-store.test.js +165 -0
  467. package/dist/test/stores/sessions-logic.test.js +191 -0
  468. package/dist/test/stores/toasts-store.test.js +66 -0
  469. package/dist/test/stores/ui-store.test.js +203 -0
  470. package/dist/test/stores/unread-store.test.js +97 -0
  471. package/dist/test/telemetry-api.test.js +54 -0
  472. package/dist/test/telemetry-sync.test.js +68 -0
  473. package/dist/test/telemetry.test.js +295 -0
  474. package/dist/test/terminal-zoom.test.js +102 -0
  475. package/dist/test/test/analytics.test.js +152 -0
  476. package/dist/test/test/auth.test.js +95 -0
  477. package/dist/test/test/branch-linker.test.js +231 -0
  478. package/dist/test/test/branch-rename.test.js +45 -0
  479. package/dist/test/test/clipboard.test.js +12 -0
  480. package/dist/test/test/config.test.js +281 -0
  481. package/dist/test/test/fs-browse.test.js +202 -0
  482. package/dist/test/test/git.test.js +67 -0
  483. package/dist/test/test/hooks.test.js +139 -0
  484. package/dist/test/test/integration-github.test.js +203 -0
  485. package/dist/test/test/integration-jira.test.js +294 -0
  486. package/dist/test/test/integration-linear.test.js +293 -0
  487. package/dist/test/test/mobile-input.test.js +193 -0
  488. package/dist/test/test/org-dashboard.test.js +240 -0
  489. package/dist/test/test/output-parser.test.js +95 -0
  490. package/dist/test/test/paths.test.js +32 -0
  491. package/dist/test/test/pr-state.test.js +220 -0
  492. package/dist/test/test/pull-requests.test.js +67 -0
  493. package/dist/test/test/review-poller.test.js +235 -0
  494. package/dist/test/test/service.test.js +43 -0
  495. package/dist/test/test/sessions.test.js +750 -0
  496. package/dist/test/test/ticket-transitions.test.js +130 -0
  497. package/dist/test/test/version.test.js +34 -0
  498. package/dist/test/test/worktrees.test.js +256 -0
  499. package/dist/test/ticket-transitions.test.js +312 -0
  500. package/dist/test/unread.test.js +23 -0
  501. package/dist/test/version.test.js +34 -0
  502. package/dist/test/webhook-manager.test.js +484 -0
  503. package/dist/test/webhooks.test.js +208 -0
  504. package/dist/test/workspace-groups.test.js +377 -0
  505. package/dist/test/worktrees.test.js +531 -0
  506. package/package.json +88 -0
@@ -0,0 +1,56 @@
1
+ import { execFile, execFileSync } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ const SUPPORTED_MIME = {
5
+ 'image/png': { ext: '.png', osascriptClass: '«class PNGf»' },
6
+ 'image/jpeg': { ext: '.jpg', osascriptClass: '«class JPEG»' },
7
+ 'image/gif': { ext: '.gif', osascriptClass: '«class GIFf»' },
8
+ 'image/webp': { ext: '.webp', osascriptClass: '«class PNGf»' },
9
+ };
10
+ let cachedTool;
11
+ export function detectClipboardTool() {
12
+ if (cachedTool !== undefined)
13
+ return cachedTool;
14
+ if (process.platform === 'darwin') {
15
+ cachedTool = 'osascript';
16
+ return cachedTool;
17
+ }
18
+ if (process.env['DISPLAY'] || process.env['WAYLAND_DISPLAY']) {
19
+ try {
20
+ execFileSync('which', ['xclip'], { stdio: 'ignore' });
21
+ cachedTool = 'xclip';
22
+ return cachedTool;
23
+ }
24
+ catch {
25
+ // xclip not found
26
+ }
27
+ }
28
+ cachedTool = null;
29
+ return cachedTool;
30
+ }
31
+ function mimeInfo(mimeType) {
32
+ const info = SUPPORTED_MIME[mimeType];
33
+ if (!info)
34
+ throw new Error(`Unsupported MIME type: ${mimeType}`);
35
+ return info;
36
+ }
37
+ export function extensionForMime(mimeType) {
38
+ return mimeInfo(mimeType).ext;
39
+ }
40
+ export async function setClipboardImage(filePath, mimeType) {
41
+ const tool = detectClipboardTool();
42
+ const info = mimeInfo(mimeType); // throws if unsupported
43
+ if (tool === 'osascript') {
44
+ const script = `set the clipboard to (read (POSIX file "${filePath}") as ${info.osascriptClass})`;
45
+ await execFileAsync('osascript', ['-e', script]);
46
+ return true;
47
+ }
48
+ if (tool === 'xclip') {
49
+ await execFileAsync('xclip', ['-selection', 'clipboard', '-t', mimeType, '-i', filePath]);
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ export function _resetForTesting() {
55
+ cachedTool = undefined;
56
+ }
@@ -0,0 +1,137 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ export const DEFAULTS = {
5
+ host: '0.0.0.0',
6
+ port: 3456,
7
+ cookieTTL: '24h',
8
+ repos: [],
9
+ claudeCommand: 'claude',
10
+ claudeArgs: [],
11
+ defaultAgent: 'claude',
12
+ defaultContinue: true,
13
+ defaultYolo: false,
14
+ launchInTmux: false,
15
+ defaultNotifications: true,
16
+ workspaces: [],
17
+ };
18
+ export function loadConfig(configPath) {
19
+ if (!fs.existsSync(configPath)) {
20
+ throw new Error(`Config file not found: ${configPath}`);
21
+ }
22
+ const raw = fs.readFileSync(configPath, 'utf8');
23
+ const parsed = JSON.parse(raw);
24
+ const config = { ...DEFAULTS, ...parsed };
25
+ // Validate and clean workspaceGroups
26
+ if (config.workspaceGroups != null) {
27
+ const validPaths = new Set(config.workspaces ?? []);
28
+ const seenPaths = new Set();
29
+ const cleaned = {};
30
+ for (const [groupName, paths] of Object.entries(config.workspaceGroups)) {
31
+ if (!Array.isArray(paths)) {
32
+ console.warn(`workspaceGroups: group "${groupName}" value is not an array, skipping`);
33
+ continue;
34
+ }
35
+ const filteredPaths = [];
36
+ for (const p of paths) {
37
+ if (!validPaths.has(p)) {
38
+ console.warn(`workspaceGroups: path "${p}" in group "${groupName}" is not in workspaces[], skipping`);
39
+ continue;
40
+ }
41
+ if (seenPaths.has(p)) {
42
+ console.warn(`workspaceGroups: path "${p}" in group "${groupName}" is already assigned to another group, skipping`);
43
+ continue;
44
+ }
45
+ seenPaths.add(p);
46
+ filteredPaths.push(p);
47
+ }
48
+ if (filteredPaths.length > 0) {
49
+ cleaned[groupName] = filteredPaths;
50
+ }
51
+ }
52
+ config.workspaceGroups = cleaned;
53
+ }
54
+ return config;
55
+ }
56
+ export function saveConfig(configPath, config) {
57
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
58
+ }
59
+ function metaDir(configPath) {
60
+ return path.join(path.dirname(configPath), 'worktree-meta');
61
+ }
62
+ function metaFilePath(configPath, worktreePath) {
63
+ const hash = crypto.createHash('sha256').update(worktreePath).digest('hex').slice(0, 16);
64
+ return path.join(metaDir(configPath), hash + '.json');
65
+ }
66
+ export function ensureMetaDir(configPath) {
67
+ const dir = metaDir(configPath);
68
+ if (!fs.existsSync(dir)) {
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ }
71
+ }
72
+ export function readMeta(configPath, worktreePath) {
73
+ const fp = metaFilePath(configPath, worktreePath);
74
+ try {
75
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
76
+ }
77
+ catch (_) {
78
+ return null;
79
+ }
80
+ }
81
+ export function writeMeta(configPath, meta) {
82
+ const fp = metaFilePath(configPath, meta.worktreePath);
83
+ ensureMetaDir(configPath);
84
+ fs.writeFileSync(fp, JSON.stringify(meta, null, 2), 'utf8');
85
+ }
86
+ export function deleteMeta(configPath, worktreePath) {
87
+ const fp = metaFilePath(configPath, worktreePath);
88
+ try {
89
+ fs.unlinkSync(fp);
90
+ }
91
+ catch (_) {
92
+ // File may not exist; ignore
93
+ }
94
+ }
95
+ export function getWorkspaceSettings(config, workspacePath) {
96
+ const globalDefaults = {
97
+ defaultAgent: config.defaultAgent,
98
+ defaultContinue: config.defaultContinue,
99
+ defaultYolo: config.defaultYolo,
100
+ launchInTmux: config.launchInTmux,
101
+ claudeArgs: config.claudeArgs,
102
+ };
103
+ const perWorkspace = config.workspaceSettings?.[workspacePath] || {};
104
+ // Per-workspace settings override global — only for defined keys
105
+ return { ...globalDefaults, ...perWorkspace };
106
+ }
107
+ export function resolveSessionSettings(config, repoPath, overrides) {
108
+ const ws = getWorkspaceSettings(config, repoPath);
109
+ return {
110
+ agent: overrides.agent ?? ws.defaultAgent ?? 'claude',
111
+ yolo: overrides.yolo ?? ws.defaultYolo ?? false,
112
+ continue: overrides.continue ?? ws.defaultContinue ?? true,
113
+ useTmux: overrides.useTmux ?? ws.launchInTmux ?? false,
114
+ claudeArgs: overrides.claudeArgs ?? ws.claudeArgs ?? [],
115
+ };
116
+ }
117
+ export function deleteWorkspaceSettingKeys(configPath, config, workspacePath, keys) {
118
+ if (!config.workspaceSettings?.[workspacePath])
119
+ return;
120
+ for (const key of keys) {
121
+ delete config.workspaceSettings[workspacePath][key];
122
+ }
123
+ // Clean up empty workspace entries
124
+ if (Object.keys(config.workspaceSettings[workspacePath]).length === 0) {
125
+ delete config.workspaceSettings[workspacePath];
126
+ }
127
+ saveConfig(configPath, config);
128
+ }
129
+ export function setWorkspaceSettings(configPath, config, workspacePath, settings) {
130
+ if (!config.workspaceSettings)
131
+ config.workspaceSettings = {};
132
+ config.workspaceSettings[workspacePath] = {
133
+ ...config.workspaceSettings[workspacePath],
134
+ ...settings,
135
+ };
136
+ saveConfig(configPath, config);
137
+ }
@@ -0,0 +1,308 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ function normalizeBranchNames(stdout) {
5
+ const branches = stdout
6
+ .split('\n')
7
+ .map((branch) => branch.trim())
8
+ .filter((branch) => branch && !branch.includes('HEAD'))
9
+ .map((branch) => branch.replace(/^origin\//, ''));
10
+ return [...new Set(branches)].sort();
11
+ }
12
+ async function listBranches(repoPath, options = {}) {
13
+ const run = options.exec || execFileAsync;
14
+ if (options.refresh) {
15
+ try {
16
+ await run('git', ['fetch', '--all', '--prune'], { cwd: repoPath });
17
+ }
18
+ catch {
19
+ // Best effort — still return the locally-known refs below.
20
+ }
21
+ }
22
+ try {
23
+ const { stdout } = await run('git', ['branch', '-a', '--format=%(refname:short)'], { cwd: repoPath });
24
+ return normalizeBranchNames(stdout);
25
+ }
26
+ catch {
27
+ return [];
28
+ }
29
+ }
30
+ async function getCurrentBranch(repoPath, options = {}) {
31
+ const run = options.exec || execFileAsync;
32
+ try {
33
+ const { stdout } = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoPath });
34
+ return stdout.trim() || null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ async function getActivityFeed(repoPath, options = {}) {
41
+ const run = options.exec || execFileAsync;
42
+ try {
43
+ const { stdout } = await run('git', [
44
+ 'log',
45
+ '--all',
46
+ '--since=24 hours ago',
47
+ '--oneline',
48
+ '--max-count=50',
49
+ '--format=%H|%h|%s|%an|%ar|%D',
50
+ ], { cwd: repoPath, timeout: 5000 });
51
+ const lines = stdout.split('\n').filter((line) => line.trim());
52
+ const entries = [];
53
+ for (const line of lines) {
54
+ try {
55
+ // Split into exactly 6 parts by the first 5 pipe characters
56
+ const parts = [];
57
+ let remaining = line;
58
+ for (let i = 0; i < 5; i++) {
59
+ const idx = remaining.indexOf('|');
60
+ if (idx === -1)
61
+ break;
62
+ parts.push(remaining.slice(0, idx));
63
+ remaining = remaining.slice(idx + 1);
64
+ }
65
+ parts.push(remaining);
66
+ if (parts.length < 5)
67
+ continue;
68
+ const hash = parts[0] ?? '';
69
+ const shortHash = parts[1] ?? '';
70
+ const message = parts[2] ?? '';
71
+ const author = parts[3] ?? '';
72
+ const timeAgo = parts[4] ?? '';
73
+ const decorations = parts[5] ?? '';
74
+ if (!hash || !shortHash)
75
+ continue;
76
+ const branches = decorations
77
+ .split(',')
78
+ .map((d) => d.trim())
79
+ .filter((d) => d && !d.startsWith('tag:') && d !== 'HEAD')
80
+ .map((d) => d.replace(/^HEAD -> /, '').replace(/^origin\//, ''));
81
+ entries.push({
82
+ hash: hash.trim(),
83
+ shortHash: shortHash.trim(),
84
+ message: message.trim(),
85
+ author: author.trim(),
86
+ timeAgo: timeAgo.trim(),
87
+ branches: [...new Set(branches)],
88
+ });
89
+ }
90
+ catch {
91
+ // Skip malformed lines
92
+ continue;
93
+ }
94
+ }
95
+ return entries;
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ }
101
+ async function getCiStatus(repoPath, branch, options = {}) {
102
+ const run = options.exec || execFileAsync;
103
+ let stdout;
104
+ let stderr;
105
+ try {
106
+ ({ stdout, stderr } = await run('gh', ['pr', 'checks', branch, '--json', 'name,state,conclusion'], { cwd: repoPath, timeout: 5000 }));
107
+ }
108
+ catch (err) {
109
+ if (err && typeof err === 'object') {
110
+ const errObj = err;
111
+ const errorText = errObj.stderr ?? errObj.message ?? '';
112
+ // gh not installed
113
+ if (errObj.code === 'ENOENT')
114
+ return null;
115
+ // Not authenticated
116
+ if (typeof errorText === 'string' &&
117
+ (errorText.includes('not logged into') || errorText.includes('authentication'))) {
118
+ return { total: 0, passing: 0, failing: 0, pending: 0, authError: true };
119
+ }
120
+ // No PR for branch
121
+ if (typeof errorText === 'string' &&
122
+ (errorText.includes('no pull requests found') || errorText.includes('Could not find'))) {
123
+ return null;
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ // gh may exit 0 but write errors or auth prompts to stderr
129
+ if (stderr && (stderr.includes('not logged into') || stderr.includes('authentication'))) {
130
+ return { total: 0, passing: 0, failing: 0, pending: 0, authError: true };
131
+ }
132
+ if (!stdout.trim())
133
+ return null;
134
+ try {
135
+ const checks = JSON.parse(stdout);
136
+ let passing = 0;
137
+ let failing = 0;
138
+ let pending = 0;
139
+ for (const check of checks) {
140
+ const conclusion = (check.conclusion ?? '').toUpperCase();
141
+ const state = (check.state ?? '').toUpperCase();
142
+ if (conclusion === 'SUCCESS' || conclusion === 'SKIPPED' || conclusion === 'NEUTRAL') {
143
+ passing++;
144
+ }
145
+ else if (conclusion === 'FAILURE' || conclusion === 'CANCELLED' || conclusion === 'TIMED_OUT') {
146
+ failing++;
147
+ }
148
+ else if (state === 'IN_PROGRESS' || state === 'QUEUED' || state === 'PENDING' || conclusion === '') {
149
+ pending++;
150
+ }
151
+ else {
152
+ // Unknown conclusion — treat as pending rather than silently ignoring
153
+ pending++;
154
+ }
155
+ }
156
+ return { total: checks.length, passing, failing, pending };
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ async function getPrForBranch(repoPath, branch, options = {}) {
163
+ const run = options.exec || execFileAsync;
164
+ let stdout;
165
+ try {
166
+ ({ stdout } = await run('gh', [
167
+ 'pr',
168
+ 'view',
169
+ branch,
170
+ '--json',
171
+ 'number,title,url,state,headRefName,baseRefName,reviewDecision,isDraft,additions,deletions,mergeable',
172
+ ], { cwd: repoPath, timeout: 5000 }));
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ if (!stdout.trim())
178
+ return null;
179
+ try {
180
+ const data = JSON.parse(stdout);
181
+ return {
182
+ number: data.number,
183
+ title: data.title,
184
+ url: data.url,
185
+ state: data.state,
186
+ headRefName: data.headRefName,
187
+ baseRefName: data.baseRefName,
188
+ isDraft: data.isDraft,
189
+ reviewDecision: data.reviewDecision ?? null,
190
+ additions: data.additions ?? 0,
191
+ deletions: data.deletions ?? 0,
192
+ mergeable: data.mergeable ?? 'UNKNOWN',
193
+ unresolvedCommentCount: 0,
194
+ };
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ async function switchBranch(repoPath, branch, options = {}) {
201
+ const run = options.exec || execFileAsync;
202
+ try {
203
+ await run('git', ['checkout', branch], { cwd: repoPath, timeout: 5000 });
204
+ return { success: true };
205
+ }
206
+ catch (err) {
207
+ if (err && typeof err === 'object') {
208
+ const errObj = err;
209
+ const errorText = errObj.stderr ?? errObj.message ?? 'Unknown error';
210
+ return { success: false, error: errorText.trim() };
211
+ }
212
+ return { success: false, error: 'Unknown error' };
213
+ }
214
+ }
215
+ async function getCommitsAhead(repoPath, branch, baseBranch, options = {}) {
216
+ const run = options.exec || execFileAsync;
217
+ try {
218
+ const { stdout } = await run('git', ['rev-list', '--count', `${baseBranch}..${branch}`], { cwd: repoPath, timeout: 5000 });
219
+ const count = parseInt(stdout.trim(), 10);
220
+ return Number.isFinite(count) ? count : 0;
221
+ }
222
+ catch {
223
+ return 0;
224
+ }
225
+ }
226
+ async function getUnresolvedCommentCount(repoPath, prNumber, options = {}) {
227
+ const run = options.exec || execFileAsync;
228
+ try {
229
+ const { stdout: repoStdout } = await run('gh', ['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner'], { cwd: repoPath, timeout: 5000 });
230
+ const nameWithOwner = repoStdout.trim();
231
+ if (!nameWithOwner)
232
+ return 0;
233
+ const [owner, repo] = nameWithOwner.split('/');
234
+ if (!owner || !repo)
235
+ return 0;
236
+ const query = `query($owner: String!, $repo: String!, $number: Int!) {
237
+ repository(owner: $owner, name: $repo) {
238
+ pullRequest(number: $number) {
239
+ reviewThreads(first: 100) {
240
+ nodes { isResolved }
241
+ }
242
+ }
243
+ }
244
+ }`;
245
+ const { stdout } = await run('gh', [
246
+ 'api', 'graphql',
247
+ '-f', `query=${query}`,
248
+ '-f', `owner=${owner}`,
249
+ '-f', `repo=${repo}`,
250
+ '-F', `number=${prNumber}`,
251
+ ], { cwd: repoPath, timeout: 10000 });
252
+ const result = JSON.parse(stdout);
253
+ const nodes = result?.data?.repository?.pullRequest?.reviewThreads?.nodes ?? [];
254
+ return nodes.filter((n) => !n.isResolved).length;
255
+ }
256
+ catch {
257
+ return 0;
258
+ }
259
+ }
260
+ async function getWorkingTreeDiff(repoPath, exec = execFileAsync) {
261
+ try {
262
+ const { stdout } = await exec('git', ['diff', '--shortstat'], { cwd: repoPath, timeout: 5000 });
263
+ // Output like: " 3 files changed, 55 insertions(+), 12 deletions(-)"
264
+ const insertions = stdout.match(/(\d+) insertion/);
265
+ const deletions = stdout.match(/(\d+) deletion/);
266
+ return {
267
+ additions: insertions?.[1] ? parseInt(insertions[1], 10) : 0,
268
+ deletions: deletions?.[1] ? parseInt(deletions[1], 10) : 0,
269
+ };
270
+ }
271
+ catch {
272
+ return { additions: 0, deletions: 0 };
273
+ }
274
+ }
275
+ /**
276
+ * Convert a git branch name to a human-readable display name.
277
+ * "fix-mobile-scroll-bug" → "Fix mobile scroll bug"
278
+ * "feature/add-auth" → "Add auth"
279
+ */
280
+ function branchToDisplayName(branch) {
281
+ const stripped = branch.replace(/^(feature|fix|chore|refactor|docs|test|ci|build)\//i, '');
282
+ const words = stripped.replace(/[-_]/g, ' ').trim();
283
+ if (!words)
284
+ return branch;
285
+ return words.charAt(0).toUpperCase() + words.slice(1);
286
+ }
287
+ async function isBranchStale(repoPath, branch, options = {}) {
288
+ const run = options.exec || execFileAsync;
289
+ try {
290
+ for (const base of ['main', 'master']) {
291
+ try {
292
+ const { stdout } = await run('git', ['rev-list', '--count', `${base}..${branch}`], { cwd: repoPath, timeout: 5000 });
293
+ const count = parseInt(stdout.trim(), 10);
294
+ if (count === 0)
295
+ return true;
296
+ return false;
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ }
302
+ return false;
303
+ }
304
+ catch {
305
+ return false;
306
+ }
307
+ }
308
+ export { listBranches, normalizeBranchNames, getActivityFeed, getCiStatus, getPrForBranch, getUnresolvedCommentCount, switchBranch, getCommitsAhead, getCurrentBranch, getWorkingTreeDiff, branchToDisplayName, isBranchStale, };
@@ -0,0 +1,196 @@
1
+ import crypto from 'node:crypto';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { Router } from 'express';
5
+ import express from 'express';
6
+ import { stripAnsi, cleanEnv } from './utils.js';
7
+ import { branchToDisplayName } from './git.js';
8
+ import { writeMeta } from './config.js';
9
+ const execFileAsync = promisify(execFile);
10
+ // ---------------------------------------------------------------------------
11
+ // Constants
12
+ // ---------------------------------------------------------------------------
13
+ const LOCALHOST_ADDRS = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
14
+ const DEFAULT_RENAME_PROMPT = 'Output ONLY a short kebab-case git branch name (no explanation, no backticks, no prefix, just the name) that describes this task:';
15
+ const RENAME_RETRY_DELAY_MS = 5000;
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+ function setAgentState(session, state, deps) {
20
+ session.agentState = state;
21
+ deps.fireStateChange(session.id, state);
22
+ session._lastHookTime = Date.now();
23
+ }
24
+ function extractToolDetail(_toolName, toolInput) {
25
+ if (toolInput && typeof toolInput === 'object') {
26
+ const input = toolInput;
27
+ if (typeof input.file_path === 'string')
28
+ return input.file_path;
29
+ if (typeof input.path === 'string')
30
+ return input.path;
31
+ if (typeof input.command === 'string')
32
+ return input.command.slice(0, 80);
33
+ }
34
+ return undefined;
35
+ }
36
+ async function spawnBranchRename(session, promptText, deps) {
37
+ const cleanedPrompt = stripAnsi(promptText).slice(0, 500);
38
+ const renamePrompt = session.branchRenamePrompt ?? DEFAULT_RENAME_PROMPT;
39
+ const fullPrompt = renamePrompt + '\n\n' + cleanedPrompt;
40
+ const env = cleanEnv();
41
+ for (let attempt = 0; attempt < 2; attempt++) {
42
+ // Check session still exists before attempting
43
+ if (!deps.getSession(session.id))
44
+ return;
45
+ if (attempt > 0) {
46
+ await new Promise((resolve) => setTimeout(resolve, RENAME_RETRY_DELAY_MS));
47
+ // Re-check after delay
48
+ if (!deps.getSession(session.id))
49
+ return;
50
+ }
51
+ try {
52
+ const { stdout } = await execFileAsync('claude', ['-p', '--model', 'haiku', fullPrompt], { cwd: session.cwd, timeout: 30000, env });
53
+ // Sanitize output
54
+ let branchName = stdout
55
+ .replace(/`/g, '')
56
+ .replace(/[^a-zA-Z0-9-]/g, '-')
57
+ .replace(/-+/g, '-')
58
+ .replace(/^-+|-+$/g, '')
59
+ .toLowerCase()
60
+ .slice(0, 60);
61
+ if (!branchName)
62
+ continue;
63
+ // Check session still exists before renaming
64
+ if (!deps.getSession(session.id))
65
+ return;
66
+ await execFileAsync('git', ['branch', '-m', branchName], { cwd: session.cwd });
67
+ session.branchName = branchName;
68
+ session.displayName = branchToDisplayName(branchName);
69
+ deps.broadcastEvent('session-renamed', {
70
+ sessionId: session.id,
71
+ branchName: session.branchName,
72
+ displayName: session.displayName,
73
+ });
74
+ if (deps.configPath) {
75
+ writeMeta(deps.configPath, {
76
+ worktreePath: session.repoPath,
77
+ displayName: session.displayName,
78
+ lastActivity: session.lastActivity,
79
+ branchName: session.branchName,
80
+ });
81
+ }
82
+ return; // success
83
+ }
84
+ catch (err) {
85
+ if (attempt === 1) {
86
+ console.error('[hooks] branch rename failed after 2 attempts:', err);
87
+ session.needsBranchRename = true;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Factory
94
+ // ---------------------------------------------------------------------------
95
+ export function createHooksRouter(deps) {
96
+ const router = Router();
97
+ // Middleware: IP allowlist — only localhost, do NOT trust X-Forwarded-For
98
+ router.use((req, res, next) => {
99
+ const remoteAddr = req.socket.remoteAddress;
100
+ if (!remoteAddr || !LOCALHOST_ADDRS.has(remoteAddr)) {
101
+ res.status(403).json({ error: 'Forbidden' });
102
+ return;
103
+ }
104
+ next();
105
+ });
106
+ // Middleware: parse JSON with generous limit for PostToolUse payloads
107
+ router.use(express.json({ limit: '5mb' }));
108
+ // Middleware: token verification
109
+ router.use((req, res, next) => {
110
+ const sessionId = req.query.sessionId;
111
+ const token = req.query.token;
112
+ if (typeof sessionId !== 'string' || !sessionId) {
113
+ res.status(400).json({ error: 'Missing sessionId' });
114
+ return;
115
+ }
116
+ if (typeof token !== 'string' || !token) {
117
+ res.status(400).json({ error: 'Missing token' });
118
+ return;
119
+ }
120
+ const session = deps.getSession(sessionId);
121
+ if (!session) {
122
+ res.status(404).json({ error: 'Session not found' });
123
+ return;
124
+ }
125
+ const tokenBuf = Buffer.from(token);
126
+ const hookTokenBuf = Buffer.from(session.hookToken);
127
+ if (tokenBuf.length !== hookTokenBuf.length || !crypto.timingSafeEqual(tokenBuf, hookTokenBuf)) {
128
+ res.status(403).json({ error: 'Invalid token' });
129
+ return;
130
+ }
131
+ req._hookSession = session;
132
+ next();
133
+ });
134
+ // ---------------------------------------------------------------------------
135
+ // Route handlers
136
+ // ---------------------------------------------------------------------------
137
+ // POST /stop → idle
138
+ router.post('/stop', (req, res) => {
139
+ const session = req._hookSession;
140
+ setAgentState(session, 'idle', deps);
141
+ res.json({ ok: true });
142
+ });
143
+ // POST /notification → permission-prompt | waiting-for-input
144
+ router.post('/notification', (req, res) => {
145
+ const session = req._hookSession;
146
+ const type = req.query.type;
147
+ if (type === 'permission_prompt') {
148
+ setAgentState(session, 'permission-prompt', deps);
149
+ session.lastAttentionNotifiedAt = Date.now();
150
+ deps.notifySessionAttention(session.id, { displayName: session.displayName, type: session.type });
151
+ }
152
+ else if (type === 'idle_prompt') {
153
+ setAgentState(session, 'waiting-for-input', deps);
154
+ session.lastAttentionNotifiedAt = Date.now();
155
+ deps.notifySessionAttention(session.id, { displayName: session.displayName, type: session.type });
156
+ }
157
+ res.json({ ok: true });
158
+ });
159
+ // POST /prompt-submit → processing (+ optional branch rename on first message)
160
+ router.post('/prompt-submit', (req, res) => {
161
+ const session = req._hookSession;
162
+ setAgentState(session, 'processing', deps);
163
+ if (session.needsBranchRename === true) {
164
+ session.needsBranchRename = false;
165
+ const promptText = typeof req.body?.prompt === 'string' ? req.body.prompt : '';
166
+ spawnBranchRename(session, promptText, deps).catch((err) => {
167
+ console.error('[hooks] spawnBranchRename error:', err);
168
+ });
169
+ }
170
+ res.json({ ok: true });
171
+ });
172
+ // POST /session-end → acknowledge hook (PTY onExit owns actual cleanup and cleanedUp flag)
173
+ router.post('/session-end', (_req, res) => {
174
+ // Acknowledge hook — PTY onExit owns actual cleanup and cleanedUp flag
175
+ res.json({ ok: true });
176
+ });
177
+ // POST /tool-use → set currentActivity
178
+ router.post('/tool-use', (req, res) => {
179
+ const session = req._hookSession;
180
+ const body = req.body;
181
+ const toolName = typeof body?.tool_name === 'string' ? body.tool_name : '';
182
+ const toolInput = body?.tool_input;
183
+ const detail = extractToolDetail(toolName, toolInput);
184
+ session.currentActivity = detail !== undefined ? { tool: toolName, detail } : { tool: toolName };
185
+ deps.broadcastEvent('session-activity-changed', { sessionId: session.id });
186
+ res.json({ ok: true });
187
+ });
188
+ // POST /tool-result → clear currentActivity
189
+ router.post('/tool-result', (req, res) => {
190
+ const session = req._hookSession;
191
+ session.currentActivity = undefined;
192
+ deps.broadcastEvent('session-activity-changed', { sessionId: session.id });
193
+ res.json({ ok: true });
194
+ });
195
+ return router;
196
+ }