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,236 @@
1
+ import { test, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import express from 'express';
7
+ import { createBranchLinkerRouter, invalidateBranchLinkerCache, } from '../server/branch-linker.js';
8
+ import { saveConfig, DEFAULTS } from '../server/config.js';
9
+ let tmpDir;
10
+ let configPath;
11
+ let server;
12
+ let baseUrl;
13
+ const WORKSPACE_PATH_A = '/fake/workspace/repo-a';
14
+ const WORKSPACE_PATH_B = '/fake/workspace/repo-b';
15
+ /**
16
+ * Creates a mock execAsync that returns configured branch lists per cwd.
17
+ * - `git branch -a` → returns newline-joined branch names or throws
18
+ */
19
+ function makeMockExec(opts) {
20
+ return async (cmd, args, options) => {
21
+ const command = cmd;
22
+ const argv = args;
23
+ const cwd = options.cwd ?? '';
24
+ if (command === 'git' && argv[0] === 'branch') {
25
+ if (opts.errorByPath?.[cwd])
26
+ throw opts.errorByPath[cwd];
27
+ const branches = opts.branchesByPath?.[cwd] ?? [];
28
+ return { stdout: branches.join('\n') + '\n', stderr: '' };
29
+ }
30
+ throw new Error(`Unexpected exec call: ${command} ${argv.join(' ')}`);
31
+ };
32
+ }
33
+ function startServer(execAsyncFn, getActiveBranchNames) {
34
+ return new Promise((resolve) => {
35
+ const app = express();
36
+ app.use(express.json());
37
+ const deps = {
38
+ configPath,
39
+ execAsync: execAsyncFn,
40
+ ...(getActiveBranchNames ? { getActiveBranchNames } : {}),
41
+ };
42
+ app.use('/branch-linker', createBranchLinkerRouter(deps));
43
+ server = app.listen(0, '127.0.0.1', () => {
44
+ const addr = server.address();
45
+ if (typeof addr === 'object' && addr) {
46
+ baseUrl = `http://127.0.0.1:${addr.port}`;
47
+ }
48
+ resolve();
49
+ });
50
+ });
51
+ }
52
+ function stopServer() {
53
+ return new Promise((resolve) => {
54
+ if (server)
55
+ server.close(() => resolve());
56
+ else
57
+ resolve();
58
+ });
59
+ }
60
+ async function getLinks() {
61
+ const res = await fetch(`${baseUrl}/branch-linker/links`);
62
+ return res.json();
63
+ }
64
+ before(() => {
65
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'branch-linker-test-'));
66
+ configPath = path.join(tmpDir, 'config.json');
67
+ // Clear any module-level cache before test suite runs
68
+ invalidateBranchLinkerCache();
69
+ });
70
+ after(async () => {
71
+ await stopServer();
72
+ fs.rmSync(tmpDir, { recursive: true, force: true });
73
+ });
74
+ test('extracts Jira ticket IDs from branch names', async () => {
75
+ await stopServer();
76
+ invalidateBranchLinkerCache();
77
+ saveConfig(configPath, {
78
+ ...DEFAULTS,
79
+ repos: [WORKSPACE_PATH_A],
80
+ });
81
+ const exec = makeMockExec({
82
+ branchesByPath: {
83
+ [WORKSPACE_PATH_A]: ['dy/fix/ACME-123-auth', 'main'],
84
+ },
85
+ });
86
+ await startServer(exec);
87
+ const data = await getLinks();
88
+ assert.ok('ACME-123' in data, 'Should extract ACME-123 from branch name');
89
+ const links = data['ACME-123'];
90
+ assert.equal(links.length, 1);
91
+ assert.equal(links[0].branchName, 'dy/fix/ACME-123-auth');
92
+ assert.equal(links[0].repoPath, WORKSPACE_PATH_A);
93
+ assert.equal(links[0].repoName, 'repo-a');
94
+ });
95
+ test('extracts GH issue IDs from gh-N branches', async () => {
96
+ await stopServer();
97
+ invalidateBranchLinkerCache();
98
+ saveConfig(configPath, {
99
+ ...DEFAULTS,
100
+ repos: [WORKSPACE_PATH_A],
101
+ });
102
+ const exec = makeMockExec({
103
+ branchesByPath: {
104
+ // Use a branch that only the GH regex matches (no embedded uppercase-only letters)
105
+ // to get a single clean GH-42 link. The Jira regex (/[A-Z]{2,}-\d+/gi) would also
106
+ // match 'gh-42' since the flag is case-insensitive, so we use a prefix that isolates
107
+ // the GH regex match by starting with 'gh-' at the very start of the branch name.
108
+ [WORKSPACE_PATH_A]: ['gh-42-login-fix'],
109
+ },
110
+ });
111
+ await startServer(exec);
112
+ const data = await getLinks();
113
+ assert.ok('GH-42' in data, 'Should extract GH-42 from branch name');
114
+ const links = data['GH-42'];
115
+ // Only the GH regex matches this branch — the Jira regex explicitly excludes 'GH'
116
+ // to avoid double-matching. Verify all links point to the correct branch and repo.
117
+ assert.ok(links.length >= 1, 'Should have at least one GH-42 link');
118
+ assert.ok(links.every((l) => l.branchName === 'gh-42-login-fix'), 'All links should reference the correct branch');
119
+ assert.ok(links.every((l) => l.repoPath === WORKSPACE_PATH_A), 'All links should reference the correct repo');
120
+ });
121
+ test('same ticket in two repos yields array of 2 BranchLinks', async () => {
122
+ await stopServer();
123
+ invalidateBranchLinkerCache();
124
+ saveConfig(configPath, {
125
+ ...DEFAULTS,
126
+ repos: [WORKSPACE_PATH_A, WORKSPACE_PATH_B],
127
+ });
128
+ const exec = makeMockExec({
129
+ branchesByPath: {
130
+ [WORKSPACE_PATH_A]: ['feature/PROJ-99-payment'],
131
+ [WORKSPACE_PATH_B]: ['bugfix/PROJ-99-payment-fix'],
132
+ },
133
+ });
134
+ await startServer(exec);
135
+ const data = await getLinks();
136
+ assert.ok('PROJ-99' in data, 'Should have PROJ-99 key');
137
+ const links = data['PROJ-99'];
138
+ assert.equal(links.length, 2, 'Should have 2 BranchLinks for the same ticket across 2 repos');
139
+ const repoPaths = links.map((l) => l.repoPath).sort();
140
+ assert.deepEqual(repoPaths, [WORKSPACE_PATH_A, WORKSPACE_PATH_B].sort());
141
+ });
142
+ test('ignores branches without ticket IDs', async () => {
143
+ await stopServer();
144
+ invalidateBranchLinkerCache();
145
+ saveConfig(configPath, {
146
+ ...DEFAULTS,
147
+ repos: [WORKSPACE_PATH_A],
148
+ });
149
+ const exec = makeMockExec({
150
+ branchesByPath: {
151
+ [WORKSPACE_PATH_A]: [
152
+ 'main',
153
+ 'develop',
154
+ 'chore/cleanup',
155
+ 'feature/new-ui',
156
+ ],
157
+ },
158
+ });
159
+ await startServer(exec);
160
+ const data = await getLinks();
161
+ assert.equal(Object.keys(data).length, 0, 'Plain branches should produce no ticket links');
162
+ });
163
+ test('hasActiveSession true when branch is in active set', async () => {
164
+ await stopServer();
165
+ invalidateBranchLinkerCache();
166
+ saveConfig(configPath, {
167
+ ...DEFAULTS,
168
+ repos: [WORKSPACE_PATH_A],
169
+ });
170
+ const activeBranch = 'feature/ACTIVE-1-work';
171
+ const exec = makeMockExec({
172
+ branchesByPath: {
173
+ [WORKSPACE_PATH_A]: [activeBranch, 'feature/INACTIVE-2-other'],
174
+ },
175
+ });
176
+ const getActiveBranchNames = () => {
177
+ return new Map([[WORKSPACE_PATH_A, new Set([activeBranch])]]);
178
+ };
179
+ await startServer(exec, getActiveBranchNames);
180
+ const data = await getLinks();
181
+ const activeLinks = data['ACTIVE-1'];
182
+ assert.ok(activeLinks, 'Should have ACTIVE-1 ticket');
183
+ assert.equal(activeLinks.length, 1);
184
+ assert.equal(activeLinks[0].hasActiveSession, true, 'Active branch should have hasActiveSession true');
185
+ const inactiveLinks = data['INACTIVE-2'];
186
+ assert.ok(inactiveLinks, 'Should have INACTIVE-2 ticket');
187
+ assert.equal(inactiveLinks[0].hasActiveSession, false, 'Inactive branch should have hasActiveSession false');
188
+ });
189
+ test('invalidateBranchLinkerCache forces fresh scan', async () => {
190
+ await stopServer();
191
+ invalidateBranchLinkerCache();
192
+ saveConfig(configPath, {
193
+ ...DEFAULTS,
194
+ repos: [WORKSPACE_PATH_A],
195
+ });
196
+ let gitCallCount = 0;
197
+ const baseExec = makeMockExec({
198
+ branchesByPath: {
199
+ [WORKSPACE_PATH_A]: ['feature/SCAN-1-fresh'],
200
+ },
201
+ });
202
+ const countingExec = async (...args) => {
203
+ const [cmd] = args;
204
+ if (cmd === 'git')
205
+ gitCallCount++;
206
+ return baseExec(...args);
207
+ };
208
+ await startServer(countingExec);
209
+ // First request — populates module-level cache
210
+ const first = await getLinks();
211
+ assert.ok('SCAN-1' in first, 'Should have SCAN-1 after first request');
212
+ assert.equal(gitCallCount, 1, 'git should be called once on first request');
213
+ // Second request — served from cache
214
+ const second = await getLinks();
215
+ assert.ok('SCAN-1' in second);
216
+ assert.equal(gitCallCount, 1, 'git should not be called again within TTL');
217
+ // Invalidate cache
218
+ invalidateBranchLinkerCache();
219
+ // Third request — cache is cleared, should fetch fresh
220
+ const third = await getLinks();
221
+ assert.ok('SCAN-1' in third);
222
+ assert.equal(gitCallCount, 2, 'git should be called again after cache invalidation');
223
+ });
224
+ test('returns empty object when no workspaces', async () => {
225
+ await stopServer();
226
+ invalidateBranchLinkerCache();
227
+ saveConfig(configPath, {
228
+ ...DEFAULTS,
229
+ repos: [],
230
+ });
231
+ // execAsync should never be called here
232
+ const exec = makeMockExec({});
233
+ await startServer(exec);
234
+ const data = await getLinks();
235
+ assert.equal(Object.keys(data).length, 0, 'Should return empty object when no workspaces configured');
236
+ });
@@ -0,0 +1,45 @@
1
+ import { test, describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { MOUNTAIN_NAMES } from '../server/types.js';
4
+ import { branchToDisplayName } from '../server/git.js';
5
+ describe('MOUNTAIN_NAMES', () => {
6
+ test('contains 30 mountain names', () => {
7
+ assert.equal(MOUNTAIN_NAMES.length, 30);
8
+ });
9
+ test('all names are lowercase kebab-case', () => {
10
+ for (const name of MOUNTAIN_NAMES) {
11
+ assert.match(name, /^[a-z][a-z0-9-]*$/, `Mountain name "${name}" is not kebab-case`);
12
+ }
13
+ });
14
+ test('no duplicate names', () => {
15
+ const unique = new Set(MOUNTAIN_NAMES);
16
+ assert.equal(unique.size, MOUNTAIN_NAMES.length);
17
+ });
18
+ test('cycling wraps around at array length', () => {
19
+ let idx = 28;
20
+ const name1 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
21
+ idx++;
22
+ const name2 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
23
+ idx++;
24
+ const name3 = MOUNTAIN_NAMES[idx % MOUNTAIN_NAMES.length];
25
+ assert.equal(name1, 'whitney');
26
+ assert.equal(name2, 'hood');
27
+ assert.equal(name3, 'everest'); // wraps back to start
28
+ });
29
+ });
30
+ describe('branchToDisplayName', () => {
31
+ test('converts kebab-case to sentence case', () => {
32
+ assert.equal(branchToDisplayName('fix-mobile-scroll-bug'), 'Fix mobile scroll bug');
33
+ });
34
+ test('strips common branch prefixes', () => {
35
+ assert.equal(branchToDisplayName('feature/add-auth'), 'Add auth');
36
+ assert.equal(branchToDisplayName('fix/api-timeout'), 'Api timeout');
37
+ assert.equal(branchToDisplayName('chore/update-deps'), 'Update deps');
38
+ });
39
+ test('handles simple names', () => {
40
+ assert.equal(branchToDisplayName('lhotse'), 'Lhotse');
41
+ });
42
+ test('handles underscores', () => {
43
+ assert.equal(branchToDisplayName('fix_the_thing'), 'Fix the thing');
44
+ });
45
+ });
@@ -0,0 +1,115 @@
1
+ import { describe, it, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { execFileSync } from 'node:child_process';
7
+ import { BranchWatcher } from '../server/watcher.js';
8
+ function makeTempGitRepo() {
9
+ // Resolve symlinks (macOS /var → /private/var) so paths match git output
10
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'branch-watcher-test-')));
11
+ execFileSync('git', ['init', '-b', 'main'], { cwd: dir });
12
+ execFileSync('git', [
13
+ '-c',
14
+ 'user.name=Test',
15
+ '-c',
16
+ 'user.email=test@test.com',
17
+ 'commit',
18
+ '--allow-empty',
19
+ '-m',
20
+ 'init',
21
+ ], { cwd: dir });
22
+ return dir;
23
+ }
24
+ describe('BranchWatcher', () => {
25
+ const cleanups = [];
26
+ afterEach(() => {
27
+ for (const fn of cleanups) {
28
+ try {
29
+ fn();
30
+ }
31
+ catch {
32
+ /* ignore */
33
+ }
34
+ }
35
+ cleanups.length = 0;
36
+ });
37
+ it('detects branch change via HEAD file write', async () => {
38
+ const repoDir = makeTempGitRepo();
39
+ const parentDir = path.dirname(repoDir);
40
+ cleanups.push(() => fs.rmSync(repoDir, { recursive: true, force: true }));
41
+ const events = [];
42
+ const watcher = new BranchWatcher((cwdPath, newBranch) => {
43
+ events.push({ cwdPath, newBranch });
44
+ });
45
+ cleanups.push(() => watcher.close());
46
+ watcher.rebuild([parentDir]);
47
+ // Let fs.watch initialize
48
+ await new Promise((resolve) => setTimeout(resolve, 200));
49
+ // Create the branch first, then simulate checkout by writing HEAD directly
50
+ // (more deterministic than git checkout which uses lock+rename)
51
+ execFileSync('git', ['branch', 'feature-test'], { cwd: repoDir });
52
+ const headPath = path.join(repoDir, '.git', 'HEAD');
53
+ fs.writeFileSync(headPath, 'ref: refs/heads/feature-test\n');
54
+ // Wait for debounce (300ms) + processing
55
+ await new Promise((resolve) => setTimeout(resolve, 800));
56
+ assert.ok(events.length > 0, 'Expected at least one branch change event');
57
+ const lastEvent = events[events.length - 1];
58
+ assert.equal(lastEvent.cwdPath, repoDir);
59
+ assert.equal(lastEvent.newBranch, 'feature-test');
60
+ });
61
+ it('does not fire callback if branch did not change', async () => {
62
+ const repoDir = makeTempGitRepo();
63
+ const parentDir = path.dirname(repoDir);
64
+ cleanups.push(() => fs.rmSync(repoDir, { recursive: true, force: true }));
65
+ const events = [];
66
+ const watcher = new BranchWatcher((cwdPath, newBranch) => {
67
+ events.push({ cwdPath, newBranch });
68
+ });
69
+ cleanups.push(() => watcher.close());
70
+ watcher.rebuild([parentDir]);
71
+ await new Promise((resolve) => setTimeout(resolve, 200));
72
+ // Touch the HEAD file without changing the branch content
73
+ const headPath = path.join(repoDir, '.git', 'HEAD');
74
+ const content = fs.readFileSync(headPath, 'utf-8');
75
+ fs.writeFileSync(headPath, content);
76
+ await new Promise((resolve) => setTimeout(resolve, 800));
77
+ assert.equal(events.length, 0, 'Should not fire callback when branch is unchanged');
78
+ });
79
+ it('detects second branch change after atomic rename (inode change)', async () => {
80
+ const repoDir = makeTempGitRepo();
81
+ const parentDir = path.dirname(repoDir);
82
+ cleanups.push(() => fs.rmSync(repoDir, { recursive: true, force: true }));
83
+ const events = [];
84
+ const watcher = new BranchWatcher((cwdPath, newBranch) => {
85
+ events.push({ cwdPath, newBranch });
86
+ });
87
+ cleanups.push(() => watcher.close());
88
+ watcher.rebuild([parentDir]);
89
+ await new Promise((resolve) => setTimeout(resolve, 200));
90
+ const headPath = path.join(repoDir, '.git', 'HEAD');
91
+ // First change: simulate git's atomic checkout (write to .lock, rename over HEAD)
92
+ // This changes the inode, which would kill kqueue-based watchers
93
+ execFileSync('git', ['branch', 'branch-one'], { cwd: repoDir });
94
+ const lockPath = headPath + '.lock';
95
+ fs.writeFileSync(lockPath, 'ref: refs/heads/branch-one\n');
96
+ fs.renameSync(lockPath, headPath);
97
+ await new Promise((resolve) => setTimeout(resolve, 800));
98
+ assert.ok(events.length >= 1, 'Expected at least one event after first atomic rename');
99
+ assert.equal(events[events.length - 1].newBranch, 'branch-one');
100
+ // Second change: simulate another atomic checkout — this would fail without
101
+ // watcher recreation because the old watcher tracked the deleted inode
102
+ execFileSync('git', ['branch', 'branch-two'], { cwd: repoDir });
103
+ const lockPath2 = headPath + '.lock';
104
+ fs.writeFileSync(lockPath2, 'ref: refs/heads/branch-two\n');
105
+ fs.renameSync(lockPath2, headPath);
106
+ await new Promise((resolve) => setTimeout(resolve, 800));
107
+ const secondChange = events.find((e) => e.newBranch === 'branch-two');
108
+ assert.ok(secondChange, 'Expected branch-two event after second atomic rename (watcher must survive inode change)');
109
+ });
110
+ it('closes cleanly', () => {
111
+ const watcher = new BranchWatcher(() => { });
112
+ watcher.close();
113
+ // No error means success
114
+ });
115
+ });
@@ -0,0 +1,91 @@
1
+ import { test, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { execFileSync } from 'node:child_process';
7
+ let tmpDir;
8
+ beforeEach(() => {
9
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'browser-cli-test-'));
10
+ fs.writeFileSync(path.join(tmpDir, 'test.html'), '<h1>Test</h1>');
11
+ });
12
+ afterEach(() => {
13
+ fs.rmSync(tmpDir, { recursive: true, force: true });
14
+ });
15
+ test('browser command with no args prints usage and exits 1', () => {
16
+ try {
17
+ execFileSync('node', ['dist/bin/claude-remote-cli.js', 'browser'], {
18
+ encoding: 'utf-8',
19
+ env: { ...process.env, PATH: process.env.PATH },
20
+ });
21
+ assert.fail('Should have exited with code 1');
22
+ }
23
+ catch (err) {
24
+ const e = err;
25
+ assert.strictEqual(e.status, 1);
26
+ assert.ok((e.stderr ?? '').includes('Usage'));
27
+ }
28
+ });
29
+ test('browser --help shows usage and exits 0', () => {
30
+ try {
31
+ const output = execFileSync('node', ['dist/bin/claude-remote-cli.js', 'browser', '--help'], {
32
+ encoding: 'utf-8',
33
+ env: { ...process.env, PATH: process.env.PATH },
34
+ });
35
+ assert.ok(output.includes('Usage') || output.includes('browser'));
36
+ }
37
+ catch (err) {
38
+ const e = err;
39
+ // --help may print to stderr
40
+ const out = (e.stdout ?? '') + (e.stderr ?? '');
41
+ assert.ok(out.includes('Usage') || out.includes('browser'));
42
+ }
43
+ });
44
+ test('browser command fails gracefully when server is not running', () => {
45
+ try {
46
+ execFileSync('node', [
47
+ 'dist/bin/claude-remote-cli.js',
48
+ 'browser',
49
+ path.join(tmpDir, 'test.html'),
50
+ ], {
51
+ encoding: 'utf-8',
52
+ env: {
53
+ ...process.env,
54
+ CLAUDE_REMOTE_PORT: '19999',
55
+ CLAUDE_REMOTE_BROWSER_TOKEN: 'test-token',
56
+ PATH: process.env.PATH,
57
+ },
58
+ });
59
+ assert.fail('Should have exited with error');
60
+ }
61
+ catch (err) {
62
+ const e = err;
63
+ assert.ok(e.status !== 0);
64
+ assert.ok((e.stderr ?? '').includes('connect') ||
65
+ (e.stderr ?? '').includes('ECONNREFUSED') ||
66
+ (e.stderr ?? '').includes('Error'));
67
+ }
68
+ });
69
+ test('browser command fails when token not set', () => {
70
+ try {
71
+ execFileSync('node', [
72
+ 'dist/bin/claude-remote-cli.js',
73
+ 'browser',
74
+ path.join(tmpDir, 'test.html'),
75
+ ], {
76
+ encoding: 'utf-8',
77
+ env: {
78
+ ...process.env,
79
+ CLAUDE_REMOTE_PORT: '19999',
80
+ CLAUDE_REMOTE_BROWSER_TOKEN: '', // empty token
81
+ PATH: process.env.PATH,
82
+ },
83
+ });
84
+ assert.fail('Should have exited with error');
85
+ }
86
+ catch (err) {
87
+ const e = err;
88
+ assert.ok(e.status !== 0);
89
+ assert.ok((e.stderr ?? '').includes('CLAUDE_REMOTE_BROWSER_TOKEN'));
90
+ }
91
+ });
@@ -0,0 +1,93 @@
1
+ import { test, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { createBrowserToken, validateToken, resolveTokenPath, generateScopedToken, validateScopedToken, cleanExpiredTokens, getTokenForPath, _resetForTesting, } from '../server/browser-content.js';
7
+ let tmpDir;
8
+ beforeEach(() => {
9
+ _resetForTesting();
10
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'browser-content-test-'));
11
+ fs.writeFileSync(path.join(tmpDir, 'index.html'), '<h1>Hello</h1>');
12
+ fs.writeFileSync(path.join(tmpDir, 'styles.css'), 'body { color: red; }');
13
+ fs.mkdirSync(path.join(tmpDir, 'sub'));
14
+ fs.writeFileSync(path.join(tmpDir, 'sub', 'nested.js'), 'console.log("hi")');
15
+ });
16
+ afterEach(() => {
17
+ fs.rmSync(tmpDir, { recursive: true, force: true });
18
+ });
19
+ // Content token tests
20
+ test('createBrowserToken returns a token string', () => {
21
+ const token = createBrowserToken(path.join(tmpDir, 'index.html'));
22
+ assert.ok(typeof token === 'string');
23
+ assert.ok(token.length > 0);
24
+ });
25
+ test('validateToken returns baseDir for valid token', () => {
26
+ const filePath = path.join(tmpDir, 'index.html');
27
+ const token = createBrowserToken(filePath);
28
+ const result = validateToken(token);
29
+ assert.ok(result);
30
+ assert.strictEqual(result.baseDir, tmpDir);
31
+ });
32
+ test('validateToken returns null for invalid token', () => {
33
+ assert.strictEqual(validateToken('nonexistent-token'), null);
34
+ });
35
+ test('resolveTokenPath serves file within baseDir', () => {
36
+ const filePath = path.join(tmpDir, 'index.html');
37
+ const token = createBrowserToken(filePath);
38
+ assert.strictEqual(resolveTokenPath(token, 'index.html'), filePath);
39
+ });
40
+ test('resolveTokenPath serves nested file', () => {
41
+ const filePath = path.join(tmpDir, 'index.html');
42
+ const token = createBrowserToken(filePath);
43
+ assert.strictEqual(resolveTokenPath(token, 'sub/nested.js'), path.join(tmpDir, 'sub', 'nested.js'));
44
+ });
45
+ test('resolveTokenPath rejects path traversal', () => {
46
+ const token = createBrowserToken(path.join(tmpDir, 'index.html'));
47
+ assert.strictEqual(resolveTokenPath(token, '../../../etc/passwd'), null);
48
+ });
49
+ test('resolveTokenPath rejects absolute path in relative', () => {
50
+ const token = createBrowserToken(path.join(tmpDir, 'index.html'));
51
+ assert.strictEqual(resolveTokenPath(token, '/etc/passwd'), null);
52
+ });
53
+ // Scoped auth token tests
54
+ test('generateScopedToken returns a hex string', () => {
55
+ const token = generateScopedToken();
56
+ assert.ok(/^[a-f0-9]+$/.test(token));
57
+ });
58
+ test('validateScopedToken returns true for correct token', () => {
59
+ const token = generateScopedToken();
60
+ assert.strictEqual(validateScopedToken(token), true);
61
+ });
62
+ test('validateScopedToken returns false for wrong token', () => {
63
+ generateScopedToken();
64
+ assert.strictEqual(validateScopedToken('wrong-token'), false);
65
+ });
66
+ // Token expiry tests
67
+ test('cleanExpiredTokens removes old tokens', () => {
68
+ const token = createBrowserToken(path.join(tmpDir, 'index.html'));
69
+ assert.ok(validateToken(token));
70
+ cleanExpiredTokens(0); // TTL of 0ms = everything expired
71
+ assert.strictEqual(validateToken(token), null);
72
+ });
73
+ test('cleanExpiredTokens keeps fresh tokens', () => {
74
+ const token = createBrowserToken(path.join(tmpDir, 'index.html'));
75
+ cleanExpiredTokens(24 * 60 * 60 * 1000);
76
+ assert.ok(validateToken(token));
77
+ });
78
+ // Idempotent token creation
79
+ test('createBrowserToken for same path returns existing token', () => {
80
+ const filePath = path.join(tmpDir, 'index.html');
81
+ const token1 = createBrowserToken(filePath);
82
+ const token2 = createBrowserToken(filePath);
83
+ assert.strictEqual(token1, token2);
84
+ });
85
+ // getTokenForPath
86
+ test('getTokenForPath returns token for known path', () => {
87
+ const filePath = path.join(tmpDir, 'index.html');
88
+ const token = createBrowserToken(filePath);
89
+ assert.strictEqual(getTokenForPath(filePath), token);
90
+ });
91
+ test('getTokenForPath returns null for unknown path', () => {
92
+ assert.strictEqual(getTokenForPath('/no/such/file'), null);
93
+ });
@@ -0,0 +1,39 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ // Test the pure logic of tab identity and deduplication.
4
+ // We can't import .svelte.ts files in node:test (they need the Svelte compiler),
5
+ // so we test the logic by duplicating the key algorithm here.
6
+ function tabKey(filePath, tabType) {
7
+ return `${tabType}::${filePath}`;
8
+ }
9
+ test('tabKey differentiates same file with different types', () => {
10
+ const diffKey = tabKey('/tmp/index.html', 'diff');
11
+ const htmlKey = tabKey('/tmp/index.html', 'html');
12
+ assert.notStrictEqual(diffKey, htmlKey);
13
+ });
14
+ test('tabKey matches same file with same type', () => {
15
+ const key1 = tabKey('/tmp/index.html', 'html');
16
+ const key2 = tabKey('/tmp/index.html', 'html');
17
+ assert.strictEqual(key1, key2);
18
+ });
19
+ test('openHtmlTab logic creates correct tab shape', () => {
20
+ const filePath = '/tmp/gstack-sketch/design-board.html';
21
+ const tab = {
22
+ filePath,
23
+ fileName: filePath.split('/').pop() ?? filePath,
24
+ isChanged: false,
25
+ tabType: 'html',
26
+ token: 'abc123',
27
+ };
28
+ assert.strictEqual(tab.fileName, 'design-board.html');
29
+ assert.strictEqual(tab.isChanged, false);
30
+ assert.strictEqual(tab.tabType, 'html');
31
+ assert.strictEqual(tab.token, 'abc123');
32
+ });
33
+ test('refreshHtmlTab logic uses version counter', () => {
34
+ const baseUrl = '/browser-content/abc123/design-board.html';
35
+ const refreshVersion = 1;
36
+ const refreshed = `${baseUrl}?v=${refreshVersion}`;
37
+ assert.ok(refreshed.includes('?v='));
38
+ assert.ok(refreshed.startsWith(baseUrl));
39
+ });