@sanurb/ringi 0.2.0 → 0.3.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 (423) hide show
  1. package/dist/cli.mjs +2132 -0
  2. package/dist/cli.mjs.map +1 -0
  3. package/dist/mcp.mjs +1057 -0
  4. package/dist/mcp.mjs.map +1 -0
  5. package/dist/runtime.mjs +3116 -0
  6. package/dist/runtime.mjs.map +1 -0
  7. package/package.json +15 -14
  8. package/server/nitro.json +17 -0
  9. package/server/public/assets/ClientOnly-QdfAxyFs.js +1 -0
  10. package/server/public/assets/_reviewId-CmXHvWLn.js +1 -0
  11. package/server/public/assets/_reviewId-DdOpDx4U.js +1 -0
  12. package/server/public/assets/abap-B1dkBSPn.js +1 -0
  13. package/server/public/assets/action-bar-DLRNvLjj.js +45 -0
  14. package/server/public/assets/actionscript-3-BT4ibYlP.js +1 -0
  15. package/server/public/assets/ada-CD92zeps.js +1 -0
  16. package/server/public/assets/andromeeda-DqSmgxi0.js +1 -0
  17. package/server/public/assets/angular-html-BDC0PfKr.js +1 -0
  18. package/server/public/assets/angular-ts-B9yoQMtj.js +1 -0
  19. package/server/public/assets/apache-D5suuoa_.js +1 -0
  20. package/server/public/assets/apex-BL-m4VHy.js +1 -0
  21. package/server/public/assets/apl-CldhY0Pn.js +1 -0
  22. package/server/public/assets/applescript-CLiBqvKT.js +1 -0
  23. package/server/public/assets/ara-LdDF8cmv.js +1 -0
  24. package/server/public/assets/asciidoc-2DZ9hC2N.js +1 -0
  25. package/server/public/assets/asm-0ZPGRSUy.js +1 -0
  26. package/server/public/assets/astro-DR6labZJ.js +1 -0
  27. package/server/public/assets/aurora-x-Da7Zfvbg.js +1 -0
  28. package/server/public/assets/awk-Bn0gn_B_.js +1 -0
  29. package/server/public/assets/ayu-dark-bqYKoqpM.js +1 -0
  30. package/server/public/assets/ayu-light-C45jTIzZ.js +1 -0
  31. package/server/public/assets/ayu-mirage-BV_FCTQi.js +1 -0
  32. package/server/public/assets/ballerina-D2nw_w8Q.js +1 -0
  33. package/server/public/assets/bat-ByUBN5gS.js +1 -0
  34. package/server/public/assets/beancount-BTb2W6Mp.js +1 -0
  35. package/server/public/assets/berry-5SO2uITG.js +1 -0
  36. package/server/public/assets/bibtex-CDBTNfUI.js +1 -0
  37. package/server/public/assets/bicep-fgxG_4rP.js +1 -0
  38. package/server/public/assets/bird2-BCwzDhwX.js +1 -0
  39. package/server/public/assets/blade-BBTRu2-g.js +1 -0
  40. package/server/public/assets/bsl-CG-fq4sc.js +1 -0
  41. package/server/public/assets/c-CEvNj7xl.js +1 -0
  42. package/server/public/assets/c3-Dlaci63_.js +1 -0
  43. package/server/public/assets/cadence-DHbRuEmm.js +1 -0
  44. package/server/public/assets/cairo-Ds0kTeYT.js +1 -0
  45. package/server/public/assets/catppuccin-frappe-DrL1fUuH.js +1 -0
  46. package/server/public/assets/catppuccin-latte-C0REgVjl.js +1 -0
  47. package/server/public/assets/catppuccin-macchiato-ChQpylWO.js +1 -0
  48. package/server/public/assets/catppuccin-mocha-Dd0JU1T0.js +1 -0
  49. package/server/public/assets/clarity-DMoTOm4G.js +1 -0
  50. package/server/public/assets/clojure-DBhE3PpS.js +1 -0
  51. package/server/public/assets/cmake-DwMc40Or.js +1 -0
  52. package/server/public/assets/cobol-BdNJSMRt.js +1 -0
  53. package/server/public/assets/codeowners-1lVr8wqV.js +1 -0
  54. package/server/public/assets/codeql-DWflolvo.js +1 -0
  55. package/server/public/assets/coffee-RA4xA24H.js +1 -0
  56. package/server/public/assets/common-lisp-CrVQ5xT-.js +1 -0
  57. package/server/public/assets/compiler-runtime-DZXZ41-q.js +1 -0
  58. package/server/public/assets/coq-CjfoyYSh.js +1 -0
  59. package/server/public/assets/cpp-BoW7e2Ow.js +1 -0
  60. package/server/public/assets/createServerFn-DTk395iP.js +9 -0
  61. package/server/public/assets/crystal-CYKRo3F9.js +1 -0
  62. package/server/public/assets/csharp-C7bIWP5y.js +1 -0
  63. package/server/public/assets/css-Ck2tii2d.js +1 -0
  64. package/server/public/assets/csv-DsAkDVtA.js +1 -0
  65. package/server/public/assets/cue-BWmQgbOB.js +1 -0
  66. package/server/public/assets/cypher-D-jVC50Q.js +1 -0
  67. package/server/public/assets/d-CaviyOrm.js +1 -0
  68. package/server/public/assets/dark-plus-DIrnwZt9.js +1 -0
  69. package/server/public/assets/dart-CZEi7JgC.js +1 -0
  70. package/server/public/assets/dax-BK-8zffy.js +1 -0
  71. package/server/public/assets/desktop-D3cjbL4D.js +1 -0
  72. package/server/public/assets/diff-sHAzLvlp.js +1 -0
  73. package/server/public/assets/docker--xs2Ng3w.js +1 -0
  74. package/server/public/assets/dotenv-Cm4nwcJ7.js +1 -0
  75. package/server/public/assets/dracula-CAUSusef.js +1 -0
  76. package/server/public/assets/dracula-soft-cjNkMFza.js +1 -0
  77. package/server/public/assets/dream-maker-fjmWTFCO.js +1 -0
  78. package/server/public/assets/edge-DxycC9wl.js +1 -0
  79. package/server/public/assets/elixir-B-50Er3p.js +1 -0
  80. package/server/public/assets/elm-B4-ygIVo.js +1 -0
  81. package/server/public/assets/emacs-lisp-CJzqStIa.js +1 -0
  82. package/server/public/assets/erb-DJvYE1L1.js +1 -0
  83. package/server/public/assets/erlang-C-m_88FN.js +1 -0
  84. package/server/public/assets/everforest-dark-DBpaSMx1.js +1 -0
  85. package/server/public/assets/everforest-light-CiGrXwia.js +1 -0
  86. package/server/public/assets/fennel-DRaXF7k8.js +1 -0
  87. package/server/public/assets/file-tree-CI3Xwwid.js +1907 -0
  88. package/server/public/assets/fish-Bn-Yh3Jj.js +1 -0
  89. package/server/public/assets/fluent-DF5F8Ks_.js +1 -0
  90. package/server/public/assets/fortran-fixed-form-Cx1lv7HN.js +1 -0
  91. package/server/public/assets/fortran-free-form-hCQHRqew.js +1 -0
  92. package/server/public/assets/fsharp-DC5k9sy2.js +1 -0
  93. package/server/public/assets/gdresource-D0EsKdgH.js +1 -0
  94. package/server/public/assets/gdscript-_C9_Hi_w.js +1 -0
  95. package/server/public/assets/gdshader-BW7b1X1Y.js +1 -0
  96. package/server/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  97. package/server/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  98. package/server/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  99. package/server/public/assets/genie-Ch_6TCHd.js +1 -0
  100. package/server/public/assets/gherkin-CaNUsmTq.js +1 -0
  101. package/server/public/assets/git-commit-BcFsuO5E.js +1 -0
  102. package/server/public/assets/git-rebase-ChGA-z50.js +1 -0
  103. package/server/public/assets/github-dark-B9ygjgg6.js +1 -0
  104. package/server/public/assets/github-dark-default-Br2bgYSx.js +1 -0
  105. package/server/public/assets/github-dark-dimmed-CmtqpPJ-.js +1 -0
  106. package/server/public/assets/github-dark-high-contrast-fSfmrZcC.js +1 -0
  107. package/server/public/assets/github-light-BZjUqfZl.js +1 -0
  108. package/server/public/assets/github-light-default-lIytXXhR.js +1 -0
  109. package/server/public/assets/github-light-high-contrast-BRrjFb7n.js +1 -0
  110. package/server/public/assets/gleam-DALMDpNs.js +1 -0
  111. package/server/public/assets/glimmer-js-maLb6ysA.js +1 -0
  112. package/server/public/assets/glimmer-ts-DGNr-OBA.js +1 -0
  113. package/server/public/assets/glsl-CmplqyQ1.js +1 -0
  114. package/server/public/assets/gn-DGjqrYN9.js +1 -0
  115. package/server/public/assets/gnuplot-BYckvgQI.js +1 -0
  116. package/server/public/assets/go-JycvP538.js +1 -0
  117. package/server/public/assets/graphql-VhP7n4--.js +1 -0
  118. package/server/public/assets/groovy-D5qMRONT.js +1 -0
  119. package/server/public/assets/gruvbox-dark-hard-M1dj1e6V.js +1 -0
  120. package/server/public/assets/gruvbox-dark-medium-cqq_ncQu.js +1 -0
  121. package/server/public/assets/gruvbox-dark-soft-B4QwL2a9.js +1 -0
  122. package/server/public/assets/gruvbox-light-hard-DLayMKOQ.js +1 -0
  123. package/server/public/assets/gruvbox-light-medium-D52XgPKf.js +1 -0
  124. package/server/public/assets/gruvbox-light-soft-Dola3KdD.js +1 -0
  125. package/server/public/assets/hack-BVSQ2bxM.js +1 -0
  126. package/server/public/assets/haml-CwTtRHoj.js +1 -0
  127. package/server/public/assets/handlebars-CcO01SVo.js +1 -0
  128. package/server/public/assets/haskell-ys7wPPEd.js +1 -0
  129. package/server/public/assets/haxe-94kiChn7.js +1 -0
  130. package/server/public/assets/hcl-DmHt_-wq.js +1 -0
  131. package/server/public/assets/hjson-xMmoJ0Gx.js +1 -0
  132. package/server/public/assets/hlsl-b-Pskdze.js +1 -0
  133. package/server/public/assets/horizon-BKMqttiR.js +1 -0
  134. package/server/public/assets/horizon-bright-HNkBlnm5.js +1 -0
  135. package/server/public/assets/houston-BkBSBSOQ.js +1 -0
  136. package/server/public/assets/html-derivative-Cz-cKMi2.js +1 -0
  137. package/server/public/assets/html-zQbUS8Is.js +1 -0
  138. package/server/public/assets/http-CaGQ9BgA.js +1 -0
  139. package/server/public/assets/hurl-BBoki9bg.js +1 -0
  140. package/server/public/assets/hxml-iQTOTWpM.js +1 -0
  141. package/server/public/assets/hy-DKl1XhBq.js +1 -0
  142. package/server/public/assets/imba-DPxkOTAg.js +1 -0
  143. package/server/public/assets/ini-lkLGq_1x.js +1 -0
  144. package/server/public/assets/java-LAx6oszV.js +1 -0
  145. package/server/public/assets/javascript-COqx-gKX.js +1 -0
  146. package/server/public/assets/jinja-x-G_qSCP.js +1 -0
  147. package/server/public/assets/jison-7oSeVkKJ.js +1 -0
  148. package/server/public/assets/json-sTLOVXhc.js +1 -0
  149. package/server/public/assets/json5-Cy6ypJuJ.js +1 -0
  150. package/server/public/assets/jsonc-Cw2ugYAK.js +1 -0
  151. package/server/public/assets/jsonl-Dp5_qBVH.js +1 -0
  152. package/server/public/assets/jsonnet-BTbmg_-u.js +1 -0
  153. package/server/public/assets/jssm-CnT7nPea.js +1 -0
  154. package/server/public/assets/jsx-zXeIBQLI.js +1 -0
  155. package/server/public/assets/julia-E-6Xm9nd.js +1 -0
  156. package/server/public/assets/just-D9n74gZy.js +1 -0
  157. package/server/public/assets/kanagawa-dragon-CxsBnuhV.js +1 -0
  158. package/server/public/assets/kanagawa-lotus-vHdxDDOS.js +1 -0
  159. package/server/public/assets/kanagawa-wave-CIkfTKWk.js +1 -0
  160. package/server/public/assets/kdl-BwK60g80.js +1 -0
  161. package/server/public/assets/kotlin-Dbd9Vi-v.js +1 -0
  162. package/server/public/assets/kusto-BuTk9usc.js +1 -0
  163. package/server/public/assets/laserwave-C0wf_d3o.js +1 -0
  164. package/server/public/assets/latex-D0t4RtEU.js +1 -0
  165. package/server/public/assets/lean-CYAW8bRN.js +1 -0
  166. package/server/public/assets/less-D4uen21c.js +1 -0
  167. package/server/public/assets/light-plus-oqYyWKEE.js +1 -0
  168. package/server/public/assets/liquid-BzXN12F6.js +1 -0
  169. package/server/public/assets/llvm-Z1xJzteV.js +1 -0
  170. package/server/public/assets/log-BGUxlsk3.js +1 -0
  171. package/server/public/assets/logo-wVUhvQ1b.js +1 -0
  172. package/server/public/assets/lua-B0Cg8RP4.js +1 -0
  173. package/server/public/assets/luau-rPFZzCmq.js +1 -0
  174. package/server/public/assets/main-FvxVz-kD.js +15 -0
  175. package/server/public/assets/make-BmPf6m0P.js +1 -0
  176. package/server/public/assets/markdown-AseU6zcW.js +1 -0
  177. package/server/public/assets/marko-BlRPXWOe.js +1 -0
  178. package/server/public/assets/material-theme-6_W6rQhR.js +1 -0
  179. package/server/public/assets/material-theme-darker-VPEo3Sem.js +1 -0
  180. package/server/public/assets/material-theme-lighter-CUhzCcZ9.js +1 -0
  181. package/server/public/assets/material-theme-ocean-B2JdsaGb.js +1 -0
  182. package/server/public/assets/material-theme-palenight-DhY-sklA.js +1 -0
  183. package/server/public/assets/matlab-BOj_BDQv.js +1 -0
  184. package/server/public/assets/mdc-FiVDZSZ4.js +1 -0
  185. package/server/public/assets/mdx-Cm6cDkDI.js +1 -0
  186. package/server/public/assets/mermaid-DLO-R4hv.js +1 -0
  187. package/server/public/assets/min-dark-D34a_pX7.js +1 -0
  188. package/server/public/assets/min-light-Cdd4KORE.js +1 -0
  189. package/server/public/assets/mipsasm-DYpHF-GA.js +1 -0
  190. package/server/public/assets/mojo-DqYVFv_G.js +1 -0
  191. package/server/public/assets/monokai-CDR4sQ2n.js +1 -0
  192. package/server/public/assets/moonbit-DRKee9wk.js +1 -0
  193. package/server/public/assets/move-DbRk6Vn9.js +1 -0
  194. package/server/public/assets/narrat-IOfmaXfb.js +1 -0
  195. package/server/public/assets/new-DOyplRwM.js +1 -0
  196. package/server/public/assets/nextflow-D-Ec_bsY.js +1 -0
  197. package/server/public/assets/nextflow-groovy-EYl0c2BQ.js +1 -0
  198. package/server/public/assets/nginx-3JLAqmJa.js +1 -0
  199. package/server/public/assets/night-owl-light-Bedht9b4.js +1 -0
  200. package/server/public/assets/night-owl-yQJ3-I0I.js +1 -0
  201. package/server/public/assets/nim-DyjFVMzT.js +1 -0
  202. package/server/public/assets/nix-C2IovEl2.js +1 -0
  203. package/server/public/assets/nord-BjZ63GNL.js +1 -0
  204. package/server/public/assets/nushell-BflTrRB5.js +1 -0
  205. package/server/public/assets/objective-c-GRClK1S7.js +1 -0
  206. package/server/public/assets/objective-cpp-l3qYw-v5.js +1 -0
  207. package/server/public/assets/ocaml-BBDyhyMH.js +1 -0
  208. package/server/public/assets/odin-jCJ7Js99.js +1 -0
  209. package/server/public/assets/one-dark-pro-PIx2Diul.js +1 -0
  210. package/server/public/assets/one-light-BFMEz49S.js +1 -0
  211. package/server/public/assets/openscad-Drf0LgCX.js +1 -0
  212. package/server/public/assets/pascal-BT2XAUTl.js +1 -0
  213. package/server/public/assets/perl-Dr47G_2Q.js +1 -0
  214. package/server/public/assets/php-BhBDWTJe.js +1 -0
  215. package/server/public/assets/pierre-dark-CTXzTLfO.js +1 -0
  216. package/server/public/assets/pierre-light-C_5rlJRo.js +1 -0
  217. package/server/public/assets/pkl-ML-dWShO.js +1 -0
  218. package/server/public/assets/plastic-BFI-Z5Z2.js +1 -0
  219. package/server/public/assets/plsql-0vd5cLro.js +1 -0
  220. package/server/public/assets/po-CbZ_uqQA.js +1 -0
  221. package/server/public/assets/poimandres-Cayhd01L.js +1 -0
  222. package/server/public/assets/polar-C4hfV8Nc.js +1 -0
  223. package/server/public/assets/postcss-osFUbTLw.js +1 -0
  224. package/server/public/assets/powerquery-CTlGUQPj.js +1 -0
  225. package/server/public/assets/powershell-DyZsOmuq.js +1 -0
  226. package/server/public/assets/preload-helper-D7oT-Xwl.js +20 -0
  227. package/server/public/assets/prisma-SS92PO_I.js +1 -0
  228. package/server/public/assets/prolog-B1O1NNVC.js +1 -0
  229. package/server/public/assets/proto-BWu3eZTs.js +1 -0
  230. package/server/public/assets/pug-Dij_IK5w.js +1 -0
  231. package/server/public/assets/puppet-tvtRVdr6.js +1 -0
  232. package/server/public/assets/purescript-Dtbpb7D-.js +1 -0
  233. package/server/public/assets/python-Dlk0Acio.js +1 -0
  234. package/server/public/assets/qml-qUwk3nhh.js +1 -0
  235. package/server/public/assets/qmldir-B-iEOngH.js +1 -0
  236. package/server/public/assets/qss-Ba0p-aHw.js +1 -0
  237. package/server/public/assets/r-WmtNicKM.js +1 -0
  238. package/server/public/assets/racket-BXDsxf2U.js +1 -0
  239. package/server/public/assets/raku-Dw1WWFXK.js +1 -0
  240. package/server/public/assets/razor-DaqiVx3Q.js +1 -0
  241. package/server/public/assets/red-BC3Ds49b.js +1 -0
  242. package/server/public/assets/reg-DXFHGaM4.js +1 -0
  243. package/server/public/assets/regexp-CiSWN5Ne.js +1 -0
  244. package/server/public/assets/rel-Dc5_Ytx2.js +1 -0
  245. package/server/public/assets/reviews-CJvVXRLH.js +1 -0
  246. package/server/public/assets/reviews-CfbuF6ib.js +1 -0
  247. package/server/public/assets/riscv-ZgswiWij.js +1 -0
  248. package/server/public/assets/ron-YghabWAH.js +1 -0
  249. package/server/public/assets/rose-pine-ByWLnVr3.js +1 -0
  250. package/server/public/assets/rose-pine-dawn-DBmeySrz.js +1 -0
  251. package/server/public/assets/rose-pine-moon-B9J-N3nK.js +1 -0
  252. package/server/public/assets/rosmsg-DTKmAsVH.js +1 -0
  253. package/server/public/assets/routes-DNxq1Fba.js +1 -0
  254. package/server/public/assets/routes-Dp0ODZ55.js +2 -0
  255. package/server/public/assets/rst-CP6xOYlY.js +1 -0
  256. package/server/public/assets/ruby-BXYLc1CM.js +1 -0
  257. package/server/public/assets/rust-nfXwuE6F.js +1 -0
  258. package/server/public/assets/sas-CFdtZutF.js +1 -0
  259. package/server/public/assets/sass-CbRjkld3.js +1 -0
  260. package/server/public/assets/scala-BzD3eypx.js +1 -0
  261. package/server/public/assets/scheme-D4d1PV1y.js +1 -0
  262. package/server/public/assets/scss-D8KhdObH.js +1 -0
  263. package/server/public/assets/sdbl-Cf-Ydnvx.js +1 -0
  264. package/server/public/assets/shaderlab-DGohHMiF.js +1 -0
  265. package/server/public/assets/shellscript-BHdEbumI.js +1 -0
  266. package/server/public/assets/shellsession-Dh-bxrap.js +1 -0
  267. package/server/public/assets/slack-dark-MszIyPZ2.js +1 -0
  268. package/server/public/assets/slack-ochin-tQ3Q0gE9.js +1 -0
  269. package/server/public/assets/smalltalk-_uWoArwn.js +1 -0
  270. package/server/public/assets/snazzy-light-9sniMEk5.js +1 -0
  271. package/server/public/assets/solarized-dark-CdD0Hxzv.js +1 -0
  272. package/server/public/assets/solarized-light-C-nsEdqF.js +1 -0
  273. package/server/public/assets/solidity-D6uC-xwP.js +1 -0
  274. package/server/public/assets/soy-cDuODfbT.js +1 -0
  275. package/server/public/assets/sparql-BgU2QITA.js +1 -0
  276. package/server/public/assets/splunk-LQYHRu14.js +1 -0
  277. package/server/public/assets/sql-CKZpK620.js +1 -0
  278. package/server/public/assets/ssh-config-B7BUl8Rd.js +1 -0
  279. package/server/public/assets/stata-BLJTbKOO.js +1 -0
  280. package/server/public/assets/styles-UDowwF7S.css +2 -0
  281. package/server/public/assets/stylus-Byjxdx_q.js +1 -0
  282. package/server/public/assets/surrealql-C96KvYaj.js +1 -0
  283. package/server/public/assets/svelte-Qnbj2GWx.js +1 -0
  284. package/server/public/assets/swift-BexLlMrU.js +1 -0
  285. package/server/public/assets/synthwave-84-BxMBwQMS.js +1 -0
  286. package/server/public/assets/system-verilog-DVGwm0mw.js +1 -0
  287. package/server/public/assets/systemd-H2IT3-p5.js +1 -0
  288. package/server/public/assets/talonscript-mKZIGM8n.js +1 -0
  289. package/server/public/assets/tasl-B7he_Ugr.js +1 -0
  290. package/server/public/assets/tcl-5mT3RxHH.js +1 -0
  291. package/server/public/assets/templ-CQPDll3D.js +1 -0
  292. package/server/public/assets/terraform-BZP0GLsT.js +1 -0
  293. package/server/public/assets/test-D7JRfog1.js +1 -0
  294. package/server/public/assets/tex-97QNLoBJ.js +1 -0
  295. package/server/public/assets/tokyo-night-CTPVdZt9.js +1 -0
  296. package/server/public/assets/toml-CTFA98he.js +1 -0
  297. package/server/public/assets/ts-tags-B8zlXe2n.js +1 -0
  298. package/server/public/assets/tsv-BayJtYdY.js +1 -0
  299. package/server/public/assets/tsx-VqRU8NCz.js +1 -0
  300. package/server/public/assets/turtle-TVCBh_kY.js +1 -0
  301. package/server/public/assets/twig-FTTF8rVk.js +1 -0
  302. package/server/public/assets/typescript-CuX0hIVY.js +1 -0
  303. package/server/public/assets/typespec-BUvaJDLF.js +1 -0
  304. package/server/public/assets/typst-8NBaY7Ec.js +1 -0
  305. package/server/public/assets/useStore-M3H8PB1v.js +1 -0
  306. package/server/public/assets/utils-DElCu2hq.js +1 -0
  307. package/server/public/assets/v-VihyTigi.js +1 -0
  308. package/server/public/assets/vala-DyFAPyX6.js +1 -0
  309. package/server/public/assets/vb-Dg1Iqi4J.js +1 -0
  310. package/server/public/assets/verilog-D2Xc-vhD.js +1 -0
  311. package/server/public/assets/vesper-DJbtqYNr.js +1 -0
  312. package/server/public/assets/vhdl-CU3BVeE7.js +1 -0
  313. package/server/public/assets/viml-hG2shuOW.js +1 -0
  314. package/server/public/assets/vitesse-black-DbG2gsc0.js +1 -0
  315. package/server/public/assets/vitesse-dark-B6WV4xXH.js +1 -0
  316. package/server/public/assets/vitesse-light-DC1pdD02.js +1 -0
  317. package/server/public/assets/vue-DXwaEU0U.js +1 -0
  318. package/server/public/assets/vue-html-QD7AJ6JJ.js +1 -0
  319. package/server/public/assets/vue-vine-Bh2m1D2Z.js +1 -0
  320. package/server/public/assets/vyper-C1wojIuk.js +1 -0
  321. package/server/public/assets/wasm-C6Y0s02M.js +1 -0
  322. package/server/public/assets/wasm-qTvCOSHz.js +1 -0
  323. package/server/public/assets/wenyan-BG5vPQF0.js +1 -0
  324. package/server/public/assets/wgsl-DrVb-Cub.js +1 -0
  325. package/server/public/assets/wikitext-PRC4s8sH.js +1 -0
  326. package/server/public/assets/wit-ChW5qvg_.js +1 -0
  327. package/server/public/assets/wolfram-B8mKuZSQ.js +1 -0
  328. package/server/public/assets/xml-BK-rcb5a.js +1 -0
  329. package/server/public/assets/xsl-dt-d2R7p.js +1 -0
  330. package/server/public/assets/yaml-UiXU3hGj.js +1 -0
  331. package/server/public/assets/zenscript-C-jEPC9j.js +1 -0
  332. package/server/public/assets/zig-EbnRGjcz.js +1 -0
  333. package/server/public/favicon.ico +0 -0
  334. package/server/public/logo192.png +0 -0
  335. package/server/public/logo512.png +0 -0
  336. package/server/public/manifest.json +25 -0
  337. package/server/public/robots.txt +3 -0
  338. package/server/public/tanstack-circle-logo.png +0 -0
  339. package/server/public/tanstack-word-logo-white.svg +1 -0
  340. package/server/server/_chunks/ssr-renderer.mjs +15 -0
  341. package/server/server/_libs/@floating-ui/core+[...].mjs +698 -0
  342. package/server/server/_libs/@floating-ui/dom+[...].mjs +644 -0
  343. package/server/server/_libs/@floating-ui/react-dom+[...].mjs +839 -0
  344. package/server/server/_libs/@pierre/diffs+[...].mjs +18578 -0
  345. package/server/server/_libs/@radix-ui/react-arrow+[...].mjs +174 -0
  346. package/server/server/_libs/@radix-ui/react-collection+[...].mjs +162 -0
  347. package/server/server/_libs/@radix-ui/react-dialog+[...].mjs +1666 -0
  348. package/server/server/_libs/@radix-ui/react-popper+[...].mjs +289 -0
  349. package/server/server/_libs/@radix-ui/react-radio-group+[...].mjs +420 -0
  350. package/server/server/_libs/@radix-ui/react-select+[...].mjs +990 -0
  351. package/server/server/_libs/@tanstack/react-router+[...].mjs +14113 -0
  352. package/server/server/_libs/_.mjs +2 -0
  353. package/server/server/_libs/chokidar+readdirp.mjs +1599 -0
  354. package/server/server/_libs/class-variance-authority+clsx.mjs +69 -0
  355. package/server/server/_libs/effect+[...].mjs +34047 -0
  356. package/server/server/_libs/h3+rou3+srvx.mjs +1195 -0
  357. package/server/server/_libs/hookable.mjs +41 -0
  358. package/server/server/_libs/lucide-react.mjs +298 -0
  359. package/server/server/_libs/pierre__theme.mjs +2668 -0
  360. package/server/server/_libs/radix-ui__number.mjs +6 -0
  361. package/server/server/_libs/radix-ui__primitive.mjs +9 -0
  362. package/server/server/_libs/radix-ui__react-direction.mjs +11 -0
  363. package/server/server/_libs/shiki.mjs +16 -0
  364. package/server/server/_libs/shikijs__langs.mjs +1355 -0
  365. package/server/server/_libs/shikijs__themes.mjs +262 -0
  366. package/server/server/_libs/tailwind-merge.mjs +1962 -0
  367. package/server/server/_libs/tanstack__history.mjs +342 -0
  368. package/server/server/_libs/tanstack__router-core.mjs +6 -0
  369. package/server/server/_libs/ufo.mjs +64 -0
  370. package/server/server/_reviewId-AWnOGz5k.mjs +33 -0
  371. package/server/server/_reviewId-Com4yOlc.mjs +29 -0
  372. package/server/server/_reviewId-DAhmekJ2.mjs +277 -0
  373. package/server/server/_reviewId-p9mhYVwa.mjs +18 -0
  374. package/server/server/_runtime.mjs +35 -0
  375. package/server/server/_ssr/action-bar-C68xGnWW.mjs +592 -0
  376. package/server/server/_ssr/api-handler-CstW2n82.mjs +189 -0
  377. package/server/server/_ssr/client-runtime-BoPuAEoA.mjs +245 -0
  378. package/server/server/_ssr/createServerRpc--0mcGlWK.mjs +12 -0
  379. package/server/server/_ssr/createSsrRpc-AwdiLXmF.mjs +16 -0
  380. package/server/server/_ssr/domain-rpc-3Ds9DPr0.mjs +287 -0
  381. package/server/server/_ssr/file-tree-CQ5w2GHh.mjs +1951 -0
  382. package/server/server/_ssr/load-scoped-diff-NL2XAcdz.mjs +45 -0
  383. package/server/server/_ssr/new-BKl_G2Ks.mjs +37 -0
  384. package/server/server/_ssr/new-BREdMFAM.mjs +12 -0
  385. package/server/server/_ssr/new-DCz5eHkb.mjs +137 -0
  386. package/server/server/_ssr/reviews-BL5Nsgst.mjs +7 -0
  387. package/server/server/_ssr/reviews-BoaEgGKs.mjs +100 -0
  388. package/server/server/_ssr/reviews-C7_NIhY8.mjs +19 -0
  389. package/server/server/_ssr/reviews-Dd69YBDa.mjs +12 -0
  390. package/server/server/_ssr/router-DLxN8FOm.mjs +415 -0
  391. package/server/server/_ssr/routes-D25G8OuS.mjs +80 -0
  392. package/server/server/_ssr/routes-lz0AN75A.mjs +929 -0
  393. package/server/server/_ssr/runtime-D9IbnMlF.mjs +1401 -0
  394. package/server/server/_ssr/server-runtime-D99qpmma.mjs +12 -0
  395. package/server/server/_ssr/ssr.mjs +5318 -0
  396. package/server/server/_ssr/start-BIQfOZtj.mjs +4 -0
  397. package/server/server/_ssr/test-CQdMYlqa.mjs +6 -0
  398. package/server/server/_ssr/todo-m_uUvxca.mjs +88 -0
  399. package/server/server/_ssr/use-keyboard-shortcuts-D5b1Mxpq.mjs +25 -0
  400. package/server/server/_ssr/utils-BuOt9_LA.mjs +8 -0
  401. package/server/server/_tanstack-start-manifest_v-CnL10NRH.mjs +71 -0
  402. package/server/server/index.mjs +2615 -0
  403. package/server/server/node_modules/detect-libc/lib/detect-libc.js +313 -0
  404. package/server/server/node_modules/detect-libc/lib/elf.js +39 -0
  405. package/server/server/node_modules/detect-libc/lib/filesystem.js +51 -0
  406. package/server/server/node_modules/detect-libc/lib/process.js +24 -0
  407. package/server/server/node_modules/detect-libc/package.json +44 -0
  408. package/server/server/node_modules/msgpackr-extract/index.js +1 -0
  409. package/server/server/node_modules/msgpackr-extract/package.json +50 -0
  410. package/server/server/node_modules/node-gyp-build-optional-packages/index.js +6 -0
  411. package/server/server/node_modules/node-gyp-build-optional-packages/node-gyp-build.js +236 -0
  412. package/server/server/node_modules/node-gyp-build-optional-packages/package.json +32 -0
  413. package/server/server/node_modules/tslib/modules/index.js +70 -0
  414. package/server/server/node_modules/tslib/modules/package.json +3 -0
  415. package/server/server/node_modules/tslib/package.json +47 -0
  416. package/server/server/node_modules/tslib/tslib.js +484 -0
  417. package/server/server/package.json +12 -0
  418. package/dist/chunk-KMYSGMD3.js +0 -3526
  419. package/dist/chunk-KMYSGMD3.js.map +0 -1
  420. package/dist/cli.js +0 -1839
  421. package/dist/cli.js.map +0 -1
  422. package/dist/mcp.js +0 -1228
  423. package/dist/mcp.js.map +0 -1
