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,547 @@
1
+ import { Router } from 'express';
2
+ import crypto from 'node:crypto';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { loadConfig, saveConfig } from './config.js';
6
+ import { extractOwnerRepo, buildRepoMap } from './git.js';
7
+ import { createLogger } from './logger.js';
8
+ // ── Smee singleton state ───────────────────────────────────────────────────────
9
+ let smeeHandle = null;
10
+ let smeeConnected = false;
11
+ let lastEventAt = null;
12
+ const logger = createLogger('webhook');
13
+ // ── Smart polling state ────────────────────────────────────────────────────────
14
+ let pollingTimer = null;
15
+ function stopSmartPolling() {
16
+ if (pollingTimer !== null) {
17
+ clearInterval(pollingTimer);
18
+ pollingTimer = null;
19
+ }
20
+ }
21
+ /**
22
+ * Starts a 30-second polling interval that broadcasts `pr-updated` and
23
+ * `ci-updated` events only for workspaces that do NOT have a working webhook
24
+ * configured (`webhookEnabled !== true` or `webhookError` is set).
25
+ *
26
+ * Calling this again replaces any existing polling timer.
27
+ */
28
+ export function startSmartPolling(configPath, broadcastEvent) {
29
+ stopSmartPolling();
30
+ const POLL_INTERVAL_MS = 30_000;
31
+ const tick = () => {
32
+ const config = loadConfig(configPath);
33
+ const workspacePaths = config.repos ?? [];
34
+ if (workspacePaths.length === 0)
35
+ return;
36
+ const repoSettings = config.repoSettings ?? {};
37
+ // Collect paths that need polling (no webhook or webhook has an error)
38
+ const unwebhookedPaths = workspacePaths.filter((wsPath) => {
39
+ const ws = repoSettings[wsPath];
40
+ return !ws?.webhookEnabled || ws?.webhookError;
41
+ });
42
+ if (unwebhookedPaths.length === 0)
43
+ return;
44
+ // Resolve owner/repo for each unwebhooked path synchronously via git config cache
45
+ // We use the async buildRepoMap but fire-and-forget inside the interval
46
+ void (async () => {
47
+ const execFn = (file, args, opts) => execFileAsync(file, args, opts);
48
+ const repoMap = await buildRepoMap(unwebhookedPaths, execFn);
49
+ // Single broadcast per poll cycle — frontend debounces invalidation
50
+ if (repoMap.size > 0) {
51
+ broadcastEvent('pr-updated', { repos: [...repoMap.keys()] });
52
+ broadcastEvent('ci-updated', { repos: [...repoMap.keys()] });
53
+ }
54
+ })();
55
+ };
56
+ pollingTimer = setInterval(tick, POLL_INTERVAL_MS);
57
+ }
58
+ function stopSmee() {
59
+ if (smeeHandle) {
60
+ try {
61
+ smeeHandle.close();
62
+ }
63
+ catch {
64
+ // Best-effort
65
+ }
66
+ smeeHandle = null;
67
+ }
68
+ smeeConnected = false;
69
+ }
70
+ function startSmee(smeeUrl, targetPort) {
71
+ stopSmee();
72
+ // Dynamic import — smee-client may not be installed
73
+ void (async () => {
74
+ try {
75
+ // Import as unknown first to avoid type-mismatch with varying smee-client versions
76
+ const smeeModule = (await import('smee-client'));
77
+ const client = new smeeModule.default({
78
+ source: smeeUrl,
79
+ target: `http://localhost:${targetPort}/webhooks`,
80
+ logger: {
81
+ info: (message, ...args) => logger.info(String(message), ...args),
82
+ error: (message, ...args) => logger.error(String(message), ...args),
83
+ },
84
+ });
85
+ // Hook connectivity events via client setters (available before start)
86
+ client.onmessage = () => {
87
+ lastEventAt = new Date().toISOString();
88
+ };
89
+ client.onerror = () => {
90
+ smeeConnected = false;
91
+ };
92
+ client.onopen = () => {
93
+ smeeConnected = true;
94
+ };
95
+ // start() returns Promise<EventSource> — await it to get the handle for close()
96
+ const es = await client.start();
97
+ smeeHandle = { close: () => void es.close() };
98
+ smeeConnected = true;
99
+ }
100
+ catch (err) {
101
+ logger.warn('smee-client not available or failed to start:', err);
102
+ smeeConnected = false;
103
+ }
104
+ })();
105
+ }
106
+ export function reloadSmee(configPath, port) {
107
+ const config = loadConfig(configPath);
108
+ const smeeUrl = config.github?.smeeUrl;
109
+ stopSmee();
110
+ if (smeeUrl) {
111
+ startSmee(smeeUrl, port);
112
+ }
113
+ }
114
+ export function getSmeeStatus() {
115
+ return { smeeConnected, lastEventAt };
116
+ }
117
+ function makeGithubApi(fetchFn) {
118
+ return async function githubApi(method, path, token, body) {
119
+ const init = {
120
+ method,
121
+ headers: {
122
+ Authorization: `Bearer ${token}`,
123
+ Accept: 'application/vnd.github+json',
124
+ 'Content-Type': 'application/json',
125
+ 'X-GitHub-Api-Version': '2022-11-28',
126
+ },
127
+ };
128
+ if (body !== undefined) {
129
+ init.body = JSON.stringify(body);
130
+ }
131
+ return fetchFn(`https://api.github.com${path}`, init);
132
+ };
133
+ }
134
+ // ── Webhook CRUD helper ────────────────────────────────────────────────────────
135
+ const execFileAsync = promisify(execFile);
136
+ async function getOwnerRepoForPath(repoPath) {
137
+ try {
138
+ const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
139
+ cwd: repoPath,
140
+ timeout: 10_000,
141
+ });
142
+ return extractOwnerRepo(stdout.trim());
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ async function createWebhookForPath(repoPath, configPath, config, githubApi) {
149
+ const token = config.github?.accessToken;
150
+ const secret = config.github?.webhookSecret;
151
+ const smeeUrl = config.github?.smeeUrl;
152
+ if (!token)
153
+ return { ok: false, error: 'not_authenticated', webhookError: null };
154
+ if (!secret || !smeeUrl)
155
+ return { ok: false, error: 'not_configured', webhookError: null };
156
+ const ownerRepo = await getOwnerRepoForPath(repoPath);
157
+ if (!ownerRepo)
158
+ return { ok: false, error: 'no_remote', webhookError: null };
159
+ const parts = ownerRepo.split('/');
160
+ const owner = parts[0];
161
+ const repo = parts[1];
162
+ if (!owner || !repo)
163
+ return { ok: false, error: 'invalid_remote', webhookError: null };
164
+ let apiRes;
165
+ try {
166
+ apiRes = await githubApi('POST', `/repos/${owner}/${repo}/hooks`, token, {
167
+ name: 'web',
168
+ active: true,
169
+ events: ['*'],
170
+ config: {
171
+ url: smeeUrl,
172
+ content_type: 'json',
173
+ secret,
174
+ insecure_ssl: '0',
175
+ },
176
+ });
177
+ }
178
+ catch (err) {
179
+ return {
180
+ ok: false,
181
+ error: `fetch_failed: ${String(err)}`,
182
+ webhookError: null,
183
+ };
184
+ }
185
+ // 422 — webhook already exists; try to find the existing webhook ID
186
+ if (apiRes.status === 422) {
187
+ try {
188
+ const listRes = await githubApi('GET', `/repos/${owner}/${repo}/hooks`, token);
189
+ if (listRes.ok) {
190
+ const hooks = (await listRes.json());
191
+ const existing = hooks.find((h) => h.config?.url === smeeUrl);
192
+ if (existing) {
193
+ persistWebhookSuccess(configPath, config, repoPath, existing.id);
194
+ return { ok: true, webhookId: existing.id, ownerRepo };
195
+ }
196
+ }
197
+ }
198
+ catch (err) {
199
+ logger.warn('Could not retrieve existing webhook ID for', ownerRepo, err);
200
+ }
201
+ // Webhook exists on GitHub but we couldn't find its ID — don't persist a fake ID
202
+ return { ok: true, webhookId: 0, ownerRepo };
203
+ }
204
+ if (apiRes.status === 403) {
205
+ persistWebhookError(configPath, config, repoPath, 'not-admin');
206
+ return { ok: false, error: 'forbidden', webhookError: 'not-admin' };
207
+ }
208
+ if (apiRes.status === 401) {
209
+ return { ok: false, error: 'unauthorized', webhookError: null };
210
+ }
211
+ if (apiRes.status === 404) {
212
+ persistWebhookError(configPath, config, repoPath, 'not-found');
213
+ return { ok: false, error: 'not_found', webhookError: 'not-found' };
214
+ }
215
+ if (!apiRes.ok) {
216
+ return {
217
+ ok: false,
218
+ error: `github_error_${apiRes.status}`,
219
+ webhookError: null,
220
+ };
221
+ }
222
+ const created = (await apiRes.json());
223
+ persistWebhookSuccess(configPath, config, repoPath, created.id);
224
+ return { ok: true, webhookId: created.id, ownerRepo };
225
+ }
226
+ function persistWebhookSuccess(configPath, config, repoPath, webhookId) {
227
+ if (!config.repoSettings)
228
+ config.repoSettings = {};
229
+ if (!config.repoSettings[repoPath])
230
+ config.repoSettings[repoPath] = {};
231
+ const ws = config.repoSettings[repoPath];
232
+ ws.webhookId = webhookId;
233
+ ws.webhookEnabled = true;
234
+ delete ws.webhookError;
235
+ saveConfig(configPath, config);
236
+ }
237
+ function persistWebhookError(configPath, config, repoPath, errorCode) {
238
+ if (!config.repoSettings)
239
+ config.repoSettings = {};
240
+ if (!config.repoSettings[repoPath])
241
+ config.repoSettings[repoPath] = {};
242
+ config.repoSettings[repoPath].webhookError = errorCode;
243
+ saveConfig(configPath, config);
244
+ }
245
+ // ── Router factory ─────────────────────────────────────────────────────────────
246
+ /**
247
+ * Creates and returns an Express Router for webhook management routes.
248
+ *
249
+ * Mount with:
250
+ * app.use('/webhooks/manage', createWebhookManagerRouter({ configPath, broadcastEvent, requireAuth }));
251
+ *
252
+ * Distinct from the `/webhooks` receiver in webhooks.ts — this router handles CRUD and lifecycle.
253
+ */
254
+ export function createWebhookManagerRouter(deps) {
255
+ const { configPath, requireAuth } = deps;
256
+ const fetchFn = deps.fetchFn ?? globalThis.fetch;
257
+ const githubApi = makeGithubApi(fetchFn);
258
+ const router = Router();
259
+ function getConfig() {
260
+ return loadConfig(configPath);
261
+ }
262
+ // All routes require authentication
263
+ router.use(requireAuth);
264
+ // ── POST /setup — Generate smee channel + secret, save config, start smee ──
265
+ router.post('/setup', async (_req, res) => {
266
+ // Create smee channel via redirect
267
+ let channelUrl;
268
+ try {
269
+ const smeeRes = await fetchFn('https://smee.io/new', {
270
+ redirect: 'manual',
271
+ });
272
+ const location = smeeRes.headers.get('location');
273
+ if (location) {
274
+ channelUrl = location;
275
+ }
276
+ else {
277
+ // No location header — try following redirect
278
+ const followRes = await fetchFn('https://smee.io/new', {
279
+ redirect: 'follow',
280
+ });
281
+ channelUrl = followRes.url;
282
+ }
283
+ if (!channelUrl || !channelUrl.startsWith('https://smee.io/')) {
284
+ res.status(502).json({ error: 'smee_channel_failed' });
285
+ return;
286
+ }
287
+ }
288
+ catch (err) {
289
+ res.status(502).json({ error: 'smee_unreachable', detail: String(err) });
290
+ return;
291
+ }
292
+ const secret = crypto.randomBytes(20).toString('hex');
293
+ const config = getConfig();
294
+ if (!config.github)
295
+ config.github = {};
296
+ config.github.webhookSecret = secret;
297
+ config.github.smeeUrl = channelUrl;
298
+ config.github.backfillOffered = false; // reset so backfill banner shows after fresh setup
299
+ saveConfig(configPath, config);
300
+ // Start smee client — non-blocking
301
+ startSmee(channelUrl, config.port ?? 3456);
302
+ res.json({ ok: true, smeeUrl: channelUrl });
303
+ });
304
+ // ── DELETE /setup — Teardown: delete all tracked webhooks, clear config, stop smee ──
305
+ router.delete('/setup', async (_req, res) => {
306
+ const config = getConfig();
307
+ const token = config.github?.accessToken;
308
+ let deleted = 0;
309
+ if (token && config.repoSettings) {
310
+ const entries = Object.entries(config.repoSettings);
311
+ for (const [repoPath, ws] of entries) {
312
+ const webhookId = ws.webhookId;
313
+ if (!webhookId)
314
+ continue;
315
+ const ownerRepo = await getOwnerRepoForPath(repoPath);
316
+ if (!ownerRepo)
317
+ continue;
318
+ const parts = ownerRepo.split('/');
319
+ const owner = parts[0];
320
+ const repo = parts[1];
321
+ if (!owner || !repo)
322
+ continue;
323
+ try {
324
+ await githubApi('DELETE', `/repos/${owner}/${repo}/hooks/${webhookId}`, token);
325
+ deleted++;
326
+ }
327
+ catch (err) {
328
+ logger.warn(`Failed to delete webhook ${webhookId} for ${ownerRepo}:`, err);
329
+ }
330
+ }
331
+ // Clear webhook fields from all repo settings
332
+ for (const ws of Object.values(config.repoSettings)) {
333
+ delete ws.webhookId;
334
+ delete ws.webhookEnabled;
335
+ delete ws.webhookError;
336
+ }
337
+ }
338
+ // Clear github webhook config fields
339
+ if (config.github) {
340
+ delete config.github.webhookSecret;
341
+ delete config.github.smeeUrl;
342
+ delete config.github.autoProvision;
343
+ delete config.github.backfillOffered;
344
+ }
345
+ saveConfig(configPath, config);
346
+ stopSmee();
347
+ res.json({ ok: true, deleted });
348
+ });
349
+ // ── GET /status — Health endpoint ──
350
+ router.get('/status', (_req, res) => {
351
+ const config = getConfig();
352
+ const github = config.github;
353
+ const configured = Boolean(github?.webhookSecret && github?.smeeUrl);
354
+ const { smeeConnected: sc, lastEventAt: lea } = getSmeeStatus();
355
+ const secret = github?.webhookSecret ?? null;
356
+ const secretPreview = secret ? `****${secret.slice(-4)}` : null;
357
+ res.json({
358
+ configured,
359
+ smeeConnected: sc,
360
+ lastEventAt: lea,
361
+ autoProvision: github?.autoProvision ?? false,
362
+ secretPreview,
363
+ });
364
+ });
365
+ // ── POST /reload — Hot-reload smee client ──
366
+ router.post('/reload', (_req, res) => {
367
+ const config = getConfig();
368
+ const smeeUrl = config.github?.smeeUrl;
369
+ stopSmee();
370
+ if (smeeUrl) {
371
+ startSmee(smeeUrl, config.port ?? 3456);
372
+ }
373
+ res.json({ ok: true });
374
+ });
375
+ // ── POST /ping — Test connection via GitHub ping API ──
376
+ router.post('/ping', async (_req, res) => {
377
+ const config = getConfig();
378
+ const token = config.github?.accessToken;
379
+ if (!token) {
380
+ res.status(400).json({ error: 'not_authenticated' });
381
+ return;
382
+ }
383
+ // Find first workspace with a webhookId
384
+ let foundOwnerRepo = null;
385
+ let foundWebhookId = null;
386
+ if (config.repoSettings) {
387
+ for (const [repoPath, ws] of Object.entries(config.repoSettings)) {
388
+ if (!ws.webhookId)
389
+ continue;
390
+ const ownerRepo = await getOwnerRepoForPath(repoPath);
391
+ if (ownerRepo) {
392
+ foundOwnerRepo = ownerRepo;
393
+ foundWebhookId = ws.webhookId;
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ if (!foundOwnerRepo || !foundWebhookId) {
399
+ res.json({ error: 'no_webhook' });
400
+ return;
401
+ }
402
+ const parts = foundOwnerRepo.split('/');
403
+ const owner = parts[0];
404
+ const repo = parts[1];
405
+ if (!owner || !repo) {
406
+ res.json({ error: 'no_webhook' });
407
+ return;
408
+ }
409
+ try {
410
+ const pingRes = await githubApi('POST', `/repos/${owner}/${repo}/hooks/${foundWebhookId}/pings`, token);
411
+ if (pingRes.ok || pingRes.status === 204) {
412
+ res.json({ ok: true });
413
+ }
414
+ else {
415
+ res
416
+ .status(pingRes.status)
417
+ .json({ error: 'ping_failed', status: pingRes.status });
418
+ }
419
+ }
420
+ catch (err) {
421
+ res.status(502).json({ error: 'ping_failed', detail: String(err) });
422
+ }
423
+ });
424
+ // ── POST /repos — Create webhook for a specific repo ──
425
+ router.post('/repos', async (req, res) => {
426
+ const body = req.body;
427
+ const repoPath = body.repoPath;
428
+ if (!repoPath || typeof repoPath !== 'string') {
429
+ res.status(400).json({ error: 'missing_repo_path' });
430
+ return;
431
+ }
432
+ const config = getConfig();
433
+ const result = await createWebhookForPath(repoPath, configPath, config, githubApi);
434
+ if (result.ok) {
435
+ res.json({ ok: true, webhookId: result.webhookId });
436
+ }
437
+ else {
438
+ const status = result.webhookError === 'not-admin' ? 403 : 400;
439
+ res
440
+ .status(status)
441
+ .json({ error: result.error, webhookError: result.webhookError });
442
+ }
443
+ });
444
+ // ── POST /repos/remove — Remove webhook for a specific repo ──
445
+ router.post('/repos/remove', async (req, res) => {
446
+ const body = req.body;
447
+ const repoPath = body.repoPath;
448
+ if (!repoPath || typeof repoPath !== 'string') {
449
+ res.status(400).json({ error: 'missing_repo_path' });
450
+ return;
451
+ }
452
+ const config = getConfig();
453
+ const token = config.github?.accessToken;
454
+ const ws = config.repoSettings?.[repoPath];
455
+ const webhookId = ws?.webhookId;
456
+ if (!webhookId) {
457
+ res.json({ ok: true });
458
+ return;
459
+ }
460
+ if (token) {
461
+ const ownerRepo = await getOwnerRepoForPath(repoPath);
462
+ if (ownerRepo) {
463
+ const parts = ownerRepo.split('/');
464
+ const owner = parts[0];
465
+ const repo = parts[1];
466
+ if (owner && repo) {
467
+ try {
468
+ const delRes = await githubApi('DELETE', `/repos/${owner}/${repo}/hooks/${webhookId}`, token);
469
+ // 404 is fine — webhook already gone
470
+ if (!delRes.ok && delRes.status !== 404) {
471
+ logger.warn(`DELETE hook returned ${delRes.status}`);
472
+ }
473
+ }
474
+ catch (err) {
475
+ logger.warn('Failed to delete webhook via API:', err);
476
+ }
477
+ }
478
+ }
479
+ }
480
+ // Clear local webhook state regardless of API result
481
+ if (config.repoSettings?.[repoPath]) {
482
+ const wsEntry = config.repoSettings[repoPath];
483
+ delete wsEntry.webhookId;
484
+ delete wsEntry.webhookEnabled;
485
+ delete wsEntry.webhookError;
486
+ }
487
+ saveConfig(configPath, config);
488
+ res.json({ ok: true });
489
+ });
490
+ // ── POST /backfill — Create webhooks for all workspaces ──
491
+ router.post('/backfill', async (_req, res) => {
492
+ const config = getConfig();
493
+ const workspacePaths = config.repos ?? [];
494
+ if (workspacePaths.length === 0) {
495
+ res.json({ total: 0, success: 0, failed: 0, results: [] });
496
+ return;
497
+ }
498
+ // Build repo map to confirm which paths have valid git remotes
499
+ const execFn = (file, args, opts) => execFileAsync(file, args, opts);
500
+ const repoMap = await buildRepoMap(workspacePaths, execFn);
501
+ const results = [];
502
+ // Bounded concurrency: 5 at a time
503
+ const CONCURRENCY = 5;
504
+ const paths = [...workspacePaths];
505
+ for (let i = 0; i < paths.length; i += CONCURRENCY) {
506
+ const batch = paths.slice(i, i + CONCURRENCY);
507
+ const batchResults = await Promise.all(batch.map(async (wsPath) => {
508
+ // Find ownerRepo from the map by value
509
+ let ownerRepo = null;
510
+ for (const [key, val] of repoMap) {
511
+ if (val === wsPath) {
512
+ ownerRepo = key;
513
+ break;
514
+ }
515
+ }
516
+ if (!ownerRepo) {
517
+ return {
518
+ path: wsPath,
519
+ ownerRepo: null,
520
+ ok: false,
521
+ error: 'no_remote',
522
+ };
523
+ }
524
+ // Reload config for each path to pick up writes from previous batch items
525
+ const freshConfig = getConfig();
526
+ const result = await createWebhookForPath(wsPath, configPath, freshConfig, githubApi);
527
+ if (result.ok) {
528
+ return { path: wsPath, ownerRepo, ok: true };
529
+ }
530
+ else {
531
+ return { path: wsPath, ownerRepo, ok: false, error: result.error };
532
+ }
533
+ }));
534
+ results.push(...batchResults);
535
+ }
536
+ const success = results.filter((r) => r.ok).length;
537
+ const failed = results.filter((r) => !r.ok).length;
538
+ // Mark backfill as offered so the banner doesn't re-show
539
+ const updatedConfig = getConfig();
540
+ if (updatedConfig.github) {
541
+ updatedConfig.github.backfillOffered = true;
542
+ saveConfig(configPath, updatedConfig);
543
+ }
544
+ res.json({ total: paths.length, success, failed, results });
545
+ });
546
+ return router;
547
+ }
@@ -0,0 +1,73 @@
1
+ import crypto from 'node:crypto';
2
+ import { Router } from 'express';
3
+ import express from 'express';
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+ function verifySignature(secret, payload, signature) {
8
+ const expected = 'sha256=' +
9
+ crypto.createHmac('sha256', secret).update(payload).digest('hex');
10
+ try {
11
+ return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ // ---------------------------------------------------------------------------
18
+ // Factory
19
+ // ---------------------------------------------------------------------------
20
+ export function createWebhookRouter(deps) {
21
+ const router = Router();
22
+ // Middleware: parse JSON and preserve raw body for signature verification
23
+ router.use(express.json({
24
+ verify: (req, _res, buf) => {
25
+ req.rawBody =
26
+ buf.toString('utf8');
27
+ },
28
+ }));
29
+ // POST / — receive GitHub webhook events
30
+ router.post('/', (req, res) => {
31
+ const secret = deps.secret();
32
+ // If no secret configured, webhooks are not set up yet
33
+ if (!secret) {
34
+ res.status(401).json({ error: 'Webhooks not configured' });
35
+ return;
36
+ }
37
+ const signature = req.headers['x-hub-signature-256'];
38
+ // Reject if signature header is missing
39
+ if (!signature || typeof signature !== 'string') {
40
+ res.status(401).json({ error: 'Missing signature' });
41
+ return;
42
+ }
43
+ // Verify signature against raw body
44
+ const rawBody = req.rawBody ?? '';
45
+ if (!verifySignature(secret, rawBody, signature)) {
46
+ res.status(401).json({ error: 'Invalid signature' });
47
+ return;
48
+ }
49
+ // Route based on event type
50
+ const event = req.headers['x-github-event'];
51
+ const repoFullName = req.body?.repository
52
+ ? req.body.repository?.full_name
53
+ : undefined;
54
+ if (event === 'pull_request' || event === 'pull_request_review') {
55
+ deps.broadcastEvent('pr-updated', repoFullName ? { repo: repoFullName } : undefined);
56
+ // If PR was merged, also broadcast worktrees-changed so sidebar refreshes with branchState: 'merged'
57
+ if (event === 'pull_request') {
58
+ const body = req.body;
59
+ const action = body.action;
60
+ const pr = body.pull_request;
61
+ if (action === 'closed' && pr?.merged === true) {
62
+ deps.broadcastEvent('worktrees-changed');
63
+ }
64
+ }
65
+ }
66
+ else if (event === 'check_suite' || event === 'check_run') {
67
+ deps.broadcastEvent('ci-updated', repoFullName ? { repo: repoFullName } : undefined);
68
+ }
69
+ // Unknown events: ignore, return 200 OK
70
+ res.json({ ok: true });
71
+ });
72
+ return router;
73
+ }