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,140 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { listBranches, normalizeBranchNames } from '../server/git.js';
4
+ import { ensureBranchLocal } from '../server/git.js';
5
+ describe('normalizeBranchNames', () => {
6
+ it('deduplicates refs, strips origin prefixes, and skips HEAD entries', () => {
7
+ const stdout = [
8
+ 'main',
9
+ 'origin/main',
10
+ 'origin/feat/remote-only',
11
+ 'feat/local',
12
+ 'remotes/origin/HEAD -> origin/main',
13
+ 'origin/feat/remote-only',
14
+ '',
15
+ ].join('\n');
16
+ assert.deepEqual(normalizeBranchNames(stdout), [
17
+ 'feat/local',
18
+ 'feat/remote-only',
19
+ 'main',
20
+ ]);
21
+ });
22
+ });
23
+ describe('listBranches', () => {
24
+ it('refreshes remotes before listing when requested', async () => {
25
+ const calls = [];
26
+ const branches = await listBranches('/tmp/repo', {
27
+ refresh: true,
28
+ exec: async (file, args, options) => {
29
+ calls.push({ file, args, cwd: options.cwd });
30
+ if (args[0] === 'fetch') {
31
+ return { stdout: '', stderr: '' };
32
+ }
33
+ return {
34
+ stdout: ['main', 'origin/main', 'origin/feature/remote'].join('\n'),
35
+ stderr: '',
36
+ };
37
+ },
38
+ });
39
+ assert.deepEqual(calls, [
40
+ { file: 'git', args: ['fetch', '--all', '--prune'], cwd: '/tmp/repo' },
41
+ {
42
+ file: 'git',
43
+ args: ['branch', '-a', '--format=%(refname:short)'],
44
+ cwd: '/tmp/repo',
45
+ },
46
+ ]);
47
+ assert.deepEqual(branches, ['feature/remote', 'main']);
48
+ });
49
+ it('falls back to locally-known refs if fetch fails', async () => {
50
+ const branches = await listBranches('/tmp/repo', {
51
+ refresh: true,
52
+ exec: async (_file, args) => {
53
+ if (args[0] === 'fetch') {
54
+ throw new Error('network down');
55
+ }
56
+ return {
57
+ stdout: ['main', 'origin/feature/stale'].join('\n'),
58
+ stderr: '',
59
+ };
60
+ },
61
+ });
62
+ assert.deepEqual(branches, ['feature/stale', 'main']);
63
+ });
64
+ it('returns an empty list when refs cannot be listed', async () => {
65
+ const branches = await listBranches('/tmp/repo', {
66
+ exec: async () => {
67
+ throw new Error('git failed');
68
+ },
69
+ });
70
+ assert.deepEqual(branches, []);
71
+ });
72
+ });
73
+ describe('ensureBranchLocal', () => {
74
+ it('returns true immediately if branch exists locally', async () => {
75
+ const calls = [];
76
+ const exec = async (_cmd, args, _opts) => {
77
+ calls.push(args);
78
+ return { stdout: 'abc123\n', stderr: '' };
79
+ };
80
+ const result = await ensureBranchLocal('/tmp/repo', 'main', { exec });
81
+ assert.equal(result.found, true);
82
+ assert.deepEqual(calls, [['rev-parse', '--verify', '--', 'main']]);
83
+ });
84
+ it('fetches from origin if branch does not exist locally', async () => {
85
+ const calls = [];
86
+ let revParseCount = 0;
87
+ const exec = async (_cmd, args, _opts) => {
88
+ calls.push(args);
89
+ if (args[0] === 'rev-parse') {
90
+ revParseCount++;
91
+ if (revParseCount === 1) {
92
+ const err = new Error('unknown revision or path not in the working tree');
93
+ throw err;
94
+ }
95
+ return { stdout: 'abc123\n', stderr: '' };
96
+ }
97
+ return { stdout: '', stderr: '' };
98
+ };
99
+ const result = await ensureBranchLocal('/tmp/repo', 'feature/remote-only', {
100
+ exec,
101
+ });
102
+ assert.equal(result.found, true);
103
+ assert.deepEqual(calls[1], [
104
+ 'fetch',
105
+ 'origin',
106
+ '--',
107
+ 'feature/remote-only:feature/remote-only',
108
+ ]);
109
+ });
110
+ it('returns found:false with reason not_found if branch does not exist anywhere', async () => {
111
+ const exec = async (_cmd, _args, _opts) => {
112
+ throw new Error("couldn't find remote ref");
113
+ };
114
+ const result = await ensureBranchLocal('/tmp/repo', 'nonexistent', {
115
+ exec,
116
+ });
117
+ assert.equal(result.found, false);
118
+ assert.equal(result.reason, 'not_found');
119
+ });
120
+ it('returns found:false with reason fetch_failed for non-ref errors on fetch', async () => {
121
+ let callCount = 0;
122
+ const exec = async (_cmd, _args, _opts) => {
123
+ callCount++;
124
+ if (callCount === 1)
125
+ throw new Error('unknown revision or path not in the working tree');
126
+ throw new Error('network timeout');
127
+ };
128
+ const result = await ensureBranchLocal('/tmp/repo', 'some-branch', {
129
+ exec,
130
+ });
131
+ assert.equal(result.found, false);
132
+ assert.equal(result.reason, 'fetch_failed');
133
+ });
134
+ it('rethrows non-git errors from rev-parse', async () => {
135
+ const exec = async () => {
136
+ throw new Error('permission denied');
137
+ };
138
+ await assert.rejects(() => ensureBranchLocal('/tmp/repo', 'main', { exec }), { message: 'permission denied' });
139
+ });
140
+ });
@@ -0,0 +1,455 @@
1
+ import { test, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import express from 'express';
7
+ import { createGitHubAppRouter, _getDeviceFlowState, } from '../server/github-app.js';
8
+ import { saveConfig, DEFAULTS } from '../server/config.js';
9
+ // ── Helpers ───────────────────────────────────────────────────────────────────
10
+ /**
11
+ * Creates a mock fetch function from a map of URL substrings to response sequences.
12
+ * Each call to a matching URL pops the next response from the front of that sequence.
13
+ */
14
+ function createMockFetch(urlMap) {
15
+ const queues = new Map(Object.entries(urlMap).map(([k, v]) => [k, [...v]]));
16
+ return async (input, _init) => {
17
+ const url = typeof input === 'string'
18
+ ? input
19
+ : input instanceof URL
20
+ ? input.href
21
+ : input.url;
22
+ for (const [pattern, queue] of queues) {
23
+ if (url.includes(pattern)) {
24
+ const next = queue.shift();
25
+ if (!next) {
26
+ throw new Error(`Mock fetch: exhausted responses for pattern "${pattern}", url: ${url}`);
27
+ }
28
+ if ('throw' in next) {
29
+ throw next.throw;
30
+ }
31
+ const status = next.status ?? 200;
32
+ const body = 'error' in next ? next.error : next.json;
33
+ return new Response(JSON.stringify(body), {
34
+ status,
35
+ headers: { 'Content-Type': 'application/json' },
36
+ });
37
+ }
38
+ }
39
+ throw new Error(`Mock fetch: no pattern matched url: ${url}`);
40
+ };
41
+ }
42
+ function startServer(opts) {
43
+ return new Promise((resolve) => {
44
+ const app = express();
45
+ app.use(express.json());
46
+ const routerDeps = {
47
+ configPath: opts.configPath,
48
+ clientId: opts.clientId ?? 'test-client-id',
49
+ ...(opts.fetchFn ? { fetchFn: opts.fetchFn } : {}),
50
+ ...(opts.onConnected ? { onConnected: opts.onConnected } : {}),
51
+ };
52
+ app.use('/auth/github', createGitHubAppRouter(routerDeps));
53
+ const srv = app.listen(0, '127.0.0.1', () => {
54
+ const addr = srv.address();
55
+ let url = '';
56
+ if (typeof addr === 'object' && addr) {
57
+ url = `http://127.0.0.1:${addr.port}`;
58
+ }
59
+ resolve({ srv, url });
60
+ });
61
+ });
62
+ }
63
+ function stopServer(srv) {
64
+ return new Promise((resolve) => {
65
+ if (srv)
66
+ srv.close(() => resolve());
67
+ else
68
+ resolve();
69
+ });
70
+ }
71
+ /** Waits for a promise to resolve within timeoutMs, otherwise rejects. */
72
+ function withTimeout(promise, timeoutMs, label) {
73
+ return new Promise((resolve, reject) => {
74
+ const timer = setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms waiting for: ${label}`)), timeoutMs);
75
+ promise.then((v) => {
76
+ clearTimeout(timer);
77
+ resolve(v);
78
+ }, (e) => {
79
+ clearTimeout(timer);
80
+ reject(e);
81
+ });
82
+ });
83
+ }
84
+ /** Returns a promise that resolves after ms milliseconds. */
85
+ function delay(ms) {
86
+ return new Promise((resolve) => setTimeout(resolve, ms));
87
+ }
88
+ /**
89
+ * Polls the status endpoint until deviceFlowStatus matches the expected value,
90
+ * or rejects after timeoutMs.
91
+ */
92
+ async function waitForFlowStatus(url, expected, timeoutMs) {
93
+ const deadline = Date.now() + timeoutMs;
94
+ while (Date.now() < deadline) {
95
+ const res = await fetch(`${url}/auth/github/status`);
96
+ const data = (await res.json());
97
+ if (data.deviceFlowStatus === expected)
98
+ return;
99
+ await delay(50);
100
+ }
101
+ throw new Error(`Timeout after ${timeoutMs}ms waiting for deviceFlowStatus=${expected}`);
102
+ }
103
+ // ── Mock responses ────────────────────────────────────────────────────────────
104
+ const DEVICE_CODE_RESPONSE = {
105
+ device_code: 'dc_test',
106
+ user_code: 'ABCD-1234',
107
+ verification_uri: 'https://github.com/login/device',
108
+ expires_in: 900,
109
+ interval: 1,
110
+ };
111
+ const DEVICE_CODE_RESPONSE_5S = { ...DEVICE_CODE_RESPONSE, interval: 5 };
112
+ const ACCESS_TOKEN_RESPONSE = { access_token: 'ghs_mock_token_123' };
113
+ const GRAPHQL_RESPONSE = { data: { viewer: { login: 'octocat' } } };
114
+ // ── Shared state ─────────────────────────────────────────────────────────────
115
+ let tmpDir;
116
+ let baseConfigPath;
117
+ /** Default server (no mock fetch — for the "no token" status test) */
118
+ let defaultServer;
119
+ let defaultBaseUrl;
120
+ // ── Setup / teardown ──────────────────────────────────────────────────────────
121
+ before(async () => {
122
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'github-app-test-'));
123
+ baseConfigPath = path.join(tmpDir, 'config.json');
124
+ saveConfig(baseConfigPath, { ...DEFAULTS });
125
+ const result = await startServer({ configPath: baseConfigPath });
126
+ defaultServer = result.srv;
127
+ defaultBaseUrl = result.url;
128
+ });
129
+ after(async () => {
130
+ await stopServer(defaultServer);
131
+ fs.rmSync(tmpDir, { recursive: true, force: true });
132
+ });
133
+ // ── Tests ─────────────────────────────────────────────────────────────────────
134
+ test('GET /auth/github initiates device flow and returns userCode', async () => {
135
+ const configPath = path.join(tmpDir, 'config-initiate.json');
136
+ saveConfig(configPath, { ...DEFAULTS });
137
+ const mockFetch = createMockFetch({
138
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE_5S }],
139
+ });
140
+ const { srv, url } = await startServer({ configPath, fetchFn: mockFetch });
141
+ try {
142
+ const res = await fetch(`${url}/auth/github`);
143
+ assert.equal(res.status, 200);
144
+ const data = (await res.json());
145
+ assert.equal(data.userCode, 'ABCD-1234');
146
+ assert.equal(data.verificationUri, 'https://github.com/login/device');
147
+ assert.equal(data.expiresIn, 900);
148
+ }
149
+ finally {
150
+ await stopServer(srv);
151
+ }
152
+ });
153
+ test('GET /auth/github/status returns { connected: false } when no token', async () => {
154
+ const res = await fetch(`${defaultBaseUrl}/auth/github/status`);
155
+ assert.equal(res.status, 200);
156
+ const data = (await res.json());
157
+ assert.equal(data.connected, false);
158
+ assert.ok(data.username === null || data.username === undefined, 'username should be null when not connected');
159
+ });
160
+ test('Device flow poll completes and saves token to config', async () => {
161
+ const configPath = path.join(tmpDir, 'config-poll-complete.json');
162
+ saveConfig(configPath, { ...DEFAULTS });
163
+ let resolveConnected;
164
+ const connectedPromise = new Promise((resolve) => {
165
+ resolveConnected = resolve;
166
+ });
167
+ const mockFetch = createMockFetch({
168
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
169
+ 'login/oauth/access_token': [
170
+ { json: { error: 'authorization_pending' } },
171
+ { json: ACCESS_TOKEN_RESPONSE },
172
+ ],
173
+ 'api.github.com/graphql': [{ json: GRAPHQL_RESPONSE }],
174
+ });
175
+ const { srv, url } = await startServer({
176
+ configPath,
177
+ fetchFn: mockFetch,
178
+ onConnected: resolveConnected,
179
+ });
180
+ try {
181
+ const res = await fetch(`${url}/auth/github`);
182
+ assert.equal(res.status, 200);
183
+ await withTimeout(connectedPromise, 10_000, 'onConnected callback');
184
+ const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
185
+ assert.equal(savedConfig.github?.accessToken, 'ghs_mock_token_123');
186
+ assert.equal(savedConfig.github?.username, 'octocat');
187
+ }
188
+ finally {
189
+ await stopServer(srv);
190
+ }
191
+ });
192
+ test('GET /auth/github/status returns connected after device flow', async () => {
193
+ const configPath = path.join(tmpDir, 'config-status-connected.json');
194
+ saveConfig(configPath, { ...DEFAULTS });
195
+ let resolveConnected;
196
+ const connectedPromise = new Promise((resolve) => {
197
+ resolveConnected = resolve;
198
+ });
199
+ const mockFetch = createMockFetch({
200
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
201
+ 'login/oauth/access_token': [{ json: ACCESS_TOKEN_RESPONSE }],
202
+ 'api.github.com/graphql': [{ json: GRAPHQL_RESPONSE }],
203
+ });
204
+ const { srv, url } = await startServer({
205
+ configPath,
206
+ fetchFn: mockFetch,
207
+ onConnected: resolveConnected,
208
+ });
209
+ try {
210
+ await fetch(`${url}/auth/github`);
211
+ await withTimeout(connectedPromise, 10_000, 'onConnected callback');
212
+ const res = await fetch(`${url}/auth/github/status`);
213
+ assert.equal(res.status, 200);
214
+ const data = (await res.json());
215
+ assert.equal(data.connected, true);
216
+ assert.equal(data.username, 'octocat');
217
+ }
218
+ finally {
219
+ await stopServer(srv);
220
+ }
221
+ });
222
+ test('access_denied sets deviceFlowStatus to denied', async () => {
223
+ const configPath = path.join(tmpDir, 'config-denied.json');
224
+ saveConfig(configPath, { ...DEFAULTS });
225
+ const mockFetch = createMockFetch({
226
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
227
+ 'login/oauth/access_token': [{ json: { error: 'access_denied' } }],
228
+ });
229
+ const { srv, url } = await startServer({ configPath, fetchFn: mockFetch });
230
+ try {
231
+ const res = await fetch(`${url}/auth/github`);
232
+ assert.equal(res.status, 200);
233
+ // Poll until the flow status changes to denied (or time out)
234
+ await waitForFlowStatus(url, 'denied', 5_000);
235
+ const statusRes = await fetch(`${url}/auth/github/status`);
236
+ assert.equal(statusRes.status, 200);
237
+ const data = (await statusRes.json());
238
+ assert.equal(data.connected, false);
239
+ assert.equal(data.username, null);
240
+ assert.equal(data.deviceFlowStatus, 'denied');
241
+ }
242
+ finally {
243
+ await stopServer(srv);
244
+ }
245
+ });
246
+ test('expired_token sets deviceFlowStatus to expired', async () => {
247
+ const configPath = path.join(tmpDir, 'config-expired.json');
248
+ saveConfig(configPath, { ...DEFAULTS });
249
+ const mockFetch = createMockFetch({
250
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
251
+ 'login/oauth/access_token': [{ json: { error: 'expired_token' } }],
252
+ });
253
+ const { srv, url } = await startServer({ configPath, fetchFn: mockFetch });
254
+ try {
255
+ const res = await fetch(`${url}/auth/github`);
256
+ assert.equal(res.status, 200);
257
+ // Poll until the flow status changes to expired (or time out)
258
+ await waitForFlowStatus(url, 'expired', 5_000);
259
+ const statusRes = await fetch(`${url}/auth/github/status`);
260
+ assert.equal(statusRes.status, 200);
261
+ const data = (await statusRes.json());
262
+ assert.equal(data.connected, false);
263
+ assert.equal(data.username, null);
264
+ assert.equal(data.deviceFlowStatus, 'expired');
265
+ }
266
+ finally {
267
+ await stopServer(srv);
268
+ }
269
+ });
270
+ test('Device code initiation failure returns 500', async () => {
271
+ const configPath = path.join(tmpDir, 'config-init-fail.json');
272
+ saveConfig(configPath, { ...DEFAULTS });
273
+ const mockFetch = createMockFetch({
274
+ 'login/device/code': [{ json: { error: 'server_error' }, status: 500 }],
275
+ });
276
+ const { srv, url } = await startServer({ configPath, fetchFn: mockFetch });
277
+ try {
278
+ const res = await fetch(`${url}/auth/github`);
279
+ assert.equal(res.status, 500);
280
+ const data = (await res.json());
281
+ assert.ok(data.error, 'Response should have an error message');
282
+ }
283
+ finally {
284
+ await stopServer(srv);
285
+ }
286
+ });
287
+ test('slow_down increases poll interval', async () => {
288
+ const configPath = path.join(tmpDir, 'config-slowdown.json');
289
+ saveConfig(configPath, { ...DEFAULTS });
290
+ let resolveConnected;
291
+ const connectedPromise = new Promise((resolve) => {
292
+ resolveConnected = resolve;
293
+ });
294
+ // Use interval: 1 so the first poll fires quickly, then slow_down bumps it to 6
295
+ const deviceCodeWith1sInterval = { ...DEVICE_CODE_RESPONSE, interval: 1 };
296
+ const mockFetch = createMockFetch({
297
+ 'login/device/code': [{ json: deviceCodeWith1sInterval }],
298
+ 'login/oauth/access_token': [
299
+ { json: { error: 'slow_down' } },
300
+ { json: { error: 'authorization_pending' } },
301
+ { json: ACCESS_TOKEN_RESPONSE },
302
+ ],
303
+ 'api.github.com/graphql': [{ json: GRAPHQL_RESPONSE }],
304
+ });
305
+ const { srv, url } = await startServer({
306
+ configPath,
307
+ fetchFn: mockFetch,
308
+ onConnected: resolveConnected,
309
+ });
310
+ try {
311
+ await fetch(`${url}/auth/github`);
312
+ // Wait long enough for the first poll (slow_down) to fire but not the second
313
+ // Initial interval is 1s; after slow_down it becomes 6s
314
+ await delay(1500);
315
+ const state = _getDeviceFlowState();
316
+ assert.equal(state.interval, 6, `Expected interval to be 6 after slow_down, got ${state.interval}`);
317
+ // Let the flow finish to clean up timers
318
+ await withTimeout(connectedPromise, 15_000, 'onConnected after slow_down');
319
+ }
320
+ finally {
321
+ await stopServer(srv);
322
+ }
323
+ });
324
+ test('Network error during poll continues polling', async () => {
325
+ const configPath = path.join(tmpDir, 'config-network-error.json');
326
+ saveConfig(configPath, { ...DEFAULTS });
327
+ let resolveConnected;
328
+ const connectedPromise = new Promise((resolve) => {
329
+ resolveConnected = resolve;
330
+ });
331
+ const mockFetch = createMockFetch({
332
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
333
+ 'login/oauth/access_token': [
334
+ { throw: new Error('Network error') },
335
+ { json: ACCESS_TOKEN_RESPONSE },
336
+ ],
337
+ 'api.github.com/graphql': [{ json: GRAPHQL_RESPONSE }],
338
+ });
339
+ const { srv, url } = await startServer({
340
+ configPath,
341
+ fetchFn: mockFetch,
342
+ onConnected: resolveConnected,
343
+ });
344
+ try {
345
+ const res = await fetch(`${url}/auth/github`);
346
+ assert.equal(res.status, 200);
347
+ await withTimeout(connectedPromise, 10_000, 'onConnected after network error recovery');
348
+ const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
349
+ assert.equal(savedConfig.github?.accessToken, 'ghs_mock_token_123');
350
+ }
351
+ finally {
352
+ await stopServer(srv);
353
+ }
354
+ });
355
+ test('Concurrent flow cancels previous', async () => {
356
+ const configPath = path.join(tmpDir, 'config-concurrent.json');
357
+ saveConfig(configPath, { ...DEFAULTS });
358
+ let resolveConnected;
359
+ const connectedPromise = new Promise((resolve) => {
360
+ resolveConnected = resolve;
361
+ });
362
+ const firstDeviceCode = { ...DEVICE_CODE_RESPONSE, device_code: 'dc_first' };
363
+ const secondDeviceCode = {
364
+ ...DEVICE_CODE_RESPONSE,
365
+ device_code: 'dc_second',
366
+ };
367
+ const secondAccessToken = { access_token: 'ghs_second_token' };
368
+ // The mock uses pattern matching: both calls to login/device/code consume from the same queue
369
+ const mockFetch = createMockFetch({
370
+ 'login/device/code': [
371
+ { json: firstDeviceCode },
372
+ { json: secondDeviceCode },
373
+ ],
374
+ 'login/oauth/access_token': [{ json: secondAccessToken }],
375
+ 'api.github.com/graphql': [{ json: GRAPHQL_RESPONSE }],
376
+ });
377
+ const { srv, url } = await startServer({
378
+ configPath,
379
+ fetchFn: mockFetch,
380
+ onConnected: resolveConnected,
381
+ });
382
+ try {
383
+ // Fire both requests close together; the second supersedes the first
384
+ await fetch(`${url}/auth/github`);
385
+ await fetch(`${url}/auth/github`);
386
+ await withTimeout(connectedPromise, 10_000, 'onConnected for second (winning) flow');
387
+ const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
388
+ assert.equal(savedConfig.github?.accessToken, 'ghs_second_token', 'Second flow token should be saved');
389
+ }
390
+ finally {
391
+ await stopServer(srv);
392
+ }
393
+ });
394
+ test('POST /disconnect preserves webhookSecret and smeeUrl', async () => {
395
+ const configPath = path.join(tmpDir, 'config-disconnect.json');
396
+ saveConfig(configPath, {
397
+ ...DEFAULTS,
398
+ github: {
399
+ accessToken: 'ghs_to_remove',
400
+ username: 'testuser',
401
+ webhookSecret: 'whsec_keep',
402
+ smeeUrl: 'https://smee.io/keep',
403
+ },
404
+ });
405
+ const { srv, url } = await startServer({ configPath });
406
+ try {
407
+ const res = await fetch(`${url}/auth/github/disconnect`, {
408
+ method: 'POST',
409
+ });
410
+ assert.equal(res.status, 200);
411
+ const data = (await res.json());
412
+ assert.equal(data.ok, true);
413
+ // Verify token and username removed, but webhook config preserved
414
+ const saved = JSON.parse(fs.readFileSync(configPath, 'utf8'));
415
+ assert.equal(saved.github?.accessToken, undefined, 'accessToken should be removed');
416
+ assert.equal(saved.github?.username, undefined, 'username should be removed');
417
+ assert.equal(saved.github?.webhookSecret, 'whsec_keep', 'webhookSecret should be preserved');
418
+ assert.equal(saved.github?.smeeUrl, 'https://smee.io/keep', 'smeeUrl should be preserved');
419
+ // Status should show disconnected
420
+ const statusRes = await fetch(`${url}/auth/github/status`);
421
+ const statusData = (await statusRes.json());
422
+ assert.equal(statusData.connected, false);
423
+ }
424
+ finally {
425
+ await stopServer(srv);
426
+ }
427
+ });
428
+ test('Token saved without username when GraphQL fails', async () => {
429
+ const configPath = path.join(tmpDir, 'config-no-username.json');
430
+ saveConfig(configPath, { ...DEFAULTS });
431
+ let resolveConnected;
432
+ const connectedPromise = new Promise((resolve) => {
433
+ resolveConnected = resolve;
434
+ });
435
+ const mockFetch = createMockFetch({
436
+ 'login/device/code': [{ json: DEVICE_CODE_RESPONSE }],
437
+ 'login/oauth/access_token': [{ json: ACCESS_TOKEN_RESPONSE }],
438
+ 'api.github.com/graphql': [{ json: {}, status: 500 }],
439
+ });
440
+ const { srv, url } = await startServer({
441
+ configPath,
442
+ fetchFn: mockFetch,
443
+ onConnected: resolveConnected,
444
+ });
445
+ try {
446
+ await fetch(`${url}/auth/github`);
447
+ await withTimeout(connectedPromise, 10_000, 'onConnected after GraphQL failure');
448
+ const saved = JSON.parse(fs.readFileSync(configPath, 'utf8'));
449
+ assert.equal(saved.github?.accessToken, 'ghs_mock_token_123', 'Token should be saved');
450
+ assert.equal(saved.github?.username, undefined, 'Username should not be saved when GraphQL fails');
451
+ }
452
+ finally {
453
+ await stopServer(srv);
454
+ }
455
+ });