relay-ide 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +259 -0
  2. package/dist/bin/claude-remote-cli.js +390 -0
  3. package/dist/bin/relay-ide.js +390 -0
  4. package/dist/frontend/assets/abap-BdImnpbu.js +1 -0
  5. package/dist/frontend/assets/actionscript-3-CoDkCxhg.js +1 -0
  6. package/dist/frontend/assets/ada-bCR0ucgS.js +1 -0
  7. package/dist/frontend/assets/andromeeda-C4gqWexZ.js +1 -0
  8. package/dist/frontend/assets/angular-html-DA-rfuFy.js +1 -0
  9. package/dist/frontend/assets/angular-ts-BrjP3tb8.js +1 -0
  10. package/dist/frontend/assets/apache-Pmp26Uib.js +1 -0
  11. package/dist/frontend/assets/apex-D8_7TLub.js +1 -0
  12. package/dist/frontend/assets/apl-CORt7UWP.js +1 -0
  13. package/dist/frontend/assets/applescript-Co6uUVPk.js +1 -0
  14. package/dist/frontend/assets/ara-BRHolxvo.js +1 -0
  15. package/dist/frontend/assets/asciidoc-Ve4PFQV2.js +1 -0
  16. package/dist/frontend/assets/asm-D_Q5rh1f.js +1 -0
  17. package/dist/frontend/assets/astro-HNnZUWAn.js +1 -0
  18. package/dist/frontend/assets/aurora-x-D-2ljcwZ.js +1 -0
  19. package/dist/frontend/assets/awk-DMzUqQB5.js +1 -0
  20. package/dist/frontend/assets/ayu-dark-DYE7WIF3.js +1 -0
  21. package/dist/frontend/assets/ayu-light-BA47KaF1.js +1 -0
  22. package/dist/frontend/assets/ayu-mirage-32ctXXKs.js +1 -0
  23. package/dist/frontend/assets/ballerina-BFfxhgS-.js +1 -0
  24. package/dist/frontend/assets/bat-BkioyH1T.js +1 -0
  25. package/dist/frontend/assets/beancount-k_qm7-4y.js +1 -0
  26. package/dist/frontend/assets/berry-uYugtg8r.js +1 -0
  27. package/dist/frontend/assets/bibtex-CHM0blh-.js +1 -0
  28. package/dist/frontend/assets/bicep-Bmn6On1c.js +1 -0
  29. package/dist/frontend/assets/bird2-BIv1doCn.js +1 -0
  30. package/dist/frontend/assets/blade-BjGOyj-B.js +1 -0
  31. package/dist/frontend/assets/bsl-BO_Y6i37.js +1 -0
  32. package/dist/frontend/assets/c-BIGW1oBm.js +1 -0
  33. package/dist/frontend/assets/c3-eo99z4R2.js +1 -0
  34. package/dist/frontend/assets/cadence-Bv_4Rxtq.js +1 -0
  35. package/dist/frontend/assets/cairo-KRGpt6FW.js +1 -0
  36. package/dist/frontend/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  37. package/dist/frontend/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  38. package/dist/frontend/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  39. package/dist/frontend/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  40. package/dist/frontend/assets/clarity-D53aC0YG.js +1 -0
  41. package/dist/frontend/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/frontend/assets/cmake-D1j8_8rp.js +1 -0
  43. package/dist/frontend/assets/cobol-nBiQ_Alo.js +1 -0
  44. package/dist/frontend/assets/codeowners-Bp6g37R7.js +1 -0
  45. package/dist/frontend/assets/codeql-DsOJ9woJ.js +1 -0
  46. package/dist/frontend/assets/coffee-Ch7k5sss.js +1 -0
  47. package/dist/frontend/assets/common-lisp-Cg-RD9OK.js +1 -0
  48. package/dist/frontend/assets/coq-DkFqJrB1.js +1 -0
  49. package/dist/frontend/assets/cpp-CofmeUqb.js +1 -0
  50. package/dist/frontend/assets/crystal-DNxU26gB.js +1 -0
  51. package/dist/frontend/assets/csharp-COcwbKMJ.js +1 -0
  52. package/dist/frontend/assets/css-CLj8gQPS.js +1 -0
  53. package/dist/frontend/assets/csv-fuZLfV_i.js +1 -0
  54. package/dist/frontend/assets/cue-D82EKSYY.js +1 -0
  55. package/dist/frontend/assets/cypher-COkxafJQ.js +1 -0
  56. package/dist/frontend/assets/d-85-TOEBH.js +1 -0
  57. package/dist/frontend/assets/dark-plus-C3mMm8J8.js +1 -0
  58. package/dist/frontend/assets/dart-bE4Kk8sk.js +1 -0
  59. package/dist/frontend/assets/dax-CEL-wOlO.js +1 -0
  60. package/dist/frontend/assets/desktop-BmXAJ9_W.js +1 -0
  61. package/dist/frontend/assets/diff-D97Zzqfu.js +1 -0
  62. package/dist/frontend/assets/docker-BcOcwvcX.js +1 -0
  63. package/dist/frontend/assets/dotenv-Da5cRb03.js +1 -0
  64. package/dist/frontend/assets/dracula-BzJJZx-M.js +1 -0
  65. package/dist/frontend/assets/dracula-soft-BXkSAIEj.js +1 -0
  66. package/dist/frontend/assets/dream-maker-BtqSS_iP.js +1 -0
  67. package/dist/frontend/assets/edge-FbVlp4U3.js +1 -0
  68. package/dist/frontend/assets/elixir-CkH2-t6x.js +1 -0
  69. package/dist/frontend/assets/elm-DbKCFpqz.js +1 -0
  70. package/dist/frontend/assets/emacs-lisp-CXvaQtF9.js +1 -0
  71. package/dist/frontend/assets/erb-BYCe7drp.js +1 -0
  72. package/dist/frontend/assets/erlang-DsQrWhSR.js +1 -0
  73. package/dist/frontend/assets/everforest-dark-BgDCqdQA.js +1 -0
  74. package/dist/frontend/assets/everforest-light-C8M2exoo.js +1 -0
  75. package/dist/frontend/assets/fennel-BYunw83y.js +1 -0
  76. package/dist/frontend/assets/fish-BvzEVeQv.js +1 -0
  77. package/dist/frontend/assets/fluent-C4IJs8-o.js +1 -0
  78. package/dist/frontend/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  79. package/dist/frontend/assets/fortran-free-form-BxgE0vQu.js +1 -0
  80. package/dist/frontend/assets/fsharp-CXgrBDvD.js +1 -0
  81. package/dist/frontend/assets/gdresource-BOOCDP_w.js +1 -0
  82. package/dist/frontend/assets/gdscript-C5YyOfLZ.js +1 -0
  83. package/dist/frontend/assets/gdshader-DkwncUOv.js +1 -0
  84. package/dist/frontend/assets/genie-D0YGMca9.js +1 -0
  85. package/dist/frontend/assets/gherkin-DyxjwDmM.js +1 -0
  86. package/dist/frontend/assets/git-commit-F4YmCXRG.js +1 -0
  87. package/dist/frontend/assets/git-rebase-r7XF79zn.js +1 -0
  88. package/dist/frontend/assets/github-dark-DHJKELXO.js +1 -0
  89. package/dist/frontend/assets/github-dark-default-Cuk6v7N8.js +1 -0
  90. package/dist/frontend/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  91. package/dist/frontend/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  92. package/dist/frontend/assets/github-light-DAi9KRSo.js +1 -0
  93. package/dist/frontend/assets/github-light-default-D7oLnXFd.js +1 -0
  94. package/dist/frontend/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  95. package/dist/frontend/assets/gleam-BspZqrRM.js +1 -0
  96. package/dist/frontend/assets/glimmer-js-ByusRIyA.js +1 -0
  97. package/dist/frontend/assets/glimmer-ts-BfAWNZQY.js +1 -0
  98. package/dist/frontend/assets/glsl-DplSGwfg.js +1 -0
  99. package/dist/frontend/assets/gn-n2N0HUVH.js +1 -0
  100. package/dist/frontend/assets/gnuplot-DdkO51Og.js +1 -0
  101. package/dist/frontend/assets/go-C27-OAKa.js +1 -0
  102. package/dist/frontend/assets/graphql-ChdNCCLP.js +1 -0
  103. package/dist/frontend/assets/groovy-gcz8RCvz.js +1 -0
  104. package/dist/frontend/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  105. package/dist/frontend/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  106. package/dist/frontend/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  107. package/dist/frontend/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  108. package/dist/frontend/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  109. package/dist/frontend/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  110. package/dist/frontend/assets/hack-i7_Ulhet.js +1 -0
  111. package/dist/frontend/assets/haml-D5jkg6IW.js +1 -0
  112. package/dist/frontend/assets/handlebars-BpdQsYii.js +1 -0
  113. package/dist/frontend/assets/haskell-Df6bDoY_.js +1 -0
  114. package/dist/frontend/assets/haxe-CzTSHFRz.js +1 -0
  115. package/dist/frontend/assets/hcl-BWvSN4gD.js +1 -0
  116. package/dist/frontend/assets/hjson-D5-asLiD.js +1 -0
  117. package/dist/frontend/assets/hlsl-D3lLCCz7.js +1 -0
  118. package/dist/frontend/assets/horizon-BUw7H-hv.js +1 -0
  119. package/dist/frontend/assets/horizon-bright-CUuTKBJd.js +1 -0
  120. package/dist/frontend/assets/houston-DnULxvSX.js +1 -0
  121. package/dist/frontend/assets/html-derivative-DlHx6ybY.js +1 -0
  122. package/dist/frontend/assets/html-pp8916En.js +1 -0
  123. package/dist/frontend/assets/http-jrhK8wxY.js +1 -0
  124. package/dist/frontend/assets/hurl-irOxFIW8.js +1 -0
  125. package/dist/frontend/assets/hxml-Bvhsp5Yf.js +1 -0
  126. package/dist/frontend/assets/hy-DFXneXwc.js +1 -0
  127. package/dist/frontend/assets/imba-DGztddWO.js +1 -0
  128. package/dist/frontend/assets/ini-BEwlwnbL.js +1 -0
  129. package/dist/frontend/assets/java-CylS5w8V.js +1 -0
  130. package/dist/frontend/assets/javascript-wDzz0qaB.js +1 -0
  131. package/dist/frontend/assets/jinja-f2NsQr07.js +1 -0
  132. package/dist/frontend/assets/jison-wvAkD_A8.js +1 -0
  133. package/dist/frontend/assets/json-Cp-IABpG.js +1 -0
  134. package/dist/frontend/assets/json5-C9tS-k6U.js +1 -0
  135. package/dist/frontend/assets/jsonc-Des-eS-w.js +1 -0
  136. package/dist/frontend/assets/jsonl-DcaNXYhu.js +1 -0
  137. package/dist/frontend/assets/jsonnet-DFQXde-d.js +1 -0
  138. package/dist/frontend/assets/jssm-C2t-YnRu.js +1 -0
  139. package/dist/frontend/assets/jsx-g9-lgVsj.js +1 -0
  140. package/dist/frontend/assets/julia-CxzCAyBv.js +1 -0
  141. package/dist/frontend/assets/just-VxiPbLrw.js +1 -0
  142. package/dist/frontend/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  143. package/dist/frontend/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  144. package/dist/frontend/assets/kanagawa-wave-DWedfzmr.js +1 -0
  145. package/dist/frontend/assets/kdl-DV7GczEv.js +1 -0
  146. package/dist/frontend/assets/kotlin-BdnUsdx6.js +1 -0
  147. package/dist/frontend/assets/kusto-wEQ09or8.js +1 -0
  148. package/dist/frontend/assets/laserwave-DUszq2jm.js +1 -0
  149. package/dist/frontend/assets/latex-CWtU0Tv5.js +1 -0
  150. package/dist/frontend/assets/lean-BZvkOJ9d.js +1 -0
  151. package/dist/frontend/assets/less-B1dDrJ26.js +1 -0
  152. package/dist/frontend/assets/light-plus-B7mTdjB0.js +1 -0
  153. package/dist/frontend/assets/liquid-C0sCDyMI.js +1 -0
  154. package/dist/frontend/assets/llvm-DjAJT7YJ.js +1 -0
  155. package/dist/frontend/assets/log-2UxHyX5q.js +1 -0
  156. package/dist/frontend/assets/logo-BtOb2qkB.js +1 -0
  157. package/dist/frontend/assets/lua-BaeVxFsk.js +1 -0
  158. package/dist/frontend/assets/luau-C-HG3fhB.js +1 -0
  159. package/dist/frontend/assets/main-CL5_Wlhv.css +32 -0
  160. package/dist/frontend/assets/main-Czet4Z1x.js +371 -0
  161. package/dist/frontend/assets/make-CHLpvVh8.js +1 -0
  162. package/dist/frontend/assets/markdown-Cvjx9yec.js +1 -0
  163. package/dist/frontend/assets/marko-DjSrsDqO.js +1 -0
  164. package/dist/frontend/assets/material-theme-D5KoaKCx.js +1 -0
  165. package/dist/frontend/assets/material-theme-darker-BfHTSMKl.js +1 -0
  166. package/dist/frontend/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  167. package/dist/frontend/assets/material-theme-ocean-CyktbL80.js +1 -0
  168. package/dist/frontend/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  169. package/dist/frontend/assets/matlab-D7o27uSR.js +1 -0
  170. package/dist/frontend/assets/mdc-DTYItulj.js +1 -0
  171. package/dist/frontend/assets/mdx-Cmh6b_Ma.js +1 -0
  172. package/dist/frontend/assets/mermaid-mWjccvbQ.js +1 -0
  173. package/dist/frontend/assets/min-dark-CafNBF8u.js +1 -0
  174. package/dist/frontend/assets/min-light-CTRr51gU.js +1 -0
  175. package/dist/frontend/assets/mipsasm-CKIfxQSi.js +1 -0
  176. package/dist/frontend/assets/mojo-rZm6bMo-.js +1 -0
  177. package/dist/frontend/assets/monokai-D4h5O-jR.js +1 -0
  178. package/dist/frontend/assets/moonbit-_H4v1dQx.js +1 -0
  179. package/dist/frontend/assets/move-IF9eRakj.js +1 -0
  180. package/dist/frontend/assets/narrat-DRg8JJMk.js +1 -0
  181. package/dist/frontend/assets/nextflow-C-mBbutL.js +1 -0
  182. package/dist/frontend/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  183. package/dist/frontend/assets/nginx-BpAMiNFr.js +1 -0
  184. package/dist/frontend/assets/night-owl-C39BiMTA.js +1 -0
  185. package/dist/frontend/assets/night-owl-light-CMTm3GFP.js +1 -0
  186. package/dist/frontend/assets/nim-BIad80T-.js +1 -0
  187. package/dist/frontend/assets/nix-CwoSXNpI.js +1 -0
  188. package/dist/frontend/assets/nord-Ddv68eIx.js +1 -0
  189. package/dist/frontend/assets/nushell-Cz2AlsmD.js +1 -0
  190. package/dist/frontend/assets/objective-c-DXmwc3jG.js +1 -0
  191. package/dist/frontend/assets/objective-cpp-CLxacb5B.js +1 -0
  192. package/dist/frontend/assets/ocaml-C0hk2d4L.js +1 -0
  193. package/dist/frontend/assets/odin-BBf5iR-q.js +1 -0
  194. package/dist/frontend/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  195. package/dist/frontend/assets/one-light-C3Wv6jpd.js +1 -0
  196. package/dist/frontend/assets/openscad-C4EeE6gA.js +1 -0
  197. package/dist/frontend/assets/pascal-D93ZcfNL.js +1 -0
  198. package/dist/frontend/assets/perl-NvoQZIq0.js +1 -0
  199. package/dist/frontend/assets/php-R6g_5hLQ.js +1 -0
  200. package/dist/frontend/assets/pkl-u5AG7uiY.js +1 -0
  201. package/dist/frontend/assets/plastic-3e1v2bzS.js +1 -0
  202. package/dist/frontend/assets/plsql-ChMvpjG-.js +1 -0
  203. package/dist/frontend/assets/po-BTJTHyun.js +1 -0
  204. package/dist/frontend/assets/poimandres-CS3Unz2-.js +1 -0
  205. package/dist/frontend/assets/polar-C0HS_06l.js +1 -0
  206. package/dist/frontend/assets/postcss-CXtECtnM.js +1 -0
  207. package/dist/frontend/assets/powerquery-CEu0bR-o.js +1 -0
  208. package/dist/frontend/assets/powershell-Dpen1YoG.js +1 -0
  209. package/dist/frontend/assets/prisma-Dd19v3D-.js +1 -0
  210. package/dist/frontend/assets/prolog-CbFg5uaA.js +1 -0
  211. package/dist/frontend/assets/proto-C7zT0LnQ.js +1 -0
  212. package/dist/frontend/assets/pug-DKIMFp6K.js +1 -0
  213. package/dist/frontend/assets/puppet-BMWR74SV.js +1 -0
  214. package/dist/frontend/assets/purescript-CklMAg4u.js +1 -0
  215. package/dist/frontend/assets/python-B6aJPvgy.js +1 -0
  216. package/dist/frontend/assets/qml-3beO22l8.js +1 -0
  217. package/dist/frontend/assets/qmldir-C8lEn-DE.js +1 -0
  218. package/dist/frontend/assets/qss-IeuSbFQv.js +1 -0
  219. package/dist/frontend/assets/r-Dspwwk_N.js +1 -0
  220. package/dist/frontend/assets/racket-BqYA7rlc.js +1 -0
  221. package/dist/frontend/assets/raku-DXvB9xmW.js +1 -0
  222. package/dist/frontend/assets/razor-BDqjjVU7.js +1 -0
  223. package/dist/frontend/assets/red-bN70gL4F.js +1 -0
  224. package/dist/frontend/assets/reg-C-SQnVFl.js +1 -0
  225. package/dist/frontend/assets/regexp-CDVJQ6XC.js +1 -0
  226. package/dist/frontend/assets/rel-C3B-1QV4.js +1 -0
  227. package/dist/frontend/assets/riscv-BM1_JUlF.js +1 -0
  228. package/dist/frontend/assets/ron-D8l8udqQ.js +1 -0
  229. package/dist/frontend/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  230. package/dist/frontend/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  231. package/dist/frontend/assets/rose-pine-qdsjHGoJ.js +1 -0
  232. package/dist/frontend/assets/rosmsg-BJDFO7_C.js +1 -0
  233. package/dist/frontend/assets/rst-CRjBmOyv.js +1 -0
  234. package/dist/frontend/assets/ruby-Wjq7vjNf.js +1 -0
  235. package/dist/frontend/assets/rust-B1yitclQ.js +1 -0
  236. package/dist/frontend/assets/sas-cz2c8ADy.js +1 -0
  237. package/dist/frontend/assets/sass-Cj5Yp3dK.js +1 -0
  238. package/dist/frontend/assets/scala-C151Ov-r.js +1 -0
  239. package/dist/frontend/assets/scheme-C98Dy4si.js +1 -0
  240. package/dist/frontend/assets/scss-D5BDwBP9.js +1 -0
  241. package/dist/frontend/assets/sdbl-DVxCFoDh.js +1 -0
  242. package/dist/frontend/assets/shaderlab-Dg9Lc6iA.js +1 -0
  243. package/dist/frontend/assets/shellscript-Yzrsuije.js +1 -0
  244. package/dist/frontend/assets/shellsession-BADoaaVG.js +1 -0
  245. package/dist/frontend/assets/slack-dark-BthQWCQV.js +1 -0
  246. package/dist/frontend/assets/slack-ochin-DqwNpetd.js +1 -0
  247. package/dist/frontend/assets/smalltalk-BERRCDM3.js +1 -0
  248. package/dist/frontend/assets/snazzy-light-Bw305WKR.js +1 -0
  249. package/dist/frontend/assets/solarized-dark-DXbdFlpD.js +1 -0
  250. package/dist/frontend/assets/solarized-light-L9t79GZl.js +1 -0
  251. package/dist/frontend/assets/solidity-rGO070M0.js +1 -0
  252. package/dist/frontend/assets/soy-8wufbnw4.js +1 -0
  253. package/dist/frontend/assets/sparql-rVzFXLq3.js +1 -0
  254. package/dist/frontend/assets/splunk-BtCnVYZw.js +1 -0
  255. package/dist/frontend/assets/sql-BLtJtn59.js +1 -0
  256. package/dist/frontend/assets/ssh-config-_ykCGR6B.js +1 -0
  257. package/dist/frontend/assets/stata-BH5u7GGu.js +1 -0
  258. package/dist/frontend/assets/stylus-BEDo0Tqx.js +1 -0
  259. package/dist/frontend/assets/surrealql-Bq5Q-fJD.js +1 -0
  260. package/dist/frontend/assets/svelte-Cy7k_4gC.js +1 -0
  261. package/dist/frontend/assets/swift-D82vCrfD.js +1 -0
  262. package/dist/frontend/assets/synthwave-84-CbfX1IO0.js +1 -0
  263. package/dist/frontend/assets/system-verilog-CnnmHF94.js +1 -0
  264. package/dist/frontend/assets/systemd-4A_iFExJ.js +1 -0
  265. package/dist/frontend/assets/talonscript-CkByrt1z.js +1 -0
  266. package/dist/frontend/assets/tasl-QIJgUcNo.js +1 -0
  267. package/dist/frontend/assets/tcl-dwOrl1Do.js +1 -0
  268. package/dist/frontend/assets/templ-DhtptRzy.js +1 -0
  269. package/dist/frontend/assets/terraform-BETggiCN.js +1 -0
  270. package/dist/frontend/assets/tex-idrVyKtj.js +1 -0
  271. package/dist/frontend/assets/tokyo-night-hegEt444.js +1 -0
  272. package/dist/frontend/assets/toml-vGWfd6FD.js +1 -0
  273. package/dist/frontend/assets/ts-tags-DQrlYJgV.js +1 -0
  274. package/dist/frontend/assets/tsv-B_m7g4N7.js +1 -0
  275. package/dist/frontend/assets/tsx-COt5Ahok.js +1 -0
  276. package/dist/frontend/assets/turtle-BsS91CYL.js +1 -0
  277. package/dist/frontend/assets/twig-xg9kU7Mw.js +1 -0
  278. package/dist/frontend/assets/typescript-BPQ3VLAy.js +1 -0
  279. package/dist/frontend/assets/typespec-CAFt9gP4.js +1 -0
  280. package/dist/frontend/assets/typst-DHCkPAjA.js +1 -0
  281. package/dist/frontend/assets/v-BcVCzyr7.js +1 -0
  282. package/dist/frontend/assets/vala-CsfeWuGM.js +1 -0
  283. package/dist/frontend/assets/vb-D17OF-Vu.js +1 -0
  284. package/dist/frontend/assets/verilog-BQ8w6xss.js +1 -0
  285. package/dist/frontend/assets/vesper-DU1UobuO.js +1 -0
  286. package/dist/frontend/assets/vhdl-CeAyd5Ju.js +1 -0
  287. package/dist/frontend/assets/viml-CJc9bBzg.js +1 -0
  288. package/dist/frontend/assets/vitesse-black-Bkuqu6BP.js +1 -0
  289. package/dist/frontend/assets/vitesse-dark-D0r3Knsf.js +1 -0
  290. package/dist/frontend/assets/vitesse-light-CVO1_9PV.js +1 -0
  291. package/dist/frontend/assets/vue-D2xRrEX4.js +1 -0
  292. package/dist/frontend/assets/vue-html-AaS7Mt5G.js +1 -0
  293. package/dist/frontend/assets/vue-vine-BoDAl6tE.js +1 -0
  294. package/dist/frontend/assets/vyper-CDx5xZoG.js +1 -0
  295. package/dist/frontend/assets/wasm-CG6Dc4jp.js +1 -0
  296. package/dist/frontend/assets/wasm-MzD3tlZU.js +1 -0
  297. package/dist/frontend/assets/wenyan-BV7otONQ.js +1 -0
  298. package/dist/frontend/assets/wgsl-Dx-B1_4e.js +1 -0
  299. package/dist/frontend/assets/wikitext-BhOHFoWU.js +1 -0
  300. package/dist/frontend/assets/wit-5i3qLPDT.js +1 -0
  301. package/dist/frontend/assets/wolfram-lXgVvXCa.js +1 -0
  302. package/dist/frontend/assets/xml-sdJ4AIDG.js +1 -0
  303. package/dist/frontend/assets/xsl-CtQFsRM5.js +1 -0
  304. package/dist/frontend/assets/yaml-Buea-lGh.js +1 -0
  305. package/dist/frontend/assets/zenscript-DVFEvuxE.js +1 -0
  306. package/dist/frontend/assets/zig-VOosw3JB.js +1 -0
  307. package/dist/frontend/icon-192.png +0 -0
  308. package/dist/frontend/icon-512.png +0 -0
  309. package/dist/frontend/icon.svg +8 -0
  310. package/dist/frontend/index.html +30 -0
  311. package/dist/frontend/manifest.json +25 -0
  312. package/dist/frontend/sw.js +66 -0
  313. package/dist/server/agent-events.js +39 -0
  314. package/dist/server/analytics.js +885 -0
  315. package/dist/server/auth.js +65 -0
  316. package/dist/server/belayer/executor.js +200 -0
  317. package/dist/server/belayer/intake.js +27 -0
  318. package/dist/server/belayer/pipeline.js +97 -0
  319. package/dist/server/belayer/pr-lifecycle.js +69 -0
  320. package/dist/server/belayer/prompts.js +154 -0
  321. package/dist/server/belayer/types.js +23 -0
  322. package/dist/server/branch-linker.js +137 -0
  323. package/dist/server/browser-content.js +145 -0
  324. package/dist/server/clipboard.js +63 -0
  325. package/dist/server/codex-hooks-adapter.js +93 -0
  326. package/dist/server/config.js +325 -0
  327. package/dist/server/gh-routes.js +163 -0
  328. package/dist/server/gh.js +276 -0
  329. package/dist/server/git-routes.js +154 -0
  330. package/dist/server/git.js +694 -0
  331. package/dist/server/github-app.js +218 -0
  332. package/dist/server/github-graphql.js +178 -0
  333. package/dist/server/hooks.js +373 -0
  334. package/dist/server/index.js +1549 -0
  335. package/dist/server/integration-github.js +137 -0
  336. package/dist/server/integration-jira.js +210 -0
  337. package/dist/server/integration-linear.js +176 -0
  338. package/dist/server/logger.js +18 -0
  339. package/dist/server/mobile-input-pipeline.js +129 -0
  340. package/dist/server/opencode-relay.js +53 -0
  341. package/dist/server/org-dashboard.js +241 -0
  342. package/dist/server/output-parsers/claude-parser.js +56 -0
  343. package/dist/server/output-parsers/codex-parser.js +13 -0
  344. package/dist/server/output-parsers/index.js +14 -0
  345. package/dist/server/output-parsers/null-parser.js +12 -0
  346. package/dist/server/output-parsers/opencode-parser.js +77 -0
  347. package/dist/server/pty-handler.js +586 -0
  348. package/dist/server/push.js +84 -0
  349. package/dist/server/review-poller.js +237 -0
  350. package/dist/server/sdk-handler.js +539 -0
  351. package/dist/server/service.js +189 -0
  352. package/dist/server/sessions.js +638 -0
  353. package/dist/server/telemetry.js +236 -0
  354. package/dist/server/ticket-transitions.js +166 -0
  355. package/dist/server/types.js +146 -0
  356. package/dist/server/utils.js +23 -0
  357. package/dist/server/watcher.js +661 -0
  358. package/dist/server/webhook-manager.js +547 -0
  359. package/dist/server/webhooks.js +73 -0
  360. package/dist/server/workspace-groups.js +363 -0
  361. package/dist/server/workspaces.js +1207 -0
  362. package/dist/server/ws.js +192 -0
  363. package/dist/test/EmptyState.spec.js +51 -0
  364. package/dist/test/action-coverage.test.js +139 -0
  365. package/dist/test/actions/registry.test.js +59 -0
  366. package/dist/test/actions/shortcuts.test.js +79 -0
  367. package/dist/test/agent-events.test.js +151 -0
  368. package/dist/test/analytics.test.js +158 -0
  369. package/dist/test/attention.test.js +91 -0
  370. package/dist/test/auth.test.js +105 -0
  371. package/dist/test/backend-state.test.js +47 -0
  372. package/dist/test/belayer-executor.test.js +33 -0
  373. package/dist/test/belayer-intake.test.js +44 -0
  374. package/dist/test/belayer-pipeline.test.js +113 -0
  375. package/dist/test/belayer-pr-lifecycle.test.js +26 -0
  376. package/dist/test/belayer-prompts.test.js +60 -0
  377. package/dist/test/belayer-types.test.js +69 -0
  378. package/dist/test/bin/claude-remote-cli.js +214 -0
  379. package/dist/test/boot-state.test.js +133 -0
  380. package/dist/test/branch-lifecycle.test.js +75 -0
  381. package/dist/test/branch-linker.test.js +236 -0
  382. package/dist/test/branch-rename.test.js +45 -0
  383. package/dist/test/branch-watcher.test.js +115 -0
  384. package/dist/test/browser-cli.test.js +91 -0
  385. package/dist/test/browser-content.test.js +93 -0
  386. package/dist/test/browser-tabs-ui.test.js +39 -0
  387. package/dist/test/changed-files-api.test.js +140 -0
  388. package/dist/test/clipboard.test.js +12 -0
  389. package/dist/test/codex-hooks-adapter.test.js +237 -0
  390. package/dist/test/components/EmptyState.spec.js +51 -0
  391. package/dist/test/components/ErrorToast.spec.js +65 -0
  392. package/dist/test/components/TuiCheckbox.spec.js +120 -0
  393. package/dist/test/components/TuiInput.spec.js +186 -0
  394. package/dist/test/components/leaf-component-migration.spec.js +104 -0
  395. package/dist/test/config-freshness.test.js +63 -0
  396. package/dist/test/config.test.js +813 -0
  397. package/dist/test/diff-summary.test.js +98 -0
  398. package/dist/test/display-state.test.js +179 -0
  399. package/dist/test/event-message-types.test.js +32 -0
  400. package/dist/test/file-tree-utils.test.js +167 -0
  401. package/dist/test/framework-types.test.js +183 -0
  402. package/dist/test/frameworks-api.test.js +93 -0
  403. package/dist/test/frontend/src/lib/pr-state.js +114 -0
  404. package/dist/test/fs-browse.test.js +246 -0
  405. package/dist/test/fuzzy-scorer.test.js +145 -0
  406. package/dist/test/gh-routes.test.js +156 -0
  407. package/dist/test/git-changed-files.test.js +152 -0
  408. package/dist/test/git-routes.test.js +146 -0
  409. package/dist/test/git-utils.test.js +68 -0
  410. package/dist/test/git-watcher.test.js +110 -0
  411. package/dist/test/git.test.js +140 -0
  412. package/dist/test/github-app.test.js +455 -0
  413. package/dist/test/github-graphql.test.js +301 -0
  414. package/dist/test/greetings.test.js +83 -0
  415. package/dist/test/hooks-agent-event.test.js +412 -0
  416. package/dist/test/hooks.test.js +149 -0
  417. package/dist/test/integration-github.test.js +220 -0
  418. package/dist/test/integration-jira.test.js +238 -0
  419. package/dist/test/integration-linear.test.js +293 -0
  420. package/dist/test/mobile-input.test.js +235 -0
  421. package/dist/test/opencode-relay.test.js +107 -0
  422. package/dist/test/org-dashboard.test.js +349 -0
  423. package/dist/test/output-parser.test.js +217 -0
  424. package/dist/test/paths.test.js +32 -0
  425. package/dist/test/pr-state.test.js +407 -0
  426. package/dist/test/pr-status.test.js +82 -0
  427. package/dist/test/presets.test.js +242 -0
  428. package/dist/test/pty-handler-multi-agent.test.js +149 -0
  429. package/dist/test/pty-handler.test.js +146 -0
  430. package/dist/test/pull-requests.test.js +78 -0
  431. package/dist/test/review-poller.test.js +349 -0
  432. package/dist/test/server/analytics.js +121 -0
  433. package/dist/test/server/auth.js +63 -0
  434. package/dist/test/server/branch-linker.js +124 -0
  435. package/dist/test/server/clipboard.js +56 -0
  436. package/dist/test/server/config.js +137 -0
  437. package/dist/test/server/git.js +308 -0
  438. package/dist/test/server/hooks.js +196 -0
  439. package/dist/test/server/index.js +1124 -0
  440. package/dist/test/server/integration-github.js +117 -0
  441. package/dist/test/server/integration-jira.js +164 -0
  442. package/dist/test/server/integration-linear.js +176 -0
  443. package/dist/test/server/mobile-input-pipeline.js +123 -0
  444. package/dist/test/server/org-dashboard.js +184 -0
  445. package/dist/test/server/output-parsers/claude-parser.js +54 -0
  446. package/dist/test/server/output-parsers/codex-parser.js +13 -0
  447. package/dist/test/server/output-parsers/index.js +7 -0
  448. package/dist/test/server/pty-handler.js +310 -0
  449. package/dist/test/server/push.js +80 -0
  450. package/dist/test/server/review-poller.js +218 -0
  451. package/dist/test/server/service.js +169 -0
  452. package/dist/test/server/sessions.js +434 -0
  453. package/dist/test/server/ticket-transitions.js +216 -0
  454. package/dist/test/server/types.js +20 -0
  455. package/dist/test/server/utils.js +22 -0
  456. package/dist/test/server/watcher.js +139 -0
  457. package/dist/test/server/workspaces.js +657 -0
  458. package/dist/test/server/ws.js +152 -0
  459. package/dist/test/server-startup.test.js +62 -0
  460. package/dist/test/service.test.js +43 -0
  461. package/dist/test/session-analytics-api.test.js +123 -0
  462. package/dist/test/session-analytics.test.js +425 -0
  463. package/dist/test/session-intent.test.js +249 -0
  464. package/dist/test/sessions.test.js +1152 -0
  465. package/dist/test/sidebar-items.test.js +164 -0
  466. package/dist/test/stores/boot-state-store.test.js +165 -0
  467. package/dist/test/stores/sessions-logic.test.js +191 -0
  468. package/dist/test/stores/toasts-store.test.js +66 -0
  469. package/dist/test/stores/ui-store.test.js +203 -0
  470. package/dist/test/stores/unread-store.test.js +97 -0
  471. package/dist/test/telemetry-api.test.js +54 -0
  472. package/dist/test/telemetry-sync.test.js +68 -0
  473. package/dist/test/telemetry.test.js +295 -0
  474. package/dist/test/terminal-zoom.test.js +102 -0
  475. package/dist/test/test/analytics.test.js +152 -0
  476. package/dist/test/test/auth.test.js +95 -0
  477. package/dist/test/test/branch-linker.test.js +231 -0
  478. package/dist/test/test/branch-rename.test.js +45 -0
  479. package/dist/test/test/clipboard.test.js +12 -0
  480. package/dist/test/test/config.test.js +281 -0
  481. package/dist/test/test/fs-browse.test.js +202 -0
  482. package/dist/test/test/git.test.js +67 -0
  483. package/dist/test/test/hooks.test.js +139 -0
  484. package/dist/test/test/integration-github.test.js +203 -0
  485. package/dist/test/test/integration-jira.test.js +294 -0
  486. package/dist/test/test/integration-linear.test.js +293 -0
  487. package/dist/test/test/mobile-input.test.js +193 -0
  488. package/dist/test/test/org-dashboard.test.js +240 -0
  489. package/dist/test/test/output-parser.test.js +95 -0
  490. package/dist/test/test/paths.test.js +32 -0
  491. package/dist/test/test/pr-state.test.js +220 -0
  492. package/dist/test/test/pull-requests.test.js +67 -0
  493. package/dist/test/test/review-poller.test.js +235 -0
  494. package/dist/test/test/service.test.js +43 -0
  495. package/dist/test/test/sessions.test.js +750 -0
  496. package/dist/test/test/ticket-transitions.test.js +130 -0
  497. package/dist/test/test/version.test.js +34 -0
  498. package/dist/test/test/worktrees.test.js +256 -0
  499. package/dist/test/ticket-transitions.test.js +312 -0
  500. package/dist/test/unread.test.js +23 -0
  501. package/dist/test/version.test.js +34 -0
  502. package/dist/test/webhook-manager.test.js +484 -0
  503. package/dist/test/webhooks.test.js +208 -0
  504. package/dist/test/workspace-groups.test.js +377 -0
  505. package/dist/test/worktrees.test.js +531 -0
  506. package/package.json +88 -0
