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,93 @@
1
+ import { describe, it, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import express from 'express';
4
+ import http from 'node:http';
5
+ import { BUILTIN_FRAMEWORKS } from '../server/types.js';
6
+ // ---------------------------------------------------------------------------
7
+ // Minimal express app that mounts just the /api/frameworks route
8
+ // (mirrors how server/index.ts registers it, without the full server bootstrap)
9
+ // ---------------------------------------------------------------------------
10
+ let server;
11
+ let port;
12
+ before(async () => {
13
+ const app = express();
14
+ app.use(express.json());
15
+ // Register the same route logic as server/index.ts will expose
16
+ app.get('/api/frameworks', (_req, res) => {
17
+ const frameworks = Object.values(BUILTIN_FRAMEWORKS).map((f) => ({
18
+ id: f.id,
19
+ displayName: f.displayName,
20
+ command: f.command,
21
+ capabilities: f.capabilities,
22
+ eventSource: f.eventSource,
23
+ }));
24
+ res.json({ frameworks });
25
+ });
26
+ server = http.createServer(app);
27
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
28
+ port = server.address().port;
29
+ });
30
+ after(() => {
31
+ server.close();
32
+ });
33
+ function url(p) {
34
+ return `http://127.0.0.1:${port}${p}`;
35
+ }
36
+ describe('GET /api/frameworks', () => {
37
+ it('returns 200 with a frameworks array', async () => {
38
+ const res = await fetch(url('/api/frameworks'));
39
+ assert.equal(res.status, 200);
40
+ const body = (await res.json());
41
+ assert.ok(Array.isArray(body.frameworks), 'response should have a frameworks array');
42
+ });
43
+ it('returns all three builtin frameworks', async () => {
44
+ const res = await fetch(url('/api/frameworks'));
45
+ const body = (await res.json());
46
+ const ids = body.frameworks.map((f) => f.id);
47
+ assert.ok(ids.includes('claude'), 'should include claude');
48
+ assert.ok(ids.includes('codex'), 'should include codex');
49
+ assert.ok(ids.includes('opencode'), 'should include opencode');
50
+ });
51
+ it('each framework entry has id, displayName, command, capabilities, eventSource', async () => {
52
+ const res = await fetch(url('/api/frameworks'));
53
+ const body = (await res.json());
54
+ for (const fw of body.frameworks) {
55
+ assert.ok(typeof fw.id === 'string', `framework ${fw.id} should have string id`);
56
+ assert.ok(typeof fw.displayName === 'string', `framework ${fw.id} should have displayName`);
57
+ assert.ok(typeof fw.command === 'string', `framework ${fw.id} should have command`);
58
+ assert.ok(typeof fw.eventSource === 'string', `framework ${fw.id} should have eventSource`);
59
+ assert.ok(fw.capabilities && typeof fw.capabilities === 'object', `framework ${fw.id} should have capabilities`);
60
+ assert.ok(typeof fw.capabilities.supportsHooks === 'boolean');
61
+ assert.ok(typeof fw.capabilities.supportsContinue === 'boolean');
62
+ assert.ok(typeof fw.capabilities.supportsYolo === 'boolean');
63
+ assert.ok(typeof fw.capabilities.supportsTelemetry === 'boolean');
64
+ }
65
+ });
66
+ it('claude framework entry has correct values', async () => {
67
+ const res = await fetch(url('/api/frameworks'));
68
+ const body = (await res.json());
69
+ const claude = body.frameworks.find((f) => f.id === 'claude');
70
+ assert.ok(claude, 'should have claude entry');
71
+ assert.equal(claude.displayName, 'Claude Code');
72
+ assert.equal(claude.command, 'claude');
73
+ assert.equal(claude.eventSource, 'hooks');
74
+ assert.equal(claude.capabilities.supportsHooks, true);
75
+ assert.equal(claude.capabilities.supportsTelemetry, true);
76
+ });
77
+ it('opencode framework entry has eventSource=plugin', async () => {
78
+ const res = await fetch(url('/api/frameworks'));
79
+ const body = (await res.json());
80
+ const opencode = body.frameworks.find((f) => f.id === 'opencode');
81
+ assert.ok(opencode, 'should have opencode entry');
82
+ assert.equal(opencode.eventSource, 'plugin');
83
+ });
84
+ it('does not include internal fields like parserType or continueArgs', async () => {
85
+ const res = await fetch(url('/api/frameworks'));
86
+ const body = (await res.json());
87
+ for (const fw of body.frameworks) {
88
+ assert.equal(fw.parserType, undefined, 'parserType should not be exposed');
89
+ assert.equal(fw.continueArgs, undefined, 'continueArgs should not be exposed');
90
+ assert.equal(fw.yoloArgs, undefined, 'yoloArgs should not be exposed');
91
+ }
92
+ });
93
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * PR Top Bar State Machine
3
+ *
4
+ * Pure function that derives the action button state from branch/PR/CI data.
5
+ *
6
+ * INPUT (branch state) ACTION BUTTON
7
+ * ─────────────────────────────────────────────────
8
+ * No commits ahead (none)
9
+ * Commits ahead, no PR [Create PR]
10
+ * PR Draft [Ready for Review]
11
+ * PR Open + CONFLICTING [Fix Conflicts]
12
+ * PR Open + CI failing [Fix Errors N/M]
13
+ * PR Open + CI pending [Checks Running...]
14
+ * PR Open + unresolved comments [Resolve Comments (N)] + [Review PR]
15
+ * PR Open + all clear [Review PR]
16
+ * PR Merged [Archive]
17
+ * PR Closed [Archive]
18
+ */
19
+ export function derivePrAction(input) {
20
+ const { commitsAhead, prState, ciFailing, ciPending, ciTotal, mergeable, unresolvedCommentCount } = input;
21
+ // No commits ahead of base — nothing to do
22
+ if (commitsAhead <= 0 && prState === null) {
23
+ return { type: 'none', color: 'none', label: '' };
24
+ }
25
+ // No PR exists but there are commits — offer to create
26
+ if (prState === null) {
27
+ return { type: 'create-pr', color: 'accent', label: 'Create PR' };
28
+ }
29
+ // PR is a draft — offer to mark ready
30
+ if (prState === 'DRAFT') {
31
+ return { type: 'ready-for-review', color: 'muted', label: 'Ready for Review' };
32
+ }
33
+ // PR is merged — offer cleanup
34
+ if (prState === 'MERGED') {
35
+ return { type: 'archive-merged', color: 'merged', label: 'Archive' };
36
+ }
37
+ // PR is closed (not merged) — offer cleanup
38
+ if (prState === 'CLOSED') {
39
+ return { type: 'archive-closed', color: 'muted', label: 'Archive' };
40
+ }
41
+ // PR is open — check for conflicts first
42
+ if (prState === 'OPEN') {
43
+ // Merge conflicts take priority
44
+ if (mergeable === 'CONFLICTING') {
45
+ return { type: 'fix-conflicts', color: 'error', label: 'Fix Conflicts' };
46
+ }
47
+ // CI checks are failing
48
+ if (ciFailing > 0) {
49
+ return {
50
+ type: 'fix-errors',
51
+ color: 'error',
52
+ label: `Fix Errors ${ciFailing}/${ciTotal}`,
53
+ };
54
+ }
55
+ // CI checks are still running (some pending, none failing)
56
+ if (ciPending > 0) {
57
+ return { type: 'checks-running', color: 'warning', label: 'Checks Running...' };
58
+ }
59
+ // Unresolved review comments
60
+ if (unresolvedCommentCount > 0) {
61
+ return {
62
+ type: 'resolve-comments',
63
+ color: 'accent',
64
+ label: `Resolve Comments (${unresolvedCommentCount})`,
65
+ };
66
+ }
67
+ // All CI checks passing (or no checks configured) — ready for review
68
+ return { type: 'review-pr', color: 'success', label: 'Review PR' };
69
+ }
70
+ // Fallback — should not reach here
71
+ return { type: 'none', color: 'none', label: '' };
72
+ }
73
+ export function deriveSecondaryAction(primary, _input) {
74
+ if (primary.type === 'resolve-comments') {
75
+ return { type: 'review-pr', color: 'muted', label: 'Review PR' };
76
+ }
77
+ return null;
78
+ }
79
+ export function getActionPrompt(action, ctx) {
80
+ switch (action.type) {
81
+ case 'create-pr':
82
+ return `Create a pull request for the branch "${ctx.branchName}". Write a clear title and description based on the changes.`;
83
+ case 'ready-for-review':
84
+ return `Mark the draft PR for branch "${ctx.branchName}" as ready for review using: gh pr ready`;
85
+ case 'review-pr':
86
+ return `Review the pull request #${ctx.prNumber} for branch "${ctx.branchName}". Read the diff, check for bugs and code quality.`;
87
+ case 'fix-conflicts':
88
+ return `There are merge conflicts with the base branch "${ctx.baseBranch}". Run \`git merge ${ctx.baseBranch}\` and resolve all conflicts.`;
89
+ case 'resolve-comments':
90
+ return `There are ${ctx.unresolvedCommentCount} unresolved review comments on PR #${ctx.prNumber}. Read each comment thread, triage them, and address the feedback.`;
91
+ case 'fix-errors':
92
+ return `The CI checks are failing on branch "${ctx.branchName}". Investigate the failing checks and fix the errors.`;
93
+ case 'archive-merged':
94
+ case 'archive-closed':
95
+ return null; // Archive is a UI action (delete worktree), not a Claude prompt
96
+ case 'checks-running':
97
+ case 'none':
98
+ return null;
99
+ }
100
+ }
101
+ export function getStatusCssVar(color) {
102
+ switch (color) {
103
+ case 'accent': return 'var(--accent)';
104
+ case 'success': return 'var(--status-success)';
105
+ case 'error': return 'var(--status-error)';
106
+ case 'warning': return 'var(--status-warning)';
107
+ case 'merged': return 'var(--status-merged)';
108
+ case 'muted': return 'var(--border)';
109
+ case 'none': return 'transparent';
110
+ }
111
+ }
112
+ export function shouldUseDarkText(color) {
113
+ return color === 'success' || color === 'warning';
114
+ }
@@ -0,0 +1,246 @@
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 server;
12
+ let baseUrl;
13
+ before(async () => {
14
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fs-browse-test-'));
15
+ configPath = path.join(tmpDir, 'config.json');
16
+ // Create a directory tree for testing
17
+ // tmpDir/
18
+ // browsable/
19
+ // visible-dir/
20
+ // nested/
21
+ // .hidden-dir/
22
+ // git-repo/
23
+ // .git/
24
+ // empty-dir/
25
+ // node_modules/
26
+ // file.txt
27
+ const browsable = path.join(tmpDir, 'browsable');
28
+ fs.mkdirSync(path.join(browsable, 'visible-dir', 'nested'), {
29
+ recursive: true,
30
+ });
31
+ fs.mkdirSync(path.join(browsable, '.hidden-dir'), { recursive: true });
32
+ fs.mkdirSync(path.join(browsable, 'git-repo', '.git'), { recursive: true });
33
+ fs.mkdirSync(path.join(browsable, 'empty-dir'), { recursive: true });
34
+ fs.mkdirSync(path.join(browsable, 'node_modules'), { recursive: true });
35
+ fs.writeFileSync(path.join(browsable, 'file.txt'), 'not a directory');
36
+ // Create 110 dirs to test truncation
37
+ const manyDir = path.join(tmpDir, 'many');
38
+ fs.mkdirSync(manyDir);
39
+ for (let i = 0; i < 110; i++) {
40
+ fs.mkdirSync(path.join(manyDir, `dir-${String(i).padStart(3, '0')}`));
41
+ }
42
+ // Save a config so the router can load it
43
+ saveConfig(configPath, { ...DEFAULTS, workspaces: [] });
44
+ // Start a test server
45
+ const app = express();
46
+ app.use(express.json());
47
+ app.use('/workspaces', createWorkspaceRouter({ configPath }));
48
+ await new Promise((resolve) => {
49
+ server = app.listen(0, '127.0.0.1', () => resolve());
50
+ });
51
+ const addr = server.address();
52
+ if (typeof addr === 'object' && addr) {
53
+ baseUrl = `http://127.0.0.1:${addr.port}`;
54
+ }
55
+ });
56
+ after(() => {
57
+ server?.close();
58
+ fs.rmSync(tmpDir, { recursive: true, force: true });
59
+ });
60
+ async function browse(query = {}) {
61
+ const params = new URLSearchParams(query);
62
+ const res = await fetch(`${baseUrl}/workspaces/browse?${params}`);
63
+ assert.equal(res.status, 200, `Expected 200 but got ${res.status}`);
64
+ return res.json();
65
+ }
66
+ describe('GET /workspaces/browse', () => {
67
+ test('lists directories in a given path', async () => {
68
+ const browsable = path.join(tmpDir, 'browsable');
69
+ const data = await browse({ path: browsable });
70
+ assert.equal(data.resolved, browsable);
71
+ const names = data.entries.map((e) => e.name);
72
+ // Should include visible directories but not files or denylisted dirs
73
+ assert.ok(names.includes('visible-dir'), 'should include visible-dir');
74
+ assert.ok(names.includes('git-repo'), 'should include git-repo');
75
+ assert.ok(names.includes('empty-dir'), 'should include empty-dir');
76
+ assert.ok(!names.includes('file.txt'), 'should exclude files');
77
+ assert.ok(!names.includes('node_modules'), 'should exclude node_modules');
78
+ });
79
+ test('hides dotfiles by default', async () => {
80
+ const browsable = path.join(tmpDir, 'browsable');
81
+ const data = await browse({ path: browsable });
82
+ const names = data.entries.map((e) => e.name);
83
+ assert.ok(!names.includes('.hidden-dir'), 'should exclude hidden dirs by default');
84
+ });
85
+ test('shows dotfiles when showHidden=true', async () => {
86
+ const browsable = path.join(tmpDir, 'browsable');
87
+ const data = await browse({ path: browsable, showHidden: 'true' });
88
+ const names = data.entries.map((e) => e.name);
89
+ assert.ok(names.includes('.hidden-dir'), 'should include hidden dirs when showHidden');
90
+ // .git should still be excluded (in denylist)
91
+ assert.ok(!names.includes('.git'), 'should still exclude .git');
92
+ });
93
+ test('filters by prefix', async () => {
94
+ const browsable = path.join(tmpDir, 'browsable');
95
+ const data = await browse({ path: browsable, prefix: 'vis' });
96
+ assert.equal(data.entries.length, 1);
97
+ assert.equal(data.entries[0].name, 'visible-dir');
98
+ });
99
+ test('prefix filter is case-insensitive', async () => {
100
+ const browsable = path.join(tmpDir, 'browsable');
101
+ const data = await browse({ path: browsable, prefix: 'VIS' });
102
+ assert.equal(data.entries.length, 1);
103
+ assert.equal(data.entries[0].name, 'visible-dir');
104
+ });
105
+ test('detects isGitRepo correctly', async () => {
106
+ const browsable = path.join(tmpDir, 'browsable');
107
+ const data = await browse({ path: browsable });
108
+ const gitRepo = data.entries.find((e) => e.name === 'git-repo');
109
+ const visibleDir = data.entries.find((e) => e.name === 'visible-dir');
110
+ assert.ok(gitRepo, 'git-repo entry should exist');
111
+ assert.equal(gitRepo.isGitRepo, true, 'git-repo should have isGitRepo=true');
112
+ assert.ok(visibleDir, 'visible-dir entry should exist');
113
+ assert.equal(visibleDir.isGitRepo, false, 'visible-dir should have isGitRepo=false');
114
+ });
115
+ test('detects hasChildren correctly', async () => {
116
+ const browsable = path.join(tmpDir, 'browsable');
117
+ const data = await browse({ path: browsable });
118
+ const visibleDir = data.entries.find((e) => e.name === 'visible-dir');
119
+ const emptyDir = data.entries.find((e) => e.name === 'empty-dir');
120
+ assert.ok(visibleDir, 'visible-dir entry should exist');
121
+ assert.equal(visibleDir.hasChildren, true, 'visible-dir should have children');
122
+ assert.ok(emptyDir, 'empty-dir entry should exist');
123
+ assert.equal(emptyDir.hasChildren, false, 'empty-dir should not have children');
124
+ });
125
+ test('truncates at 100 entries', async () => {
126
+ const manyDir = path.join(tmpDir, 'many');
127
+ const data = await browse({ path: manyDir });
128
+ assert.equal(data.entries.length, 100);
129
+ assert.equal(data.truncated, true);
130
+ assert.equal(data.total, 110);
131
+ });
132
+ test('sorts alphabetically case-insensitive', async () => {
133
+ const browsable = path.join(tmpDir, 'browsable');
134
+ const data = await browse({ path: browsable });
135
+ const names = data.entries.map((e) => e.name);
136
+ const sorted = [...names].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
137
+ assert.deepEqual(names, sorted, 'entries should be sorted alphabetically');
138
+ });
139
+ test('returns 400 for non-existent path', async () => {
140
+ const params = new URLSearchParams({
141
+ path: path.join(tmpDir, 'nonexistent'),
142
+ });
143
+ const res = await fetch(`${baseUrl}/workspaces/browse?${params}`);
144
+ assert.equal(res.status, 400);
145
+ });
146
+ test('returns 400 for file path', async () => {
147
+ const params = new URLSearchParams({
148
+ path: path.join(tmpDir, 'browsable', 'file.txt'),
149
+ });
150
+ const res = await fetch(`${baseUrl}/workspaces/browse?${params}`);
151
+ assert.equal(res.status, 400);
152
+ });
153
+ test('defaults to home directory when no path given', async () => {
154
+ const data = await browse();
155
+ assert.equal(data.resolved, os.homedir());
156
+ // Should have at least some entries (home dir is not empty)
157
+ assert.ok(data.entries.length > 0, 'home directory should have entries');
158
+ });
159
+ test('includes files when includeFiles=true', async () => {
160
+ const browsable = path.join(tmpDir, 'browsable');
161
+ const data = await browse({ path: browsable, includeFiles: 'true' });
162
+ const names = data.entries.map((e) => e.name);
163
+ assert.ok(names.includes('file.txt'), 'should include file.txt');
164
+ assert.ok(names.includes('visible-dir'), 'should still include directories');
165
+ });
166
+ test('directories sort before files with includeFiles=true', async () => {
167
+ const browsable = path.join(tmpDir, 'browsable');
168
+ const data = await browse({ path: browsable, includeFiles: 'true' });
169
+ const entries = data.entries;
170
+ const firstFileIdx = entries.findIndex((e) => e.isDirectory === false);
171
+ let lastDirIdx = -1;
172
+ for (let i = entries.length - 1; i >= 0; i--) {
173
+ if (entries[i].isDirectory === true) {
174
+ lastDirIdx = i;
175
+ break;
176
+ }
177
+ }
178
+ if (firstFileIdx !== -1 && lastDirIdx !== -1) {
179
+ assert.ok(lastDirIdx < firstFileIdx, 'all directories should come before files');
180
+ }
181
+ });
182
+ test('files have isDirectory=false and size field with includeFiles=true', async () => {
183
+ const browsable = path.join(tmpDir, 'browsable');
184
+ const data = await browse({ path: browsable, includeFiles: 'true' });
185
+ const fileEntry = data.entries.find((e) => e.name === 'file.txt');
186
+ assert.ok(fileEntry, 'file.txt should exist');
187
+ assert.equal(fileEntry.isDirectory, false, 'file should have isDirectory=false');
188
+ assert.equal(typeof fileEntry.size, 'number', 'file should have size');
189
+ assert.ok(fileEntry.size > 0, 'file should have non-zero size');
190
+ });
191
+ test('excludes files when includeFiles is not set', async () => {
192
+ const browsable = path.join(tmpDir, 'browsable');
193
+ const data = await browse({ path: browsable });
194
+ const names = data.entries.map((e) => e.name);
195
+ assert.ok(!names.includes('file.txt'), 'should exclude files by default');
196
+ });
197
+ });
198
+ describe('POST /workspaces/bulk', () => {
199
+ test('adds multiple workspaces', async () => {
200
+ const dir1 = path.join(tmpDir, 'browsable', 'visible-dir');
201
+ const dir2 = path.join(tmpDir, 'browsable', 'empty-dir');
202
+ const res = await fetch(`${baseUrl}/workspaces/bulk`, {
203
+ method: 'POST',
204
+ headers: { 'Content-Type': 'application/json' },
205
+ body: JSON.stringify({ paths: [dir1, dir2] }),
206
+ });
207
+ assert.equal(res.status, 201);
208
+ const data = (await res.json());
209
+ assert.equal(data.added.length, 2);
210
+ assert.equal(data.errors.length, 0);
211
+ });
212
+ test('rejects duplicate workspaces', async () => {
213
+ const dir1 = path.join(tmpDir, 'browsable', 'visible-dir');
214
+ const res = await fetch(`${baseUrl}/workspaces/bulk`, {
215
+ method: 'POST',
216
+ headers: { 'Content-Type': 'application/json' },
217
+ body: JSON.stringify({ paths: [dir1] }),
218
+ });
219
+ assert.equal(res.status, 201);
220
+ const data = (await res.json());
221
+ assert.equal(data.added.length, 0);
222
+ assert.equal(data.errors.length, 1);
223
+ assert.ok(data.errors[0].error.includes('Already exists'));
224
+ });
225
+ test('returns 400 for empty paths array', async () => {
226
+ const res = await fetch(`${baseUrl}/workspaces/bulk`, {
227
+ method: 'POST',
228
+ headers: { 'Content-Type': 'application/json' },
229
+ body: JSON.stringify({ paths: [] }),
230
+ });
231
+ assert.equal(res.status, 400);
232
+ });
233
+ test('handles mixed valid/invalid paths', async () => {
234
+ const validDir = path.join(tmpDir, 'browsable', 'git-repo');
235
+ const invalidDir = path.join(tmpDir, 'nonexistent');
236
+ const res = await fetch(`${baseUrl}/workspaces/bulk`, {
237
+ method: 'POST',
238
+ headers: { 'Content-Type': 'application/json' },
239
+ body: JSON.stringify({ paths: [validDir, invalidDir] }),
240
+ });
241
+ assert.equal(res.status, 201);
242
+ const data = (await res.json());
243
+ assert.equal(data.added.length, 1);
244
+ assert.equal(data.errors.length, 1);
245
+ });
246
+ });
@@ -0,0 +1,145 @@
1
+ import { test, describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { scorePath } from '../frontend/src/lib/fuzzy-scorer.js';
4
+ // Helper: score multiple paths and return them sorted by score descending
5
+ function rank(query, paths) {
6
+ const scored = paths
7
+ .map((p) => ({ path: p, result: scorePath(query, p) }))
8
+ .filter((s) => s.result !== null)
9
+ .sort((a, b) => b.result.score - a.result.score);
10
+ return scored.map((s) => s.path);
11
+ }
12
+ describe('scorePath', () => {
13
+ // ── Basic behavior ──
14
+ test('returns null for empty query', () => {
15
+ assert.equal(scorePath('', 'App.svelte'), null);
16
+ });
17
+ test('returns null for empty path', () => {
18
+ assert.equal(scorePath('app', ''), null);
19
+ });
20
+ test('returns null when no match exists', () => {
21
+ assert.equal(scorePath('zzz', 'App.svelte'), null);
22
+ });
23
+ test('returns null when query is longer than path', () => {
24
+ assert.equal(scorePath('abcdefghijk', 'abc.ts'), null);
25
+ });
26
+ // ── Score properties ──
27
+ test('exact filename match scores highest', () => {
28
+ const result = scorePath('App.svelte', 'frontend/src/App.svelte');
29
+ assert.ok(result !== null);
30
+ assert.ok(result.score > 0);
31
+ });
32
+ test('filename prefix match gets boost', () => {
33
+ const prefixResult = scorePath('App', 'frontend/src/App.svelte');
34
+ const substringResult = scorePath('vel', 'frontend/src/App.svelte');
35
+ assert.ok(prefixResult !== null);
36
+ // prefix match should score substantially higher
37
+ if (substringResult) {
38
+ assert.ok(prefixResult.score > substringResult.score);
39
+ }
40
+ });
41
+ test('consecutive characters score higher than scattered', () => {
42
+ const files = ['filter.ts', 'f_i_l_t_e_r.ts'];
43
+ const ranked = rank('filter', files);
44
+ assert.equal(ranked[0], 'filter.ts');
45
+ });
46
+ test('camelCase matching works', () => {
47
+ const result = scorePath('CP', 'CommandPalette.svelte');
48
+ assert.ok(result !== null);
49
+ assert.ok(result.score > 0);
50
+ });
51
+ test('word boundary matching works', () => {
52
+ const result = scorePath('fs', 'file-scorer.ts');
53
+ assert.ok(result !== null);
54
+ assert.ok(result.score > 0);
55
+ });
56
+ test('path separator bonus works', () => {
57
+ const result = scorePath('sc', 'src/components/App.svelte');
58
+ assert.ok(result !== null);
59
+ });
60
+ test('scattered match scores lower than compact', () => {
61
+ const compact = scorePath('app', 'App.svelte');
62
+ const scattered = scorePath('app', 'a_p_p.svelte');
63
+ assert.ok(compact !== null);
64
+ if (scattered) {
65
+ assert.ok(compact.score > scattered.score);
66
+ }
67
+ });
68
+ // ── Match positions ──
69
+ test('match positions are correct for filename match', () => {
70
+ const result = scorePath('App', 'src/App.svelte');
71
+ assert.ok(result !== null);
72
+ // matches should be in the "App" portion of "src/App.svelte"
73
+ // "src/" is 4 chars, so "A" is at index 4
74
+ assert.ok(result.matches.length > 0);
75
+ const firstStart = result.matches[0][0];
76
+ assert.ok(firstStart >= 4, `expected match start >= 4 (in filename), got ${firstStart}`);
77
+ });
78
+ test('match positions are correct for path match', () => {
79
+ const result = scorePath('src', 'src/App.svelte');
80
+ assert.ok(result !== null);
81
+ assert.ok(result.matches.length > 0);
82
+ // "src" should match at the beginning
83
+ assert.equal(result.matches[0][0], 0);
84
+ });
85
+ // ── Edge cases ──
86
+ test('unicode filenames do not crash', () => {
87
+ const result = scorePath('readme', 'docs/日本語/readme.md');
88
+ // should either match or return null, but not throw
89
+ if (result) {
90
+ assert.ok(result.score > 0);
91
+ }
92
+ });
93
+ test('empty file list produces no results', () => {
94
+ const ranked = rank('app', []);
95
+ assert.equal(ranked.length, 0);
96
+ });
97
+ test('single character query works', () => {
98
+ const result = scorePath('a', 'App.svelte');
99
+ assert.ok(result !== null);
100
+ assert.ok(result.score > 0);
101
+ });
102
+ test('case-insensitive matching works', () => {
103
+ const result = scorePath('app', 'App.svelte');
104
+ assert.ok(result !== null);
105
+ assert.ok(result.score > 0);
106
+ });
107
+ });
108
+ describe('ranking stability', () => {
109
+ test('filename prefix beats path-only match', () => {
110
+ const ranked = rank('app', [
111
+ 'App.svelte',
112
+ 'src/app/index.ts',
113
+ 'AppLayout.svelte',
114
+ ]);
115
+ // App.svelte should come first (exact prefix on filename)
116
+ assert.equal(ranked[0], 'App.svelte');
117
+ // AppLayout.svelte before src/app/index.ts (filename match > path match)
118
+ const appLayoutIdx = ranked.indexOf('AppLayout.svelte');
119
+ const srcAppIdx = ranked.indexOf('src/app/index.ts');
120
+ assert.ok(appLayoutIdx < srcAppIdx, `AppLayout.svelte (${appLayoutIdx}) should rank above src/app/index.ts (${srcAppIdx})`);
121
+ });
122
+ test('exact filename beats deep path', () => {
123
+ const ranked = rank('readme', ['README.md', 'src/lib/readme-utils.ts']);
124
+ assert.equal(ranked[0], 'README.md');
125
+ });
126
+ test('shallow path beats deep path for same filename', () => {
127
+ const ranked = rank('deep', ['a/b/c/deep.ts', 'deep.ts']);
128
+ assert.equal(ranked[0], 'deep.ts');
129
+ });
130
+ test('camelCase match ranks correctly', () => {
131
+ const ranked = rank('CP', [
132
+ 'CommandPalette.svelte',
133
+ 'components/Palette.svelte',
134
+ ]);
135
+ // CommandPalette matches C+P as camelCase boundaries
136
+ assert.equal(ranked[0], 'CommandPalette.svelte');
137
+ });
138
+ test('filename match always beats path-only match', () => {
139
+ const ranked = rank('index', [
140
+ 'src/components/deep/nested/index.ts',
141
+ 'index.ts',
142
+ ]);
143
+ assert.equal(ranked[0], 'index.ts');
144
+ });
145
+ });