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,158 @@
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
28
+ .prepare("SELECT name FROM sqlite_master WHERE type='table'")
29
+ .all();
30
+ assert.ok(tables.some((t) => t.name === 'events'), 'events table should exist');
31
+ db.close();
32
+ });
33
+ test('trackEvent inserts a row', () => {
34
+ initAnalytics(tmpDir);
35
+ trackEvent({
36
+ category: 'session',
37
+ action: 'created',
38
+ target: 'session-123',
39
+ properties: { workspace: '/proj', agent: 'claude' },
40
+ session_id: 'session-123',
41
+ device: 'desktop',
42
+ });
43
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
44
+ const rows = db.prepare('SELECT * FROM events').all();
45
+ assert.equal(rows.length, 1);
46
+ assert.equal(rows[0].category, 'session');
47
+ assert.equal(rows[0].action, 'created');
48
+ assert.equal(rows[0].target, 'session-123');
49
+ assert.equal(rows[0].device, 'desktop');
50
+ const props = JSON.parse(rows[0].properties);
51
+ assert.equal(props.workspace, '/proj');
52
+ assert.equal(props.agent, 'claude');
53
+ db.close();
54
+ });
55
+ test('trackEvent handles optional fields as null', () => {
56
+ initAnalytics(tmpDir);
57
+ trackEvent({ category: 'ui', action: 'click' });
58
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
59
+ const rows = db.prepare('SELECT * FROM events').all();
60
+ assert.equal(rows.length, 1);
61
+ assert.equal(rows[0].target, null);
62
+ assert.equal(rows[0].properties, null);
63
+ assert.equal(rows[0].session_id, null);
64
+ assert.equal(rows[0].device, null);
65
+ db.close();
66
+ });
67
+ test('trackEvent is no-op before initAnalytics', () => {
68
+ // Should not throw
69
+ trackEvent({ category: 'test', action: 'noop' });
70
+ });
71
+ test('getDbSize returns file size after writes', () => {
72
+ initAnalytics(tmpDir);
73
+ const sizeBefore = getDbSize(tmpDir);
74
+ assert.ok(sizeBefore > 0, 'DB file should have non-zero size after init');
75
+ for (let i = 0; i < 10; i++) {
76
+ trackEvent({ category: 'bulk', action: 'test', properties: { i } });
77
+ }
78
+ const sizeAfter = getDbSize(tmpDir);
79
+ assert.ok(sizeAfter >= sizeBefore, 'Size should grow after writes');
80
+ });
81
+ test('getDbSize returns 0 for non-existent path', () => {
82
+ assert.equal(getDbSize('/nonexistent/path'), 0);
83
+ });
84
+ test('initAnalytics is idempotent (schema already exists)', () => {
85
+ initAnalytics(tmpDir);
86
+ trackEvent({ category: 'test', action: 'first' });
87
+ closeAnalytics();
88
+ // Re-init should not throw or lose data
89
+ initAnalytics(tmpDir);
90
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
91
+ const rows = db.prepare('SELECT * FROM events').all();
92
+ assert.equal(rows.length, 1);
93
+ db.close();
94
+ });
95
+ // ── Router endpoint tests ──────────────────────────────────────────────
96
+ // These test the Express Router in isolation (same pattern as fs-browse.test.ts)
97
+ import express from 'express';
98
+ import http from 'node:http';
99
+ import { createAnalyticsRouter } from '../server/analytics.js';
100
+ test('POST /analytics/events batch inserts events', async () => {
101
+ initAnalytics(tmpDir);
102
+ const app = express();
103
+ app.use(express.json());
104
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
105
+ const server = http.createServer(app);
106
+ await new Promise((resolve) => server.listen(0, resolve));
107
+ const port = server.address().port;
108
+ const res = await fetch(`http://localhost:${port}/analytics/events`, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({
112
+ events: [
113
+ { category: 'ui', action: 'click', target: 'test-btn' },
114
+ { category: 'session', action: 'created' },
115
+ ],
116
+ }),
117
+ });
118
+ const data = (await res.json());
119
+ assert.equal(data.ok, true);
120
+ assert.equal(data.count, 2);
121
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
122
+ const rows = db.prepare('SELECT * FROM events').all();
123
+ assert.equal(rows.length, 2);
124
+ db.close();
125
+ server.close();
126
+ });
127
+ test('GET /analytics/size returns bytes', async () => {
128
+ initAnalytics(tmpDir);
129
+ const app = express();
130
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
131
+ const server = http.createServer(app);
132
+ await new Promise((resolve) => server.listen(0, resolve));
133
+ const port = server.address().port;
134
+ const res = await fetch(`http://localhost:${port}/analytics/size`);
135
+ const data = (await res.json());
136
+ assert.ok(data.bytes > 0);
137
+ server.close();
138
+ });
139
+ test('DELETE /analytics/events clears all events', async () => {
140
+ initAnalytics(tmpDir);
141
+ trackEvent({ category: 'test', action: 'to-delete' });
142
+ const app = express();
143
+ app.use(express.json());
144
+ app.use('/analytics', createAnalyticsRouter(tmpDir));
145
+ const server = http.createServer(app);
146
+ await new Promise((resolve) => server.listen(0, resolve));
147
+ const port = server.address().port;
148
+ const res = await fetch(`http://localhost:${port}/analytics/events`, {
149
+ method: 'DELETE',
150
+ });
151
+ const data = (await res.json());
152
+ assert.equal(data.ok, true);
153
+ const db = new Database(getDbPath(tmpDir), { readonly: true });
154
+ const rows = db.prepare('SELECT * FROM events').all();
155
+ assert.equal(rows.length, 0);
156
+ db.close();
157
+ server.close();
158
+ });
@@ -0,0 +1,91 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { computeAttentionScore, sortByAttention, } from '../frontend/src/lib/state/attention.js';
4
+ function makeScoreItem(overrides) {
5
+ return {
6
+ id: 'test',
7
+ kind: 'worktree',
8
+ path: '/test',
9
+ repoPath: '/repo',
10
+ displayName: 'test',
11
+ branchName: 'main',
12
+ lastActivity: new Date().toISOString(),
13
+ lastKnownBackendState: null,
14
+ sessions: [],
15
+ ...overrides,
16
+ };
17
+ }
18
+ describe('computeAttentionScore', () => {
19
+ it('permission scores highest', () => {
20
+ const permission = computeAttentionScore(makeScoreItem({ displayState: 'permission' }));
21
+ const running = computeAttentionScore(makeScoreItem({ displayState: 'running' }));
22
+ assert.ok(permission > running);
23
+ });
24
+ it('needs-answer scores above error', () => {
25
+ const needsAnswer = computeAttentionScore(makeScoreItem({ displayState: 'needs-answer' }));
26
+ const error = computeAttentionScore(makeScoreItem({ displayState: 'error' }));
27
+ assert.ok(needsAnswer > error);
28
+ });
29
+ it('error scores above unseen-idle', () => {
30
+ const error = computeAttentionScore(makeScoreItem({ displayState: 'error' }));
31
+ const unseen = computeAttentionScore(makeScoreItem({ displayState: 'unseen-idle' }));
32
+ assert.ok(error > unseen);
33
+ });
34
+ it('unseen-idle scores above running', () => {
35
+ const unseen = computeAttentionScore(makeScoreItem({ displayState: 'unseen-idle' }));
36
+ const running = computeAttentionScore(makeScoreItem({ displayState: 'running' }));
37
+ assert.ok(unseen > running);
38
+ });
39
+ it('inactive scores lowest', () => {
40
+ const inactive = computeAttentionScore(makeScoreItem({ displayState: 'inactive' }));
41
+ const seenIdle = computeAttentionScore(makeScoreItem({ displayState: 'seen-idle' }));
42
+ assert.ok(inactive < seenIdle);
43
+ });
44
+ it('unread bonus stacks with state score', () => {
45
+ const unread = computeAttentionScore(makeScoreItem({ displayState: 'unseen-idle', isUnread: true }));
46
+ const read = computeAttentionScore(makeScoreItem({ displayState: 'unseen-idle', isUnread: false }));
47
+ assert.ok(unread > read);
48
+ });
49
+ it('changes-requested PR adds urgency', () => {
50
+ const withPr = computeAttentionScore(makeScoreItem({
51
+ displayState: 'running',
52
+ prStatus: 'changes-requested',
53
+ }));
54
+ const withoutPr = computeAttentionScore(makeScoreItem({
55
+ displayState: 'running',
56
+ }));
57
+ assert.ok(withPr > withoutPr);
58
+ assert.equal(withPr - withoutPr, 200);
59
+ });
60
+ it('review-requested PR adds urgency', () => {
61
+ const withPr = computeAttentionScore(makeScoreItem({
62
+ displayState: 'running',
63
+ prStatus: 'review-requested',
64
+ }));
65
+ const withoutPr = computeAttentionScore(makeScoreItem({
66
+ displayState: 'running',
67
+ }));
68
+ assert.equal(withPr - withoutPr, 150);
69
+ });
70
+ it('recency contributes to score', () => {
71
+ const recent = computeAttentionScore(makeScoreItem({
72
+ displayState: 'running',
73
+ lastActivity: new Date().toISOString(),
74
+ }));
75
+ const old = computeAttentionScore(makeScoreItem({
76
+ displayState: 'running',
77
+ lastActivity: new Date(Date.now() - 120 * 60_000).toISOString(),
78
+ }));
79
+ assert.ok(recent > old);
80
+ });
81
+ });
82
+ describe('sortByAttention', () => {
83
+ it('sorts permission above running', () => {
84
+ const items = [
85
+ makeScoreItem({ id: 'a', displayState: 'running' }),
86
+ makeScoreItem({ id: 'b', displayState: 'permission' }),
87
+ ];
88
+ const sorted = sortByAttention(items);
89
+ assert.equal(sorted[0].id, 'b');
90
+ });
91
+ });
@@ -0,0 +1,105 @@
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
+ });
96
+ test('verifyPin returns false for undefined hash', async () => {
97
+ _resetForTesting();
98
+ const result = await verifyPin('1234', undefined);
99
+ assert.strictEqual(result, false);
100
+ });
101
+ test('verifyPin returns false for null hash', async () => {
102
+ _resetForTesting();
103
+ const result = await verifyPin('1234', null);
104
+ assert.strictEqual(result, false);
105
+ });
@@ -0,0 +1,47 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { computeBackendState, fireBackendStateIfChanged, onBackendStateChange, } from '../server/sessions.js';
4
+ // Minimal mock session shape for computeBackendState
5
+ function mockState(agentState, idle) {
6
+ return { agentState, idle };
7
+ }
8
+ describe('computeBackendState', () => {
9
+ it('maps processing + idle=false to running', () => {
10
+ assert.equal(computeBackendState(mockState('processing', false)), 'running');
11
+ });
12
+ it('maps initializing + idle=false to initializing', () => {
13
+ assert.equal(computeBackendState(mockState('initializing', false)), 'initializing');
14
+ });
15
+ it('maps idle + idle=true to idle', () => {
16
+ assert.equal(computeBackendState(mockState('idle', true)), 'idle');
17
+ });
18
+ it('maps waiting-for-input + idle=true to idle', () => {
19
+ assert.equal(computeBackendState(mockState('waiting-for-input', true)), 'idle');
20
+ });
21
+ it('maps permission-prompt + idle=false to permission', () => {
22
+ assert.equal(computeBackendState(mockState('permission-prompt', false)), 'permission');
23
+ });
24
+ it('maps error + idle=false to error', () => {
25
+ assert.equal(computeBackendState(mockState('error', false)), 'error');
26
+ });
27
+ });
28
+ describe('fireBackendStateIfChanged', () => {
29
+ it('fires callback only once when called twice with the same state', () => {
30
+ const calls = [];
31
+ onBackendStateChange((sessionId, state) => {
32
+ calls.push([sessionId, state]);
33
+ });
34
+ // Minimal session mock — only the fields fireBackendStateIfChanged needs
35
+ const session = {
36
+ id: 'test-session-dedup',
37
+ agentState: 'processing',
38
+ idle: false,
39
+ _lastEmittedBackendState: undefined,
40
+ };
41
+ fireBackendStateIfChanged(session);
42
+ fireBackendStateIfChanged(session); // same state — should be a no-op
43
+ const relevant = calls.filter(([id]) => id === 'test-session-dedup');
44
+ assert.equal(relevant.length, 1, 'callback should fire exactly once for duplicate state');
45
+ assert.equal(relevant[0][1], 'running');
46
+ });
47
+ });
@@ -0,0 +1,33 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildClaudeArgs, parseVerdictFile } from '../server/belayer/executor.js';
4
+ test('buildClaudeArgs includes -p and --dangerously-skip-permissions', () => {
5
+ const args = buildClaudeArgs('opus');
6
+ assert.ok(args.includes('-p'));
7
+ assert.ok(args.includes('--dangerously-skip-permissions'));
8
+ });
9
+ test('buildClaudeArgs includes --model flag', () => {
10
+ const args = buildClaudeArgs('sonnet');
11
+ const modelIdx = args.indexOf('--model');
12
+ assert.ok(modelIdx >= 0);
13
+ assert.equal(args[modelIdx + 1], 'sonnet');
14
+ });
15
+ test('parseVerdictFile parses valid verdict JSON', () => {
16
+ const json = JSON.stringify({
17
+ goalName: 'Test',
18
+ pass: true,
19
+ criteriaResults: [{ criterion: 'Works', met: true }],
20
+ summary: 'All good',
21
+ timestamp: '2026-03-05T00:00:00.000Z',
22
+ });
23
+ const verdict = parseVerdictFile(json);
24
+ assert.ok(verdict);
25
+ assert.equal(verdict.pass, true);
26
+ assert.equal(verdict.goalName, 'Test');
27
+ });
28
+ test('parseVerdictFile returns null for invalid JSON', () => {
29
+ assert.equal(parseVerdictFile('not json'), null);
30
+ });
31
+ test('parseVerdictFile returns null for missing required fields', () => {
32
+ assert.equal(parseVerdictFile(JSON.stringify({ pass: true })), null);
33
+ });
@@ -0,0 +1,44 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { TextSource, resolveTaskSource } from '../server/belayer/intake.js';
4
+ test('TextSource.canHandle returns true for any non-empty string', () => {
5
+ const source = new TextSource();
6
+ assert.equal(source.canHandle('hello world'), true);
7
+ assert.equal(source.canHandle(''), false);
8
+ });
9
+ test('TextSource.name is "text"', () => {
10
+ const source = new TextSource();
11
+ assert.equal(source.name, 'text');
12
+ });
13
+ test('TextSource.fetch extracts title from first line', async () => {
14
+ const source = new TextSource();
15
+ const spec = await source.fetch('Add expense export feature\nShould export to CSV and PDF');
16
+ assert.equal(spec.source, 'text');
17
+ assert.equal(spec.title, 'Add expense export feature');
18
+ assert.equal(spec.description, 'Should export to CSV and PDF');
19
+ });
20
+ test('TextSource.fetch handles single-line input', async () => {
21
+ const source = new TextSource();
22
+ const spec = await source.fetch('Fix the login bug');
23
+ assert.equal(spec.title, 'Fix the login bug');
24
+ assert.equal(spec.description, 'Fix the login bug');
25
+ });
26
+ test('TextSource.fetch trims whitespace', async () => {
27
+ const source = new TextSource();
28
+ const spec = await source.fetch(' Trim me \n Some description ');
29
+ assert.equal(spec.title, 'Trim me');
30
+ assert.equal(spec.description, 'Some description');
31
+ });
32
+ test('TextSource.fetch handles multi-line descriptions', async () => {
33
+ const source = new TextSource();
34
+ const spec = await source.fetch('Title\nLine 1\nLine 2\nLine 3');
35
+ assert.equal(spec.title, 'Title');
36
+ assert.equal(spec.description, 'Line 1\nLine 2\nLine 3');
37
+ });
38
+ test('resolveTaskSource returns TextSource for plain text', () => {
39
+ const source = resolveTaskSource('Just some text');
40
+ assert.equal(source.name, 'text');
41
+ });
42
+ test('resolveTaskSource throws for empty input', () => {
43
+ assert.throws(() => resolveTaskSource(''), /No task source can handle empty input/);
44
+ });
@@ -0,0 +1,113 @@
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 { createPipeline, getPipeline, listPipelines, transitionPipeline, deletePipeline, loadAllPipelines, setPipelinesDir, clearPipelines } from '../server/belayer/pipeline.js';
7
+ import { DEFAULT_PIPELINE_CONFIG } from '../server/belayer/types.js';
8
+ let tmpDir;
9
+ before(() => {
10
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'belayer-pipeline-test-'));
11
+ setPipelinesDir(tmpDir);
12
+ });
13
+ afterEach(() => {
14
+ // Clean up in-memory and disk state between tests
15
+ clearPipelines();
16
+ for (const entry of fs.readdirSync(tmpDir)) {
17
+ const fullPath = path.join(tmpDir, entry);
18
+ fs.rmSync(fullPath, { recursive: true, force: true });
19
+ }
20
+ });
21
+ after(() => {
22
+ fs.rmSync(tmpDir, { recursive: true, force: true });
23
+ });
24
+ const testTask = {
25
+ source: 'text',
26
+ title: 'Test Task',
27
+ description: 'A test task description',
28
+ };
29
+ test('createPipeline creates a pipeline in intake state', () => {
30
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
31
+ assert.equal(pipeline.state, 'intake');
32
+ assert.equal(pipeline.task.title, 'Test Task');
33
+ assert.equal(pipeline.attempts, 0);
34
+ assert.ok(pipeline.id);
35
+ assert.ok(pipeline.createdAt);
36
+ });
37
+ test('createPipeline persists to disk', () => {
38
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
39
+ const filePath = path.join(tmpDir, pipeline.id + '.json');
40
+ assert.ok(fs.existsSync(filePath));
41
+ const loaded = JSON.parse(fs.readFileSync(filePath, 'utf8'));
42
+ assert.equal(loaded.id, pipeline.id);
43
+ });
44
+ test('getPipeline returns a stored pipeline', () => {
45
+ const created = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
46
+ const fetched = getPipeline(created.id);
47
+ assert.ok(fetched);
48
+ assert.equal(fetched.id, created.id);
49
+ assert.equal(fetched.state, 'intake');
50
+ });
51
+ test('getPipeline returns null for unknown id', () => {
52
+ assert.equal(getPipeline('nonexistent'), null);
53
+ });
54
+ test('listPipelines returns all pipelines', () => {
55
+ createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
56
+ createPipeline({ ...testTask, title: 'Second' }, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
57
+ const list = listPipelines();
58
+ assert.equal(list.length, 2);
59
+ });
60
+ test('transitionPipeline moves to valid next state', () => {
61
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
62
+ const updated = transitionPipeline(pipeline.id, 'brainstorming');
63
+ assert.equal(updated.state, 'brainstorming');
64
+ });
65
+ test('transitionPipeline rejects invalid transition', () => {
66
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
67
+ assert.throws(() => transitionPipeline(pipeline.id, 'executing'), /Invalid transition from intake to executing/);
68
+ });
69
+ test('transitionPipeline persists state change', () => {
70
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
71
+ transitionPipeline(pipeline.id, 'brainstorming');
72
+ const filePath = path.join(tmpDir, pipeline.id + '.json');
73
+ const loaded = JSON.parse(fs.readFileSync(filePath, 'utf8'));
74
+ assert.equal(loaded.state, 'brainstorming');
75
+ });
76
+ test('transitionPipeline updates updatedAt timestamp', () => {
77
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
78
+ const before = pipeline.updatedAt;
79
+ // Small delay to ensure timestamp differs
80
+ const updated = transitionPipeline(pipeline.id, 'brainstorming');
81
+ assert.ok(updated.updatedAt >= before);
82
+ });
83
+ test('deletePipeline removes pipeline from memory and disk', () => {
84
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
85
+ deletePipeline(pipeline.id);
86
+ assert.equal(getPipeline(pipeline.id), null);
87
+ const filePath = path.join(tmpDir, pipeline.id + '.json');
88
+ assert.ok(!fs.existsSync(filePath));
89
+ });
90
+ test('loadAllPipelines restores from disk', () => {
91
+ const p1 = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
92
+ const p2 = createPipeline({ ...testTask, title: 'Other' }, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
93
+ // Clear in-memory state
94
+ deletePipeline(p1.id);
95
+ deletePipeline(p2.id);
96
+ // Write files back manually to simulate server restart
97
+ fs.writeFileSync(path.join(tmpDir, 'fake-id.json'), JSON.stringify({ ...p1, id: 'fake-id' }));
98
+ loadAllPipelines();
99
+ const loaded = getPipeline('fake-id');
100
+ assert.ok(loaded);
101
+ assert.equal(loaded.task.title, 'Test Task');
102
+ });
103
+ test('transitionPipeline to retry increments attempts', () => {
104
+ const pipeline = createPipeline(testTask, { ...DEFAULT_PIPELINE_CONFIG, targetRepo: '/tmp/repo' });
105
+ transitionPipeline(pipeline.id, 'brainstorming');
106
+ transitionPipeline(pipeline.id, 'prd_review');
107
+ transitionPipeline(pipeline.id, 'planning');
108
+ transitionPipeline(pipeline.id, 'plan_review');
109
+ transitionPipeline(pipeline.id, 'executing');
110
+ transitionPipeline(pipeline.id, 'reviewing');
111
+ const retried = transitionPipeline(pipeline.id, 'retry');
112
+ assert.equal(retried.attempts, 1);
113
+ });
@@ -0,0 +1,26 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildPrTitle, buildPrBody } from '../server/belayer/pr-lifecycle.js';
4
+ const testTask = {
5
+ source: 'text',
6
+ title: 'Add CSV export',
7
+ description: 'Users should be able to export data as CSV files',
8
+ };
9
+ test('buildPrTitle returns formatted title', () => {
10
+ const title = buildPrTitle(testTask);
11
+ assert.equal(title, 'feat: Add CSV export');
12
+ });
13
+ test('buildPrBody includes task description', () => {
14
+ const body = buildPrBody(testTask, '# PRD content', 1);
15
+ assert.ok(body.includes('Add CSV export'));
16
+ assert.ok(body.includes('export data as CSV files'));
17
+ });
18
+ test('buildPrBody includes attempt count', () => {
19
+ const body = buildPrBody(testTask, '# PRD', 2);
20
+ assert.ok(body.includes('2'));
21
+ });
22
+ test('buildPrBody includes PRD content in collapsible section', () => {
23
+ const body = buildPrBody(testTask, '# My PRD', 1);
24
+ assert.ok(body.includes('<details>'));
25
+ assert.ok(body.includes('# My PRD'));
26
+ });