package/dist/cli.mjs ADDED
@@ -0,0 +1,2132 @@
1
+ #!/usr/bin/env node
2
+ import { a as ReviewService, c as ReviewFileRepo, d as parseDiff, f as CommentService, g as ReviewNotFound, i as TodoService, l as serializeHunks, m as TodoNotFound, n as GhService, o as GitService, r as ExportService, s as ReviewRepo, t as CoreLive, u as getDiffSummary } from "./runtime.mjs";
3
+ import { exec, execFileSync, fork } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import * as Schema from "effect/Schema";
7
+ import * as Result from "effect/Result";
8
+ import { writeFile } from "node:fs/promises";
9
+ import { ServiceMap } from "effect";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Option from "effect/Option";
13
+ import * as ManagedRuntime from "effect/ManagedRuntime";
14
+ import * as ConfigProvider from "effect/ConfigProvider";
15
+ //#region ../../packages/core/src/services/pr-preflight.ts
16
+ var PreflightFailure = class extends Schema.TaggedErrorClass()("PreflightFailure", {
17
+ exitCode: Schema.Number,
18
+ message: Schema.String,
19
+ phase: Schema.String
20
+ }) {};
21
+ /**
22
+ * Extract repository name from a git remote URL.
23
+ *
24
+ * Handles:
25
+ * - SSH: `git@github.com:owner/repo.git`
26
+ * - HTTPS: `https://github.com/owner/repo.git`
27
+ * - HTTPS: `https://github.com/owner/repo`
28
+ */
29
+ const extractRepoNameFromRemote = (remote) => {
30
+ return remote.match(/[/:]([^/]+?)(?:\.git)?$/)?.[1] ?? null;
31
+ };
32
+ /**
33
+ * Runs the strict fail-fast preflight sequence for a PR review:
34
+ *
35
+ * 1. Verify `gh` installed
36
+ * 2. Verify `gh` auth for target host
37
+ * 3. Verify local git repository
38
+ * 4. Check repository affinity (warn if mismatch, fail if no repo)
39
+ * 5. Fetch PR metadata and validate it has changed files
40
+ * 6. Fetch PR diff
41
+ */
42
+ const runPreflight = Effect.fn("PrPreflight.run")(function* (target) {
43
+ const gh = yield* GhService;
44
+ const git = yield* GitService;
45
+ yield* gh.ensureInstalled.pipe(Effect.mapError((e) => new PreflightFailure({
46
+ exitCode: 1,
47
+ message: e.message,
48
+ phase: "gh_install"
49
+ })));
50
+ yield* gh.ensureAuthenticated(target.host).pipe(Effect.mapError((e) => new PreflightFailure({
51
+ exitCode: 5,
52
+ message: e.message,
53
+ phase: "gh_auth"
54
+ })));
55
+ const localRepoPath = yield* git.getRepositoryPath.pipe(Effect.mapError(() => new PreflightFailure({
56
+ exitCode: 4,
57
+ message: "Not inside a git repository. Navigate to a repo or use --repo.",
58
+ phase: "repo_discovery"
59
+ })));
60
+ const repoInfo = yield* git.getRepositoryInfo.pipe(Effect.mapError(() => new PreflightFailure({
61
+ exitCode: 1,
62
+ message: "Could not read repository info.",
63
+ phase: "repo_info"
64
+ })));
65
+ let affinityMatch = false;
66
+ let affinityWarning = null;
67
+ if (repoInfo.remote) if (extractRepoNameFromRemote(repoInfo.remote)?.toLowerCase() === target.repo.toLowerCase()) affinityMatch = true;
68
+ else affinityWarning = `PR is from ${target.nwoRef} but local remote points to ${repoInfo.remote}. Review will be stored in ${localRepoPath}/.ringi/`;
69
+ else affinityWarning = `Local repository has no remote configured. Review will be stored in ${localRepoPath}/.ringi/`;
70
+ const metadata = yield* gh.fetchPrMetadata(target).pipe(Effect.mapError((e) => new PreflightFailure({
71
+ exitCode: 1,
72
+ message: `PR not accessible: ${e.message}`,
73
+ phase: "pr_fetch"
74
+ })));
75
+ if (metadata.changedFiles === 0) return yield* new PreflightFailure({
76
+ exitCode: 1,
77
+ message: `PR #${target.prNumber} has no changed files.`,
78
+ phase: "pr_validation"
79
+ });
80
+ const diff = yield* gh.fetchPrDiff(target).pipe(Effect.mapError((e) => new PreflightFailure({
81
+ exitCode: 1,
82
+ message: `Failed to fetch PR diff: ${e.message}`,
83
+ phase: "diff_fetch"
84
+ })));
85
+ if (!diff.trim()) return yield* new PreflightFailure({
86
+ exitCode: 1,
87
+ message: `PR #${target.prNumber} returned an empty diff.`,
88
+ phase: "diff_fetch"
89
+ });
90
+ return {
91
+ affinityMatch,
92
+ affinityWarning,
93
+ diff,
94
+ localRepoPath,
95
+ metadata,
96
+ target
97
+ };
98
+ });
99
+ //#endregion
100
+ //#region ../../packages/core/src/services/pr-session.ts
101
+ var PrSessionError = class extends Schema.TaggedErrorClass()("PrSessionError", {
102
+ code: Schema.String,
103
+ message: Schema.String
104
+ }) {};
105
+ /** Canonical source_ref for a PR: `host/owner/repo#number`. */
106
+ const prSourceRef = (target) => `${target.host}/${target.owner}/${target.repo}#${target.prNumber}`;
107
+ const parseStoredManifest = (raw) => {
108
+ try {
109
+ return JSON.parse(raw);
110
+ } catch {
111
+ return {};
112
+ }
113
+ };
114
+ /**
115
+ * Creates a new PR review session or resumes an existing non-terminal one.
116
+ *
117
+ * Resume logic:
118
+ * - Looks for an existing review with `source_type = "pull_request"` and
119
+ * matching `source_ref`.
120
+ * - If found and non-terminal (`in_progress` or `changes_requested`):
121
+ * resumes, checking for upstream drift via head OID comparison.
122
+ * - If found but terminal (`approved`): creates a new session.
123
+ * - If not found: creates a new session.
124
+ *
125
+ * New sessions persist the full diff as hunks in `review_files`, making
126
+ * the review fully offline-resumable after initial fetch.
127
+ */
128
+ const createOrResumePrSession = Effect.fn("PrSession.createOrResume")(function* (preflight) {
129
+ const gh = yield* GhService;
130
+ const repo = yield* ReviewRepo;
131
+ const fileRepo = yield* ReviewFileRepo;
132
+ const { target, metadata, diff } = preflight;
133
+ const sourceRef = prSourceRef(target);
134
+ const resumable = (yield* repo.findAll({
135
+ repositoryPath: preflight.localRepoPath,
136
+ sourceType: "pull_request",
137
+ pageSize: 100
138
+ })).data.find((r) => r.sourceRef === sourceRef && r.status !== "approved");
139
+ if (resumable) {
140
+ const currentHeadOid = yield* gh.fetchPrHeadOid(target).pipe(Effect.catch((e) => Effect.logDebug(`Could not fetch current head OID for drift check: ${e.message}`).pipe(Effect.as(metadata.headRefOid))));
141
+ const storedHeadOid = parseStoredManifest(resumable.snapshotData).headOidAtFetch ?? "";
142
+ const isStale = storedHeadOid !== "" && storedHeadOid !== currentHeadOid;
143
+ return {
144
+ isResumed: true,
145
+ isStale,
146
+ reviewId: resumable.id,
147
+ staleWarning: isStale ? `PR head has changed (${storedHeadOid.slice(0, 7)} → ${currentHeadOid.slice(0, 7)}). Review data reflects the previous version. Use --force-refresh to re-fetch.` : null
148
+ };
149
+ }
150
+ const files = parseDiff(diff);
151
+ if (files.length === 0) return yield* new PrSessionError({
152
+ code: "NO_CHANGES",
153
+ message: `PR #${target.prNumber} diff parsed to zero files.`
154
+ });
155
+ const reviewId = crypto.randomUUID();
156
+ const snapshotData = JSON.stringify({
157
+ diffByteSize: Buffer.byteLength(diff, "utf8"),
158
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
159
+ headOidAtFetch: metadata.headRefOid,
160
+ metadata,
161
+ source: "pull_request",
162
+ target,
163
+ version: 1
164
+ });
165
+ const fileInputs = files.map((f) => ({
166
+ additions: f.additions,
167
+ deletions: f.deletions,
168
+ filePath: f.newPath,
169
+ hunksData: serializeHunks(f.hunks),
170
+ oldPath: f.oldPath !== f.newPath ? f.oldPath : null,
171
+ reviewId,
172
+ status: f.status
173
+ }));
174
+ yield* repo.create({
175
+ baseRef: metadata.baseRefOid,
176
+ id: reviewId,
177
+ repositoryPath: preflight.localRepoPath,
178
+ snapshotData,
179
+ sourceRef,
180
+ sourceType: "pull_request",
181
+ status: "in_progress"
182
+ });
183
+ yield* fileRepo.createBulk(fileInputs);
184
+ return {
185
+ isResumed: false,
186
+ isStale: false,
187
+ reviewId,
188
+ staleWarning: null
189
+ };
190
+ });
191
+ /**
192
+ * Re-fetches PR data for an existing session and updates stored diff/metadata.
193
+ * Existing annotations are preserved (re-anchoring is a v1.1 feature).
194
+ */
195
+ const forceRefreshPrSession = Effect.fn("PrSession.forceRefresh")(function* (reviewId, target) {
196
+ const gh = yield* GhService;
197
+ const repo = yield* ReviewRepo;
198
+ const fileRepo = yield* ReviewFileRepo;
199
+ if (!(yield* repo.findById(reviewId))) return yield* new PrSessionError({
200
+ code: "NOT_FOUND",
201
+ message: `Review session ${reviewId} not found.`
202
+ });
203
+ const metadata = yield* gh.fetchPrMetadata(target);
204
+ const diff = yield* gh.fetchPrDiff(target);
205
+ const files = parseDiff(diff);
206
+ const snapshotData = JSON.stringify({
207
+ diffByteSize: Buffer.byteLength(diff, "utf8"),
208
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
209
+ headOidAtFetch: metadata.headRefOid,
210
+ metadata,
211
+ source: "pull_request",
212
+ target,
213
+ version: 1
214
+ });
215
+ yield* repo.updateSnapshotData(reviewId, snapshotData);
216
+ yield* fileRepo.deleteByReview(reviewId);
217
+ const fileInputs = files.map((f) => ({
218
+ additions: f.additions,
219
+ deletions: f.deletions,
220
+ filePath: f.newPath,
221
+ hunksData: serializeHunks(f.hunks),
222
+ oldPath: f.oldPath !== f.newPath ? f.oldPath : null,
223
+ reviewId,
224
+ status: f.status
225
+ }));
226
+ yield* fileRepo.createBulk(fileInputs);
227
+ return {
228
+ filesUpdated: files.length,
229
+ headOid: metadata.headRefOid,
230
+ reviewId,
231
+ summary: getDiffSummary(files)
232
+ };
233
+ });
234
+ //#endregion
235
+ //#region ../../packages/core/src/services/pr-url.ts
236
+ var InvalidPrUrl = class extends Schema.TaggedErrorClass()("InvalidPrUrl", {
237
+ message: Schema.String,
238
+ url: Schema.String
239
+ }) {};
240
+ /**
241
+ * Quick heuristic: does this string look like a PR URL?
242
+ *
243
+ * Used by the CLI parser to distinguish `review <url>` from `review <verb>`.
244
+ * No ambiguity: no review verb starts with `http`.
245
+ */
246
+ const looksLikePrUrl = (s) => /^https?:\/\/[^/]+\/[^/]+\/[^/]+\/pull\/\d+/.test(s);
247
+ /**
248
+ * Parses a GitHub PR URL into structured components.
249
+ *
250
+ * Supports:
251
+ * - `https://github.com/owner/repo/pull/42`
252
+ * - `https://github.com/owner/repo/pull/42/files`
253
+ * - `https://github.com/owner/repo/pull/42/commits`
254
+ * - `https://ghe.corp.com/owner/repo/pull/42`
255
+ * - `http://...` (for GHE behind VPN)
256
+ *
257
+ * Does NOT support:
258
+ * - SSH URLs, API URLs, short references like `owner/repo#42`
259
+ */
260
+ const parsePrUrl = Effect.fn("parsePrUrl")(function* (raw) {
261
+ const url = yield* Effect.try({
262
+ catch: () => new InvalidPrUrl({
263
+ message: "Not a valid URL. Expected: https://github.com/<owner>/<repo>/pull/<number>",
264
+ url: raw
265
+ }),
266
+ try: () => new URL(raw)
267
+ });
268
+ if (url.protocol !== "https:" && url.protocol !== "http:") return yield* new InvalidPrUrl({
269
+ message: `Unsupported protocol: ${url.protocol}. Expected https:// or http://`,
270
+ url: raw
271
+ });
272
+ const segments = url.pathname.split("/").filter(Boolean);
273
+ if (segments.length < 4 || segments[2] !== "pull") return yield* new InvalidPrUrl({
274
+ message: "URL path must match /<owner>/<repo>/pull/<number>",
275
+ url: raw
276
+ });
277
+ const owner = segments[0];
278
+ const repo = segments[1];
279
+ const prNumber = Number.parseInt(segments[3], 10);
280
+ if (!Number.isFinite(prNumber) || prNumber <= 0) return yield* new InvalidPrUrl({
281
+ message: `Invalid PR number: ${segments[3]}`,
282
+ url: raw
283
+ });
284
+ return {
285
+ host: url.host,
286
+ nwoRef: `${owner}/${repo}`,
287
+ owner,
288
+ prNumber,
289
+ repo,
290
+ url: `${url.protocol}//${url.host}/${owner}/${repo}/pull/${prNumber}`
291
+ };
292
+ });
293
+ //#endregion
294
+ //#region src/cli/config.ts
295
+ var CliConfig = class extends ServiceMap.Service()("@ringi/CliConfig") {};
296
+ /**
297
+ * Wraps a concrete {@link CliConfigShape} in a layer for the Effect runtime.
298
+ */
299
+ const CliConfigLive = (config) => Layer.succeed(CliConfig, CliConfig.of(config));
300
+ //#endregion
301
+ //#region src/cli/contracts.ts
302
+ const ExitCode = {
303
+ AuthFailure: 5,
304
+ ResourceNotFound: 3,
305
+ RuntimeFailure: 1,
306
+ StateUnavailable: 4,
307
+ Success: 0,
308
+ UsageError: 2
309
+ };
310
+ const success = (command, result, nextActions = []) => ({
311
+ command,
312
+ next_actions: nextActions,
313
+ ok: true,
314
+ result
315
+ });
316
+ const failure = (command, error, fix, nextActions = []) => ({
317
+ command,
318
+ error,
319
+ fix,
320
+ next_actions: nextActions,
321
+ ok: false
322
+ });
323
+ /**
324
+ * Carries an exit code and optional operator-facing details so callers can
325
+ * present a short message without losing the underlying reason.
326
+ */
327
+ var CliFailure = class extends Schema.TaggedErrorClass()("CliFailure", {
328
+ details: Schema.String.pipe(Schema.optionalKey),
329
+ exitCode: Schema.Number,
330
+ message: Schema.String
331
+ }) {};
332
+ //#endregion
333
+ //#region src/cli/commands.ts
334
+ const formatTable = (headers, rows) => {
335
+ const widths = headers.map((header, index) => {
336
+ const cellWidths = rows.map((row) => row[index]?.length ?? 0);
337
+ return Math.max(header.length, ...cellWidths);
338
+ });
339
+ const renderRow = (row) => row.map((cell, index) => cell.padEnd(widths.at(index) ?? 0)).join(" ").trimEnd();
340
+ return [
341
+ renderRow(headers),
342
+ renderRow(widths.map((width) => "-".repeat(width))),
343
+ ...rows.map(renderRow)
344
+ ].join("\n");
345
+ };
346
+ const renderReviewList = (reviews) => {
347
+ if (reviews.length === 0) return "No reviews found.";
348
+ return formatTable([
349
+ "ID",
350
+ "STATUS",
351
+ "SOURCE",
352
+ "FILES",
353
+ "CREATED"
354
+ ], reviews.map((review) => [
355
+ review.id,
356
+ review.status,
357
+ review.sourceType,
358
+ String(review.fileCount),
359
+ review.createdAt
360
+ ]));
361
+ };
362
+ const renderReviewShow = (input) => {
363
+ const { comments, review, todos } = input;
364
+ const lines = [
365
+ `Review ${review.id}`,
366
+ `Status: ${review.status}`,
367
+ `Source: ${review.sourceType}${review.sourceRef ? ` (${review.sourceRef})` : ""}`,
368
+ `Created: ${review.createdAt}`,
369
+ `Files: ${review.summary.totalFiles}`,
370
+ `Diff: +${review.summary.totalAdditions} / -${review.summary.totalDeletions}`
371
+ ];
372
+ if (review.files.length > 0) {
373
+ lines.push("", "Files:");
374
+ for (const file of review.files) lines.push(`- ${file.status.toUpperCase()} ${file.filePath} (+${file.additions} -${file.deletions})`);
375
+ }
376
+ if (comments && comments.length > 0) {
377
+ lines.push("", "Comments:");
378
+ for (const comment of comments) {
379
+ const location = `${comment.filePath}:${comment.lineNumber ?? "-"}`;
380
+ const state = comment.resolved ? "resolved" : "open";
381
+ lines.push(`- [${state}] ${location} ${comment.content}`);
382
+ }
383
+ }
384
+ if (todos && todos.length > 0) {
385
+ lines.push("", "Todos:");
386
+ for (const todo of todos) {
387
+ const marker = todo.completed ? "x" : " ";
388
+ lines.push(`- [${marker}] (${todo.position + 1}) ${todo.content}`);
389
+ }
390
+ }
391
+ return lines.join("\n");
392
+ };
393
+ const renderTodoList = (todos) => {
394
+ if (todos.length === 0) return "No todos found.";
395
+ return todos.map((todo) => `- [${todo.completed ? "x" : " "}] (${todo.position + 1}) ${todo.content}`).join("\n");
396
+ };
397
+ const renderSourceList = (input) => {
398
+ const lines = [
399
+ `Repository: ${input.repo.name}`,
400
+ `Path: ${input.repo.path}`,
401
+ `Current branch: ${input.repo.branch}`,
402
+ `Staged files: ${input.stagedFiles.length}`
403
+ ];
404
+ if (input.stagedFiles.length > 0) {
405
+ lines.push("", "Staged:");
406
+ for (const file of input.stagedFiles) lines.push(`- ${file.status} ${file.path}`);
407
+ }
408
+ if (input.branches.length > 0) {
409
+ lines.push("", "Branches:");
410
+ for (const branch of input.branches.slice(0, 10)) lines.push(`- ${branch.current ? "*" : " "} ${branch.name}`);
411
+ }
412
+ if (input.commits.length > 0) {
413
+ lines.push("", "Recent commits:");
414
+ for (const commit of input.commits.slice(0, 5)) lines.push(`- ${commit.hash.slice(0, 8)} ${commit.message} (${commit.author})`);
415
+ }
416
+ return lines.join("\n");
417
+ };
418
+ /**
419
+ * Resolves the special "last" selector before show/export handlers ask the
420
+ * shared services for a concrete review id.
421
+ */
422
+ const resolveReviewSelector = Effect.fn("CLI.resolveReviewSelector")(function* resolveReviewSelector(selector) {
423
+ if (selector !== "last") return selector;
424
+ const cliConfig = yield* CliConfig;
425
+ const [review] = (yield* (yield* ReviewService).list({
426
+ page: 1,
427
+ pageSize: 1,
428
+ repositoryPath: cliConfig.repoRoot
429
+ })).reviews;
430
+ if (!review) return yield* new CliFailure({
431
+ exitCode: ExitCode.ResourceNotFound,
432
+ message: "No review sessions exist for this repository yet."
433
+ });
434
+ return review.id;
435
+ });
436
+ /**
437
+ * Mutating CLI commands stay server-backed so they share the same write path as
438
+ * the other clients instead of growing a second local-only behavior surface.
439
+ */
440
+ const requireServerMode = (label) => Effect.fail(new CliFailure({
441
+ details: "Start 'ringi serve' and retry the command.",
442
+ exitCode: ExitCode.StateUnavailable,
443
+ message: `${label} requires a running local Ringi server. Standalone local writes are intentionally unsupported.`
444
+ }));
445
+ const diffSourceStrategies = {
446
+ branch: (git, command) => git.getBranchDiff(command.branch ?? ""),
447
+ commits: (git, command) => git.getCommitDiff((command.commits ?? "").split(",").map((item) => item.trim()).filter(Boolean)),
448
+ staged: (git) => git.getStagedDiff
449
+ };
450
+ const runReviewList = Effect.fn("CLI.reviewList")(function* runReviewList(command) {
451
+ const reviewService = yield* ReviewService;
452
+ const cliConfig = yield* CliConfig;
453
+ const result = yield* reviewService.list({
454
+ page: command.page,
455
+ pageSize: command.limit,
456
+ repositoryPath: cliConfig.repoRoot,
457
+ sourceType: command.source,
458
+ status: command.status
459
+ });
460
+ const nextActions = [];
461
+ for (const review of result.reviews.slice(0, 3)) nextActions.push({
462
+ command: `ringi review show ${review.id} --comments --todos`,
463
+ description: `Inspect review ${review.id} (${review.status})`
464
+ });
465
+ if (result.reviews.length > 0) nextActions.push({
466
+ command: "ringi review show <id> [--comments] [--todos]",
467
+ description: "Show full review details",
468
+ params: { id: {
469
+ description: "Review ID or 'last'",
470
+ required: true
471
+ } }
472
+ });
473
+ nextActions.push({
474
+ command: "ringi review create [--source <source>]",
475
+ description: "Create a new review session",
476
+ params: { source: {
477
+ default: "staged",
478
+ enum: [
479
+ "staged",
480
+ "branch",
481
+ "commits"
482
+ ]
483
+ } }
484
+ });
485
+ return {
486
+ data: result,
487
+ human: renderReviewList(result.reviews),
488
+ nextActions
489
+ };
490
+ });
491
+ const runReviewShow = Effect.fn("CLI.reviewShow")(function* runReviewShow(command) {
492
+ const reviewService = yield* ReviewService;
493
+ const todoService = yield* TodoService;
494
+ const commentService = yield* CommentService;
495
+ const reviewId = yield* resolveReviewSelector(command.id);
496
+ const review = yield* reviewService.getById(reviewId);
497
+ const data = {
498
+ comments: command.comments ? yield* commentService.getByReview(reviewId) : void 0,
499
+ review,
500
+ todos: command.todos ? (yield* todoService.list({ reviewId })).data : void 0
501
+ };
502
+ const nextActions = [
503
+ {
504
+ command: `ringi review export ${reviewId}`,
505
+ description: "Export this review as markdown"
506
+ },
507
+ {
508
+ command: `ringi review show ${reviewId} --comments --todos`,
509
+ description: "Show with comments and todos"
510
+ },
511
+ {
512
+ command: "ringi todo list [--review <review-id>] [--status <status>]",
513
+ description: "List todos for this review",
514
+ params: {
515
+ "review-id": { value: reviewId },
516
+ status: {
517
+ default: "pending",
518
+ enum: [
519
+ "pending",
520
+ "done",
521
+ "all"
522
+ ]
523
+ }
524
+ }
525
+ },
526
+ {
527
+ command: "ringi review list",
528
+ description: "Back to review list"
529
+ }
530
+ ];
531
+ return {
532
+ data,
533
+ human: renderReviewShow(data),
534
+ nextActions
535
+ };
536
+ });
537
+ const runReviewExport = Effect.fn("CLI.reviewExport")(function* runReviewExport(command) {
538
+ if (command.noResolved || command.noSnippets) yield* new CliFailure({
539
+ exitCode: ExitCode.UsageError,
540
+ message: "--no-resolved and --no-snippets are documented, but the shared export service does not support adapter-level filtering yet."
541
+ });
542
+ const exportService = yield* ExportService;
543
+ const cliConfig = yield* CliConfig;
544
+ const reviewId = yield* resolveReviewSelector(command.id);
545
+ const markdown = yield* exportService.exportReview(reviewId);
546
+ const outputPath = command.outputPath ? resolve(cliConfig.cwd, command.outputPath) : void 0;
547
+ if (outputPath) yield* Effect.tryPromise({
548
+ catch: (error) => new CliFailure({
549
+ exitCode: ExitCode.RuntimeFailure,
550
+ message: `Failed to write export to ${outputPath}: ${String(error)}`
551
+ }),
552
+ try: () => writeFile(outputPath, markdown, "utf8")
553
+ });
554
+ const shouldPrintMarkdown = command.stdout || !outputPath;
555
+ const data = {
556
+ markdown,
557
+ outputPath: outputPath ?? null,
558
+ reviewId
559
+ };
560
+ const nextActions = [{
561
+ command: `ringi review show ${reviewId}`,
562
+ description: "View the exported review"
563
+ }, {
564
+ command: "ringi review list",
565
+ description: "Back to review list"
566
+ }];
567
+ return {
568
+ data,
569
+ human: shouldPrintMarkdown ? markdown : `Exported review ${reviewId} to ${outputPath}.`,
570
+ nextActions
571
+ };
572
+ });
573
+ const runSourceList = Effect.fn("CLI.sourceList")(function* runSourceList() {
574
+ const gitService = yield* GitService;
575
+ const repo = yield* gitService.getRepositoryInfo;
576
+ const stagedFiles = yield* gitService.getStagedFiles;
577
+ const data = {
578
+ branches: yield* gitService.getBranches,
579
+ commits: (yield* gitService.getCommits({
580
+ limit: 10,
581
+ offset: 0
582
+ })).commits,
583
+ repo,
584
+ stagedFiles
585
+ };
586
+ return {
587
+ data,
588
+ human: renderSourceList(data),
589
+ nextActions: [
590
+ {
591
+ command: "ringi source diff <source> [--stat]",
592
+ description: "View diff for a source",
593
+ params: { source: { enum: [
594
+ "staged",
595
+ "branch",
596
+ "commits"
597
+ ] } }
598
+ },
599
+ {
600
+ command: "ringi review create [--source <source>]",
601
+ description: "Create a review from a source",
602
+ params: { source: {
603
+ default: "staged",
604
+ enum: [
605
+ "staged",
606
+ "branch",
607
+ "commits"
608
+ ]
609
+ } }
610
+ },
611
+ {
612
+ command: "ringi review list",
613
+ description: "List existing reviews"
614
+ }
615
+ ]
616
+ };
617
+ });
618
+ const runSourceDiff = Effect.fn("CLI.sourceDiff")(function* runSourceDiff(command) {
619
+ const gitService = yield* GitService;
620
+ const strategy = diffSourceStrategies[command.source];
621
+ if (!strategy) return yield* new CliFailure({
622
+ exitCode: ExitCode.UsageError,
623
+ message: "Unsupported review source."
624
+ });
625
+ const diffText = yield* strategy(gitService, command);
626
+ if (!diffText.trim()) yield* new CliFailure({
627
+ exitCode: ExitCode.RuntimeFailure,
628
+ message: "No diff available for the requested source."
629
+ });
630
+ const files = parseDiff(diffText);
631
+ const data = {
632
+ diff: diffText,
633
+ source: command.source,
634
+ summary: getDiffSummary(files)
635
+ };
636
+ const nextActions = [{
637
+ command: `ringi review create --source ${command.source}`,
638
+ description: `Create a review from this ${command.source} diff`
639
+ }, {
640
+ command: "ringi source list",
641
+ description: "List repository sources"
642
+ }];
643
+ return {
644
+ data,
645
+ human: command.stat ? [
646
+ `Source: ${command.source}`,
647
+ `Files: ${data.summary.totalFiles}`,
648
+ `Additions: ${data.summary.totalAdditions}`,
649
+ `Deletions: ${data.summary.totalDeletions}`
650
+ ].join("\n") : diffText,
651
+ nextActions
652
+ };
653
+ });
654
+ const runReviewStatus = Effect.fn("CLI.reviewStatus")(function* runReviewStatus(command) {
655
+ const reviewService = yield* ReviewService;
656
+ const todoService = yield* TodoService;
657
+ const commentService = yield* CommentService;
658
+ const gitService = yield* GitService;
659
+ const cliConfig = yield* CliConfig;
660
+ const repo = yield* gitService.getRepositoryInfo;
661
+ const stagedFiles = yield* gitService.getStagedFiles;
662
+ let reviewId;
663
+ if (command.reviewId) reviewId = yield* resolveReviewSelector(command.reviewId);
664
+ const reviews = yield* reviewService.list({
665
+ page: 1,
666
+ pageSize: 1,
667
+ repositoryPath: cliConfig.repoRoot,
668
+ sourceType: command.source
669
+ });
670
+ const latestReview = reviewId ? yield* reviewService.getById(reviewId) : reviews.reviews[0];
671
+ let commentStats;
672
+ let todoStats;
673
+ if (latestReview) {
674
+ commentStats = yield* commentService.getStats(latestReview.id);
675
+ todoStats = yield* todoService.getStats();
676
+ }
677
+ const data = {
678
+ commentStats: commentStats ?? null,
679
+ repository: {
680
+ branch: repo.branch,
681
+ name: repo.name,
682
+ path: repo.path,
683
+ stagedFileCount: stagedFiles.length
684
+ },
685
+ review: latestReview ? {
686
+ createdAt: latestReview.createdAt,
687
+ id: latestReview.id,
688
+ sourceType: latestReview.sourceType,
689
+ status: latestReview.status
690
+ } : null,
691
+ todoStats: todoStats ?? null
692
+ };
693
+ const lines = [
694
+ `Repository: ${repo.name}`,
695
+ `Branch: ${repo.branch}`,
696
+ `Staged files: ${stagedFiles.length}`
697
+ ];
698
+ if (latestReview) {
699
+ lines.push("", `Review: ${latestReview.id}`, `Status: ${latestReview.status}`, `Source: ${latestReview.sourceType}`);
700
+ if (commentStats) lines.push(`Comments: ${commentStats.unresolved ?? 0} unresolved / ${commentStats.total} total`);
701
+ if (todoStats) lines.push(`Todos: ${todoStats.pending} pending / ${todoStats.total} total`);
702
+ } else lines.push("", "No review sessions found.");
703
+ const nextActions = [];
704
+ if (latestReview) nextActions.push({
705
+ command: `ringi review show ${latestReview.id} --comments --todos`,
706
+ description: "Inspect the latest review"
707
+ }, {
708
+ command: `ringi review export ${latestReview.id}`,
709
+ description: "Export the latest review"
710
+ });
711
+ nextActions.push({
712
+ command: "ringi review create [--source <source>]",
713
+ description: "Create a new review session",
714
+ params: { source: {
715
+ default: "staged",
716
+ enum: [
717
+ "staged",
718
+ "branch",
719
+ "commits"
720
+ ]
721
+ } }
722
+ });
723
+ return {
724
+ data,
725
+ human: lines.join("\n"),
726
+ nextActions
727
+ };
728
+ });
729
+ const runTodoList = Effect.fn("CLI.todoList")(function* runTodoList(command) {
730
+ const result = yield* (yield* TodoService).list({
731
+ completed: command.status === "all" ? void 0 : command.status === "done",
732
+ limit: command.limit,
733
+ offset: command.offset,
734
+ reviewId: command.reviewId
735
+ });
736
+ const nextActions = [];
737
+ if (command.reviewId) nextActions.push({
738
+ command: `ringi review show ${command.reviewId}`,
739
+ description: "View the associated review"
740
+ });
741
+ nextActions.push({
742
+ command: "ringi todo add --text <text> [--review <review-id>]",
743
+ description: "Add a new todo",
744
+ params: { text: {
745
+ description: "Todo text",
746
+ required: true
747
+ } }
748
+ }, {
749
+ command: "ringi review list",
750
+ description: "List reviews"
751
+ });
752
+ return {
753
+ data: result,
754
+ human: renderTodoList(result.data),
755
+ nextActions
756
+ };
757
+ });
758
+ const runReviewPr = Effect.fn("CLI.reviewPr")(function* runReviewPr(command) {
759
+ const target = yield* parsePrUrl(command.prUrl).pipe(Effect.mapError((e) => new CliFailure({
760
+ exitCode: ExitCode.UsageError,
761
+ message: e.message
762
+ })));
763
+ const preflight = yield* runPreflight(target).pipe(Effect.mapError((e) => new CliFailure({
764
+ exitCode: e.exitCode,
765
+ message: e.message
766
+ })));
767
+ if (preflight.affinityWarning) yield* Effect.logWarning(preflight.affinityWarning);
768
+ let session;
769
+ if (command.forceRefresh) {
770
+ const reviewService = yield* ReviewService;
771
+ const sourceRef = prSourceRef(target);
772
+ const cliConfig = yield* CliConfig;
773
+ const resumable = (yield* reviewService.list({
774
+ repositoryPath: cliConfig.repoRoot,
775
+ sourceType: "pull_request",
776
+ pageSize: 100
777
+ })).reviews.find((r) => r.sourceRef === sourceRef && r.status !== "approved");
778
+ if (resumable) {
779
+ yield* forceRefreshPrSession(resumable.id, target).pipe(Effect.mapError((e) => new CliFailure({
780
+ exitCode: ExitCode.RuntimeFailure,
781
+ message: e.message
782
+ })));
783
+ session = {
784
+ isResumed: true,
785
+ isStale: false,
786
+ reviewId: resumable.id,
787
+ staleWarning: null
788
+ };
789
+ } else session = yield* createOrResumePrSession(preflight).pipe(Effect.mapError((e) => new CliFailure({
790
+ exitCode: ExitCode.RuntimeFailure,
791
+ message: e.message
792
+ })));
793
+ } else session = yield* createOrResumePrSession(preflight).pipe(Effect.mapError((e) => new CliFailure({
794
+ exitCode: ExitCode.RuntimeFailure,
795
+ message: e.message
796
+ })));
797
+ if (session.staleWarning) yield* Effect.logWarning(session.staleWarning);
798
+ const serverUrl = `http://localhost:${command.port}`;
799
+ const reviewUrl = `${serverUrl}/review/${session.reviewId}`;
800
+ const data = {
801
+ isResumed: session.isResumed,
802
+ isStale: session.isStale,
803
+ prNumber: target.prNumber,
804
+ prUrl: target.url,
805
+ reviewId: session.reviewId,
806
+ reviewUrl
807
+ };
808
+ const statusLabel = session.isResumed ? command.forceRefresh ? "(refreshed)" : "(resumed)" : "(new)";
809
+ const humanLines = [
810
+ `PR #${target.prNumber}: ${preflight.metadata.title}`,
811
+ `Review: ${session.reviewId} ${statusLabel}`,
812
+ `Author: ${preflight.metadata.author.login}`,
813
+ `Branch: ${preflight.metadata.headRefName} → ${preflight.metadata.baseRefName}`,
814
+ `Files: ${preflight.metadata.changedFiles} (+${preflight.metadata.additions} -${preflight.metadata.deletions})`,
815
+ "",
816
+ `Server: ${serverUrl}`,
817
+ `Review: ${reviewUrl}`
818
+ ];
819
+ if (preflight.metadata.isDraft) humanLines.splice(1, 0, "⚠ Draft PR");
820
+ if (preflight.metadata.state === "CLOSED" || preflight.metadata.state === "MERGED") humanLines.splice(1, 0, `⚠ This PR is ${preflight.metadata.state}`);
821
+ const nextActions = [{
822
+ command: `ringi review show ${session.reviewId} --comments --todos`,
823
+ description: "Inspect review details"
824
+ }, {
825
+ command: `ringi review export ${session.reviewId}`,
826
+ description: "Export review as markdown"
827
+ }];
828
+ if (session.isStale) nextActions.unshift({
829
+ command: `ringi review ${command.prUrl} --force-refresh`,
830
+ description: "Re-fetch PR data with latest changes"
831
+ });
832
+ return {
833
+ data,
834
+ human: humanLines.join("\n"),
835
+ nextActions
836
+ };
837
+ });
838
+ /**
839
+ * Data-driven command registry. Each command kind maps to its handler.
840
+ * Adding a new command means adding one entry — no switch duplication.
841
+ */
842
+ const COMMAND_HANDLERS = {
843
+ "data-migrate": () => requireServerMode("ringi data migrate"),
844
+ "data-reset": () => requireServerMode("ringi data reset"),
845
+ doctor: () => Effect.succeed({
846
+ data: {
847
+ checks: [],
848
+ ok: true
849
+ },
850
+ human: "ringi doctor: not yet implemented.",
851
+ nextActions: []
852
+ }),
853
+ events: () => requireServerMode("ringi events"),
854
+ mcp: () => Effect.fail(new CliFailure({
855
+ exitCode: ExitCode.UsageError,
856
+ message: "ringi mcp is a runtime command. Use it directly, not through the command dispatcher."
857
+ })),
858
+ "review-create": () => requireServerMode("ringi review create"),
859
+ "review-export": (c) => runReviewExport(c),
860
+ "review-list": (c) => runReviewList(c),
861
+ "review-pr": (c) => runReviewPr(c),
862
+ "review-resolve": () => requireServerMode("ringi review resolve"),
863
+ "review-show": (c) => runReviewShow(c),
864
+ "review-status": (c) => runReviewStatus(c),
865
+ serve: () => Effect.fail(new CliFailure({
866
+ exitCode: ExitCode.UsageError,
867
+ message: "ringi serve is a runtime command. Use it directly, not through the command dispatcher."
868
+ })),
869
+ "source-diff": (c) => runSourceDiff(c),
870
+ "source-list": () => runSourceList(),
871
+ "todo-add": () => requireServerMode("ringi todo add"),
872
+ "todo-clear": () => requireServerMode("ringi todo clear"),
873
+ "todo-done": () => requireServerMode("ringi todo done"),
874
+ "todo-list": (c) => runTodoList(c),
875
+ "todo-move": () => requireServerMode("ringi todo move"),
876
+ "todo-remove": () => requireServerMode("ringi todo remove"),
877
+ "todo-undone": () => requireServerMode("ringi todo undone")
878
+ };
879
+ /** Human-readable command label for the JSON envelope `command` field. */
880
+ const COMMAND_LABELS = {
881
+ "data-migrate": "ringi data migrate",
882
+ "data-reset": "ringi data reset",
883
+ doctor: "ringi doctor",
884
+ events: "ringi events",
885
+ mcp: "ringi mcp",
886
+ "review-create": "ringi review create",
887
+ "review-export": "ringi review export",
888
+ "review-list": "ringi review list",
889
+ "review-pr": "ringi review <pr-url>",
890
+ "review-resolve": "ringi review resolve",
891
+ "review-show": "ringi review show",
892
+ "review-status": "ringi review status",
893
+ serve: "ringi serve",
894
+ "source-diff": "ringi source diff",
895
+ "source-list": "ringi source list",
896
+ "todo-add": "ringi todo add",
897
+ "todo-clear": "ringi todo clear",
898
+ "todo-done": "ringi todo done",
899
+ "todo-list": "ringi todo list",
900
+ "todo-move": "ringi todo move",
901
+ "todo-remove": "ringi todo remove",
902
+ "todo-undone": "ringi todo undone"
903
+ };
904
+ const commandLabel = (command) => COMMAND_LABELS[command.kind] ?? `ringi ${command.kind}`;
905
+ const runCommand = (command) => {
906
+ const handler = COMMAND_HANDLERS[command.kind];
907
+ if (!handler) return Effect.fail(new CliFailure({
908
+ exitCode: ExitCode.UsageError,
909
+ message: `No executable handler exists for ${command.kind}.`
910
+ }));
911
+ return handler(command);
912
+ };
913
+ //#endregion
914
+ //#region src/cli/parser.ts
915
+ const REVIEW_SOURCES = new Set([
916
+ "branch",
917
+ "commits",
918
+ "pull_request",
919
+ "staged"
920
+ ]);
921
+ const REVIEW_STATUSES = new Set([
922
+ "approved",
923
+ "changes_requested",
924
+ "in_progress"
925
+ ]);
926
+ const TODO_STATUSES = new Set([
927
+ "all",
928
+ "done",
929
+ "pending"
930
+ ]);
931
+ const usageError = (message) => new CliFailure({
932
+ exitCode: ExitCode.UsageError,
933
+ message
934
+ });
935
+ /**
936
+ * Consumes the next token as a flag value, advancing the cursor by 2.
937
+ * Rejects another flag in the value slot so typos fail fast.
938
+ */
939
+ const requireValue = (state, flag) => {
940
+ const value = state.tokens[state.index + 1];
941
+ if (!value || value.startsWith("-")) return Option.some(usageError(`Missing value for ${flag}.`));
942
+ state.index += 2;
943
+ return Option.none();
944
+ };
945
+ /** Peek at the value that {@link requireValue} would consume. */
946
+ const peekValue = (state) => state.tokens[state.index + 1] ?? "";
947
+ const decodePositiveInt = (raw, flag) => {
948
+ const value = Number.parseInt(raw, 10);
949
+ if (!Number.isInteger(value) || value < 0) return Result.fail(usageError(`${flag} must be a non-negative integer.`));
950
+ return Result.succeed(value);
951
+ };
952
+ const decodeEnum = (raw, valid, label) => {
953
+ if (!valid.has(raw)) return Result.fail(usageError(`Invalid ${label}: ${raw}.`));
954
+ return Result.succeed(raw);
955
+ };
956
+ /** Boolean flag: sets a key to `true`, advances cursor by 1. */
957
+ const boolFlag = (key) => (state, acc) => {
958
+ acc[key] = true;
959
+ state.index += 1;
960
+ return Option.none();
961
+ };
962
+ /** String flag: consumes next token, assigns to key. */
963
+ const stringFlag = (key) => (state, acc) => {
964
+ const flag = state.tokens[state.index] ?? "";
965
+ const value = peekValue(state);
966
+ const error = requireValue(state, flag);
967
+ if (Option.isSome(error)) return error;
968
+ acc[key] = value;
969
+ return Option.none();
970
+ };
971
+ /** Positive integer flag with an optional minimum (exclusive). */
972
+ const positiveIntFlag = (key, opts) => (state, acc) => {
973
+ const flag = state.tokens[state.index] ?? "";
974
+ const raw = peekValue(state);
975
+ const error = requireValue(state, flag);
976
+ if (Option.isSome(error)) return error;
977
+ const decoded = decodePositiveInt(raw, flag);
978
+ if (Result.isFailure(decoded)) return Option.some(decoded.failure);
979
+ if (opts?.min !== void 0 && decoded.success <= opts.min) return Option.some(usageError(`${flag} must be greater than ${opts.min}.`));
980
+ acc[key] = decoded.success;
981
+ return Option.none();
982
+ };
983
+ /** Enum flag: consumes next token, validates membership, assigns to key. */
984
+ const enumFlag = (key, valid, label) => (state, acc) => {
985
+ const flag = state.tokens[state.index] ?? "";
986
+ const raw = peekValue(state);
987
+ const error = requireValue(state, flag);
988
+ if (Option.isSome(error)) return error;
989
+ const decoded = decodeEnum(raw, valid, label);
990
+ if (Result.isFailure(decoded)) return Option.some(decoded.failure);
991
+ acc[key] = decoded.success;
992
+ return Option.none();
993
+ };
994
+ const createDefaultOptions = () => ({
995
+ color: true,
996
+ dbPath: void 0,
997
+ help: false,
998
+ json: false,
999
+ quiet: false,
1000
+ repo: void 0,
1001
+ verbose: false,
1002
+ version: false
1003
+ });
1004
+ /**
1005
+ * Global flags are accepted before or after subcommands because wrappers often
1006
+ * prepend them without preserving the CLI's preferred ordering.
1007
+ */
1008
+ const GLOBAL_FLAG_HANDLERS = {
1009
+ "--db-path": stringFlag("dbPath"),
1010
+ "--help": boolFlag("help"),
1011
+ "--json": boolFlag("json"),
1012
+ "--no-color": (state, acc) => {
1013
+ acc.color = false;
1014
+ state.index += 1;
1015
+ return Option.none();
1016
+ },
1017
+ "--quiet": boolFlag("quiet"),
1018
+ "--repo": stringFlag("repo"),
1019
+ "--verbose": boolFlag("verbose"),
1020
+ "--version": boolFlag("version")
1021
+ };
1022
+ const maybeParseGlobalFlag = (state) => {
1023
+ const token = state.tokens[state.index];
1024
+ if (!token) return false;
1025
+ const handler = GLOBAL_FLAG_HANDLERS[token];
1026
+ if (!handler) return false;
1027
+ handler(state, state.options);
1028
+ return true;
1029
+ };
1030
+ /**
1031
+ * Consumes all remaining tokens in {@link state} by dispatching to the matching
1032
+ * handler in {@link handlers}. Global flags are tried first. Unknown flags
1033
+ * produce a usage error naming the {@link commandLabel}.
1034
+ */
1035
+ const runFlagLoop = (state, acc, handlers, commandLabel) => {
1036
+ while (state.index < state.tokens.length) {
1037
+ if (maybeParseGlobalFlag(state)) continue;
1038
+ const token = state.tokens[state.index] ?? "";
1039
+ const handler = handlers[token];
1040
+ if (!handler) return Option.some(usageError(`Unknown flag for ${commandLabel}: ${token}.`));
1041
+ const error = handler(state, acc);
1042
+ if (Option.isSome(error)) return error;
1043
+ }
1044
+ return Option.none();
1045
+ };
1046
+ const REVIEW_LIST_FLAGS = {
1047
+ "--limit": positiveIntFlag("limit", { min: 0 }),
1048
+ "--page": positiveIntFlag("page", { min: 0 }),
1049
+ "--source": enumFlag("source", REVIEW_SOURCES, "review source"),
1050
+ "--status": enumFlag("status", REVIEW_STATUSES, "review status")
1051
+ };
1052
+ const parseReviewList = (state) => {
1053
+ const acc = {
1054
+ limit: 20,
1055
+ page: 1,
1056
+ source: void 0,
1057
+ status: void 0
1058
+ };
1059
+ const error = runFlagLoop(state, acc, REVIEW_LIST_FLAGS, "review list");
1060
+ if (Option.isSome(error)) return Result.fail(error.value);
1061
+ return Result.succeed({
1062
+ kind: "review-list",
1063
+ ...acc
1064
+ });
1065
+ };
1066
+ const REVIEW_SHOW_FLAGS = {
1067
+ "--comments": boolFlag("comments"),
1068
+ "--todos": boolFlag("todos")
1069
+ };
1070
+ const parseReviewShow = (state) => {
1071
+ const id = state.tokens[state.index];
1072
+ if (!id) return Result.fail(usageError("review show requires <id|last>."));
1073
+ state.index += 1;
1074
+ const acc = {
1075
+ comments: false,
1076
+ todos: false
1077
+ };
1078
+ const error = runFlagLoop(state, acc, REVIEW_SHOW_FLAGS, "review show");
1079
+ if (Option.isSome(error)) return Result.fail(error.value);
1080
+ return Result.succeed({
1081
+ id,
1082
+ kind: "review-show",
1083
+ ...acc
1084
+ });
1085
+ };
1086
+ const REVIEW_EXPORT_FLAGS = {
1087
+ "--no-resolved": boolFlag("noResolved"),
1088
+ "--no-snippets": boolFlag("noSnippets"),
1089
+ "--output": stringFlag("outputPath"),
1090
+ "--stdout": boolFlag("stdout")
1091
+ };
1092
+ const parseReviewExport = (state) => {
1093
+ const id = state.tokens[state.index];
1094
+ if (!id) return Result.fail(usageError("review export requires <id|last>."));
1095
+ state.index += 1;
1096
+ const acc = {
1097
+ noResolved: false,
1098
+ noSnippets: false,
1099
+ outputPath: void 0,
1100
+ stdout: false
1101
+ };
1102
+ const error = runFlagLoop(state, acc, REVIEW_EXPORT_FLAGS, "review export");
1103
+ if (Option.isSome(error)) return Result.fail(error.value);
1104
+ return Result.succeed({
1105
+ id,
1106
+ kind: "review-export",
1107
+ ...acc
1108
+ });
1109
+ };
1110
+ const REVIEW_CREATE_FLAGS = {
1111
+ "--branch": stringFlag("branch"),
1112
+ "--commits": stringFlag("commits"),
1113
+ "--source": enumFlag("source", REVIEW_SOURCES, "review source"),
1114
+ "--title": stringFlag("title")
1115
+ };
1116
+ const validateReviewCreate = (acc) => {
1117
+ if (acc.source === "branch" && !acc.branch) return Option.some(usageError("review create --source branch requires --branch."));
1118
+ if (acc.source === "commits" && !acc.commits) return Option.some(usageError("review create --source commits requires --commits."));
1119
+ if (acc.source === "staged" && (acc.branch || acc.commits)) return Option.some(usageError("review create --source staged does not accept --branch or --commits."));
1120
+ return Option.none();
1121
+ };
1122
+ const parseReviewCreate = (state) => {
1123
+ const acc = {
1124
+ branch: void 0,
1125
+ commits: void 0,
1126
+ source: "staged",
1127
+ title: void 0
1128
+ };
1129
+ const error = runFlagLoop(state, acc, REVIEW_CREATE_FLAGS, "review create");
1130
+ if (Option.isSome(error)) return Result.fail(error.value);
1131
+ const validationError = validateReviewCreate(acc);
1132
+ if (Option.isSome(validationError)) return Result.fail(validationError.value);
1133
+ return Result.succeed({
1134
+ kind: "review-create",
1135
+ ...acc
1136
+ });
1137
+ };
1138
+ const SOURCE_DIFF_FLAGS = {
1139
+ "--branch": stringFlag("branch"),
1140
+ "--commits": stringFlag("commits"),
1141
+ "--stat": boolFlag("stat")
1142
+ };
1143
+ const validateSourceDiff = (source, acc) => {
1144
+ if (source === "branch" && !acc.branch) return Option.some(usageError("source diff branch requires --branch."));
1145
+ if (source === "commits" && !acc.commits) return Option.some(usageError("source diff commits requires --commits."));
1146
+ return Option.none();
1147
+ };
1148
+ const parseSourceDiff = (state) => {
1149
+ const source = state.tokens[state.index];
1150
+ if (!source || !REVIEW_SOURCES.has(source)) return Result.fail(usageError("source diff requires <staged|branch|commits>."));
1151
+ state.index += 1;
1152
+ const acc = {
1153
+ branch: void 0,
1154
+ commits: void 0,
1155
+ stat: false
1156
+ };
1157
+ const error = runFlagLoop(state, acc, SOURCE_DIFF_FLAGS, "source diff");
1158
+ if (Option.isSome(error)) return Result.fail(error.value);
1159
+ const validationError = validateSourceDiff(source, acc);
1160
+ if (Option.isSome(validationError)) return Result.fail(validationError.value);
1161
+ return Result.succeed({
1162
+ kind: "source-diff",
1163
+ source,
1164
+ ...acc
1165
+ });
1166
+ };
1167
+ const TODO_LIST_FLAGS = {
1168
+ "--limit": positiveIntFlag("limit"),
1169
+ "--offset": positiveIntFlag("offset"),
1170
+ "--review": stringFlag("reviewId"),
1171
+ "--status": enumFlag("status", TODO_STATUSES, "todo status")
1172
+ };
1173
+ const parseTodoList = (state) => {
1174
+ const acc = {
1175
+ limit: void 0,
1176
+ offset: 0,
1177
+ reviewId: void 0,
1178
+ status: "pending"
1179
+ };
1180
+ const error = runFlagLoop(state, acc, TODO_LIST_FLAGS, "todo list");
1181
+ if (Option.isSome(error)) return Result.fail(error.value);
1182
+ return Result.succeed({
1183
+ kind: "todo-list",
1184
+ ...acc
1185
+ });
1186
+ };
1187
+ /**
1188
+ * Factory for commands that take exactly one positional `<id>` and no
1189
+ * command-specific flags (only globals). Avoids duplication for done/undone.
1190
+ */
1191
+ const positionalIdParser = (kind, label) => (state) => {
1192
+ const id = state.tokens[state.index];
1193
+ if (!id) return Result.fail(usageError(`${label} requires <id>.`));
1194
+ state.index += 1;
1195
+ while (state.index < state.tokens.length) if (!maybeParseGlobalFlag(state)) return Result.fail(usageError(`Unknown flag for ${label}: ${state.tokens[state.index]}.`));
1196
+ return Result.succeed({
1197
+ id,
1198
+ kind
1199
+ });
1200
+ };
1201
+ const parseTodoDone = positionalIdParser("todo-done", "todo done");
1202
+ const parseTodoUndone = positionalIdParser("todo-undone", "todo undone");
1203
+ const TODO_MOVE_FLAGS = { "--position": positiveIntFlag("position") };
1204
+ const parseTodoMove = (state) => {
1205
+ const id = state.tokens[state.index];
1206
+ if (!id) return Result.fail(usageError("todo move requires <id>."));
1207
+ state.index += 1;
1208
+ const acc = { position: void 0 };
1209
+ const error = runFlagLoop(state, acc, TODO_MOVE_FLAGS, "todo move");
1210
+ if (Option.isSome(error)) return Result.fail(error.value);
1211
+ if (acc.position === void 0) return Result.fail(usageError("todo move requires --position."));
1212
+ return Result.succeed({
1213
+ id,
1214
+ kind: "todo-move",
1215
+ position: acc.position
1216
+ });
1217
+ };
1218
+ const TODO_REMOVE_FLAGS = { "--yes": boolFlag("yes") };
1219
+ const parseTodoRemove = (state) => {
1220
+ const id = state.tokens[state.index];
1221
+ if (!id) return Result.fail(usageError("todo remove requires <id>."));
1222
+ state.index += 1;
1223
+ const acc = { yes: false };
1224
+ const error = runFlagLoop(state, acc, TODO_REMOVE_FLAGS, "todo remove");
1225
+ if (Option.isSome(error)) return Result.fail(error.value);
1226
+ return Result.succeed({
1227
+ id,
1228
+ kind: "todo-remove",
1229
+ ...acc
1230
+ });
1231
+ };
1232
+ const TODO_CLEAR_FLAGS = {
1233
+ "--all": boolFlag("all"),
1234
+ "--done-only": boolFlag("doneOnly"),
1235
+ "--review": stringFlag("reviewId"),
1236
+ "--yes": boolFlag("yes")
1237
+ };
1238
+ const parseTodoClear = (state) => {
1239
+ const acc = {
1240
+ all: false,
1241
+ doneOnly: true,
1242
+ reviewId: void 0,
1243
+ yes: false
1244
+ };
1245
+ const error = runFlagLoop(state, acc, TODO_CLEAR_FLAGS, "todo clear");
1246
+ if (Option.isSome(error)) return Result.fail(error.value);
1247
+ return Result.succeed({
1248
+ kind: "todo-clear",
1249
+ ...acc
1250
+ });
1251
+ };
1252
+ const REVIEW_STATUS_FLAGS = {
1253
+ "--review": stringFlag("reviewId"),
1254
+ "--source": enumFlag("source", REVIEW_SOURCES, "review source")
1255
+ };
1256
+ const parseReviewStatus = (state) => {
1257
+ const acc = {
1258
+ reviewId: void 0,
1259
+ source: void 0
1260
+ };
1261
+ const error = runFlagLoop(state, acc, REVIEW_STATUS_FLAGS, "review status");
1262
+ if (Option.isSome(error)) return Result.fail(error.value);
1263
+ return Result.succeed({
1264
+ kind: "review-status",
1265
+ ...acc
1266
+ });
1267
+ };
1268
+ const REVIEW_RESOLVE_FLAGS = {
1269
+ "--all-comments": boolFlag("allComments"),
1270
+ "--yes": boolFlag("yes")
1271
+ };
1272
+ const parseReviewResolve = (state) => {
1273
+ const id = state.tokens[state.index];
1274
+ if (!id) return Result.fail(usageError("review resolve requires <id|last>."));
1275
+ state.index += 1;
1276
+ const acc = {
1277
+ allComments: true,
1278
+ yes: false
1279
+ };
1280
+ const error = runFlagLoop(state, acc, REVIEW_RESOLVE_FLAGS, "review resolve");
1281
+ if (Option.isSome(error)) return Result.fail(error.value);
1282
+ return Result.succeed({
1283
+ id,
1284
+ kind: "review-resolve",
1285
+ ...acc
1286
+ });
1287
+ };
1288
+ const TODO_ADD_FLAGS = {
1289
+ "--position": positiveIntFlag("position"),
1290
+ "--review": stringFlag("reviewId"),
1291
+ "--text": stringFlag("text")
1292
+ };
1293
+ const parseTodoAdd = (state) => {
1294
+ const acc = {
1295
+ position: void 0,
1296
+ reviewId: void 0,
1297
+ text: ""
1298
+ };
1299
+ const error = runFlagLoop(state, acc, TODO_ADD_FLAGS, "todo add");
1300
+ if (Option.isSome(error)) return Result.fail(error.value);
1301
+ if (!acc.text.trim()) return Result.fail(usageError("todo add requires --text."));
1302
+ return Result.succeed({
1303
+ kind: "todo-add",
1304
+ ...acc
1305
+ });
1306
+ };
1307
+ const SERVE_FLAGS = {
1308
+ "--auth": boolFlag("auth"),
1309
+ "--cert": stringFlag("cert"),
1310
+ "--host": stringFlag("host"),
1311
+ "--https": boolFlag("https"),
1312
+ "--key": stringFlag("key"),
1313
+ "--no-open": boolFlag("noOpen"),
1314
+ "--password": stringFlag("password"),
1315
+ "--port": positiveIntFlag("port", { min: 0 }),
1316
+ "--username": stringFlag("username")
1317
+ };
1318
+ const parseServe = (state) => {
1319
+ const acc = {
1320
+ auth: false,
1321
+ cert: void 0,
1322
+ host: "127.0.0.1",
1323
+ https: false,
1324
+ key: void 0,
1325
+ noOpen: false,
1326
+ password: void 0,
1327
+ port: 3e3,
1328
+ username: void 0
1329
+ };
1330
+ const error = runFlagLoop(state, acc, SERVE_FLAGS, "serve");
1331
+ if (Option.isSome(error)) return Result.fail(error.value);
1332
+ return Result.succeed({
1333
+ kind: "serve",
1334
+ ...acc
1335
+ });
1336
+ };
1337
+ const MCP_LOG_LEVELS = new Set([
1338
+ "debug",
1339
+ "error",
1340
+ "info",
1341
+ "silent"
1342
+ ]);
1343
+ const MCP_FLAGS = {
1344
+ "--log-level": enumFlag("logLevel", MCP_LOG_LEVELS, "log level"),
1345
+ "--readonly": boolFlag("readonly")
1346
+ };
1347
+ const parseMcp = (state) => {
1348
+ const acc = {
1349
+ logLevel: "error",
1350
+ readonly: false
1351
+ };
1352
+ const error = runFlagLoop(state, acc, MCP_FLAGS, "mcp");
1353
+ if (Option.isSome(error)) return Result.fail(error.value);
1354
+ return Result.succeed({
1355
+ kind: "mcp",
1356
+ ...acc
1357
+ });
1358
+ };
1359
+ const EVENT_TYPES = new Set([
1360
+ "comments",
1361
+ "files",
1362
+ "reviews",
1363
+ "todos"
1364
+ ]);
1365
+ const EVENTS_FLAGS = {
1366
+ "--since": positiveIntFlag("since"),
1367
+ "--type": enumFlag("type", EVENT_TYPES, "event type")
1368
+ };
1369
+ const parseEvents = (state) => {
1370
+ const acc = {
1371
+ since: void 0,
1372
+ type: void 0
1373
+ };
1374
+ const error = runFlagLoop(state, acc, EVENTS_FLAGS, "events");
1375
+ if (Option.isSome(error)) return Result.fail(error.value);
1376
+ return Result.succeed({
1377
+ kind: "events",
1378
+ ...acc
1379
+ });
1380
+ };
1381
+ const DATA_RESET_FLAGS = {
1382
+ "--keep-exports": boolFlag("keepExports"),
1383
+ "--yes": boolFlag("yes")
1384
+ };
1385
+ const parseDataReset = (state) => {
1386
+ const acc = {
1387
+ keepExports: false,
1388
+ yes: false
1389
+ };
1390
+ const error = runFlagLoop(state, acc, DATA_RESET_FLAGS, "data reset");
1391
+ if (Option.isSome(error)) return Result.fail(error.value);
1392
+ return Result.succeed({
1393
+ kind: "data-reset",
1394
+ ...acc
1395
+ });
1396
+ };
1397
+ const ensureNoExtraArgs = (state, label) => {
1398
+ if (state.index < state.tokens.length) return Option.some(usageError(`Unexpected argument for ${label}: ${state.tokens[state.index]}.`));
1399
+ return Option.none();
1400
+ };
1401
+ /** Review verb parsers keyed by verb name. */
1402
+ const REVIEW_VERB_PARSERS = {
1403
+ create: parseReviewCreate,
1404
+ export: parseReviewExport,
1405
+ list: parseReviewList,
1406
+ resolve: parseReviewResolve,
1407
+ show: parseReviewShow,
1408
+ status: parseReviewStatus
1409
+ };
1410
+ /** Todo verb parsers keyed by verb name. */
1411
+ const TODO_VERB_PARSERS = {
1412
+ add: parseTodoAdd,
1413
+ clear: parseTodoClear,
1414
+ done: parseTodoDone,
1415
+ list: parseTodoList,
1416
+ move: parseTodoMove,
1417
+ remove: parseTodoRemove,
1418
+ undone: parseTodoUndone
1419
+ };
1420
+ const REVIEW_PR_FLAGS = {
1421
+ "--force-refresh": boolFlag("forceRefresh"),
1422
+ "--no-open": boolFlag("noOpen"),
1423
+ "--port": positiveIntFlag("port", { min: 0 })
1424
+ };
1425
+ const parseReviewPr = (state, prUrl) => {
1426
+ const acc = {
1427
+ forceRefresh: false,
1428
+ noOpen: false,
1429
+ port: 3e3
1430
+ };
1431
+ const error = runFlagLoop(state, acc, REVIEW_PR_FLAGS, "review <pr-url>");
1432
+ if (Option.isSome(error)) return Result.fail(error.value);
1433
+ return Result.succeed({
1434
+ kind: "review-pr",
1435
+ prUrl,
1436
+ ...acc
1437
+ });
1438
+ };
1439
+ /** Subcommand family parsers keyed by family name. */
1440
+ /** Data verb parsers keyed by verb name. */
1441
+ const DATA_VERB_PARSERS = {
1442
+ migrate: (state) => {
1443
+ const error = ensureNoExtraArgs(state, "data migrate");
1444
+ if (Option.isSome(error)) return Result.fail(error.value);
1445
+ return Result.succeed({ kind: "data-migrate" });
1446
+ },
1447
+ reset: parseDataReset
1448
+ };
1449
+ /** Subcommand family parsers keyed by family name. */
1450
+ const FAMILY_PARSERS = {
1451
+ data: (state) => {
1452
+ const verb = state.tokens[state.index];
1453
+ if (!verb) return Result.succeed({
1454
+ kind: "help",
1455
+ topic: ["data"]
1456
+ });
1457
+ state.index += 1;
1458
+ const parser = DATA_VERB_PARSERS[verb];
1459
+ if (!parser) return Result.fail(usageError(`Unknown data command: ${verb}.`));
1460
+ return parser(state);
1461
+ },
1462
+ doctor: (state) => {
1463
+ const error = ensureNoExtraArgs(state, "doctor");
1464
+ if (Option.isSome(error)) return Result.fail(error.value);
1465
+ return Result.succeed({ kind: "doctor" });
1466
+ },
1467
+ events: parseEvents,
1468
+ export: parseReviewExport,
1469
+ mcp: parseMcp,
1470
+ review: (state) => {
1471
+ const verb = state.tokens[state.index];
1472
+ if (!verb) return Result.succeed({
1473
+ kind: "help",
1474
+ topic: ["review"]
1475
+ });
1476
+ if (looksLikePrUrl(verb)) {
1477
+ state.index += 1;
1478
+ return parseReviewPr(state, verb);
1479
+ }
1480
+ state.index += 1;
1481
+ const parser = REVIEW_VERB_PARSERS[verb];
1482
+ if (!parser) return Result.fail(usageError(`Unknown review command: ${verb}.`));
1483
+ return parser(state);
1484
+ },
1485
+ serve: parseServe,
1486
+ source: (state) => {
1487
+ const verb = state.tokens[state.index];
1488
+ if (!verb) return Result.succeed({
1489
+ kind: "help",
1490
+ topic: ["source"]
1491
+ });
1492
+ state.index += 1;
1493
+ if (verb === "list") {
1494
+ const error = ensureNoExtraArgs(state, "source list");
1495
+ if (Option.isSome(error)) return Result.fail(error.value);
1496
+ return Result.succeed({ kind: "source-list" });
1497
+ }
1498
+ if (verb === "diff") return parseSourceDiff(state);
1499
+ return Result.fail(usageError(`Unknown source command: ${verb}.`));
1500
+ },
1501
+ todo: (state) => {
1502
+ const verb = state.tokens[state.index];
1503
+ if (!verb) return Result.succeed({
1504
+ kind: "help",
1505
+ topic: ["todo"]
1506
+ });
1507
+ state.index += 1;
1508
+ const parser = TODO_VERB_PARSERS[verb];
1509
+ if (!parser) return Result.fail(usageError(`Unknown todo command: ${verb}.`));
1510
+ return parser(state);
1511
+ }
1512
+ };
1513
+ /**
1514
+ * Strips any leading globals with {@link maybeParseGlobalFlag} before dispatching
1515
+ * into a command family so `--help` and `--version` behave consistently.
1516
+ */
1517
+ const parseWithState = (state) => {
1518
+ while (state.index < state.tokens.length && maybeParseGlobalFlag(state));
1519
+ if (state.options.version) return Result.succeed({ kind: "version" });
1520
+ if (state.index >= state.tokens.length) return Result.succeed({
1521
+ kind: "help",
1522
+ topic: []
1523
+ });
1524
+ const first = state.tokens[state.index];
1525
+ if (!first) return Result.succeed({
1526
+ kind: "help",
1527
+ topic: []
1528
+ });
1529
+ if (first === "help") {
1530
+ const topic = state.tokens.slice(state.index + 1);
1531
+ state.index = state.tokens.length;
1532
+ return Result.succeed({
1533
+ kind: "help",
1534
+ topic
1535
+ });
1536
+ }
1537
+ if (state.options.help) return Result.succeed({
1538
+ kind: "help",
1539
+ topic: state.tokens.slice(state.index)
1540
+ });
1541
+ state.index += 1;
1542
+ const familyParser = FAMILY_PARSERS[first];
1543
+ if (!familyParser) return Result.fail(usageError(`Unknown command: ${first}.`));
1544
+ return familyParser(state);
1545
+ };
1546
+ const parseCliArgs = (argv) => {
1547
+ const options = createDefaultOptions();
1548
+ const result = parseWithState({
1549
+ index: 0,
1550
+ options,
1551
+ tokens: argv
1552
+ });
1553
+ if (Result.isFailure(result)) return Result.fail(result.failure);
1554
+ return Result.succeed({
1555
+ command: result.success,
1556
+ options
1557
+ });
1558
+ };
1559
+ //#endregion
1560
+ //#region src/cli/runtime.ts
1561
+ const resolveRepositoryRoot = (repoOverride) => {
1562
+ const cwd = repoOverride ? resolve(repoOverride) : process.cwd();
1563
+ try {
1564
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
1565
+ cwd,
1566
+ encoding: "utf8"
1567
+ }).trim();
1568
+ } catch {
1569
+ return new CliFailure({
1570
+ exitCode: ExitCode.StateUnavailable,
1571
+ message: repoOverride ? `Path ${cwd} is not a Git repository. Use --repo <path> with a valid repository root.` : `Could not resolve a Git repository from ${cwd}. Use --repo <path> with a valid repository root.`
1572
+ });
1573
+ }
1574
+ };
1575
+ const resolveDbPath = (repoRoot, dbPathOverride) => dbPathOverride ? resolve(dbPathOverride) : resolve(repoRoot, ".ringi/reviews.db");
1576
+ const commandNeedsRepository = (command) => command.kind !== "help" && command.kind !== "version" && command.kind !== "mcp" && command.kind !== "serve";
1577
+ /**
1578
+ * Commands that need the database to already exist. `review-pr` is NOT here
1579
+ * because it auto-initializes `.ringi/` (like `serve` does).
1580
+ */
1581
+ const commandNeedsDatabase = (command) => command.kind === "review-list" || command.kind === "review-show" || command.kind === "review-export" || command.kind === "review-status" || command.kind === "todo-list" || command.kind === "doctor";
1582
+ const commandUsesCoreRuntime = (command) => command.kind === "review-list" || command.kind === "review-show" || command.kind === "review-export" || command.kind === "review-pr" || command.kind === "review-status" || command.kind === "todo-list" || command.kind === "review-create" || command.kind === "todo-add" || command.kind === "doctor";
1583
+ const resolveCliConfig = (args) => {
1584
+ const repoRootResult = resolveRepositoryRoot(args.repo);
1585
+ if (repoRootResult instanceof CliFailure) return repoRootResult;
1586
+ return {
1587
+ color: args.color,
1588
+ cwd: process.cwd(),
1589
+ dbPath: resolveDbPath(repoRootResult, args.dbPath),
1590
+ outputMode: "human",
1591
+ quiet: args.quiet,
1592
+ repoRoot: repoRootResult,
1593
+ verbose: args.verbose
1594
+ };
1595
+ };
1596
+ const ensureLocalStateAvailable = (config) => {
1597
+ if (!existsSync(config.dbPath)) return new CliFailure({
1598
+ exitCode: ExitCode.StateUnavailable,
1599
+ message: `Local state is missing at ${config.dbPath}. Run 'ringi data migrate' or start 'ringi serve' once to initialize local state.`
1600
+ });
1601
+ };
1602
+ const makeConfigLayer = (config) => ConfigProvider.layer(ConfigProvider.fromUnknown({
1603
+ DB_PATH: config.dbPath,
1604
+ REPOSITORY_PATH: config.repoRoot
1605
+ }));
1606
+ const createCoreCliRuntime = (config) => ManagedRuntime.make(Layer.mergeAll(CoreLive, CliConfigLive(config)).pipe(Layer.provideMerge(makeConfigLayer(config))));
1607
+ const createGitCliRuntime = (config) => ManagedRuntime.make(Layer.mergeAll(GitService.Default, CliConfigLive(config)).pipe(Layer.provideMerge(makeConfigLayer(config))));
1608
+ const createCliRuntimeResources = (command, args) => {
1609
+ if (!commandNeedsRepository(command)) return null;
1610
+ const configResult = resolveCliConfig(args);
1611
+ if (configResult instanceof CliFailure) return configResult;
1612
+ if (commandNeedsDatabase(command)) {
1613
+ const stateError = ensureLocalStateAvailable(configResult);
1614
+ if (stateError) return stateError;
1615
+ }
1616
+ return {
1617
+ config: configResult,
1618
+ runtime: commandUsesCoreRuntime(command) ? createCoreCliRuntime(configResult) : createGitCliRuntime(configResult)
1619
+ };
1620
+ };
1621
+ //#endregion
1622
+ //#region src/cli/main.ts
1623
+ const CLI_VERSION = "0.3.0";
1624
+ const COMMAND_TREE = {
1625
+ commands: [
1626
+ {
1627
+ description: "List review sessions",
1628
+ name: "review list",
1629
+ usage: "ringi review list [--status <status>] [--source <type>] [--limit <n>] [--page <n>]"
1630
+ },
1631
+ {
1632
+ description: "Show review details",
1633
+ name: "review show",
1634
+ usage: "ringi review show <id|last> [--comments] [--todos]"
1635
+ },
1636
+ {
1637
+ description: "Create a review session",
1638
+ name: "review create",
1639
+ usage: "ringi review create [--source <staged|branch|commits>] [--branch <name>] [--commits <range>]"
1640
+ },
1641
+ {
1642
+ description: "Export review as markdown",
1643
+ name: "review export",
1644
+ usage: "ringi review export <id|last> [--output <path>] [--stdout]"
1645
+ },
1646
+ {
1647
+ description: "Resolve a review session",
1648
+ name: "review resolve",
1649
+ usage: "ringi review resolve <id|last> [--all-comments] [--yes]"
1650
+ },
1651
+ {
1652
+ description: "Show repository and review status",
1653
+ name: "review status",
1654
+ usage: "ringi review status [--review <id|last>] [--source <type>]"
1655
+ },
1656
+ {
1657
+ description: "List repository sources",
1658
+ name: "source list",
1659
+ usage: "ringi source list"
1660
+ },
1661
+ {
1662
+ description: "Show diff for a source",
1663
+ name: "source diff",
1664
+ usage: "ringi source diff <staged|branch|commits> [--branch <name>] [--commits <range>] [--stat]"
1665
+ },
1666
+ {
1667
+ description: "List todo items",
1668
+ name: "todo list",
1669
+ usage: "ringi todo list [--review <id>] [--status <pending|done|all>] [--limit <n>] [--offset <n>]"
1670
+ },
1671
+ {
1672
+ description: "Add a todo item",
1673
+ name: "todo add",
1674
+ usage: "ringi todo add --text <text> [--review <id>] [--position <n>]"
1675
+ },
1676
+ {
1677
+ description: "Mark a todo as done",
1678
+ name: "todo done",
1679
+ usage: "ringi todo done <id>"
1680
+ },
1681
+ {
1682
+ description: "Reopen a completed todo",
1683
+ name: "todo undone",
1684
+ usage: "ringi todo undone <id>"
1685
+ },
1686
+ {
1687
+ description: "Move a todo to a position",
1688
+ name: "todo move",
1689
+ usage: "ringi todo move <id> --position <n>"
1690
+ },
1691
+ {
1692
+ description: "Remove a todo",
1693
+ name: "todo remove",
1694
+ usage: "ringi todo remove <id> [--yes]"
1695
+ },
1696
+ {
1697
+ description: "Clear completed todos",
1698
+ name: "todo clear",
1699
+ usage: "ringi todo clear [--review <id>] [--done-only] [--all] [--yes]"
1700
+ },
1701
+ {
1702
+ description: "Start the local Ringi server",
1703
+ name: "serve",
1704
+ usage: "ringi serve [--host <host>] [--port <port>] [--https] [--auth] [--no-open]"
1705
+ },
1706
+ {
1707
+ description: "Start the MCP stdio server",
1708
+ name: "mcp",
1709
+ usage: "ringi mcp [--readonly] [--log-level <level>]"
1710
+ },
1711
+ {
1712
+ description: "Run local diagnostics",
1713
+ name: "doctor",
1714
+ usage: "ringi doctor"
1715
+ },
1716
+ {
1717
+ description: "Tail server events",
1718
+ name: "events",
1719
+ usage: "ringi events [--type <reviews|comments|todos|files>]"
1720
+ },
1721
+ {
1722
+ description: "Run database migrations",
1723
+ name: "data migrate",
1724
+ usage: "ringi data migrate"
1725
+ },
1726
+ {
1727
+ description: "Reset local data",
1728
+ name: "data reset",
1729
+ usage: "ringi data reset [--yes] [--keep-exports]"
1730
+ }
1731
+ ],
1732
+ description: "ringi — local-first code review CLI",
1733
+ version: CLI_VERSION
1734
+ };
1735
+ const ROOT_NEXT_ACTIONS = [
1736
+ {
1737
+ command: "ringi review list [--status <status>] [--source <type>]",
1738
+ description: "List review sessions",
1739
+ params: {
1740
+ source: { enum: [
1741
+ "staged",
1742
+ "branch",
1743
+ "commits"
1744
+ ] },
1745
+ status: { enum: [
1746
+ "in_progress",
1747
+ "approved",
1748
+ "changes_requested"
1749
+ ] }
1750
+ }
1751
+ },
1752
+ {
1753
+ command: "ringi source list",
1754
+ description: "List repository sources"
1755
+ },
1756
+ {
1757
+ command: "ringi review create [--source <source>]",
1758
+ description: "Create a new review session",
1759
+ params: { source: {
1760
+ default: "staged",
1761
+ enum: [
1762
+ "staged",
1763
+ "branch",
1764
+ "commits"
1765
+ ]
1766
+ } }
1767
+ },
1768
+ {
1769
+ command: "ringi todo list [--status <status>]",
1770
+ description: "List todos",
1771
+ params: { status: {
1772
+ default: "pending",
1773
+ enum: [
1774
+ "pending",
1775
+ "done",
1776
+ "all"
1777
+ ]
1778
+ } }
1779
+ },
1780
+ {
1781
+ command: "ringi review status",
1782
+ description: "Show repository and review status"
1783
+ }
1784
+ ];
1785
+ const ROOT_HELP = `ringi — local-first review CLI
1786
+
1787
+ Usage:
1788
+ ringi [global options] <command>
1789
+
1790
+ Global options:
1791
+ --json Emit structured JSON envelope to stdout
1792
+ --repo <path> Use a specific Git repository root
1793
+ --db-path <path> Override the SQLite database path
1794
+ --quiet Suppress human-readable success output
1795
+ --verbose Include stack traces on failures
1796
+ --no-color Disable ANSI color output
1797
+ --help Show help
1798
+ --version Show version
1799
+
1800
+ Commands:
1801
+ review list [--status <status>] [--source <type>] [--limit <n>] [--page <n>]
1802
+ review show <id|last> [--comments] [--todos]
1803
+ review create [--source <staged|branch|commits>] [--branch <name>] [--commits <range>]
1804
+ review export <id|last> [--output <path>] [--stdout]
1805
+ review resolve <id|last> [--all-comments] [--yes]
1806
+ review status [--review <id|last>] [--source <type>]
1807
+ source list
1808
+ source diff <staged|branch|commits> [--branch <name>] [--commits <range>] [--stat]
1809
+ todo list [--review <id>] [--status <pending|done|all>] [--limit <n>] [--offset <n>]
1810
+ todo add --text <text> [--review <id>]
1811
+ todo done <id>
1812
+ todo undone <id>
1813
+ todo move <id> --position <n>
1814
+ todo remove <id> [--yes]
1815
+ todo clear [--review <id>] [--done-only] [--all] [--yes]
1816
+ export <id|last> [--output <path>] [--stdout]
1817
+ `;
1818
+ const HELP_TOPICS = {
1819
+ data: `ringi data
1820
+
1821
+ Usage:
1822
+ ringi data migrate
1823
+ ringi data reset [--yes] [--keep-exports]
1824
+ `,
1825
+ review: `ringi review
1826
+
1827
+ Usage:
1828
+ ringi review list [--status <status>] [--source <type>] [--limit <n>] [--page <n>]
1829
+ ringi review show <id|last> [--comments] [--todos]
1830
+ ringi review create [--source <staged|branch|commits>] [--branch <name>] [--commits <range>]
1831
+ ringi review export <id|last> [--output <path>] [--stdout]
1832
+ ringi review resolve <id|last> [--all-comments] [--yes]
1833
+ ringi review status [--review <id|last>] [--source <type>]
1834
+ `,
1835
+ source: `ringi source
1836
+
1837
+ Usage:
1838
+ ringi source list
1839
+ ringi source diff <staged|branch|commits> [--branch <name>] [--commits <range>] [--stat]
1840
+ `,
1841
+ todo: `ringi todo
1842
+
1843
+ Usage:
1844
+ ringi todo list [--review <id>] [--status <pending|done|all>] [--limit <n>] [--offset <n>]
1845
+ ringi todo add --text <text> [--review <id>] [--position <n>]
1846
+ ringi todo done <id>
1847
+ ringi todo undone <id>
1848
+ ringi todo move <id> --position <n>
1849
+ ringi todo remove <id> [--yes]
1850
+ ringi todo clear [--review <id>] [--done-only] [--all] [--yes]
1851
+ `
1852
+ };
1853
+ const renderHelp = (command) => {
1854
+ if (command.kind !== "help") return ROOT_HELP;
1855
+ const [topic] = command.topic;
1856
+ return (topic && HELP_TOPICS[topic]) ?? ROOT_HELP;
1857
+ };
1858
+ const writeJson = (payload) => {
1859
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1860
+ };
1861
+ const writeHuman = (text) => {
1862
+ if (text && text.length > 0) process.stdout.write(`${text}\n`);
1863
+ };
1864
+ /** Maps exit codes to error categories and retryable status. */
1865
+ const EXIT_CODE_META = {
1866
+ [ExitCode.AuthFailure]: {
1867
+ category: "auth",
1868
+ code: "AUTH_FAILURE",
1869
+ retryable: false
1870
+ },
1871
+ [ExitCode.ResourceNotFound]: {
1872
+ category: "not_found",
1873
+ code: "RESOURCE_NOT_FOUND",
1874
+ retryable: false
1875
+ },
1876
+ [ExitCode.RuntimeFailure]: {
1877
+ category: "server",
1878
+ code: "RUNTIME_FAILURE",
1879
+ retryable: true
1880
+ },
1881
+ [ExitCode.StateUnavailable]: {
1882
+ category: "config",
1883
+ code: "STATE_UNAVAILABLE",
1884
+ retryable: false
1885
+ },
1886
+ [ExitCode.UsageError]: {
1887
+ category: "validation",
1888
+ code: "USAGE_ERROR",
1889
+ retryable: false
1890
+ }
1891
+ };
1892
+ const mapFailure = (error) => {
1893
+ if (error instanceof CliFailure) {
1894
+ const meta = EXIT_CODE_META[error.exitCode] ?? {
1895
+ category: "server",
1896
+ code: "UNKNOWN",
1897
+ retryable: false
1898
+ };
1899
+ return {
1900
+ category: meta.category,
1901
+ code: meta.code,
1902
+ exitCode: error.exitCode,
1903
+ message: error.message,
1904
+ retryable: meta.retryable,
1905
+ verbose: error.details
1906
+ };
1907
+ }
1908
+ if (error instanceof ReviewNotFound || error instanceof TodoNotFound) return {
1909
+ category: "not_found",
1910
+ code: "RESOURCE_NOT_FOUND",
1911
+ exitCode: ExitCode.ResourceNotFound,
1912
+ message: error.message,
1913
+ retryable: false,
1914
+ verbose: error.stack
1915
+ };
1916
+ if (error instanceof Error) return {
1917
+ category: "server",
1918
+ code: "RUNTIME_FAILURE",
1919
+ exitCode: ExitCode.RuntimeFailure,
1920
+ message: error.message,
1921
+ retryable: true,
1922
+ verbose: error.stack
1923
+ };
1924
+ return {
1925
+ category: "server",
1926
+ code: "UNKNOWN_FAILURE",
1927
+ exitCode: ExitCode.RuntimeFailure,
1928
+ message: "Unknown CLI failure.",
1929
+ retryable: false
1930
+ };
1931
+ };
1932
+ /** Actionable fix guidance based on error category. */
1933
+ const FIX_GUIDANCE = {
1934
+ auth: "Check authentication credentials or run 'ringi serve --auth' with valid credentials.",
1935
+ config: "Run 'ringi serve' once to initialize local state, or check --repo and --db-path flags.",
1936
+ conflict: "Resolve the conflict and retry the operation.",
1937
+ connection: "Ensure the Ringi server is running: ringi serve",
1938
+ not_found: "Verify the resource ID. Use 'ringi review list' or 'ringi todo list' to find valid IDs.",
1939
+ server: "Retry the command. If the error persists, check 'ringi serve' logs.",
1940
+ validation: "Check command usage with 'ringi --help'. Verify flag names and values."
1941
+ };
1942
+ /** Build recovery next_actions based on error category. */
1943
+ const errorNextActions = (commandStr, normalized) => {
1944
+ const actions = [];
1945
+ if (normalized.retryable) actions.push({
1946
+ command: commandStr,
1947
+ description: "Retry the failed command"
1948
+ });
1949
+ if (normalized.category === "config" || normalized.category === "connection") actions.push({
1950
+ command: "ringi serve",
1951
+ description: "Start the local Ringi server"
1952
+ });
1953
+ if (normalized.category === "not_found") actions.push({
1954
+ command: "ringi review list",
1955
+ description: "List available reviews"
1956
+ }, {
1957
+ command: "ringi todo list",
1958
+ description: "List available todos"
1959
+ });
1960
+ if (normalized.category === "validation") actions.push({
1961
+ command: `${commandStr.split(" ").slice(0, 3).join(" ")} --help`,
1962
+ description: "Show command usage"
1963
+ });
1964
+ return actions;
1965
+ };
1966
+ /** Build a full error envelope from a normalized failure. */
1967
+ const buildErrorEnvelope = (commandStr, normalized) => {
1968
+ return failure(commandStr, {
1969
+ category: normalized.category,
1970
+ code: normalized.code,
1971
+ message: normalized.message,
1972
+ retryable: normalized.retryable,
1973
+ type: `ringi://errors/${normalized.code}`
1974
+ }, normalized.verbose ?? FIX_GUIDANCE[normalized.category], errorNextActions(commandStr, normalized));
1975
+ };
1976
+ const installSignalHandlers = (dispose) => {
1977
+ const shutdown = async () => {
1978
+ await dispose();
1979
+ process.exit(ExitCode.RuntimeFailure);
1980
+ };
1981
+ process.once("SIGINT", shutdown);
1982
+ process.once("SIGTERM", shutdown);
1983
+ return () => {
1984
+ process.off("SIGINT", shutdown);
1985
+ process.off("SIGTERM", shutdown);
1986
+ };
1987
+ };
1988
+ /**
1989
+ * Resolves the built Nitro server entry point.
1990
+ *
1991
+ * Lookup order (first match wins):
1992
+ * 1. **Packaged location** — `<pkg-root>/server/server/index.mjs`
1993
+ * The published npm package includes `server/` at the package root, which
1994
+ * contains `server/index.mjs` (the Nitro entry) and `public/` (static
1995
+ * assets). From the bundled CLI at `dist/cli.mjs`, the package root is
1996
+ * `import.meta.dirname/..`.
1997
+ * 2. **Monorepo development** — `apps/web/.output/server/index.mjs`
1998
+ * During local development the web build output lives inside the web
1999
+ * workspace. We resolve it relative to the CLI package root.
2000
+ * 3. **CWD fallback** — `.output/server/index.mjs` relative to the current
2001
+ * working directory, for running the server in a standalone checkout.
2002
+ */
2003
+ const resolveServerEntry = () => {
2004
+ const candidates = [];
2005
+ if (import.meta.dirname) {
2006
+ const pkgRoot = resolve(import.meta.dirname, "..");
2007
+ candidates.push(resolve(pkgRoot, "server", "server", "index.mjs"));
2008
+ candidates.push(resolve(pkgRoot, "..", "web", ".output", "server", "index.mjs"));
2009
+ }
2010
+ candidates.push(resolve(process.cwd(), ".output", "server", "index.mjs"));
2011
+ return candidates.find((candidate) => existsSync(candidate));
2012
+ };
2013
+ const runServe = (command) => {
2014
+ const serverEntry = resolveServerEntry();
2015
+ if (!serverEntry) {
2016
+ const hint = import.meta.dirname && !import.meta.dirname.includes("apps/cli/") ? "The installed package is missing its server assets. Try reinstalling: npm install -g @sanurb/ringi" : "Run 'pnpm build' at the monorepo root, then 'pnpm --filter @sanurb/ringi build:server' to copy the server assets.";
2017
+ process.stderr.write(`No built server found.\n${hint}\n`);
2018
+ process.exit(ExitCode.RuntimeFailure);
2019
+ }
2020
+ const env = {
2021
+ ...process.env,
2022
+ NITRO_HOST: command.host,
2023
+ NITRO_PORT: String(command.port)
2024
+ };
2025
+ if (command.https && command.cert && command.key) {
2026
+ env.NITRO_SSL_CERT = command.cert;
2027
+ env.NITRO_SSL_KEY = command.key;
2028
+ }
2029
+ const url = `${command.https ? "https" : "http"}://${command.host === "0.0.0.0" ? "localhost" : command.host}:${command.port}`;
2030
+ process.stderr.write(`ringi server starting on ${url}\n`);
2031
+ const child = fork(serverEntry, [], {
2032
+ env,
2033
+ execArgv: [],
2034
+ stdio: "inherit"
2035
+ });
2036
+ if (!command.noOpen) setTimeout(() => {
2037
+ let openCmd = "xdg-open";
2038
+ if (process.platform === "darwin") openCmd = "open";
2039
+ else if (process.platform === "win32") openCmd = "start";
2040
+ exec(`${openCmd} ${url}`, () => {});
2041
+ }, 1500);
2042
+ const shutdown = () => {
2043
+ child.kill("SIGTERM");
2044
+ };
2045
+ process.once("SIGINT", shutdown);
2046
+ process.once("SIGTERM", shutdown);
2047
+ child.on("exit", (code) => {
2048
+ process.off("SIGINT", shutdown);
2049
+ process.off("SIGTERM", shutdown);
2050
+ process.exit(code ?? ExitCode.Success);
2051
+ });
2052
+ };
2053
+ /** Single path for all CLI error exits. */
2054
+ const failAndExit = (opts) => {
2055
+ const normalized = mapFailure(opts.error);
2056
+ if (opts.json) writeJson(buildErrorEnvelope(opts.cmdStr, normalized));
2057
+ process.stderr.write(`${normalized.message}\n`);
2058
+ if (opts.verbose && normalized.verbose) process.stderr.write(`${normalized.verbose}\n`);
2059
+ return process.exit(normalized.exitCode);
2060
+ };
2061
+ const main = async () => {
2062
+ const argv = process.argv.slice(2);
2063
+ const parseResult = parseCliArgs(argv);
2064
+ if (Result.isFailure(parseResult)) return failAndExit({
2065
+ cmdStr: "ringi",
2066
+ error: parseResult.failure,
2067
+ json: argv.includes("--json"),
2068
+ verbose: false
2069
+ });
2070
+ const { command, options } = parseResult.success;
2071
+ if (command.kind === "help") {
2072
+ if (options.json) writeJson(success("ringi", COMMAND_TREE, ROOT_NEXT_ACTIONS));
2073
+ else writeHuman(renderHelp(command));
2074
+ process.exit(ExitCode.Success);
2075
+ }
2076
+ if (command.kind === "version") {
2077
+ if (options.json) writeJson(success("ringi --version", { version: CLI_VERSION }));
2078
+ else writeHuman(CLI_VERSION);
2079
+ process.exit(ExitCode.Success);
2080
+ }
2081
+ if (command.kind === "serve") {
2082
+ runServe(command);
2083
+ return;
2084
+ }
2085
+ const runtimeResources = createCliRuntimeResources(command, {
2086
+ color: options.color,
2087
+ dbPath: options.dbPath,
2088
+ quiet: options.quiet,
2089
+ repo: options.repo,
2090
+ verbose: options.verbose
2091
+ });
2092
+ if (runtimeResources === null) process.exit(ExitCode.Success);
2093
+ const cmdStr = commandLabel(command);
2094
+ if (runtimeResources instanceof CliFailure) return failAndExit({
2095
+ cmdStr,
2096
+ error: runtimeResources,
2097
+ json: options.json,
2098
+ verbose: options.verbose
2099
+ });
2100
+ const removeSignalHandlers = installSignalHandlers(() => runtimeResources.runtime.dispose());
2101
+ try {
2102
+ const output = await runtimeResources.runtime.runPromise(runCommand(command));
2103
+ if (options.json) writeJson(success(cmdStr, output.data, output.nextActions ?? []));
2104
+ else if (!options.quiet) writeHuman(output.human);
2105
+ await runtimeResources.runtime.dispose();
2106
+ removeSignalHandlers();
2107
+ process.exit(ExitCode.Success);
2108
+ } catch (error) {
2109
+ await runtimeResources.runtime.dispose();
2110
+ removeSignalHandlers();
2111
+ failAndExit({
2112
+ cmdStr,
2113
+ error,
2114
+ json: options.json,
2115
+ verbose: options.verbose
2116
+ });
2117
+ }
2118
+ };
2119
+ try {
2120
+ await main();
2121
+ } catch (error) {
2122
+ failAndExit({
2123
+ cmdStr: "ringi",
2124
+ error,
2125
+ json: process.argv.slice(2).includes("--json"),
2126
+ verbose: false
2127
+ });
2128
+ }
2129
+ //#endregion
2130
+ export {};
2131
+
2132
+ //# sourceMappingURL=cli.mjs.map