@sanurb/ringi 0.2.1 → 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 (417) hide show
  1. package/dist/cli.mjs +505 -114
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/mcp.mjs +35 -39
  4. package/dist/mcp.mjs.map +1 -1
  5. package/dist/runtime.mjs +569 -428
  6. package/dist/runtime.mjs.map +1 -1
  7. package/package.json +9 -8
  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
package/dist/cli.mjs CHANGED
@@ -1,23 +1,302 @@
1
1
  #!/usr/bin/env node
2
- import { a as GitService, c as CommentService, f as ReviewNotFound, i as ReviewService, n as ExportService, o as getDiffSummary, r as TodoService, s as parseDiff, t as CoreLive, u as TodoNotFound } from "./runtime.mjs";
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
3
  import { exec, execFileSync, fork } from "node:child_process";
4
4
  import { existsSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
6
  import * as Schema from "effect/Schema";
7
- import * as Either from "effect/Either";
7
+ import * as Result from "effect/Result";
8
8
  import { writeFile } from "node:fs/promises";
9
+ import { ServiceMap } from "effect";
9
10
  import * as Effect from "effect/Effect";
10
- import * as Option from "effect/Option";
11
- import * as Context from "effect/Context";
12
11
  import * as Layer from "effect/Layer";
12
+ import * as Option from "effect/Option";
13
13
  import * as ManagedRuntime from "effect/ManagedRuntime";
14
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
15
294
  //#region src/cli/config.ts
16
- var CliConfig = class extends Context.Tag("@ringi/CliConfig")() {};
295
+ var CliConfig = class extends ServiceMap.Service()("@ringi/CliConfig") {};
17
296
  /**
18
297
  * Wraps a concrete {@link CliConfigShape} in a layer for the Effect runtime.
19
298
  */
20
- const CliConfigLive = (config) => Layer.succeed(CliConfig, config);
299
+ const CliConfigLive = (config) => Layer.succeed(CliConfig, CliConfig.of(config));
21
300
  //#endregion
22
301
  //#region src/cli/contracts.ts
23
302
  const ExitCode = {
@@ -45,8 +324,8 @@ const failure = (command, error, fix, nextActions = []) => ({
45
324
  * Carries an exit code and optional operator-facing details so callers can
46
325
  * present a short message without losing the underlying reason.
47
326
  */
48
- var CliFailure = class extends Schema.TaggedError()("CliFailure", {
49
- details: Schema.optional(Schema.String),
327
+ var CliFailure = class extends Schema.TaggedErrorClass()("CliFailure", {
328
+ details: Schema.String.pipe(Schema.optionalKey),
50
329
  exitCode: Schema.Number,
51
330
  message: Schema.String
52
331
  }) {};
@@ -476,6 +755,86 @@ const runTodoList = Effect.fn("CLI.todoList")(function* runTodoList(command) {
476
755
  nextActions
477
756
  };
478
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
+ });
479
838
  /**
480
839
  * Data-driven command registry. Each command kind maps to its handler.
481
840
  * Adding a new command means adding one entry — no switch duplication.
@@ -499,6 +858,7 @@ const COMMAND_HANDLERS = {
499
858
  "review-create": () => requireServerMode("ringi review create"),
500
859
  "review-export": (c) => runReviewExport(c),
501
860
  "review-list": (c) => runReviewList(c),
861
+ "review-pr": (c) => runReviewPr(c),
502
862
  "review-resolve": () => requireServerMode("ringi review resolve"),
503
863
  "review-show": (c) => runReviewShow(c),
504
864
  "review-status": (c) => runReviewStatus(c),
@@ -526,6 +886,7 @@ const COMMAND_LABELS = {
526
886
  "review-create": "ringi review create",
527
887
  "review-export": "ringi review export",
528
888
  "review-list": "ringi review list",
889
+ "review-pr": "ringi review <pr-url>",
529
890
  "review-resolve": "ringi review resolve",
530
891
  "review-show": "ringi review show",
531
892
  "review-status": "ringi review status",
@@ -554,6 +915,7 @@ const runCommand = (command) => {
554
915
  const REVIEW_SOURCES = new Set([
555
916
  "branch",
556
917
  "commits",
918
+ "pull_request",
557
919
  "staged"
558
920
  ]);
559
921
  const REVIEW_STATUSES = new Set([
@@ -584,12 +946,12 @@ const requireValue = (state, flag) => {
584
946
  const peekValue = (state) => state.tokens[state.index + 1] ?? "";
585
947
  const decodePositiveInt = (raw, flag) => {
586
948
  const value = Number.parseInt(raw, 10);
587
- if (!Number.isInteger(value) || value < 0) return Either.left(usageError(`${flag} must be a non-negative integer.`));
588
- return Either.right(value);
949
+ if (!Number.isInteger(value) || value < 0) return Result.fail(usageError(`${flag} must be a non-negative integer.`));
950
+ return Result.succeed(value);
589
951
  };
590
952
  const decodeEnum = (raw, valid, label) => {
591
- if (!valid.has(raw)) return Either.left(usageError(`Invalid ${label}: ${raw}.`));
592
- return Either.right(raw);
953
+ if (!valid.has(raw)) return Result.fail(usageError(`Invalid ${label}: ${raw}.`));
954
+ return Result.succeed(raw);
593
955
  };
594
956
  /** Boolean flag: sets a key to `true`, advances cursor by 1. */
595
957
  const boolFlag = (key) => (state, acc) => {
@@ -613,9 +975,9 @@ const positiveIntFlag = (key, opts) => (state, acc) => {
613
975
  const error = requireValue(state, flag);
614
976
  if (Option.isSome(error)) return error;
615
977
  const decoded = decodePositiveInt(raw, flag);
616
- if (Either.isLeft(decoded)) return Option.some(decoded.left);
617
- if (opts?.min !== void 0 && decoded.right <= opts.min) return Option.some(usageError(`${flag} must be greater than ${opts.min}.`));
618
- acc[key] = decoded.right;
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;
619
981
  return Option.none();
620
982
  };
621
983
  /** Enum flag: consumes next token, validates membership, assigns to key. */
@@ -625,8 +987,8 @@ const enumFlag = (key, valid, label) => (state, acc) => {
625
987
  const error = requireValue(state, flag);
626
988
  if (Option.isSome(error)) return error;
627
989
  const decoded = decodeEnum(raw, valid, label);
628
- if (Either.isLeft(decoded)) return Option.some(decoded.left);
629
- acc[key] = decoded.right;
990
+ if (Result.isFailure(decoded)) return Option.some(decoded.failure);
991
+ acc[key] = decoded.success;
630
992
  return Option.none();
631
993
  };
632
994
  const createDefaultOptions = () => ({
@@ -695,8 +1057,8 @@ const parseReviewList = (state) => {
695
1057
  status: void 0
696
1058
  };
697
1059
  const error = runFlagLoop(state, acc, REVIEW_LIST_FLAGS, "review list");
698
- if (Option.isSome(error)) return Either.left(error.value);
699
- return Either.right({
1060
+ if (Option.isSome(error)) return Result.fail(error.value);
1061
+ return Result.succeed({
700
1062
  kind: "review-list",
701
1063
  ...acc
702
1064
  });
@@ -707,15 +1069,15 @@ const REVIEW_SHOW_FLAGS = {
707
1069
  };
708
1070
  const parseReviewShow = (state) => {
709
1071
  const id = state.tokens[state.index];
710
- if (!id) return Either.left(usageError("review show requires <id|last>."));
1072
+ if (!id) return Result.fail(usageError("review show requires <id|last>."));
711
1073
  state.index += 1;
712
1074
  const acc = {
713
1075
  comments: false,
714
1076
  todos: false
715
1077
  };
716
1078
  const error = runFlagLoop(state, acc, REVIEW_SHOW_FLAGS, "review show");
717
- if (Option.isSome(error)) return Either.left(error.value);
718
- return Either.right({
1079
+ if (Option.isSome(error)) return Result.fail(error.value);
1080
+ return Result.succeed({
719
1081
  id,
720
1082
  kind: "review-show",
721
1083
  ...acc
@@ -729,7 +1091,7 @@ const REVIEW_EXPORT_FLAGS = {
729
1091
  };
730
1092
  const parseReviewExport = (state) => {
731
1093
  const id = state.tokens[state.index];
732
- if (!id) return Either.left(usageError("review export requires <id|last>."));
1094
+ if (!id) return Result.fail(usageError("review export requires <id|last>."));
733
1095
  state.index += 1;
734
1096
  const acc = {
735
1097
  noResolved: false,
@@ -738,8 +1100,8 @@ const parseReviewExport = (state) => {
738
1100
  stdout: false
739
1101
  };
740
1102
  const error = runFlagLoop(state, acc, REVIEW_EXPORT_FLAGS, "review export");
741
- if (Option.isSome(error)) return Either.left(error.value);
742
- return Either.right({
1103
+ if (Option.isSome(error)) return Result.fail(error.value);
1104
+ return Result.succeed({
743
1105
  id,
744
1106
  kind: "review-export",
745
1107
  ...acc
@@ -765,10 +1127,10 @@ const parseReviewCreate = (state) => {
765
1127
  title: void 0
766
1128
  };
767
1129
  const error = runFlagLoop(state, acc, REVIEW_CREATE_FLAGS, "review create");
768
- if (Option.isSome(error)) return Either.left(error.value);
1130
+ if (Option.isSome(error)) return Result.fail(error.value);
769
1131
  const validationError = validateReviewCreate(acc);
770
- if (Option.isSome(validationError)) return Either.left(validationError.value);
771
- return Either.right({
1132
+ if (Option.isSome(validationError)) return Result.fail(validationError.value);
1133
+ return Result.succeed({
772
1134
  kind: "review-create",
773
1135
  ...acc
774
1136
  });
@@ -785,7 +1147,7 @@ const validateSourceDiff = (source, acc) => {
785
1147
  };
786
1148
  const parseSourceDiff = (state) => {
787
1149
  const source = state.tokens[state.index];
788
- if (!source || !REVIEW_SOURCES.has(source)) return Either.left(usageError("source diff requires <staged|branch|commits>."));
1150
+ if (!source || !REVIEW_SOURCES.has(source)) return Result.fail(usageError("source diff requires <staged|branch|commits>."));
789
1151
  state.index += 1;
790
1152
  const acc = {
791
1153
  branch: void 0,
@@ -793,10 +1155,10 @@ const parseSourceDiff = (state) => {
793
1155
  stat: false
794
1156
  };
795
1157
  const error = runFlagLoop(state, acc, SOURCE_DIFF_FLAGS, "source diff");
796
- if (Option.isSome(error)) return Either.left(error.value);
1158
+ if (Option.isSome(error)) return Result.fail(error.value);
797
1159
  const validationError = validateSourceDiff(source, acc);
798
- if (Option.isSome(validationError)) return Either.left(validationError.value);
799
- return Either.right({
1160
+ if (Option.isSome(validationError)) return Result.fail(validationError.value);
1161
+ return Result.succeed({
800
1162
  kind: "source-diff",
801
1163
  source,
802
1164
  ...acc
@@ -816,8 +1178,8 @@ const parseTodoList = (state) => {
816
1178
  status: "pending"
817
1179
  };
818
1180
  const error = runFlagLoop(state, acc, TODO_LIST_FLAGS, "todo list");
819
- if (Option.isSome(error)) return Either.left(error.value);
820
- return Either.right({
1181
+ if (Option.isSome(error)) return Result.fail(error.value);
1182
+ return Result.succeed({
821
1183
  kind: "todo-list",
822
1184
  ...acc
823
1185
  });
@@ -828,10 +1190,10 @@ const parseTodoList = (state) => {
828
1190
  */
829
1191
  const positionalIdParser = (kind, label) => (state) => {
830
1192
  const id = state.tokens[state.index];
831
- if (!id) return Either.left(usageError(`${label} requires <id>.`));
1193
+ if (!id) return Result.fail(usageError(`${label} requires <id>.`));
832
1194
  state.index += 1;
833
- while (state.index < state.tokens.length) if (!maybeParseGlobalFlag(state)) return Either.left(usageError(`Unknown flag for ${label}: ${state.tokens[state.index]}.`));
834
- return Either.right({
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({
835
1197
  id,
836
1198
  kind
837
1199
  });
@@ -841,13 +1203,13 @@ const parseTodoUndone = positionalIdParser("todo-undone", "todo undone");
841
1203
  const TODO_MOVE_FLAGS = { "--position": positiveIntFlag("position") };
842
1204
  const parseTodoMove = (state) => {
843
1205
  const id = state.tokens[state.index];
844
- if (!id) return Either.left(usageError("todo move requires <id>."));
1206
+ if (!id) return Result.fail(usageError("todo move requires <id>."));
845
1207
  state.index += 1;
846
1208
  const acc = { position: void 0 };
847
1209
  const error = runFlagLoop(state, acc, TODO_MOVE_FLAGS, "todo move");
848
- if (Option.isSome(error)) return Either.left(error.value);
849
- if (acc.position === void 0) return Either.left(usageError("todo move requires --position."));
850
- return Either.right({
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({
851
1213
  id,
852
1214
  kind: "todo-move",
853
1215
  position: acc.position
@@ -856,12 +1218,12 @@ const parseTodoMove = (state) => {
856
1218
  const TODO_REMOVE_FLAGS = { "--yes": boolFlag("yes") };
857
1219
  const parseTodoRemove = (state) => {
858
1220
  const id = state.tokens[state.index];
859
- if (!id) return Either.left(usageError("todo remove requires <id>."));
1221
+ if (!id) return Result.fail(usageError("todo remove requires <id>."));
860
1222
  state.index += 1;
861
1223
  const acc = { yes: false };
862
1224
  const error = runFlagLoop(state, acc, TODO_REMOVE_FLAGS, "todo remove");
863
- if (Option.isSome(error)) return Either.left(error.value);
864
- return Either.right({
1225
+ if (Option.isSome(error)) return Result.fail(error.value);
1226
+ return Result.succeed({
865
1227
  id,
866
1228
  kind: "todo-remove",
867
1229
  ...acc
@@ -881,8 +1243,8 @@ const parseTodoClear = (state) => {
881
1243
  yes: false
882
1244
  };
883
1245
  const error = runFlagLoop(state, acc, TODO_CLEAR_FLAGS, "todo clear");
884
- if (Option.isSome(error)) return Either.left(error.value);
885
- return Either.right({
1246
+ if (Option.isSome(error)) return Result.fail(error.value);
1247
+ return Result.succeed({
886
1248
  kind: "todo-clear",
887
1249
  ...acc
888
1250
  });
@@ -897,8 +1259,8 @@ const parseReviewStatus = (state) => {
897
1259
  source: void 0
898
1260
  };
899
1261
  const error = runFlagLoop(state, acc, REVIEW_STATUS_FLAGS, "review status");
900
- if (Option.isSome(error)) return Either.left(error.value);
901
- return Either.right({
1262
+ if (Option.isSome(error)) return Result.fail(error.value);
1263
+ return Result.succeed({
902
1264
  kind: "review-status",
903
1265
  ...acc
904
1266
  });
@@ -909,15 +1271,15 @@ const REVIEW_RESOLVE_FLAGS = {
909
1271
  };
910
1272
  const parseReviewResolve = (state) => {
911
1273
  const id = state.tokens[state.index];
912
- if (!id) return Either.left(usageError("review resolve requires <id|last>."));
1274
+ if (!id) return Result.fail(usageError("review resolve requires <id|last>."));
913
1275
  state.index += 1;
914
1276
  const acc = {
915
1277
  allComments: true,
916
1278
  yes: false
917
1279
  };
918
1280
  const error = runFlagLoop(state, acc, REVIEW_RESOLVE_FLAGS, "review resolve");
919
- if (Option.isSome(error)) return Either.left(error.value);
920
- return Either.right({
1281
+ if (Option.isSome(error)) return Result.fail(error.value);
1282
+ return Result.succeed({
921
1283
  id,
922
1284
  kind: "review-resolve",
923
1285
  ...acc
@@ -935,9 +1297,9 @@ const parseTodoAdd = (state) => {
935
1297
  text: ""
936
1298
  };
937
1299
  const error = runFlagLoop(state, acc, TODO_ADD_FLAGS, "todo add");
938
- if (Option.isSome(error)) return Either.left(error.value);
939
- if (!acc.text.trim()) return Either.left(usageError("todo add requires --text."));
940
- return Either.right({
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({
941
1303
  kind: "todo-add",
942
1304
  ...acc
943
1305
  });
@@ -966,8 +1328,8 @@ const parseServe = (state) => {
966
1328
  username: void 0
967
1329
  };
968
1330
  const error = runFlagLoop(state, acc, SERVE_FLAGS, "serve");
969
- if (Option.isSome(error)) return Either.left(error.value);
970
- return Either.right({
1331
+ if (Option.isSome(error)) return Result.fail(error.value);
1332
+ return Result.succeed({
971
1333
  kind: "serve",
972
1334
  ...acc
973
1335
  });
@@ -988,8 +1350,8 @@ const parseMcp = (state) => {
988
1350
  readonly: false
989
1351
  };
990
1352
  const error = runFlagLoop(state, acc, MCP_FLAGS, "mcp");
991
- if (Option.isSome(error)) return Either.left(error.value);
992
- return Either.right({
1353
+ if (Option.isSome(error)) return Result.fail(error.value);
1354
+ return Result.succeed({
993
1355
  kind: "mcp",
994
1356
  ...acc
995
1357
  });
@@ -1010,8 +1372,8 @@ const parseEvents = (state) => {
1010
1372
  type: void 0
1011
1373
  };
1012
1374
  const error = runFlagLoop(state, acc, EVENTS_FLAGS, "events");
1013
- if (Option.isSome(error)) return Either.left(error.value);
1014
- return Either.right({
1375
+ if (Option.isSome(error)) return Result.fail(error.value);
1376
+ return Result.succeed({
1015
1377
  kind: "events",
1016
1378
  ...acc
1017
1379
  });
@@ -1026,8 +1388,8 @@ const parseDataReset = (state) => {
1026
1388
  yes: false
1027
1389
  };
1028
1390
  const error = runFlagLoop(state, acc, DATA_RESET_FLAGS, "data reset");
1029
- if (Option.isSome(error)) return Either.left(error.value);
1030
- return Either.right({
1391
+ if (Option.isSome(error)) return Result.fail(error.value);
1392
+ return Result.succeed({
1031
1393
  kind: "data-reset",
1032
1394
  ...acc
1033
1395
  });
@@ -1055,13 +1417,32 @@ const TODO_VERB_PARSERS = {
1055
1417
  remove: parseTodoRemove,
1056
1418
  undone: parseTodoUndone
1057
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
+ };
1058
1439
  /** Subcommand family parsers keyed by family name. */
1059
1440
  /** Data verb parsers keyed by verb name. */
1060
1441
  const DATA_VERB_PARSERS = {
1061
1442
  migrate: (state) => {
1062
1443
  const error = ensureNoExtraArgs(state, "data migrate");
1063
- if (Option.isSome(error)) return Either.left(error.value);
1064
- return Either.right({ kind: "data-migrate" });
1444
+ if (Option.isSome(error)) return Result.fail(error.value);
1445
+ return Result.succeed({ kind: "data-migrate" });
1065
1446
  },
1066
1447
  reset: parseDataReset
1067
1448
  };
@@ -1069,59 +1450,63 @@ const DATA_VERB_PARSERS = {
1069
1450
  const FAMILY_PARSERS = {
1070
1451
  data: (state) => {
1071
1452
  const verb = state.tokens[state.index];
1072
- if (!verb) return Either.right({
1453
+ if (!verb) return Result.succeed({
1073
1454
  kind: "help",
1074
1455
  topic: ["data"]
1075
1456
  });
1076
1457
  state.index += 1;
1077
1458
  const parser = DATA_VERB_PARSERS[verb];
1078
- if (!parser) return Either.left(usageError(`Unknown data command: ${verb}.`));
1459
+ if (!parser) return Result.fail(usageError(`Unknown data command: ${verb}.`));
1079
1460
  return parser(state);
1080
1461
  },
1081
1462
  doctor: (state) => {
1082
1463
  const error = ensureNoExtraArgs(state, "doctor");
1083
- if (Option.isSome(error)) return Either.left(error.value);
1084
- return Either.right({ kind: "doctor" });
1464
+ if (Option.isSome(error)) return Result.fail(error.value);
1465
+ return Result.succeed({ kind: "doctor" });
1085
1466
  },
1086
1467
  events: parseEvents,
1087
1468
  export: parseReviewExport,
1088
1469
  mcp: parseMcp,
1089
1470
  review: (state) => {
1090
1471
  const verb = state.tokens[state.index];
1091
- if (!verb) return Either.right({
1472
+ if (!verb) return Result.succeed({
1092
1473
  kind: "help",
1093
1474
  topic: ["review"]
1094
1475
  });
1476
+ if (looksLikePrUrl(verb)) {
1477
+ state.index += 1;
1478
+ return parseReviewPr(state, verb);
1479
+ }
1095
1480
  state.index += 1;
1096
1481
  const parser = REVIEW_VERB_PARSERS[verb];
1097
- if (!parser) return Either.left(usageError(`Unknown review command: ${verb}.`));
1482
+ if (!parser) return Result.fail(usageError(`Unknown review command: ${verb}.`));
1098
1483
  return parser(state);
1099
1484
  },
1100
1485
  serve: parseServe,
1101
1486
  source: (state) => {
1102
1487
  const verb = state.tokens[state.index];
1103
- if (!verb) return Either.right({
1488
+ if (!verb) return Result.succeed({
1104
1489
  kind: "help",
1105
1490
  topic: ["source"]
1106
1491
  });
1107
1492
  state.index += 1;
1108
1493
  if (verb === "list") {
1109
1494
  const error = ensureNoExtraArgs(state, "source list");
1110
- if (Option.isSome(error)) return Either.left(error.value);
1111
- return Either.right({ kind: "source-list" });
1495
+ if (Option.isSome(error)) return Result.fail(error.value);
1496
+ return Result.succeed({ kind: "source-list" });
1112
1497
  }
1113
1498
  if (verb === "diff") return parseSourceDiff(state);
1114
- return Either.left(usageError(`Unknown source command: ${verb}.`));
1499
+ return Result.fail(usageError(`Unknown source command: ${verb}.`));
1115
1500
  },
1116
1501
  todo: (state) => {
1117
1502
  const verb = state.tokens[state.index];
1118
- if (!verb) return Either.right({
1503
+ if (!verb) return Result.succeed({
1119
1504
  kind: "help",
1120
1505
  topic: ["todo"]
1121
1506
  });
1122
1507
  state.index += 1;
1123
1508
  const parser = TODO_VERB_PARSERS[verb];
1124
- if (!parser) return Either.left(usageError(`Unknown todo command: ${verb}.`));
1509
+ if (!parser) return Result.fail(usageError(`Unknown todo command: ${verb}.`));
1125
1510
  return parser(state);
1126
1511
  }
1127
1512
  };
@@ -1131,31 +1516,31 @@ const FAMILY_PARSERS = {
1131
1516
  */
1132
1517
  const parseWithState = (state) => {
1133
1518
  while (state.index < state.tokens.length && maybeParseGlobalFlag(state));
1134
- if (state.options.version) return Either.right({ kind: "version" });
1135
- if (state.index >= state.tokens.length) return Either.right({
1519
+ if (state.options.version) return Result.succeed({ kind: "version" });
1520
+ if (state.index >= state.tokens.length) return Result.succeed({
1136
1521
  kind: "help",
1137
1522
  topic: []
1138
1523
  });
1139
1524
  const first = state.tokens[state.index];
1140
- if (!first) return Either.right({
1525
+ if (!first) return Result.succeed({
1141
1526
  kind: "help",
1142
1527
  topic: []
1143
1528
  });
1144
1529
  if (first === "help") {
1145
1530
  const topic = state.tokens.slice(state.index + 1);
1146
1531
  state.index = state.tokens.length;
1147
- return Either.right({
1532
+ return Result.succeed({
1148
1533
  kind: "help",
1149
1534
  topic
1150
1535
  });
1151
1536
  }
1152
- if (state.options.help) return Either.right({
1537
+ if (state.options.help) return Result.succeed({
1153
1538
  kind: "help",
1154
1539
  topic: state.tokens.slice(state.index)
1155
1540
  });
1156
1541
  state.index += 1;
1157
1542
  const familyParser = FAMILY_PARSERS[first];
1158
- if (!familyParser) return Either.left(usageError(`Unknown command: ${first}.`));
1543
+ if (!familyParser) return Result.fail(usageError(`Unknown command: ${first}.`));
1159
1544
  return familyParser(state);
1160
1545
  };
1161
1546
  const parseCliArgs = (argv) => {
@@ -1165,23 +1550,14 @@ const parseCliArgs = (argv) => {
1165
1550
  options,
1166
1551
  tokens: argv
1167
1552
  });
1168
- if (Either.isLeft(result)) return Either.left(result.left);
1169
- return Either.right({
1170
- command: result.right,
1553
+ if (Result.isFailure(result)) return Result.fail(result.failure);
1554
+ return Result.succeed({
1555
+ command: result.success,
1171
1556
  options
1172
1557
  });
1173
1558
  };
1174
1559
  //#endregion
1175
1560
  //#region src/cli/runtime.ts
1176
- /**
1177
- * Resolves the repository root up front so every later git and database path is
1178
- * anchored to the same directory, even when the user invoked the CLI elsewhere.
1179
- *
1180
- * NOTE: This uses execFileSync intentionally — it runs once at CLI startup
1181
- * before the Effect runtime is constructed, so there is no fiber to block.
1182
- * Moving this into the Effect runtime would create a circular dependency
1183
- * (config → git → config).
1184
- */
1185
1561
  const resolveRepositoryRoot = (repoOverride) => {
1186
1562
  const cwd = repoOverride ? resolve(repoOverride) : process.cwd();
1187
1563
  try {
@@ -1198,8 +1574,12 @@ const resolveRepositoryRoot = (repoOverride) => {
1198
1574
  };
1199
1575
  const resolveDbPath = (repoRoot, dbPathOverride) => dbPathOverride ? resolve(dbPathOverride) : resolve(repoRoot, ".ringi/reviews.db");
1200
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
+ */
1201
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";
1202
- const commandUsesCoreRuntime = (command) => command.kind === "review-list" || command.kind === "review-show" || command.kind === "review-export" || command.kind === "review-status" || command.kind === "todo-list" || command.kind === "review-create" || command.kind === "todo-add" || 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";
1203
1583
  const resolveCliConfig = (args) => {
1204
1584
  const repoRootResult = resolveRepositoryRoot(args.repo);
1205
1585
  if (repoRootResult instanceof CliFailure) return repoRootResult;
@@ -1213,23 +1593,18 @@ const resolveCliConfig = (args) => {
1213
1593
  verbose: args.verbose
1214
1594
  };
1215
1595
  };
1216
- /**
1217
- * Read-only commands still depend on initialized local state because the shared
1218
- * services use the same database-backed storage as the other clients.
1219
- */
1220
1596
  const ensureLocalStateAvailable = (config) => {
1221
1597
  if (!existsSync(config.dbPath)) return new CliFailure({
1222
1598
  exitCode: ExitCode.StateUnavailable,
1223
1599
  message: `Local state is missing at ${config.dbPath}. Run 'ringi data migrate' or start 'ringi serve' once to initialize local state.`
1224
1600
  });
1225
1601
  };
1226
- const makeConfigLayer = (config) => Layer.setConfigProvider(ConfigProvider.fromMap(new Map([["DB_PATH", config.dbPath], ["REPOSITORY_PATH", config.repoRoot]])));
1602
+ const makeConfigLayer = (config) => ConfigProvider.layer(ConfigProvider.fromUnknown({
1603
+ DB_PATH: config.dbPath,
1604
+ REPOSITORY_PATH: config.repoRoot
1605
+ }));
1227
1606
  const createCoreCliRuntime = (config) => ManagedRuntime.make(Layer.mergeAll(CoreLive, CliConfigLive(config)).pipe(Layer.provideMerge(makeConfigLayer(config))));
1228
1607
  const createGitCliRuntime = (config) => ManagedRuntime.make(Layer.mergeAll(GitService.Default, CliConfigLive(config)).pipe(Layer.provideMerge(makeConfigLayer(config))));
1229
- /**
1230
- * Centralizes runtime selection. Returns either valid resources or a CliFailure.
1231
- * Returns null for help/version commands that don't need a runtime.
1232
- */
1233
1608
  const createCliRuntimeResources = (command, args) => {
1234
1609
  if (!commandNeedsRepository(command)) return null;
1235
1610
  const configResult = resolveCliConfig(args);
@@ -1245,7 +1620,7 @@ const createCliRuntimeResources = (command, args) => {
1245
1620
  };
1246
1621
  //#endregion
1247
1622
  //#region src/cli/main.ts
1248
- const CLI_VERSION = "0.2.1";
1623
+ const CLI_VERSION = "0.3.0";
1249
1624
  const COMMAND_TREE = {
1250
1625
  commands: [
1251
1626
  {
@@ -1611,19 +1986,35 @@ const installSignalHandlers = (dispose) => {
1611
1986
  };
1612
1987
  };
1613
1988
  /**
1614
- * Resolves the built Nitro server entry point. Returns the absolute path to
1615
- * `.output/server/index.mjs` relative to the package root (the directory
1616
- * containing this CLI entry point after bundling).
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.
1617
2002
  */
1618
2003
  const resolveServerEntry = () => {
1619
- const candidates = [resolve(process.cwd(), ".output", "server", "index.mjs")];
1620
- if (import.meta.dirname) candidates.push(resolve(import.meta.dirname, "..", ".output", "server", "index.mjs"));
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"));
1621
2011
  return candidates.find((candidate) => existsSync(candidate));
1622
2012
  };
1623
2013
  const runServe = (command) => {
1624
2014
  const serverEntry = resolveServerEntry();
1625
2015
  if (!serverEntry) {
1626
- process.stderr.write("No built server found. Run 'pnpm build' first, then retry 'ringi serve'.\n");
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`);
1627
2018
  process.exit(ExitCode.RuntimeFailure);
1628
2019
  }
1629
2020
  const env = {
@@ -1670,13 +2061,13 @@ const failAndExit = (opts) => {
1670
2061
  const main = async () => {
1671
2062
  const argv = process.argv.slice(2);
1672
2063
  const parseResult = parseCliArgs(argv);
1673
- if (Either.isLeft(parseResult)) return failAndExit({
2064
+ if (Result.isFailure(parseResult)) return failAndExit({
1674
2065
  cmdStr: "ringi",
1675
- error: parseResult.left,
2066
+ error: parseResult.failure,
1676
2067
  json: argv.includes("--json"),
1677
2068
  verbose: false
1678
2069
  });
1679
- const { command, options } = parseResult.right;
2070
+ const { command, options } = parseResult.success;
1680
2071
  if (command.kind === "help") {
1681
2072
  if (options.json) writeJson(success("ringi", COMMAND_TREE, ROOT_NEXT_ACTIONS));
1682
2073
  else writeHuman(renderHelp(command));