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,750 @@
1
+ import { describe, it, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import * as sessions from '../server/sessions.js';
7
+ import { resolveTmuxSpawn, generateTmuxSessionName } from '../server/pty-handler.js';
8
+ import { serializeAll, restoreFromDisk } from '../server/sessions.js';
9
+ // Track created session IDs so we can clean up after each test
10
+ const createdIds = [];
11
+ afterEach(() => {
12
+ // Kill any remaining sessions created during tests
13
+ for (const id of createdIds) {
14
+ try {
15
+ const session = sessions.get(id);
16
+ if (session) {
17
+ sessions.kill(id);
18
+ }
19
+ }
20
+ catch {
21
+ // Already killed or exited, ignore
22
+ }
23
+ }
24
+ createdIds.length = 0;
25
+ });
26
+ describe('sessions', () => {
27
+ it('list returns empty array initially', () => {
28
+ const result = sessions.list();
29
+ assert.ok(Array.isArray(result));
30
+ assert.strictEqual(result.length, 0);
31
+ });
32
+ it('create spawns PTY and adds session to registry', () => {
33
+ const result = sessions.create({
34
+ repoName: 'test-repo',
35
+ repoPath: '/tmp',
36
+ command: '/bin/echo',
37
+ args: ['hello'],
38
+ cols: 80,
39
+ rows: 24,
40
+ });
41
+ createdIds.push(result.id);
42
+ assert.ok(result.id, 'should have an id');
43
+ assert.strictEqual(result.repoName, 'test-repo');
44
+ assert.strictEqual(result.repoPath, '/tmp');
45
+ assert.ok(typeof result.pid === 'number', 'should have a numeric pid');
46
+ assert.ok(result.createdAt, 'should have a createdAt timestamp');
47
+ assert.strictEqual('pty' in result, false, 'should not expose pty object');
48
+ const list = sessions.list();
49
+ assert.strictEqual(list.length, 1);
50
+ assert.strictEqual(list[0]?.id, result.id);
51
+ });
52
+ it('get returns session by id', () => {
53
+ const result = sessions.create({
54
+ repoName: 'test-repo',
55
+ repoPath: '/tmp',
56
+ command: '/bin/echo',
57
+ args: ['hello'],
58
+ });
59
+ createdIds.push(result.id);
60
+ const session = sessions.get(result.id);
61
+ assert.ok(session, 'should return the session');
62
+ assert.strictEqual(session.id, result.id);
63
+ assert.strictEqual(session.repoName, 'test-repo');
64
+ assert.strictEqual(session.mode, 'pty');
65
+ assert.ok(session.pty, 'get should include the pty object');
66
+ });
67
+ it('get returns undefined for nonexistent id', () => {
68
+ const session = sessions.get('nonexistent-id-12345');
69
+ assert.strictEqual(session, undefined);
70
+ });
71
+ it('kill removes session from registry', () => {
72
+ const result = sessions.create({
73
+ repoName: 'test-repo',
74
+ repoPath: '/tmp',
75
+ command: '/bin/echo',
76
+ args: ['hello'],
77
+ });
78
+ createdIds.push(result.id);
79
+ sessions.kill(result.id);
80
+ // Remove from tracking since it's already killed
81
+ createdIds.splice(createdIds.indexOf(result.id), 1);
82
+ const session = sessions.get(result.id);
83
+ assert.strictEqual(session, undefined, 'session should be removed after kill');
84
+ const list = sessions.list();
85
+ assert.ok(!list.some((s) => s.id === result.id), 'killed session should not appear in list');
86
+ });
87
+ it('kill throws for nonexistent session', () => {
88
+ assert.throws(() => sessions.kill('nonexistent-id'), /Session not found/);
89
+ });
90
+ it('resize throws for nonexistent session', () => {
91
+ assert.throws(() => sessions.resize('nonexistent-id', 100, 40), /Session not found/);
92
+ });
93
+ it('write sends data to PTY stdin', (_, done) => {
94
+ const result = sessions.create({
95
+ repoName: 'test-repo',
96
+ repoPath: '/tmp',
97
+ command: '/bin/cat',
98
+ args: [],
99
+ cols: 80,
100
+ rows: 24,
101
+ });
102
+ createdIds.push(result.id);
103
+ const session = sessions.get(result.id);
104
+ assert.ok(session);
105
+ assert.strictEqual(session.mode, 'pty');
106
+ const ptySession = session;
107
+ let output = '';
108
+ ptySession.pty.onData((data) => {
109
+ output += data;
110
+ if (output.includes('hello')) {
111
+ done();
112
+ }
113
+ });
114
+ sessions.write(result.id, 'hello');
115
+ });
116
+ it('write throws for nonexistent session', () => {
117
+ assert.throws(() => sessions.write('nonexistent-id', 'data'), /Session not found/);
118
+ });
119
+ it('session starts as not idle', () => {
120
+ const result = sessions.create({
121
+ repoName: 'test-repo',
122
+ repoPath: '/tmp',
123
+ command: '/bin/cat',
124
+ args: [],
125
+ });
126
+ createdIds.push(result.id);
127
+ const session = sessions.get(result.id);
128
+ assert.ok(session);
129
+ assert.strictEqual(session.idle, false);
130
+ });
131
+ it('list includes idle field', () => {
132
+ const result = sessions.create({
133
+ repoName: 'test-repo',
134
+ repoPath: '/tmp',
135
+ command: '/bin/cat',
136
+ args: [],
137
+ });
138
+ createdIds.push(result.id);
139
+ const list = sessions.list();
140
+ assert.strictEqual(list.length, 1);
141
+ assert.strictEqual(list[0]?.idle, false);
142
+ });
143
+ it('type defaults to worktree when not specified', () => {
144
+ const result = sessions.create({
145
+ repoName: 'test-repo',
146
+ repoPath: '/tmp',
147
+ command: '/bin/echo',
148
+ args: ['hello'],
149
+ });
150
+ createdIds.push(result.id);
151
+ assert.strictEqual(result.type, 'worktree');
152
+ const session = sessions.get(result.id);
153
+ assert.ok(session);
154
+ assert.strictEqual(session.type, 'worktree');
155
+ });
156
+ it('type is set to repo when specified', () => {
157
+ const result = sessions.create({
158
+ type: 'repo',
159
+ repoName: 'test-repo',
160
+ repoPath: '/tmp',
161
+ command: '/bin/echo',
162
+ args: ['hello'],
163
+ });
164
+ createdIds.push(result.id);
165
+ assert.strictEqual(result.type, 'repo');
166
+ const session = sessions.get(result.id);
167
+ assert.ok(session);
168
+ assert.strictEqual(session.type, 'repo');
169
+ });
170
+ it('list includes type field', () => {
171
+ const r1 = sessions.create({
172
+ type: 'repo',
173
+ repoName: 'repo-a',
174
+ repoPath: '/tmp/a',
175
+ command: '/bin/echo',
176
+ args: ['hello'],
177
+ });
178
+ createdIds.push(r1.id);
179
+ const r2 = sessions.create({
180
+ type: 'worktree',
181
+ repoName: 'repo-b',
182
+ repoPath: '/tmp/b',
183
+ command: '/bin/echo',
184
+ args: ['hello'],
185
+ });
186
+ createdIds.push(r2.id);
187
+ const list = sessions.list();
188
+ const repoSession = list.find(function (s) { return s.id === r1.id; });
189
+ const wtSession = list.find(function (s) { return s.id === r2.id; });
190
+ assert.ok(repoSession);
191
+ assert.strictEqual(repoSession.type, 'repo');
192
+ assert.ok(wtSession);
193
+ assert.strictEqual(wtSession.type, 'worktree');
194
+ });
195
+ it('findRepoSession returns undefined when no repo sessions exist', () => {
196
+ const result = sessions.findRepoSession('/tmp');
197
+ assert.strictEqual(result, undefined);
198
+ });
199
+ it('findRepoSession returns repo session matching repoPath', () => {
200
+ const created = sessions.create({
201
+ type: 'repo',
202
+ repoName: 'test-repo',
203
+ repoPath: '/tmp/my-repo',
204
+ command: '/bin/echo',
205
+ args: ['hello'],
206
+ });
207
+ createdIds.push(created.id);
208
+ const found = sessions.findRepoSession('/tmp/my-repo');
209
+ assert.ok(found, 'should find the repo session');
210
+ assert.strictEqual(found.id, created.id);
211
+ assert.strictEqual(found.type, 'repo');
212
+ });
213
+ it('findRepoSession ignores worktree sessions at same path', () => {
214
+ const created = sessions.create({
215
+ type: 'worktree',
216
+ repoName: 'test-repo',
217
+ repoPath: '/tmp/my-repo',
218
+ command: '/bin/echo',
219
+ args: ['hello'],
220
+ });
221
+ createdIds.push(created.id);
222
+ const found = sessions.findRepoSession('/tmp/my-repo');
223
+ assert.strictEqual(found, undefined, 'should not match worktree sessions');
224
+ });
225
+ it('branchName defaults to worktreeName when not specified', () => {
226
+ const result = sessions.create({
227
+ repoName: 'test-repo',
228
+ repoPath: '/tmp',
229
+ worktreeName: 'dy-feat-my-feature',
230
+ command: '/bin/echo',
231
+ args: ['hello'],
232
+ });
233
+ createdIds.push(result.id);
234
+ assert.strictEqual(result.branchName, 'dy-feat-my-feature');
235
+ });
236
+ it('branchName is set independently from worktreeName', () => {
237
+ const result = sessions.create({
238
+ repoName: 'test-repo',
239
+ repoPath: '/tmp',
240
+ worktreeName: 'dy-feat-my-feature',
241
+ branchName: 'dy/feat/my-feature',
242
+ command: '/bin/echo',
243
+ args: ['hello'],
244
+ });
245
+ createdIds.push(result.id);
246
+ assert.strictEqual(result.worktreeName, 'dy-feat-my-feature');
247
+ assert.strictEqual(result.branchName, 'dy/feat/my-feature');
248
+ });
249
+ it('list includes branchName field', () => {
250
+ const result = sessions.create({
251
+ repoName: 'test-repo',
252
+ repoPath: '/tmp',
253
+ worktreeName: 'my-wt',
254
+ branchName: 'feat/my-branch',
255
+ command: '/bin/echo',
256
+ args: ['hello'],
257
+ });
258
+ createdIds.push(result.id);
259
+ const list = sessions.list();
260
+ const session = list.find(s => s.id === result.id);
261
+ assert.ok(session);
262
+ assert.strictEqual(session.branchName, 'feat/my-branch');
263
+ });
264
+ it('branchName defaults to empty string when neither branchName nor worktreeName provided', () => {
265
+ const result = sessions.create({
266
+ type: 'repo',
267
+ repoName: 'test-repo',
268
+ repoPath: '/tmp',
269
+ command: '/bin/echo',
270
+ args: ['hello'],
271
+ });
272
+ createdIds.push(result.id);
273
+ assert.strictEqual(result.branchName, '');
274
+ });
275
+ it('resolveTmuxSpawn returns correct tmux command and args', () => {
276
+ const result = resolveTmuxSpawn('claude', ['--continue'], 'test-session');
277
+ assert.deepStrictEqual(result, {
278
+ command: 'tmux',
279
+ args: [
280
+ '-u', 'new-session', '-s', 'test-session', '--', 'claude', '--continue',
281
+ ';', 'set', 'set-clipboard', 'on',
282
+ ';', 'set', 'allow-passthrough', 'on',
283
+ ';', 'set', 'mode-keys', 'vi',
284
+ ],
285
+ });
286
+ });
287
+ it('generateTmuxSessionName has crc- prefix', () => {
288
+ const name = generateTmuxSessionName('my-session', 'abcdef1234567890');
289
+ assert.ok(name.startsWith('crc-'), `expected crc- prefix, got: ${name}`);
290
+ });
291
+ it('generateTmuxSessionName sanitizes special characters', () => {
292
+ const name = generateTmuxSessionName('feat/auth-flow', 'abcdef1234567890');
293
+ assert.ok(name.startsWith('crc-feat-auth-flow-'), `expected sanitized name, got: ${name}`);
294
+ });
295
+ it('generateTmuxSessionName limits display name to 30 chars', () => {
296
+ const longName = 'a-very-long-display-name-that-exceeds-thirty-characters';
297
+ const id = 'abcdef1234567890';
298
+ const name = generateTmuxSessionName(longName, id);
299
+ // Format is crc-<sanitized up to 30>-<8 char id>
300
+ // The sanitized portion should be at most 30 chars
301
+ const withoutPrefix = name.slice('crc-'.length);
302
+ const parts = withoutPrefix.split('-');
303
+ const idPart = parts[parts.length - 1];
304
+ const displayPart = withoutPrefix.slice(0, withoutPrefix.length - idPart.length - 1);
305
+ assert.ok(displayPart.length <= 30, `display portion should be <= 30 chars, got: ${displayPart.length}`);
306
+ });
307
+ it('generateTmuxSessionName uses 8 chars from the provided id', () => {
308
+ const id = 'abcdef1234567890';
309
+ const name = generateTmuxSessionName('my-session', id);
310
+ assert.ok(name.endsWith(id.slice(0, 8)), `expected name to end with ${id.slice(0, 8)}, got: ${name}`);
311
+ });
312
+ it('agent defaults to claude when not specified', () => {
313
+ const result = sessions.create({
314
+ repoName: 'test-repo',
315
+ repoPath: '/tmp',
316
+ command: '/bin/echo',
317
+ args: ['hello'],
318
+ });
319
+ createdIds.push(result.id);
320
+ assert.strictEqual(result.agent, 'claude');
321
+ });
322
+ it('agent is set when specified', () => {
323
+ const result = sessions.create({
324
+ repoName: 'test-repo',
325
+ repoPath: '/tmp',
326
+ agent: 'codex',
327
+ command: '/bin/echo',
328
+ args: ['hello'],
329
+ });
330
+ createdIds.push(result.id);
331
+ assert.strictEqual(result.agent, 'codex');
332
+ });
333
+ it('list includes agent field', () => {
334
+ const result = sessions.create({
335
+ repoName: 'test-repo',
336
+ repoPath: '/tmp',
337
+ agent: 'codex',
338
+ command: '/bin/echo',
339
+ args: ['hello'],
340
+ });
341
+ createdIds.push(result.id);
342
+ const list = sessions.list();
343
+ const session = list.find(s => s.id === result.id);
344
+ assert.ok(session);
345
+ assert.strictEqual(session.agent, 'codex');
346
+ });
347
+ it('useTmux defaults to false when not specified', () => {
348
+ const result = sessions.create({
349
+ repoName: 'test-repo',
350
+ repoPath: '/tmp',
351
+ command: '/bin/echo',
352
+ args: ['hello'],
353
+ });
354
+ createdIds.push(result.id);
355
+ assert.strictEqual(result.useTmux, false);
356
+ assert.strictEqual(result.tmuxSessionName, '');
357
+ });
358
+ it('useTmux is disabled when custom command is provided even if useTmux is true', () => {
359
+ const result = sessions.create({
360
+ repoName: 'test-repo',
361
+ repoPath: '/tmp',
362
+ command: '/bin/echo',
363
+ args: ['hello'],
364
+ useTmux: true,
365
+ });
366
+ createdIds.push(result.id);
367
+ // Custom command sessions should never use tmux
368
+ assert.strictEqual(result.useTmux, false);
369
+ assert.strictEqual(result.tmuxSessionName, '');
370
+ });
371
+ it('list includes useTmux and tmuxSessionName fields', () => {
372
+ const result = sessions.create({
373
+ repoName: 'test-repo',
374
+ repoPath: '/tmp',
375
+ command: '/bin/echo',
376
+ args: ['hello'],
377
+ });
378
+ createdIds.push(result.id);
379
+ const list = sessions.list();
380
+ const session = list.find(s => s.id === result.id);
381
+ assert.ok(session);
382
+ assert.strictEqual(session.useTmux, false);
383
+ assert.strictEqual(session.tmuxSessionName, '');
384
+ });
385
+ it('calls onPtyReplaced when continue-arg process fails quickly', (_, done) => {
386
+ const result = sessions.create({
387
+ repoName: 'test-repo',
388
+ repoPath: '/tmp',
389
+ command: '/bin/false',
390
+ args: [...sessions.AGENT_CONTINUE_ARGS.claude],
391
+ });
392
+ createdIds.push(result.id);
393
+ const session = sessions.get(result.id);
394
+ assert.ok(session);
395
+ assert.strictEqual(session.mode, 'pty');
396
+ const ptySession = session;
397
+ ptySession.onPtyReplacedCallbacks.push((newPty) => {
398
+ assert.ok(newPty, 'should receive new PTY');
399
+ assert.strictEqual(ptySession.pty, newPty, 'session.pty should be updated to new PTY');
400
+ done();
401
+ });
402
+ });
403
+ it('session survives after continue-arg retry', (_, done) => {
404
+ const result = sessions.create({
405
+ repoName: 'test-repo',
406
+ repoPath: '/tmp',
407
+ command: '/bin/false',
408
+ args: [...sessions.AGENT_CONTINUE_ARGS.claude],
409
+ });
410
+ createdIds.push(result.id);
411
+ const session = sessions.get(result.id);
412
+ assert.ok(session);
413
+ assert.strictEqual(session.mode, 'pty');
414
+ const ptySession = session;
415
+ ptySession.onPtyReplacedCallbacks.push(() => {
416
+ const stillExists = sessions.get(result.id);
417
+ assert.ok(stillExists, 'session should still exist after retry');
418
+ done();
419
+ });
420
+ });
421
+ it('retries when continue-arg process exits quickly with code 0 (tmux behavior)', (_, done) => {
422
+ const result = sessions.create({
423
+ repoName: 'test-repo',
424
+ repoPath: '/tmp',
425
+ command: '/bin/sh',
426
+ args: ['-c', 'exit 0', ...sessions.AGENT_CONTINUE_ARGS.claude],
427
+ });
428
+ createdIds.push(result.id);
429
+ const session = sessions.get(result.id);
430
+ assert.ok(session);
431
+ assert.strictEqual(session.mode, 'pty');
432
+ const ptySession = session;
433
+ ptySession.onPtyReplacedCallbacks.push((newPty) => {
434
+ assert.ok(newPty, 'should receive new PTY even with exit code 0');
435
+ assert.strictEqual(ptySession.pty, newPty, 'session.pty should be updated');
436
+ const stillExists = sessions.get(result.id);
437
+ assert.ok(stillExists, 'session should still exist after retry');
438
+ done();
439
+ });
440
+ });
441
+ it('create accepts a predetermined id', () => {
442
+ const result = sessions.create({
443
+ id: 'custom-id-12345678',
444
+ repoName: 'test-repo',
445
+ repoPath: '/tmp',
446
+ command: '/bin/echo',
447
+ args: ['hello'],
448
+ });
449
+ createdIds.push(result.id);
450
+ assert.strictEqual(result.id, 'custom-id-12345678');
451
+ const session = sessions.get('custom-id-12345678');
452
+ assert.ok(session);
453
+ });
454
+ it('create accepts initialScrollback', () => {
455
+ const result = sessions.create({
456
+ repoName: 'test-repo',
457
+ repoPath: '/tmp',
458
+ command: '/bin/echo',
459
+ args: ['hello'],
460
+ initialScrollback: ['prior output\r\n'],
461
+ });
462
+ createdIds.push(result.id);
463
+ const session = sessions.get(result.id);
464
+ assert.ok(session);
465
+ assert.strictEqual(session.mode, 'pty');
466
+ assert.ok(session.scrollback.length >= 1);
467
+ assert.strictEqual(session.scrollback[0], 'prior output\r\n');
468
+ });
469
+ });
470
+ describe('session persistence', () => {
471
+ let tmpDir;
472
+ afterEach(() => {
473
+ // Clean up any sessions created during tests
474
+ for (const s of sessions.list()) {
475
+ try {
476
+ sessions.kill(s.id);
477
+ }
478
+ catch { /* ignore */ }
479
+ }
480
+ // Clean up temp directory
481
+ if (tmpDir) {
482
+ try {
483
+ fs.rmSync(tmpDir, { recursive: true, force: true });
484
+ }
485
+ catch { /* ignore */ }
486
+ }
487
+ });
488
+ function createTmpDir() {
489
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'crc-test-'));
490
+ return tmpDir;
491
+ }
492
+ it('serializeAll writes pending-sessions.json and scrollback files', () => {
493
+ const configDir = createTmpDir();
494
+ const s = sessions.create({
495
+ repoName: 'test-repo',
496
+ repoPath: '/tmp',
497
+ command: '/bin/cat',
498
+ args: [],
499
+ });
500
+ // Manually push some scrollback
501
+ const session = sessions.get(s.id);
502
+ assert.ok(session);
503
+ assert.strictEqual(session.mode, 'pty');
504
+ session.scrollback.push('hello world');
505
+ serializeAll(configDir);
506
+ // Check pending-sessions.json
507
+ const pendingPath = path.join(configDir, 'pending-sessions.json');
508
+ assert.ok(fs.existsSync(pendingPath), 'pending-sessions.json should exist');
509
+ const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf-8'));
510
+ assert.strictEqual(pending.version, 1);
511
+ assert.ok(pending.timestamp);
512
+ assert.strictEqual(pending.sessions.length, 1);
513
+ assert.strictEqual(pending.sessions[0].id, s.id);
514
+ assert.strictEqual(pending.sessions[0].repoPath, '/tmp');
515
+ // Check scrollback file
516
+ const scrollbackPath = path.join(configDir, 'scrollback', s.id + '.buf');
517
+ assert.ok(fs.existsSync(scrollbackPath), 'scrollback file should exist');
518
+ const scrollbackData = fs.readFileSync(scrollbackPath, 'utf-8');
519
+ assert.ok(scrollbackData.includes('hello world'));
520
+ });
521
+ it('restoreFromDisk restores sessions with original IDs', async () => {
522
+ const configDir = createTmpDir();
523
+ // Create and serialize a session
524
+ const s = sessions.create({
525
+ repoName: 'test-repo',
526
+ repoPath: '/tmp',
527
+ command: '/bin/cat',
528
+ args: [],
529
+ displayName: 'my-session',
530
+ });
531
+ const originalId = s.id;
532
+ const session = sessions.get(originalId);
533
+ assert.ok(session);
534
+ assert.strictEqual(session.mode, 'pty');
535
+ session.scrollback.push('saved output');
536
+ serializeAll(configDir);
537
+ // Kill the original session
538
+ sessions.kill(originalId);
539
+ assert.strictEqual(sessions.get(originalId), undefined);
540
+ // Restore
541
+ const restored = await restoreFromDisk(configDir);
542
+ assert.strictEqual(restored, 1);
543
+ // Verify session exists with original ID
544
+ const restoredSession = sessions.get(originalId);
545
+ assert.ok(restoredSession, 'restored session should exist');
546
+ assert.strictEqual(restoredSession.repoPath, '/tmp');
547
+ assert.strictEqual(restoredSession.displayName, 'my-session');
548
+ // Scrollback should be restored
549
+ assert.strictEqual(restoredSession.mode, 'pty');
550
+ assert.ok(restoredSession.scrollback.length >= 1);
551
+ assert.strictEqual(restoredSession.scrollback[0], 'saved output');
552
+ // pending-sessions.json should be cleaned up
553
+ assert.ok(!fs.existsSync(path.join(configDir, 'pending-sessions.json')));
554
+ });
555
+ it('restoreFromDisk ignores stale files (>5 min old)', async () => {
556
+ const configDir = createTmpDir();
557
+ // Write a stale pending file
558
+ const staleTime = new Date(Date.now() - 6 * 60 * 1000).toISOString();
559
+ const pending = {
560
+ version: 1,
561
+ timestamp: staleTime,
562
+ sessions: [{ id: 'stale-id', type: 'repo', agent: 'claude', root: '', repoName: 'test', repoPath: '/tmp', worktreeName: '', branchName: '', displayName: 'test', createdAt: staleTime, lastActivity: staleTime, useTmux: false, tmuxSessionName: '', customCommand: null, cwd: '/tmp' }],
563
+ };
564
+ fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending));
565
+ const restored = await restoreFromDisk(configDir);
566
+ assert.strictEqual(restored, 0, 'should not restore stale sessions');
567
+ assert.ok(!fs.existsSync(path.join(configDir, 'pending-sessions.json')), 'stale file should be deleted');
568
+ });
569
+ it('restoreFromDisk handles missing scrollback gracefully', async () => {
570
+ const configDir = createTmpDir();
571
+ // Create a session, serialize, then delete scrollback file
572
+ const s = sessions.create({
573
+ repoName: 'test-repo',
574
+ repoPath: '/tmp',
575
+ command: '/bin/cat',
576
+ args: [],
577
+ });
578
+ serializeAll(configDir);
579
+ sessions.kill(s.id);
580
+ // Delete scrollback file
581
+ const scrollbackPath = path.join(configDir, 'scrollback', s.id + '.buf');
582
+ try {
583
+ fs.unlinkSync(scrollbackPath);
584
+ }
585
+ catch { /* ignore */ }
586
+ const restored = await restoreFromDisk(configDir);
587
+ assert.strictEqual(restored, 1, 'should still restore without scrollback');
588
+ });
589
+ it('restoreFromDisk returns 0 when no pending file exists', async () => {
590
+ const configDir = createTmpDir();
591
+ const restored = await restoreFromDisk(configDir);
592
+ assert.strictEqual(restored, 0);
593
+ });
594
+ it('restoreFromDisk preserves tmuxSessionName for tmux sessions', async () => {
595
+ const configDir = createTmpDir();
596
+ // Write a pending file with a tmux session
597
+ const pending = {
598
+ version: 1,
599
+ timestamp: new Date().toISOString(),
600
+ sessions: [{
601
+ id: 'tmux-test-id',
602
+ type: 'worktree',
603
+ agent: 'claude',
604
+ root: '',
605
+ repoName: 'test-repo',
606
+ repoPath: '/tmp',
607
+ worktreeName: 'my-wt',
608
+ branchName: 'my-branch',
609
+ displayName: 'my-session',
610
+ createdAt: new Date().toISOString(),
611
+ lastActivity: new Date().toISOString(),
612
+ useTmux: true,
613
+ tmuxSessionName: 'crc-my-session-tmux-tes',
614
+ customCommand: '/bin/cat', // Use /bin/cat to avoid spawning real claude binary in test
615
+ cwd: '/tmp',
616
+ }],
617
+ };
618
+ fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending));
619
+ const restored = await restoreFromDisk(configDir);
620
+ assert.strictEqual(restored, 1);
621
+ const session = sessions.get('tmux-test-id');
622
+ assert.ok(session, 'restored session should exist');
623
+ assert.strictEqual(session.mode, 'pty');
624
+ assert.strictEqual(session.tmuxSessionName, 'crc-my-session-tmux-tes', 'tmuxSessionName should be preserved from serialized data');
625
+ });
626
+ it('restored session remains in list after PTY exits (disconnected status)', async () => {
627
+ const configDir = createTmpDir();
628
+ const pending = {
629
+ version: 1,
630
+ timestamp: new Date().toISOString(),
631
+ sessions: [{
632
+ id: 'restore-exit-test',
633
+ type: 'worktree',
634
+ agent: 'claude',
635
+ root: '',
636
+ repoName: 'test-repo',
637
+ repoPath: '/tmp',
638
+ worktreeName: 'my-wt',
639
+ branchName: 'my-branch',
640
+ displayName: 'restored-session',
641
+ createdAt: new Date().toISOString(),
642
+ lastActivity: new Date().toISOString(),
643
+ useTmux: false,
644
+ tmuxSessionName: '',
645
+ customCommand: '/bin/false',
646
+ cwd: '/tmp',
647
+ }],
648
+ };
649
+ fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending));
650
+ await restoreFromDisk(configDir);
651
+ // Wait for PTY to exit
652
+ await new Promise(resolve => setTimeout(resolve, 500));
653
+ // Session should still be in the list with disconnected status
654
+ const list = sessions.list();
655
+ const found = list.find(s => s.id === 'restore-exit-test');
656
+ assert.ok(found, 'restored session should remain in list after PTY exit');
657
+ assert.strictEqual(found.status, 'disconnected');
658
+ });
659
+ it('full serialize-restore round trip preserves all session fields including tmuxSessionName', async () => {
660
+ const configDir = createTmpDir();
661
+ // Create sessions of different types
662
+ const repo = sessions.create({
663
+ type: 'repo',
664
+ repoName: 'my-repo',
665
+ repoPath: '/tmp/repo',
666
+ command: '/bin/cat',
667
+ args: [],
668
+ displayName: 'My Repo',
669
+ });
670
+ const terminal = sessions.create({
671
+ type: 'terminal',
672
+ repoPath: '/tmp',
673
+ command: '/bin/sh',
674
+ args: [],
675
+ displayName: 'Terminal 1',
676
+ });
677
+ // Serialize all
678
+ serializeAll(configDir);
679
+ // Kill originals
680
+ sessions.kill(repo.id);
681
+ sessions.kill(terminal.id);
682
+ assert.strictEqual(sessions.list().length, 0);
683
+ // Also inject a tmux-style session into the pending file to test tmuxSessionName round-trip.
684
+ // Use customCommand so restore spawns that instead of claude --continue (which would exit instantly).
685
+ const pendingPath = path.join(configDir, 'pending-sessions.json');
686
+ const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf-8'));
687
+ pending.sessions.push({
688
+ id: 'tmux-roundtrip-id',
689
+ type: 'worktree',
690
+ agent: 'claude',
691
+ root: '',
692
+ repoName: 'tmux-repo',
693
+ repoPath: '/tmp',
694
+ worktreeName: 'tmux-wt',
695
+ branchName: 'feat/tmux',
696
+ displayName: 'Tmux Session',
697
+ createdAt: new Date().toISOString(),
698
+ lastActivity: new Date().toISOString(),
699
+ useTmux: true,
700
+ tmuxSessionName: 'crc-tmux-session-tmux-rou',
701
+ customCommand: '/bin/cat',
702
+ cwd: '/tmp',
703
+ });
704
+ fs.writeFileSync(pendingPath, JSON.stringify(pending));
705
+ // Restore
706
+ const restored = await restoreFromDisk(configDir);
707
+ assert.strictEqual(restored, 3);
708
+ // Verify all sessions exist
709
+ const list = sessions.list();
710
+ assert.strictEqual(list.length, 3);
711
+ const restoredRepo = list.find(s => s.id === repo.id);
712
+ assert.ok(restoredRepo);
713
+ assert.strictEqual(restoredRepo.type, 'repo');
714
+ assert.strictEqual(restoredRepo.displayName, 'My Repo');
715
+ assert.strictEqual(restoredRepo.status, 'active');
716
+ const restoredTerminal = list.find(s => s.id === terminal.id);
717
+ assert.ok(restoredTerminal);
718
+ assert.strictEqual(restoredTerminal.type, 'terminal');
719
+ assert.strictEqual(restoredTerminal.displayName, 'Terminal 1');
720
+ // Verify tmux session name survived the round trip
721
+ const restoredTmux = sessions.get('tmux-roundtrip-id');
722
+ assert.ok(restoredTmux);
723
+ assert.strictEqual(restoredTmux.mode, 'pty');
724
+ assert.strictEqual(restoredTmux.tmuxSessionName, 'crc-tmux-session-tmux-rou');
725
+ assert.strictEqual(restoredTmux.displayName, 'Tmux Session');
726
+ });
727
+ it('serializeAll captures session state before kill', () => {
728
+ const configDir = createTmpDir();
729
+ const s = sessions.create({
730
+ repoName: 'test-repo',
731
+ repoPath: '/tmp',
732
+ command: '/bin/cat',
733
+ args: [],
734
+ displayName: 'before-kill',
735
+ });
736
+ const session = sessions.get(s.id);
737
+ assert.ok(session);
738
+ assert.strictEqual(session.mode, 'pty');
739
+ session.scrollback.push('important output');
740
+ serializeAll(configDir);
741
+ // Kill after serialize (mimics gracefulShutdown sequence)
742
+ sessions.kill(s.id);
743
+ // Verify data is on disk
744
+ const pendingPath = path.join(configDir, 'pending-sessions.json');
745
+ assert.ok(fs.existsSync(pendingPath));
746
+ const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf-8'));
747
+ assert.strictEqual(pending.sessions.length, 1);
748
+ assert.strictEqual(pending.sessions[0].displayName, 'before-kill');
749
+ });
750
+ });