@@ -0,0 +1,140 @@
1
+ import { describe, test, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import express from 'express';
7
+ import { createWorkspaceRouter } from '../server/workspaces.js';
8
+ import { saveConfig, DEFAULTS } from '../server/config.js';
9
+ let tmpDir;
10
+ let configPath;
11
+ let repoDir;
12
+ let server;
13
+ let baseUrl;
14
+ before(async () => {
15
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'changed-files-test-'));
16
+ configPath = path.join(tmpDir, 'config.json');
17
+ repoDir = path.join(tmpDir, 'repo');
18
+ fs.mkdirSync(path.join(repoDir, '.git'), { recursive: true });
19
+ saveConfig(configPath, { ...DEFAULTS, repos: [repoDir] });
20
+ const app = express();
21
+ app.use(express.json());
22
+ const mockExec = async (file, args, _opts) => {
23
+ if (args[0] === 'status' && args.includes('--porcelain=v1')) {
24
+ return { stdout: ' M server/git.ts\0?? new-file.ts\0', stderr: '' };
25
+ }
26
+ if (args[0] === 'diff' && args.includes('--numstat')) {
27
+ return { stdout: '10\t2\tserver/git.ts\n', stderr: '' };
28
+ }
29
+ if (args[0] === 'diff' && args.includes('--unified=3')) {
30
+ return { stdout: 'diff output for file', stderr: '' };
31
+ }
32
+ if (file === 'wc') {
33
+ return { stdout: ' 20 new-file.ts', stderr: '' };
34
+ }
35
+ if (args[0] === 'rev-parse' && args[1] === '--git-dir') {
36
+ return { stdout: '.git\n', stderr: '' };
37
+ }
38
+ if (args[0] === 'symbolic-ref') {
39
+ return { stdout: 'origin/main\n', stderr: '' };
40
+ }
41
+ return { stdout: '', stderr: '' };
42
+ };
43
+ app.use('/workspaces', createWorkspaceRouter({ configPath, execAsync: mockExec }));
44
+ await new Promise((resolve) => {
45
+ server = app.listen(0, '127.0.0.1', resolve);
46
+ });
47
+ const addr = server.address();
48
+ baseUrl = `http://127.0.0.1:${addr.port}`;
49
+ });
50
+ after(async () => {
51
+ server?.close();
52
+ fs.rmSync(tmpDir, { recursive: true, force: true });
53
+ });
54
+ describe('GET /workspaces/changed-files', () => {
55
+ test('returns changed files for a workspace', async () => {
56
+ const res = await fetch(`${baseUrl}/workspaces/changed-files?path=${encodeURIComponent(repoDir)}`);
57
+ assert.equal(res.status, 200);
58
+ const data = (await res.json());
59
+ assert.ok(Array.isArray(data.files));
60
+ assert.ok(data.aggregate);
61
+ assert.equal(data.aggregate.fileCount, 2);
62
+ });
63
+ test('returns 400 without path parameter', async () => {
64
+ const res = await fetch(`${baseUrl}/workspaces/changed-files`);
65
+ assert.equal(res.status, 400);
66
+ });
67
+ });
68
+ describe('GET /workspaces/file-diff', () => {
69
+ test('returns diff for a specific file', async () => {
70
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=server/git.ts`);
71
+ assert.equal(res.status, 200);
72
+ const data = (await res.json());
73
+ assert.ok(typeof data.diff === 'string');
74
+ });
75
+ test('returns 400 without file parameter', async () => {
76
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}`);
77
+ assert.equal(res.status, 400);
78
+ });
79
+ test('rejects absolute paths that did not start with ~', async () => {
80
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=/etc/passwd`);
81
+ assert.equal(res.status, 400);
82
+ const data = (await res.json());
83
+ assert.equal(data.error, 'invalid file path');
84
+ });
85
+ test('rejects .. traversal in relative paths', async () => {
86
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=../../etc/passwd`);
87
+ assert.equal(res.status, 400);
88
+ const data = (await res.json());
89
+ assert.equal(data.error, 'invalid file path');
90
+ });
91
+ test('reads ~/file paths directly via fs', async () => {
92
+ const testFile = path.join(os.homedir(), '.claude-remote-cli-test-tilde');
93
+ fs.writeFileSync(testFile, 'tilde-test-content', 'utf-8');
94
+ try {
95
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=~/.claude-remote-cli-test-tilde`);
96
+ assert.equal(res.status, 200);
97
+ const data = (await res.json());
98
+ assert.equal(data.diff, 'tilde-test-content');
99
+ }
100
+ finally {
101
+ fs.unlinkSync(testFile);
102
+ }
103
+ });
104
+ test('rejects ~/../../ etc traversal attempts', async () => {
105
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=~/../../etc/passwd`);
106
+ assert.equal(res.status, 400);
107
+ const data = (await res.json());
108
+ assert.equal(data.error, 'invalid file path');
109
+ });
110
+ test('returns 404 for non-existent ~/file', async () => {
111
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=~/.claude-remote-cli-nonexistent-file`);
112
+ assert.equal(res.status, 404);
113
+ });
114
+ test('returns 400 for directory ~/path', async () => {
115
+ const testDir = path.join(os.homedir(), '.claude-remote-cli-test-dir');
116
+ fs.mkdirSync(testDir, { recursive: true });
117
+ try {
118
+ const res = await fetch(`${baseUrl}/workspaces/file-diff?path=${encodeURIComponent(repoDir)}&file=~/.claude-remote-cli-test-dir`);
119
+ assert.equal(res.status, 400);
120
+ const data = (await res.json());
121
+ assert.equal(data.error, 'not a regular file');
122
+ }
123
+ finally {
124
+ fs.rmdirSync(testDir);
125
+ }
126
+ });
127
+ });
128
+ describe('GET /workspaces/default-branch', () => {
129
+ test('returns default branch for a workspace', async () => {
130
+ const res = await fetch(`${baseUrl}/workspaces/default-branch?path=${encodeURIComponent(repoDir)}`);
131
+ assert.equal(res.status, 200);
132
+ const data = (await res.json());
133
+ assert.equal(typeof data.branch, 'string');
134
+ assert.ok(data.branch.length > 0);
135
+ });
136
+ test('returns 400 without path parameter', async () => {
137
+ const res = await fetch(`${baseUrl}/workspaces/default-branch`);
138
+ assert.equal(res.status, 400);
139
+ });
140
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { detectClipboardTool, setClipboardImage } from '../server/clipboard.js';
4
+ describe('clipboard', () => {
5
+ it('detectClipboardTool returns a string or null', () => {
6
+ const result = detectClipboardTool();
7
+ assert.ok(result === null || typeof result === 'string');
8
+ });
9
+ it('setClipboardImage rejects unsupported mime types', async () => {
10
+ await assert.rejects(() => setClipboardImage('/tmp/test.txt', 'text/plain'), /Unsupported/);
11
+ });
12
+ });
@@ -0,0 +1,237 @@
1
+ import { describe, it, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { writeCodexHooksAdapter, CODEX_EVENTS, EVENT_MAP, } from '../server/codex-hooks-adapter.js';
7
+ let tmpConfigDir;
8
+ before(() => {
9
+ tmpConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'crc-codex-hooks-test-'));
10
+ });
11
+ after(() => {
12
+ fs.rmSync(tmpConfigDir, { recursive: true, force: true });
13
+ });
14
+ describe('CODEX_EVENTS', () => {
15
+ it('contains all 5 expected codex hook event names', () => {
16
+ const expected = [
17
+ 'SessionStart',
18
+ 'Stop',
19
+ 'UserPromptSubmit',
20
+ 'PreToolUse',
21
+ 'PostToolUse',
22
+ ];
23
+ assert.strictEqual(CODEX_EVENTS.length, 5);
24
+ for (const ev of expected) {
25
+ assert.ok(CODEX_EVENTS.includes(ev), `CODEX_EVENTS should include ${ev}`);
26
+ }
27
+ });
28
+ });
29
+ describe('EVENT_MAP', () => {
30
+ it('maps SessionStart to session.started', () => {
31
+ assert.strictEqual(EVENT_MAP['SessionStart'], 'session.started');
32
+ });
33
+ it('maps Stop to session.ended', () => {
34
+ assert.strictEqual(EVENT_MAP['Stop'], 'session.ended');
35
+ });
36
+ it('maps UserPromptSubmit to prompt.submitted', () => {
37
+ assert.strictEqual(EVENT_MAP['UserPromptSubmit'], 'prompt.submitted');
38
+ });
39
+ it('maps PreToolUse to tool.started', () => {
40
+ assert.strictEqual(EVENT_MAP['PreToolUse'], 'tool.started');
41
+ });
42
+ it('maps PostToolUse to tool.finished', () => {
43
+ assert.strictEqual(EVENT_MAP['PostToolUse'], 'tool.finished');
44
+ });
45
+ it('has exactly 5 mappings', () => {
46
+ assert.strictEqual(Object.keys(EVENT_MAP).length, 5);
47
+ });
48
+ });
49
+ describe('writeCodexHooksAdapter', () => {
50
+ it('returns a string path', () => {
51
+ const result = writeCodexHooksAdapter('sess-001', 3456, 'tok-abc', tmpConfigDir);
52
+ assert.ok(typeof result === 'string', 'should return a string path');
53
+ });
54
+ it('creates the temp directory', () => {
55
+ const dir = writeCodexHooksAdapter('sess-002', 3456, 'tok-abc', tmpConfigDir);
56
+ assert.ok(fs.existsSync(dir), 'temp directory should exist');
57
+ });
58
+ it('creates relay.sh in the temp directory', () => {
59
+ const dir = writeCodexHooksAdapter('sess-003', 3456, 'tok-abc', tmpConfigDir);
60
+ const relayPath = path.join(dir, 'relay.sh');
61
+ assert.ok(fs.existsSync(relayPath), 'relay.sh should exist');
62
+ });
63
+ it('creates relay.sh that is executable', () => {
64
+ const dir = writeCodexHooksAdapter('sess-004', 3456, 'tok-abc', tmpConfigDir);
65
+ const relayPath = path.join(dir, 'relay.sh');
66
+ const stat = fs.statSync(relayPath);
67
+ // Check owner execute bit (0o100)
68
+ assert.ok((stat.mode & 0o100) !== 0, 'relay.sh should be executable by owner');
69
+ });
70
+ it('creates hooks.json in the temp directory', () => {
71
+ const dir = writeCodexHooksAdapter('sess-005', 3456, 'tok-abc', tmpConfigDir);
72
+ const hooksPath = path.join(dir, 'hooks.json');
73
+ assert.ok(fs.existsSync(hooksPath), 'hooks.json should exist');
74
+ });
75
+ it('hooks.json contains all 5 codex events', () => {
76
+ const dir = writeCodexHooksAdapter('sess-006', 3456, 'tok-abc', tmpConfigDir);
77
+ const hooksPath = path.join(dir, 'hooks.json');
78
+ const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
79
+ const expected = [
80
+ 'SessionStart',
81
+ 'Stop',
82
+ 'UserPromptSubmit',
83
+ 'PreToolUse',
84
+ 'PostToolUse',
85
+ ];
86
+ for (const ev of expected) {
87
+ assert.ok(ev in hooks, `hooks.json should have key ${ev}`);
88
+ }
89
+ });
90
+ it('hooks.json entries are arrays of { type: "command", command: <path-to-relay.sh> }', () => {
91
+ const dir = writeCodexHooksAdapter('sess-007', 3456, 'tok-abc', tmpConfigDir);
92
+ const hooksPath = path.join(dir, 'hooks.json');
93
+ const relayPath = path.join(dir, 'relay.sh');
94
+ const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
95
+ const expected = [
96
+ 'SessionStart',
97
+ 'Stop',
98
+ 'UserPromptSubmit',
99
+ 'PreToolUse',
100
+ 'PostToolUse',
101
+ ];
102
+ for (const ev of expected) {
103
+ const entries = hooks[ev];
104
+ assert.ok(Array.isArray(entries), `${ev} should be an array`);
105
+ assert.ok(entries.length >= 1, `${ev} should have at least one entry`);
106
+ const last = entries[entries.length - 1];
107
+ assert.strictEqual(last.type, 'command', `${ev} entry should have type: "command"`);
108
+ assert.strictEqual(last.command, relayPath, `${ev} entry command should point to relay.sh`);
109
+ }
110
+ });
111
+ it('session.json contains the correct port, sessionId, and hookToken', () => {
112
+ const dir = writeCodexHooksAdapter('sess-008', 9999, 'super-secret-token', tmpConfigDir);
113
+ const configPath = path.join(dir, 'session.json');
114
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
115
+ assert.strictEqual(config.port, 9999, 'session.json should contain the correct port');
116
+ assert.strictEqual(config.sessionId, 'sess-008', 'session.json should contain the correct sessionId');
117
+ assert.strictEqual(config.hookToken, 'super-secret-token', 'session.json should contain the correct hookToken');
118
+ });
119
+ it('relay.sh reads config from session.json', () => {
120
+ const dir = writeCodexHooksAdapter('my-unique-session-id', 3456, 'tok-abc', tmpConfigDir);
121
+ const relayPath = path.join(dir, 'relay.sh');
122
+ const content = fs.readFileSync(relayPath, 'utf-8');
123
+ assert.ok(content.includes('session.json'), 'relay.sh should reference session.json config file');
124
+ assert.ok(content.includes('CONFIG_FILE='), 'relay.sh should set CONFIG_FILE variable');
125
+ });
126
+ it('relay.sh posts to /hooks/agent-event', () => {
127
+ const dir = writeCodexHooksAdapter('sess-011', 3456, 'tok-abc', tmpConfigDir);
128
+ const relayPath = path.join(dir, 'relay.sh');
129
+ const content = fs.readFileSync(relayPath, 'utf-8');
130
+ assert.ok(content.includes('/hooks/agent-event'), 'relay.sh should POST to /hooks/agent-event');
131
+ });
132
+ it('relay.sh uses best-effort delivery (|| true)', () => {
133
+ const dir = writeCodexHooksAdapter('sess-012', 3456, 'tok-abc', tmpConfigDir);
134
+ const relayPath = path.join(dir, 'relay.sh');
135
+ const content = fs.readFileSync(relayPath, 'utf-8');
136
+ assert.ok(content.includes('|| true'), 'relay.sh should use || true for best-effort delivery');
137
+ });
138
+ it('relay.sh maps SessionStart to session.started in the case statement', () => {
139
+ const dir = writeCodexHooksAdapter('sess-013', 3456, 'tok-abc', tmpConfigDir);
140
+ const relayPath = path.join(dir, 'relay.sh');
141
+ const content = fs.readFileSync(relayPath, 'utf-8');
142
+ assert.ok(content.includes('SessionStart'), 'relay.sh should contain SessionStart case');
143
+ assert.ok(content.includes('session.started'), 'relay.sh should map to session.started');
144
+ });
145
+ it('relay.sh maps Stop to session.ended in the case statement', () => {
146
+ const dir = writeCodexHooksAdapter('sess-014', 3456, 'tok-abc', tmpConfigDir);
147
+ const relayPath = path.join(dir, 'relay.sh');
148
+ const content = fs.readFileSync(relayPath, 'utf-8');
149
+ assert.ok(content.includes('Stop'), 'relay.sh should contain Stop case');
150
+ assert.ok(content.includes('session.ended'), 'relay.sh should map to session.ended');
151
+ });
152
+ it('relay.sh maps UserPromptSubmit to prompt.submitted in the case statement', () => {
153
+ const dir = writeCodexHooksAdapter('sess-015', 3456, 'tok-abc', tmpConfigDir);
154
+ const relayPath = path.join(dir, 'relay.sh');
155
+ const content = fs.readFileSync(relayPath, 'utf-8');
156
+ assert.ok(content.includes('UserPromptSubmit'), 'relay.sh should contain UserPromptSubmit case');
157
+ assert.ok(content.includes('prompt.submitted'), 'relay.sh should map to prompt.submitted');
158
+ });
159
+ it('relay.sh maps PreToolUse to tool.started in the case statement', () => {
160
+ const dir = writeCodexHooksAdapter('sess-016', 3456, 'tok-abc', tmpConfigDir);
161
+ const relayPath = path.join(dir, 'relay.sh');
162
+ const content = fs.readFileSync(relayPath, 'utf-8');
163
+ assert.ok(content.includes('PreToolUse'), 'relay.sh should contain PreToolUse case');
164
+ assert.ok(content.includes('tool.started'), 'relay.sh should map to tool.started');
165
+ });
166
+ it('relay.sh maps PostToolUse to tool.finished in the case statement', () => {
167
+ const dir = writeCodexHooksAdapter('sess-017', 3456, 'tok-abc', tmpConfigDir);
168
+ const relayPath = path.join(dir, 'relay.sh');
169
+ const content = fs.readFileSync(relayPath, 'utf-8');
170
+ assert.ok(content.includes('PostToolUse'), 'relay.sh should contain PostToolUse case');
171
+ assert.ok(content.includes('tool.finished'), 'relay.sh should map to tool.finished');
172
+ });
173
+ it('relay.sh has a shebang line', () => {
174
+ const dir = writeCodexHooksAdapter('sess-018', 3456, 'tok-abc', tmpConfigDir);
175
+ const relayPath = path.join(dir, 'relay.sh');
176
+ const content = fs.readFileSync(relayPath, 'utf-8');
177
+ assert.ok(content.startsWith('#!/usr/bin/env bash'), 'relay.sh should start with #!/usr/bin/env bash');
178
+ });
179
+ it('temp directory path includes the sessionId', () => {
180
+ const sessionId = 'uniqueid-xyz-789';
181
+ const dir = writeCodexHooksAdapter(sessionId, 3456, 'tok-abc', tmpConfigDir);
182
+ assert.ok(dir.includes(sessionId), 'temp dir path should include the sessionId');
183
+ });
184
+ it('is idempotent — calling twice for same sessionId returns consistent results', () => {
185
+ const sessionId = 'idempotent-sess';
186
+ const dir1 = writeCodexHooksAdapter(sessionId, 3456, 'tok-abc', tmpConfigDir);
187
+ const dir2 = writeCodexHooksAdapter(sessionId, 3456, 'tok-abc', tmpConfigDir);
188
+ assert.strictEqual(dir1, dir2, 'should return same path for same sessionId');
189
+ assert.ok(fs.existsSync(path.join(dir2, 'relay.sh')), 'relay.sh should still exist after second call');
190
+ assert.ok(fs.existsSync(path.join(dir2, 'hooks.json')), 'hooks.json should still exist after second call');
191
+ });
192
+ it('merges with existing user hooks when ~/.codex/hooks.json has entries', () => {
193
+ // Create a fake user hooks directory
194
+ const fakeHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'crc-fake-home-'));
195
+ try {
196
+ const codexDir = path.join(fakeHomeDir, '.codex');
197
+ fs.mkdirSync(codexDir, { recursive: true });
198
+ const existingHooks = {
199
+ SessionStart: [
200
+ { type: 'command', command: '/usr/local/bin/my-existing-hook.sh' },
201
+ ],
202
+ };
203
+ fs.writeFileSync(path.join(codexDir, 'hooks.json'), JSON.stringify(existingHooks));
204
+ // We can't easily mock os.homedir(), so we test the merge logic indirectly:
205
+ // If no ~/.codex/hooks.json exists (default), each event has exactly 1 entry (our relay).
206
+ // This test verifies the base case: no existing hooks → exactly 1 relay per event.
207
+ const dir = writeCodexHooksAdapter('sess-merge', 3456, 'tok-abc', tmpConfigDir);
208
+ const hooks = JSON.parse(fs.readFileSync(path.join(dir, 'hooks.json'), 'utf-8'));
209
+ const expected = [
210
+ 'SessionStart',
211
+ 'Stop',
212
+ 'UserPromptSubmit',
213
+ 'PreToolUse',
214
+ 'PostToolUse',
215
+ ];
216
+ for (const ev of expected) {
217
+ assert.ok(Array.isArray(hooks[ev]), `${ev} should be an array`);
218
+ assert.ok(hooks[ev].length >= 1, `${ev} should have at least one hook entry`);
219
+ }
220
+ }
221
+ finally {
222
+ fs.rmSync(fakeHomeDir, { recursive: true, force: true });
223
+ }
224
+ });
225
+ it('reads stdin via INPUT=$(cat) in relay.sh', () => {
226
+ const dir = writeCodexHooksAdapter('sess-019', 3456, 'tok-abc', tmpConfigDir);
227
+ const relayPath = path.join(dir, 'relay.sh');
228
+ const content = fs.readFileSync(relayPath, 'utf-8');
229
+ assert.ok(content.includes('INPUT=$(cat)'), 'relay.sh should read stdin via INPUT=$(cat)');
230
+ });
231
+ it('relay.sh reads HOOK_EVENT_NAME env var', () => {
232
+ const dir = writeCodexHooksAdapter('sess-020', 3456, 'tok-abc', tmpConfigDir);
233
+ const relayPath = path.join(dir, 'relay.sh');
234
+ const content = fs.readFileSync(relayPath, 'utf-8');
235
+ assert.ok(content.includes('HOOK_EVENT_NAME'), 'relay.sh should use HOOK_EVENT_NAME env var');
236
+ });
237
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync, existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname } from 'node:path';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ describe('EmptyState', () => {
10
+ const projectRoot = join(__dirname, '../../..');
11
+ const componentPath = join(projectRoot, 'frontend/src/components/EmptyState.tsx');
12
+ const cssPath = join(projectRoot, 'frontend/src/components/EmptyState.css');
13
+ it('EmptyState.tsx file exists', () => {
14
+ assert.ok(existsSync(componentPath), 'EmptyState.tsx should exist');
15
+ });
16
+ it('EmptyState.css file exists', () => {
17
+ assert.ok(existsSync(cssPath), 'EmptyState.css should exist');
18
+ });
19
+ it('exports EmptyState component', () => {
20
+ const content = readFileSync(componentPath, 'utf-8');
21
+ assert.ok(content.includes('export function EmptyState'), 'Should export EmptyState function');
22
+ assert.ok(content.includes('export default EmptyState'), 'Should have default export');
23
+ });
24
+ it('exports EmptyStateProps interface', () => {
25
+ const content = readFileSync(componentPath, 'utf-8');
26
+ assert.ok(content.includes('interface EmptyStateProps'), 'Should define EmptyStateProps interface');
27
+ });
28
+ it('has required props', () => {
29
+ const content = readFileSync(componentPath, 'utf-8');
30
+ assert.ok(content.includes('heading: string'), 'Should have heading prop');
31
+ assert.ok(content.includes('icon?:'), 'Should have optional icon prop');
32
+ assert.ok(content.includes('description?:'), 'Should have optional description prop');
33
+ assert.ok(content.includes('actionLabel?:'), 'Should have optional actionLabel prop');
34
+ assert.ok(content.includes('onAction?:'), 'Should have optional onAction prop');
35
+ });
36
+ it('imports TuiButton', () => {
37
+ const content = readFileSync(componentPath, 'utf-8');
38
+ assert.ok(content.includes("from './TuiButton'"), 'Should import TuiButton');
39
+ });
40
+ it('imports CSS', () => {
41
+ const content = readFileSync(componentPath, 'utf-8');
42
+ assert.ok(content.includes("import './EmptyState.css'"), 'Should import EmptyState.css');
43
+ });
44
+ it('CSS has required classes', () => {
45
+ const content = readFileSync(cssPath, 'utf-8');
46
+ assert.ok(content.includes('.empty-state'), 'Should have .empty-state class');
47
+ assert.ok(content.includes('.empty-icon'), 'Should have .empty-icon class');
48
+ assert.ok(content.includes('.empty-heading'), 'Should have .empty-heading class');
49
+ assert.ok(content.includes('.empty-description'), 'Should have .empty-description class');
50
+ });
51
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, it, beforeEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ describe('toasts.store', () => {
4
+ let useToastStore;
5
+ let showToast;
6
+ let dismissToast;
7
+ let getToasts;
8
+ beforeEach(async () => {
9
+ const store = await import('../../frontend/src/lib/state/toasts.store.js');
10
+ useToastStore = store.useToastStore;
11
+ showToast = store.showToast;
12
+ dismissToast = store.dismissToast;
13
+ getToasts = store.getToasts;
14
+ useToastStore.setState({ toasts: [] });
15
+ });
16
+ it('initializes with empty toasts', () => {
17
+ const state = useToastStore.getState();
18
+ assert.deepStrictEqual(state.toasts, []);
19
+ });
20
+ it('showToast adds a toast to the store', () => {
21
+ showToast('Test message', 'error');
22
+ const state = useToastStore.getState();
23
+ assert.strictEqual(state.toasts.length, 1);
24
+ assert.strictEqual(state.toasts[0]?.message, 'Test message');
25
+ assert.strictEqual(state.toasts[0]?.variant, 'error');
26
+ });
27
+ it('showToast uses default variant', () => {
28
+ showToast('Default test');
29
+ const state = useToastStore.getState();
30
+ assert.strictEqual(state.toasts[0]?.variant, 'error');
31
+ });
32
+ it('showToast supports info variant', () => {
33
+ showToast('Info message', 'info');
34
+ const state = useToastStore.getState();
35
+ assert.strictEqual(state.toasts[0]?.variant, 'info');
36
+ });
37
+ it('dismissToast removes a toast by id', () => {
38
+ showToast('First', 'error');
39
+ showToast('Second', 'info');
40
+ let state = useToastStore.getState();
41
+ assert.strictEqual(state.toasts.length, 2);
42
+ const firstId = state.toasts[0]?.id;
43
+ if (firstId !== undefined) {
44
+ dismissToast(firstId);
45
+ }
46
+ state = useToastStore.getState();
47
+ assert.strictEqual(state.toasts.length, 1);
48
+ assert.strictEqual(state.toasts[0]?.message, 'Second');
49
+ });
50
+ it('getToasts returns current toasts array', () => {
51
+ showToast('Toast 1');
52
+ showToast('Toast 2');
53
+ const toasts = getToasts();
54
+ assert.strictEqual(toasts.length, 2);
55
+ });
56
+ it('toast ids are unique', () => {
57
+ showToast('First');
58
+ showToast('Second');
59
+ showToast('Third');
60
+ const state = useToastStore.getState();
61
+ const ids = state.toasts.map((t) => t.id);
62
+ const uniqueIds = new Set(ids);
63
+ assert.strictEqual(uniqueIds.size, ids.length, 'All toast IDs should be unique');
64
+ });
65
+ });
@@ -0,0 +1,120 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ // Note: Full DOM testing would require a test runner with JSX support (e.g., Vitest + React Testing Library)
4
+ describe('TuiCheckbox', () => {
5
+ describe('Props Interface', () => {
6
+ it('should have required checked prop', () => {
7
+ const validProps = { checked: true };
8
+ assert.strictEqual(validProps.checked, true);
9
+ });
10
+ it('should have optional disabled prop with default false', () => {
11
+ const propsWithDisabled = { checked: false, disabled: true };
12
+ assert.strictEqual(propsWithDisabled.disabled, true);
13
+ const propsWithoutDisabled = { checked: false };
14
+ assert.strictEqual(propsWithoutDisabled.disabled, undefined);
15
+ });
16
+ it('should have optional onChange callback', () => {
17
+ let calledWith;
18
+ const handleChange = (checked) => {
19
+ calledWith = checked;
20
+ };
21
+ const props = { checked: false, onChange: handleChange };
22
+ assert.strictEqual(typeof props.onChange, 'function');
23
+ props.onChange?.(true);
24
+ assert.strictEqual(calledWith, true);
25
+ });
26
+ it('should support children prop', () => {
27
+ const props = {
28
+ checked: false,
29
+ children: 'Label text'
30
+ };
31
+ assert.strictEqual(props.children, 'Label text');
32
+ });
33
+ it('should support className prop', () => {
34
+ const props = {
35
+ checked: false,
36
+ className: 'custom-class'
37
+ };
38
+ assert.strictEqual(props.className, 'custom-class');
39
+ });
40
+ it('should support rest props spread', () => {
41
+ const props = {
42
+ checked: false,
43
+ 'data-testid': 'test-checkbox',
44
+ 'aria-label': 'Test checkbox'
45
+ };
46
+ assert.strictEqual(props['data-testid'], 'test-checkbox');
47
+ assert.strictEqual(props['aria-label'], 'Test checkbox');
48
+ });
49
+ });
50
+ describe('Component Logic', () => {
51
+ it('should display [x] when checked', () => {
52
+ const checked = true;
53
+ const displayText = checked ? '[x]' : '[ ]';
54
+ assert.strictEqual(displayText, '[x]');
55
+ });
56
+ it('should display [ ] when unchecked', () => {
57
+ const checked = false;
58
+ const displayText = checked ? '[x]' : '[ ]';
59
+ assert.strictEqual(displayText, '[ ]');
60
+ });
61
+ it('should apply disabled class when disabled', () => {
62
+ const disabled = true;
63
+ const classes = ['tui-checkbox', disabled && 'disabled'].filter(Boolean).join(' ');
64
+ assert.strictEqual(classes, 'tui-checkbox disabled');
65
+ });
66
+ it('should not apply disabled class when not disabled', () => {
67
+ const disabled = false;
68
+ const classes = ['tui-checkbox', disabled && 'disabled'].filter(Boolean).join(' ');
69
+ assert.strictEqual(classes, 'tui-checkbox');
70
+ });
71
+ it('should combine custom className with base classes', () => {
72
+ const className = 'custom-class';
73
+ const disabled = false;
74
+ const classes = ['tui-checkbox', disabled && 'disabled', className]
75
+ .filter(Boolean)
76
+ .join(' ');
77
+ assert.strictEqual(classes, 'tui-checkbox custom-class');
78
+ });
79
+ it('should not call onChange when disabled', () => {
80
+ const disabled = true;
81
+ let called = false;
82
+ const onChange = (checked) => {
83
+ called = true;
84
+ };
85
+ const handleChange = (e) => {
86
+ if (!disabled && onChange) {
87
+ onChange(e.target.checked);
88
+ }
89
+ };
90
+ handleChange({ target: { checked: true } });
91
+ assert.strictEqual(called, false);
92
+ });
93
+ it('should call onChange when not disabled', () => {
94
+ const disabled = false;
95
+ let calledWith;
96
+ const onChange = (checked) => {
97
+ calledWith = checked;
98
+ };
99
+ const handleChange = (e) => {
100
+ if (!disabled && onChange) {
101
+ onChange(e.target.checked);
102
+ }
103
+ };
104
+ handleChange({ target: { checked: true } });
105
+ assert.strictEqual(calledWith, true);
106
+ });
107
+ });
108
+ describe('CSS Classes', () => {
109
+ it('should have correct base class', () => {
110
+ const expectedClasses = {
111
+ container: 'tui-checkbox',
112
+ check: 'tui-check',
113
+ disabled: 'disabled'
114
+ };
115
+ assert.strictEqual(expectedClasses.container, 'tui-checkbox');
116
+ assert.strictEqual(expectedClasses.check, 'tui-check');
117
+ assert.strictEqual(expectedClasses.disabled, 'disabled');
118
+ });
119
+ });
120
+ });