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,295 @@
1
+ import { test, before, afterEach, 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 { createTelemetryRouter, getAccountTelemetry, getTelemetryForSession, startTelemetry, stopTelemetry, } from '../server/telemetry.js';
8
+ const noopAuth = (_req, _res, next) => next();
9
+ let tmpDir;
10
+ before(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-remote-cli-telemetry-test-'));
12
+ });
13
+ afterEach(() => {
14
+ stopTelemetry();
15
+ for (const entry of fs.readdirSync(tmpDir)) {
16
+ fs.rmSync(path.join(tmpDir, entry), { recursive: true, force: true });
17
+ }
18
+ });
19
+ after(() => {
20
+ fs.rmSync(tmpDir, { recursive: true, force: true });
21
+ });
22
+ function makeSession(id) {
23
+ return { id };
24
+ }
25
+ function makeDeps(sessionIds, events, configDir = tmpDir) {
26
+ return {
27
+ configDir,
28
+ getActiveSessions: () => sessionIds.map(makeSession),
29
+ broadcastEvent: (type, data) => {
30
+ if (data === undefined)
31
+ events.push({ type });
32
+ else
33
+ events.push({ type, data });
34
+ },
35
+ };
36
+ }
37
+ function writeStatusLineFile(sessionId, payload, configDir = tmpDir) {
38
+ const telemetryDir = path.join(configDir, 'telemetry');
39
+ fs.mkdirSync(telemetryDir, { recursive: true });
40
+ const filePath = path.join(telemetryDir, `${sessionId}.json`);
41
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
42
+ return filePath;
43
+ }
44
+ function writePendingTelemetryFile(configDir, payload) {
45
+ const filePath = path.join(configDir, 'pending-telemetry.json');
46
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
47
+ return filePath;
48
+ }
49
+ function sampleStatusLinePayload() {
50
+ return {
51
+ session_id: 'abc-123',
52
+ model: { id: 'claude-opus-4-6', display_name: 'Claude Opus 4.6' },
53
+ context_window: {
54
+ total_input_tokens: 12400,
55
+ total_output_tokens: 3200,
56
+ context_window_size: 200000,
57
+ used_percentage: 7.8,
58
+ remaining_percentage: 92.2,
59
+ current_usage: {
60
+ input_tokens: 500,
61
+ output_tokens: 100,
62
+ cache_creation_input_tokens: 1000,
63
+ cache_read_input_tokens: 5000,
64
+ },
65
+ },
66
+ cost: { total_cost_usd: 0.42 },
67
+ rate_limits: {
68
+ five_hour: { used_percentage: 22, resets_at: '2026-03-31T19:30:00Z' },
69
+ seven_day: { used_percentage: 8, resets_at: '2026-04-03T00:00:00Z' },
70
+ },
71
+ };
72
+ }
73
+ test('parses session telemetry from the statusLine file', () => {
74
+ const events = [];
75
+ writeStatusLineFile('abc-123', sampleStatusLinePayload());
76
+ startTelemetry(makeDeps(['abc-123'], events));
77
+ const session = getTelemetryForSession('abc-123');
78
+ const account = getAccountTelemetry();
79
+ assert.deepEqual(session, {
80
+ sessionId: 'abc-123',
81
+ model: 'Claude Opus 4.6',
82
+ totalInputTokens: 12400,
83
+ totalOutputTokens: 3200,
84
+ totalCacheRead: 5000,
85
+ totalCacheWrite: 1000,
86
+ contextPercent: 7.8,
87
+ contextWindowSize: 200000,
88
+ costUsd: 0.42,
89
+ source: 'statusLine',
90
+ updatedAt: session?.updatedAt,
91
+ });
92
+ assert.deepEqual(account, {
93
+ fiveHourUsedPercent: 22,
94
+ fiveHourResetsAt: '2026-03-31T19:30:00Z',
95
+ sevenDayUsedPercent: 8,
96
+ sevenDayResetsAt: '2026-04-03T00:00:00Z',
97
+ updatedAt: account?.updatedAt,
98
+ });
99
+ assert.equal(events.filter((event) => event.type === 'session-telemetry').length, 1);
100
+ assert.equal(events.filter((event) => event.type === 'account-telemetry').length, 1);
101
+ });
102
+ test('missing statusLine file leaves telemetry undefined', () => {
103
+ const events = [];
104
+ startTelemetry(makeDeps(['missing-session'], events));
105
+ assert.equal(getTelemetryForSession('missing-session'), undefined);
106
+ assert.equal(getAccountTelemetry(), null);
107
+ assert.equal(events.length, 0);
108
+ });
109
+ test('inactive sessions are pruned once active telemetry is available', () => {
110
+ writePendingTelemetryFile(tmpDir, {
111
+ version: 1,
112
+ timestamp: new Date().toISOString(),
113
+ sessions: {
114
+ stale: {
115
+ sessionId: 'stale',
116
+ model: 'Claude Sonnet 4',
117
+ totalInputTokens: 10,
118
+ totalOutputTokens: 20,
119
+ totalCacheRead: 30,
120
+ totalCacheWrite: 40,
121
+ contextPercent: 11,
122
+ contextWindowSize: 999,
123
+ costUsd: 1.23,
124
+ source: 'statusLine',
125
+ updatedAt: '2026-03-31T17:00:00Z',
126
+ },
127
+ },
128
+ account: null,
129
+ });
130
+ writeStatusLineFile('fresh', sampleStatusLinePayload());
131
+ startTelemetry(makeDeps(['fresh'], []));
132
+ assert.equal(getTelemetryForSession('stale'), undefined);
133
+ assert.ok(getTelemetryForSession('fresh'));
134
+ });
135
+ test('malformed statusLine JSON is ignored without crashing', () => {
136
+ const events = [];
137
+ const telemetryDir = path.join(tmpDir, 'telemetry');
138
+ fs.mkdirSync(telemetryDir, { recursive: true });
139
+ fs.writeFileSync(path.join(telemetryDir, 'bad-session.json'), '{"model":');
140
+ assert.doesNotThrow(() => {
141
+ startTelemetry(makeDeps(['bad-session'], events));
142
+ });
143
+ assert.equal(getTelemetryForSession('bad-session'), undefined);
144
+ assert.equal(getAccountTelemetry(), null);
145
+ assert.equal(events.length, 0);
146
+ });
147
+ test('restores pending telemetry from disk on startup', () => {
148
+ const restored = {
149
+ version: 1,
150
+ timestamp: new Date().toISOString(),
151
+ sessions: {
152
+ restored: {
153
+ sessionId: 'restored',
154
+ model: 'Claude Sonnet 4',
155
+ totalInputTokens: 10,
156
+ totalOutputTokens: 20,
157
+ totalCacheRead: 30,
158
+ totalCacheWrite: 40,
159
+ contextPercent: 11,
160
+ contextWindowSize: 999,
161
+ costUsd: 1.23,
162
+ source: 'statusLine',
163
+ updatedAt: '2026-03-31T18:00:00Z',
164
+ },
165
+ },
166
+ account: {
167
+ fiveHourUsedPercent: 44,
168
+ fiveHourResetsAt: '2026-03-31T19:30:00Z',
169
+ sevenDayUsedPercent: 55,
170
+ sevenDayResetsAt: '2026-04-03T00:00:00Z',
171
+ updatedAt: '2026-03-31T18:00:00Z',
172
+ },
173
+ };
174
+ const pendingPath = writePendingTelemetryFile(tmpDir, restored);
175
+ const events = [];
176
+ startTelemetry(makeDeps([], events));
177
+ assert.deepEqual(getTelemetryForSession('restored'), {
178
+ sessionId: 'restored',
179
+ model: 'Claude Sonnet 4',
180
+ totalInputTokens: 10,
181
+ totalOutputTokens: 20,
182
+ totalCacheRead: 30,
183
+ totalCacheWrite: 40,
184
+ contextPercent: 11,
185
+ contextWindowSize: 999,
186
+ costUsd: 1.23,
187
+ source: 'statusLine',
188
+ updatedAt: '2026-03-31T18:00:00Z',
189
+ });
190
+ assert.deepEqual(getAccountTelemetry(), restored.account);
191
+ assert.equal(fs.existsSync(pendingPath), false, 'pending telemetry should be cleared after restore');
192
+ assert.equal(events.length, 0);
193
+ });
194
+ test('GET /telemetry endpoints return session and account telemetry', async () => {
195
+ const restored = {
196
+ version: 1,
197
+ timestamp: new Date().toISOString(),
198
+ sessions: {
199
+ endpoint: {
200
+ sessionId: 'endpoint',
201
+ model: 'Claude Opus 4.6',
202
+ totalInputTokens: 100,
203
+ totalOutputTokens: 200,
204
+ totalCacheRead: 300,
205
+ totalCacheWrite: 400,
206
+ contextPercent: 12.5,
207
+ contextWindowSize: 123456,
208
+ costUsd: 4.56,
209
+ source: 'statusLine',
210
+ updatedAt: '2026-03-31T18:00:00Z',
211
+ },
212
+ },
213
+ account: {
214
+ fiveHourUsedPercent: 9,
215
+ fiveHourResetsAt: '2026-03-31T19:30:00Z',
216
+ sevenDayUsedPercent: 18,
217
+ sevenDayResetsAt: '2026-04-03T00:00:00Z',
218
+ updatedAt: '2026-03-31T18:00:00Z',
219
+ },
220
+ };
221
+ writePendingTelemetryFile(tmpDir, restored);
222
+ startTelemetry(makeDeps([], []));
223
+ let server;
224
+ try {
225
+ const app = express();
226
+ app.use(express.json());
227
+ app.use('/telemetry', noopAuth, createTelemetryRouter());
228
+ server = await new Promise((resolve) => {
229
+ const srv = app.listen(0, '127.0.0.1', () => resolve(srv));
230
+ });
231
+ const addr = server.address();
232
+ assert.equal(typeof addr, 'object');
233
+ assert.ok(addr);
234
+ const baseUrl = `http://127.0.0.1:${addr.port}`;
235
+ const sessionsRes = await fetch(`${baseUrl}/telemetry/sessions`);
236
+ assert.equal(sessionsRes.status, 200);
237
+ assert.deepEqual(await sessionsRes.json(), {
238
+ endpoint: {
239
+ sessionId: 'endpoint',
240
+ model: 'Claude Opus 4.6',
241
+ totalInputTokens: 100,
242
+ totalOutputTokens: 200,
243
+ totalCacheRead: 300,
244
+ totalCacheWrite: 400,
245
+ contextPercent: 12.5,
246
+ contextWindowSize: 123456,
247
+ costUsd: 4.56,
248
+ source: 'statusLine',
249
+ updatedAt: '2026-03-31T18:00:00Z',
250
+ },
251
+ });
252
+ const accountRes = await fetch(`${baseUrl}/telemetry/account`);
253
+ assert.equal(accountRes.status, 200);
254
+ assert.deepEqual(await accountRes.json(), restored.account);
255
+ const setupStatusRes = await fetch(`${baseUrl}/telemetry/setup-status`);
256
+ assert.equal(setupStatusRes.status, 200);
257
+ assert.deepEqual(await setupStatusRes.json(), { installed: true });
258
+ }
259
+ finally {
260
+ await new Promise((resolve) => {
261
+ if (server)
262
+ server.close(() => resolve());
263
+ else
264
+ resolve();
265
+ });
266
+ }
267
+ });
268
+ test('stopTelemetry ignores pending persistence write failures', () => {
269
+ const blockedPath = path.join(tmpDir, 'not-a-directory');
270
+ fs.writeFileSync(blockedPath, 'blocked');
271
+ startTelemetry(makeDeps([], [], blockedPath));
272
+ assert.doesNotThrow(() => {
273
+ stopTelemetry();
274
+ });
275
+ });
276
+ test('collectTelemetry reuses a single active session snapshot per poll', () => {
277
+ const events = [];
278
+ let calls = 0;
279
+ writeStatusLineFile('abc-123', sampleStatusLinePayload());
280
+ startTelemetry({
281
+ configDir: tmpDir,
282
+ getActiveSessions: () => {
283
+ calls++;
284
+ return [makeSession('abc-123')];
285
+ },
286
+ broadcastEvent: (type, data) => {
287
+ if (data === undefined)
288
+ events.push({ type });
289
+ else
290
+ events.push({ type, data });
291
+ },
292
+ });
293
+ assert.equal(calls, 1);
294
+ assert.equal(events.filter((event) => event.type === 'session-telemetry').length, 1);
295
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ // Pure zoom functions inlined here because the frontend source imports from
4
+ // .svelte.ts modules that the Node.js test runner cannot process.
5
+ // These mirror the implementations in frontend/src/lib/terminal-zoom.ts.
6
+ const DEFAULT = 14;
7
+ const MIN = 8;
8
+ const MAX = 28;
9
+ function clampFontSize(size) {
10
+ if (!Number.isFinite(size))
11
+ return DEFAULT;
12
+ return Math.max(MIN, Math.min(MAX, Math.round(size)));
13
+ }
14
+ function zoomPercentage(fontSize) {
15
+ return Math.round((fontSize / DEFAULT) * 100);
16
+ }
17
+ function scaledTerminalDimensions(windowWidth, windowHeight, fontSize) {
18
+ const ratio = fontSize / DEFAULT;
19
+ const charWidth = 8 * ratio;
20
+ const lineHeight = 17 * ratio;
21
+ return {
22
+ cols: Math.max(80, Math.floor((windowWidth - 60) / charWidth)),
23
+ rows: Math.max(24, Math.floor((windowHeight - 120) / lineHeight)),
24
+ };
25
+ }
26
+ describe('terminal zoom', () => {
27
+ describe('clampFontSize', () => {
28
+ it('returns value within bounds', () => {
29
+ assert.equal(clampFontSize(14), 14);
30
+ assert.equal(clampFontSize(20), 20);
31
+ });
32
+ it('clamps to minimum', () => {
33
+ assert.equal(clampFontSize(4), MIN);
34
+ assert.equal(clampFontSize(0), MIN);
35
+ assert.equal(clampFontSize(-5), MIN);
36
+ });
37
+ it('clamps to maximum', () => {
38
+ assert.equal(clampFontSize(30), MAX);
39
+ assert.equal(clampFontSize(100), MAX);
40
+ });
41
+ it('rounds fractional values', () => {
42
+ assert.equal(clampFontSize(14.6), 15);
43
+ assert.equal(clampFontSize(14.4), 14);
44
+ });
45
+ it('handles boundary values exactly', () => {
46
+ assert.equal(clampFontSize(MIN), MIN);
47
+ assert.equal(clampFontSize(MAX), MAX);
48
+ });
49
+ it('returns default for NaN and non-finite values', () => {
50
+ assert.equal(clampFontSize(NaN), DEFAULT);
51
+ assert.equal(clampFontSize(Infinity), DEFAULT);
52
+ assert.equal(clampFontSize(-Infinity), DEFAULT);
53
+ });
54
+ });
55
+ describe('zoomPercentage', () => {
56
+ it('returns 100% at default', () => {
57
+ assert.equal(zoomPercentage(DEFAULT), 100);
58
+ });
59
+ it('scales proportionally', () => {
60
+ assert.equal(zoomPercentage(28), 200);
61
+ assert.equal(zoomPercentage(7), 50);
62
+ assert.equal(zoomPercentage(21), 150);
63
+ });
64
+ it('handles minimum and maximum', () => {
65
+ assert.equal(zoomPercentage(MIN), Math.round((MIN / DEFAULT) * 100));
66
+ assert.equal(zoomPercentage(MAX), Math.round((MAX / DEFAULT) * 100));
67
+ });
68
+ });
69
+ describe('scaledTerminalDimensions', () => {
70
+ it('matches original hardcoded values at default font size', () => {
71
+ const dims = scaledTerminalDimensions(1920, 1080, DEFAULT);
72
+ // Original formula: Math.floor((1920 - 60) / 8) = 232, Math.floor((1080 - 120) / 17) = 56
73
+ assert.equal(dims.cols, Math.floor((1920 - 60) / 8));
74
+ assert.equal(dims.rows, Math.floor((1080 - 120) / 17));
75
+ });
76
+ it('returns fewer cols/rows at larger font sizes', () => {
77
+ const normal = scaledTerminalDimensions(1920, 1080, 14);
78
+ const large = scaledTerminalDimensions(1920, 1080, 28);
79
+ assert.ok(large.cols < normal.cols, 'larger font should give fewer cols');
80
+ assert.ok(large.rows < normal.rows, 'larger font should give fewer rows');
81
+ });
82
+ it('returns more cols/rows at smaller font sizes', () => {
83
+ const normal = scaledTerminalDimensions(1920, 1080, 14);
84
+ const small = scaledTerminalDimensions(1920, 1080, 8);
85
+ assert.ok(small.cols > normal.cols, 'smaller font should give more cols');
86
+ assert.ok(small.rows > normal.rows, 'smaller font should give more rows');
87
+ });
88
+ it('enforces minimums on small screens', () => {
89
+ const dims = scaledTerminalDimensions(100, 100, 28);
90
+ assert.ok(dims.cols >= 80, 'cols must be at least 80');
91
+ assert.ok(dims.rows >= 24, 'rows must be at least 24');
92
+ });
93
+ it('scales linearly with font size', () => {
94
+ const at14 = scaledTerminalDimensions(1920, 1080, 14);
95
+ const at28 = scaledTerminalDimensions(1920, 1080, 28);
96
+ // At 2x font size, char width doubles, so cols should approximately halve
97
+ // (not exactly due to the (width - 60) offset and Math.floor)
98
+ const ratio = at14.cols / at28.cols;
99
+ assert.ok(ratio > 1.8 && ratio < 2.2, `col ratio should be ~2, got ${ratio}`);
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,152 @@
1
+ import { test, before, after, 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 Database from 'better-sqlite3';
7
+ import { initAnalytics, closeAnalytics, trackEvent, getDbSize, getDbPath } from '../server/analytics.js';
8
+ let tmpDir;
9
+ before(() => {
10
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-remote-cli-analytics-test-'));
11
+ });
12
+ afterEach(() => {
13
+ closeAnalytics();
14
+ // Clean up DB files between tests
15
+ for (const entry of fs.readdirSync(tmpDir)) {
16
+ fs.unlinkSync(path.join(tmpDir, entry));
17
+ }
18
+ });
19
+ after(() => {
20
+ fs.rmSync(tmpDir, { recursive: true });
21
+ });
22
+ test('initAnalytics creates database and schema', () => {
23
+ initAnalytics(tmpDir);
24
+ const dbPath = getDbPath(tmpDir);
25
+ assert.ok(fs.existsSync(dbPath), 'DB file should exist');
26
+ const db = new Database(dbPath, { readonly: true });
27
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
28
+ assert.ok(tables.some(t => t.name === 'events'), 'events table should exist');
29
+ db.close();
30
+ });
31
+ test('trackEvent inserts a row', () => {
32
+ initAnalytics(tmpDir);
33
+ trackEvent({
34
+ category: 'session',
35
+ action: 'created',
36
+ target: 'session-123',
37
+ properties: { workspace: '/proj', agent: 'claude' },
38
+ session_id: 'session-123',
39
+ device: 'desktop',
40
+ });
41
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
42
+ const rows = db.prepare('SELECT * FROM events').all();
43
+ assert.equal(rows.length, 1);
44
+ assert.equal(rows[0].category, 'session');
45
+ assert.equal(rows[0].action, 'created');
46
+ assert.equal(rows[0].target, 'session-123');
47
+ assert.equal(rows[0].device, 'desktop');
48
+ const props = JSON.parse(rows[0].properties);
49
+ assert.equal(props.workspace, '/proj');
50
+ assert.equal(props.agent, 'claude');
51
+ db.close();
52
+ });
53
+ test('trackEvent handles optional fields as null', () => {
54
+ initAnalytics(tmpDir);
55
+ trackEvent({ category: 'ui', action: 'click' });
56
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
57
+ const rows = db.prepare('SELECT * FROM events').all();
58
+ assert.equal(rows.length, 1);
59
+ assert.equal(rows[0].target, null);
60
+ assert.equal(rows[0].properties, null);
61
+ assert.equal(rows[0].session_id, null);
62
+ assert.equal(rows[0].device, null);
63
+ db.close();
64
+ });
65
+ test('trackEvent is no-op before initAnalytics', () => {
66
+ // Should not throw
67
+ trackEvent({ category: 'test', action: 'noop' });
68
+ });
69
+ test('getDbSize returns file size after writes', () => {
70
+ initAnalytics(tmpDir);
71
+ const sizeBefore = getDbSize(tmpDir);
72
+ assert.ok(sizeBefore > 0, 'DB file should have non-zero size after init');
73
+ for (let i = 0; i < 10; i++) {
74
+ trackEvent({ category: 'bulk', action: 'test', properties: { i } });
75
+ }
76
+ const sizeAfter = getDbSize(tmpDir);
77
+ assert.ok(sizeAfter >= sizeBefore, 'Size should grow after writes');
78
+ });
79
+ test('getDbSize returns 0 for non-existent path', () => {
80
+ assert.equal(getDbSize('/nonexistent/path'), 0);
81
+ });
82
+ test('initAnalytics is idempotent (schema already exists)', () => {
83
+ initAnalytics(tmpDir);
84
+ trackEvent({ category: 'test', action: 'first' });
85
+ closeAnalytics();
86
+ // Re-init should not throw or lose data
87
+ initAnalytics(tmpDir);
88
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
89
+ const rows = db.prepare('SELECT * FROM events').all();
90
+ assert.equal(rows.length, 1);
91
+ db.close();
92
+ });
93
+ // ── Router endpoint tests ──────────────────────────────────────────────
94
+ // These test the Express Router in isolation (same pattern as fs-browse.test.ts)
95
+ import express from 'express';
96
+ import http from 'node:http';
97
+ import { createAnalyticsRouter } from '../server/analytics.js';
98
+ test('POST /analytics/events batch inserts events', async () => {
99
+ initAnalytics(tmpDir);
100
+ const app = express();
101
+ app.use(express.json());
102
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
103
+ const server = http.createServer(app);
104
+ await new Promise(resolve => server.listen(0, resolve));
105
+ const port = server.address().port;
106
+ const res = await fetch(`http://localhost:${port}/analytics/events`, {
107
+ method: 'POST',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({ events: [
110
+ { category: 'ui', action: 'click', target: 'test-btn' },
111
+ { category: 'session', action: 'created' },
112
+ ] }),
113
+ });
114
+ const data = await res.json();
115
+ assert.equal(data.ok, true);
116
+ assert.equal(data.count, 2);
117
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
118
+ const rows = db.prepare('SELECT * FROM events').all();
119
+ assert.equal(rows.length, 2);
120
+ db.close();
121
+ server.close();
122
+ });
123
+ test('GET /analytics/size returns bytes', async () => {
124
+ initAnalytics(tmpDir);
125
+ const app = express();
126
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
127
+ const server = http.createServer(app);
128
+ await new Promise(resolve => server.listen(0, resolve));
129
+ const port = server.address().port;
130
+ const res = await fetch(`http://localhost:${port}/analytics/size`);
131
+ const data = await res.json();
132
+ assert.ok(data.bytes > 0);
133
+ server.close();
134
+ });
135
+ test('DELETE /analytics/events clears all events', async () => {
136
+ initAnalytics(tmpDir);
137
+ trackEvent({ category: 'test', action: 'to-delete' });
138
+ const app = express();
139
+ app.use(express.json());
140
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
141
+ const server = http.createServer(app);
142
+ await new Promise(resolve => server.listen(0, resolve));
143
+ const port = server.address().port;
144
+ const res = await fetch(`http://localhost:${port}/analytics/events`, { method: 'DELETE' });
145
+ const data = await res.json();
146
+ assert.equal(data.ok, true);
147
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
148
+ const rows = db.prepare('SELECT * FROM events').all();
149
+ assert.equal(rows.length, 0);
150
+ db.close();
151
+ server.close();
152
+ });
@@ -0,0 +1,95 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { hashPin, verifyPin, isLegacyHash, isRateLimited, recordFailedAttempt, generateCookieToken, _resetForTesting, } from '../server/auth.js';
4
+ test('hashPin returns scrypt hash with expected format', async () => {
5
+ _resetForTesting();
6
+ const hash = await hashPin('1234');
7
+ assert.ok(hash.startsWith('scrypt:'), `Expected hash to start with scrypt:, got: ${hash}`);
8
+ const parts = hash.split(':');
9
+ assert.strictEqual(parts.length, 3, 'Hash should have 3 colon-separated parts');
10
+ });
11
+ test('verifyPin returns true for correct PIN', async () => {
12
+ _resetForTesting();
13
+ const hash = await hashPin('1234');
14
+ const result = await verifyPin('1234', hash);
15
+ assert.strictEqual(result, true);
16
+ });
17
+ test('verifyPin returns false for wrong PIN', async () => {
18
+ _resetForTesting();
19
+ const hash = await hashPin('1234');
20
+ const result = await verifyPin('9999', hash);
21
+ assert.strictEqual(result, false);
22
+ });
23
+ test('rate limiter blocks after 5 failures', () => {
24
+ _resetForTesting();
25
+ const ip = '127.0.0.1';
26
+ for (let i = 0; i < 5; i++) {
27
+ recordFailedAttempt(ip);
28
+ }
29
+ assert.strictEqual(isRateLimited(ip), true);
30
+ });
31
+ test('rate limiter allows under threshold', () => {
32
+ _resetForTesting();
33
+ const ip = '127.0.0.1';
34
+ for (let i = 0; i < 4; i++) {
35
+ recordFailedAttempt(ip);
36
+ }
37
+ assert.strictEqual(isRateLimited(ip), false);
38
+ });
39
+ test('verifyPin returns false for legacy bcrypt hash (requires PIN reset)', async () => {
40
+ _resetForTesting();
41
+ const legacyHash = '$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012';
42
+ const result = await verifyPin('1234', legacyHash);
43
+ assert.strictEqual(result, false);
44
+ });
45
+ test('verifyPin returns false for malformed scrypt hash (missing parts)', async () => {
46
+ _resetForTesting();
47
+ const result = await verifyPin('1234', 'scrypt:saltonly');
48
+ assert.strictEqual(result, false);
49
+ });
50
+ test('verifyPin returns false for scrypt hash with empty salt', async () => {
51
+ _resetForTesting();
52
+ const result = await verifyPin('1234', 'scrypt::deadbeef');
53
+ assert.strictEqual(result, false);
54
+ });
55
+ test('verifyPin returns false for scrypt hash with wrong key length', async () => {
56
+ _resetForTesting();
57
+ // Valid hex but wrong length (should be 64 bytes = 128 hex chars)
58
+ const result = await verifyPin('1234', 'scrypt:abcd1234:deadbeef');
59
+ assert.strictEqual(result, false);
60
+ });
61
+ test('verifyPin returns false for completely empty hash', async () => {
62
+ _resetForTesting();
63
+ const result = await verifyPin('1234', '');
64
+ assert.strictEqual(result, false);
65
+ });
66
+ test('verifyPin returns false for garbage input', async () => {
67
+ _resetForTesting();
68
+ const result = await verifyPin('1234', 'not-a-valid-hash-at-all');
69
+ assert.strictEqual(result, false);
70
+ });
71
+ test('hashPin produces unique salts', async () => {
72
+ _resetForTesting();
73
+ const hash1 = await hashPin('1234');
74
+ const hash2 = await hashPin('1234');
75
+ assert.notStrictEqual(hash1, hash2, 'Two hashes of the same PIN should have different salts');
76
+ // But both should verify correctly
77
+ assert.strictEqual(await verifyPin('1234', hash1), true);
78
+ assert.strictEqual(await verifyPin('1234', hash2), true);
79
+ });
80
+ test('isLegacyHash returns true for bcrypt hashes', () => {
81
+ assert.strictEqual(isLegacyHash('$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012'), true);
82
+ assert.strictEqual(isLegacyHash('$2a$10$someotherbcrypthashvalue'), true);
83
+ });
84
+ test('isLegacyHash returns false for scrypt hashes', async () => {
85
+ const hash = await hashPin('1234');
86
+ assert.strictEqual(isLegacyHash(hash), false);
87
+ });
88
+ test('isLegacyHash returns false for empty string', () => {
89
+ assert.strictEqual(isLegacyHash(''), false);
90
+ });
91
+ test('generateCookieToken returns non-empty string', () => {
92
+ _resetForTesting();
93
+ const token = generateCookieToken();
94
+ assert.ok(typeof token === 'string' && token.length > 0);
95
+ });