diffing 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 (421) hide show
  1. package/README.md +185 -4
  2. package/dist/{cli-agent-Vbs-ePHv.mjs → cli-agent-KxNAFIPf.mjs} +8 -1
  3. package/dist/cli-gh-CDGSpndZ.mjs +147 -0
  4. package/dist/cli.mjs +1594 -68
  5. package/dist/client/assets/{angular-html-fdYbuxHF.js → angular-html-DbbrJavS.js} +1 -1
  6. package/dist/client/assets/{angular-ts-D3iFqR-R.js → angular-ts-tCrxb1ht.js} +1 -1
  7. package/dist/client/assets/{apl-MnBV-iQN.js → apl-DawII0w7.js} +1 -1
  8. package/dist/client/assets/arc-DIRV7ZXr.js +1 -0
  9. package/dist/client/assets/architecture-7EHR7CIX-BM5HxebS.js +1 -0
  10. package/dist/client/assets/architectureDiagram-3BPJPVTR-3IoH78OV.js +36 -0
  11. package/dist/client/assets/array-CkF66D10.js +1 -0
  12. package/dist/client/assets/{astro-BrpK01E7.js → astro-DIyGTFXc.js} +1 -1
  13. package/dist/client/assets/{blade-CGmyBuwD.js → blade-CuF4Z1mP.js} +1 -1
  14. package/dist/client/assets/blockDiagram-GPEHLZMM-CFxPndC0.js +132 -0
  15. package/dist/client/assets/{bsl-Dx5tFn3i.js → bsl-BrHd4RA2.js} +1 -1
  16. package/dist/client/assets/{c-xQnwtG9Y.js → c-DUrjo9T5.js} +1 -1
  17. package/dist/client/assets/c4Diagram-AAUBKEIU-CGMBnkiv.js +10 -0
  18. package/dist/client/assets/{cairo-wCCQTdA9.js → cairo-BDZ6TRZS.js} +1 -1
  19. package/dist/client/assets/channel-D5AdjEWX.js +1 -0
  20. package/dist/client/assets/chunk-2J33WTMH-CJMBLaXy.js +1 -0
  21. package/dist/client/assets/chunk-3OPIFGDE-C9HQWVLl.js +62 -0
  22. package/dist/client/assets/chunk-4BX2VUAB-Qqg72GQd.js +1 -0
  23. package/dist/client/assets/chunk-4EGX6M5U-C70lfA2p.js +1 -0
  24. package/dist/client/assets/chunk-55IACEB6-DZF8brTR.js +1 -0
  25. package/dist/client/assets/chunk-5DO6E6H7-PprNUwlI.js +1 -0
  26. package/dist/client/assets/chunk-5ZQYHXKU-BflVFV5z.js +2 -0
  27. package/dist/client/assets/chunk-727SXJPM-COn6BRLY.js +206 -0
  28. package/dist/client/assets/chunk-AQP2D5EJ-CNOjODUR.js +231 -0
  29. package/dist/client/assets/chunk-BR22UD5L-D8XCYMy9.js +1 -0
  30. package/dist/client/assets/chunk-BSJP7CBP-PQX4ASCI.js +1 -0
  31. package/dist/client/assets/chunk-CSCIHK7Q-BfmUvL-L.js +124 -0
  32. package/dist/client/assets/chunk-FHYWG6QK-CcHPrHH1.js +1 -0
  33. package/dist/client/assets/chunk-FMBD7UC4-DorfwLnQ.js +15 -0
  34. package/dist/client/assets/chunk-KSCS5N6A-CXY93mId.js +10 -0
  35. package/dist/client/assets/chunk-L5ZTLDWV-BjMrSVmH.js +1 -0
  36. package/dist/client/assets/chunk-LZXEDZCA-q8jj6Nyb.js +2 -0
  37. package/dist/client/assets/chunk-MPE355IW-Bf9yHfDS.js +1 -0
  38. package/dist/client/assets/chunk-MZUSXYTE-CnUIUKI4.js +1 -0
  39. package/dist/client/assets/chunk-N66VUXT2-ahAf9GvV.js +1 -0
  40. package/dist/client/assets/chunk-ND2GUHAM-BRRQbYHl.js +1 -0
  41. package/dist/client/assets/chunk-NNHCCRGN-DHF3V75i.js +159 -0
  42. package/dist/client/assets/chunk-NZK2D7GU-Czt1qHrO.js +1 -0
  43. package/dist/client/assets/chunk-O5CBEL6O-DEopURak.js +70 -0
  44. package/dist/client/assets/chunk-PUPMXCY4-D0KKIhut.js +1 -0
  45. package/dist/client/assets/chunk-QZHKN3VN-BE917fn9.js +1 -0
  46. package/dist/client/assets/chunk-UIBZB4QT-TYsZ_c8u.js +1 -0
  47. package/dist/client/assets/chunk-WCWK7LTN-Dj8SfnMU.js +1 -0
  48. package/dist/client/assets/chunk-WU5MYG2G-DdoWnPla.js +1 -0
  49. package/dist/client/assets/chunk-XPW4576I-DocCoKUT.js +32 -0
  50. package/dist/client/assets/classDiagram-4FO5ZUOK-CxjhKO7s.js +1 -0
  51. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CSScAAQ4.js +1 -0
  52. package/dist/client/assets/{cobol-ypVzOIQu.js → cobol-BylylR2x.js} +1 -1
  53. package/dist/client/assets/{coffee-CrtE1uj2.js → coffee-DCKfk8xx.js} +1 -1
  54. package/dist/client/assets/cose-bilkent-S5V4N54A-Cm0BD5CS.js +1 -0
  55. package/dist/client/assets/{cpp-DLd4XEKA.js → cpp-CfKCIrGK.js} +1 -1
  56. package/dist/client/assets/{crystal-CJYvpLt_.js → crystal-v-9RIu3l.js} +1 -1
  57. package/dist/client/assets/{css-DrX2B9os.js → css-XjlXeGmZ.js} +1 -1
  58. package/dist/client/assets/cytoscape.esm-B7MQoI9k.js +321 -0
  59. package/dist/client/assets/dagre-BM42HDAG-kqgFUaF1.js +4 -0
  60. package/dist/client/assets/dagre-DAhKikc0.js +1 -0
  61. package/dist/client/assets/defaultLocale-B_TxGd0k.js +1 -0
  62. package/dist/client/assets/diagram-2AECGRRQ-B3hBvGUc.js +43 -0
  63. package/dist/client/assets/diagram-5GNKFQAL-r8zU8C-Y.js +10 -0
  64. package/dist/client/assets/diagram-KO2AKTUF-CDWD5zkZ.js +3 -0
  65. package/dist/client/assets/diagram-LMA3HP47-CRRfS6RD.js +24 -0
  66. package/dist/client/assets/diagram-OG6HWLK6-ChBS6E-K.js +24 -0
  67. package/dist/client/assets/dist-DUTeKbN7.js +1 -0
  68. package/dist/client/assets/{edge-BsQYk9OW.js → edge-Cwh953i8.js} +1 -1
  69. package/dist/client/assets/{elixir-DU_VfGYR.js → elixir-C4MgLzTj.js} +1 -1
  70. package/dist/client/assets/{elm-W4eOLEDv.js → elm-CFBu8r7i.js} +1 -1
  71. package/dist/client/assets/erDiagram-TEJ5UH35-_03Fb9RH.js +85 -0
  72. package/dist/client/assets/{erb-D9FdIJ2e.js → erb-CrCna7R1.js} +1 -1
  73. package/dist/client/assets/{erlang-CtK0kQ6z.js → erlang-cuhwpmsu.js} +1 -1
  74. package/dist/client/assets/eventmodeling-FCH6USID-hlXE2E34.js +1 -0
  75. package/dist/client/assets/flowDiagram-I6XJVG4X-D9pSaH_8.js +162 -0
  76. package/dist/client/assets/{fortran-fixed-form-DBM4e0_k.js → fortran-fixed-form-DWr0EnJn.js} +1 -1
  77. package/dist/client/assets/{fsharp-CmzzGQLe.js → fsharp-CQ5iYSPA.js} +1 -1
  78. package/dist/client/assets/ganttDiagram-6RSMTGT7-DKBfXY9D.js +292 -0
  79. package/dist/client/assets/{gdresource-B_wbPqHd.js → gdresource-CN-lsNu5.js} +1 -1
  80. package/dist/client/assets/{git-commit-1z4D6NzL.js → git-commit-6e79eN7Q.js} +1 -1
  81. package/dist/client/assets/{git-rebase-BtPCXjbS.js → git-rebase-CExLk2dD.js} +1 -1
  82. package/dist/client/assets/gitGraph-WXDBUCRP-D_3ZhTam.js +1 -0
  83. package/dist/client/assets/gitGraphDiagram-PVQCEYII-BPQ2kpJA.js +106 -0
  84. package/dist/client/assets/{glimmer-js-BVfUGMUb.js → glimmer-js-wjBSKaMu.js} +1 -1
  85. package/dist/client/assets/{glimmer-ts-BZEaHDZL.js → glimmer-ts-CyjMXDgZ.js} +1 -1
  86. package/dist/client/assets/{glsl-CSiNDuwt.js → glsl-CWOoN8or.js} +1 -1
  87. package/dist/client/assets/graphlib-DBdw4opr.js +1 -0
  88. package/dist/client/assets/{graphql-Crf08Rhb.js → graphql-DI5CB58U.js} +1 -1
  89. package/dist/client/assets/{hack-DUiQuO20.js → hack-Ct_Cm7Pr.js} +1 -1
  90. package/dist/client/assets/{haml-BtvVTDRG.js → haml-DzAjli8d.js} +1 -1
  91. package/dist/client/assets/{handlebars-Bu_cdZA_.js → handlebars-BfJB0RYx.js} +1 -1
  92. package/dist/client/assets/{html-C4cun3zD.js → html-CK8auhK8.js} +1 -1
  93. package/dist/client/assets/{html-derivative-CQnD80KV.js → html-derivative-B7VntYnF.js} +1 -1
  94. package/dist/client/assets/{http-DuOJF9Kg.js → http-_Zy71Sv3.js} +1 -1
  95. package/dist/client/assets/{hurl-DgrdYsYh.js → hurl-VbL4I95z.js} +1 -1
  96. package/dist/client/assets/{hxml-DC4KwKt2.js → hxml-nwJci6H5.js} +1 -1
  97. package/dist/client/assets/index-C3p7VKOX.css +1 -0
  98. package/dist/client/assets/index-Dv9jPmOc.js +3921 -0
  99. package/dist/client/assets/info-J43DQDTF-CAllC7mK.js +1 -0
  100. package/dist/client/assets/infoDiagram-5YYISTIA-B5RN-4Ke.js +2 -0
  101. package/dist/client/assets/init-Ce66tIvf.js +1 -0
  102. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DpbSX3Zy.js +70 -0
  103. package/dist/client/assets/{java-D-vtFiv8.js → java-0CC2DnK9.js} +1 -1
  104. package/dist/client/assets/{javascript-CFYFXvdN.js → javascript-DKSTP7rz.js} +1 -1
  105. package/dist/client/assets/{jinja-fZvIg2_s.js → jinja-DoGBHNJ6.js} +1 -1
  106. package/dist/client/assets/{jison-DEzxLq5g.js → jison-Bnl3lPA2.js} +1 -1
  107. package/dist/client/assets/journeyDiagram-JHISSGLW-B_X4_qPO.js +139 -0
  108. package/dist/client/assets/{json-QM4VBqMC.js → json-BQ3Ddj6g.js} +1 -1
  109. package/dist/client/assets/{jsx-DLzUM_cC.js → jsx-C_aFDuFZ.js} +1 -1
  110. package/dist/client/assets/{julia-Dwt1SIpn.js → julia-nIjp5yMR.js} +1 -1
  111. package/dist/client/assets/{just-Dwp-hLcL.js → just-zc4yJ46n.js} +1 -1
  112. package/dist/client/assets/kanban-definition-UN3LZRKU-BSs4C5DW.js +89 -0
  113. package/dist/client/assets/katex-eRFNlMh0.js +257 -0
  114. package/dist/client/assets/{latex-CMF1JViz.js → latex-BszBC80j.js} +1 -1
  115. package/dist/client/assets/line-C9HWVwPi.js +1 -0
  116. package/dist/client/assets/linear-DatJfpKY.js +1 -0
  117. package/dist/client/assets/{liquid-czkpOZWY.js → liquid-DglQpTge.js} +1 -1
  118. package/dist/client/assets/{lua-DE5PD0Vh.js → lua-BcbliFeR.js} +1 -1
  119. package/dist/client/assets/{marko-DHeEvZiW.js → marko-BPm4M8j-.js} +1 -1
  120. package/dist/client/assets/{mdc-B941liUQ.js → mdc-BNUj07UF.js} +1 -1
  121. package/dist/client/assets/mermaid-parser.core-BVEKlqQr.js +4 -0
  122. package/dist/client/assets/mermaid.core-DVis21V_.js +9 -0
  123. package/dist/client/assets/mindmap-definition-RKZ34NQL-Cuo7KewF.js +96 -0
  124. package/dist/client/assets/{nextflow-CApkvBGW.js → nextflow-DSfydIxt.js} +1 -1
  125. package/dist/client/assets/{nginx-Pb9t8c-r.js → nginx-CSSJwkd6.js} +1 -1
  126. package/dist/client/assets/{nim-MMUvW_R1.js → nim-nQAskwTR.js} +1 -1
  127. package/dist/client/assets/ordinal-5ziJS8mo.js +1 -0
  128. package/dist/client/assets/packet-YPE3B663-Dy58IAiO.js +1 -0
  129. package/dist/client/assets/path-g3G46IZB.js +1 -0
  130. package/dist/client/assets/{perl-DXwXRmuC.js → perl-CtH8xYH6.js} +1 -1
  131. package/dist/client/assets/{php-D42kv59r.js → php-Dcc3T2PH.js} +1 -1
  132. package/dist/client/assets/pie-LRSECV5Y-FXFFfdz2.js +1 -0
  133. package/dist/client/assets/pieDiagram-4H26LBE5-BfpvCRmo.js +30 -0
  134. package/dist/client/assets/{pug-v0iesski.js → pug-BCLaymlV.js} +1 -1
  135. package/dist/client/assets/{qml-spxQQ-ei.js → qml-BpKydcOi.js} +1 -1
  136. package/dist/client/assets/quadrantDiagram-W4KKPZXB-CJY6un0-.js +7 -0
  137. package/dist/client/assets/{r-CobWTBG9.js → r-BzCB9dpd.js} +1 -1
  138. package/dist/client/assets/radar-GUYGQ44K-BKTzSQ68.js +1 -0
  139. package/dist/client/assets/{razor-B-_Ww3MB.js → razor-BWmujqiQ.js} +1 -1
  140. package/dist/client/assets/{regexp-BJMVs8R0.js → regexp-urCI3YiZ.js} +1 -1
  141. package/dist/client/assets/requirementDiagram-4Y6WPE33-DIHWguS0.js +84 -0
  142. package/dist/client/assets/rough.esm-BBrzuQ0N.js +1 -0
  143. package/dist/client/assets/{rst-BHcaWsY0.js → rst-YTDC9H6u.js} +1 -1
  144. package/dist/client/assets/{ruby-BCMT6kog.js → ruby-v1IXQAFZ.js} +1 -1
  145. package/dist/client/assets/sankeyDiagram-5OEKKPKP-DJRPEO_F.js +40 -0
  146. package/dist/client/assets/{sas-DHfZnd3R.js → sas-KDUG3BSk.js} +1 -1
  147. package/dist/client/assets/{scss-DjDsJvDO.js → scss-pLIGFpjD.js} +1 -1
  148. package/dist/client/assets/sequenceDiagram-3UESZ5HK-DX0jVCOU.js +162 -0
  149. package/dist/client/assets/{shaderlab-DXAhMdPo.js → shaderlab-CbbQwRPF.js} +1 -1
  150. package/dist/client/assets/{shellscript-B6qaIiU-.js → shellscript-B5ZTyEsK.js} +1 -1
  151. package/dist/client/assets/{shellsession-ByCeir2b.js → shellsession-DVK3bw4p.js} +1 -1
  152. package/dist/client/assets/{soy-C6WpnMSF.js → soy-BXgyj2-T.js} +1 -1
  153. package/dist/client/assets/{sparql-BdcL8cbr.js → sparql-DEoxFJ5d.js} +1 -1
  154. package/dist/client/assets/{sql-BIvSNLV_.js → sql-G2vZCmw6.js} +1 -1
  155. package/dist/client/assets/src-D9zkqEEX.js +1 -0
  156. package/dist/client/assets/{stata-PG9iZ--I.js → stata-C4mEnygx.js} +1 -1
  157. package/dist/client/assets/stateDiagram-AJRCARHV-AMtMxKgQ.js +1 -0
  158. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C89CM-_4.js +1 -0
  159. package/dist/client/assets/{surrealql-CmvCzG09.js → surrealql-Bf8gsTcP.js} +1 -1
  160. package/dist/client/assets/{svelte-ChumG88s.js → svelte-C1oIyl9t.js} +1 -1
  161. package/dist/client/assets/{templ-CmzuRY_a.js → templ-PJcWUuTF.js} +1 -1
  162. package/dist/client/assets/{tex-DL8t-_eJ.js → tex-B7Fnujsi.js} +1 -1
  163. package/dist/client/assets/timeline-definition-PNZ67QCA-BZJSqcAg.js +120 -0
  164. package/dist/client/assets/treeView-BLDUP644-DtjHwb6x.js +1 -0
  165. package/dist/client/assets/treemap-LRROVOQU-GqTXzMFU.js +1 -0
  166. package/dist/client/assets/{ts-tags-BFfE0Egm.js → ts-tags-CAAsFnUt.js} +1 -1
  167. package/dist/client/assets/{tsx-C1bFyHcE.js → tsx-DAKyTsWW.js} +1 -1
  168. package/dist/client/assets/{twig-DkIluZEQ.js → twig-Bd4dieZF.js} +1 -1
  169. package/dist/client/assets/{typescript-Gc86Bc3j.js → typescript-8myByDGd.js} +1 -1
  170. package/dist/client/assets/vennDiagram-CIIHVFJN-CMjBZNs2.js +34 -0
  171. package/dist/client/assets/{vue-C88JmL53.js → vue-fBA2B-QU.js} +1 -1
  172. package/dist/client/assets/{vue-html-Cry4aksC.js → vue-html-DEZECpsR.js} +1 -1
  173. package/dist/client/assets/{vue-vine-zlDkA3Ui.js → vue-vine-B_gG939R.js} +1 -1
  174. package/dist/client/assets/wardley-L42UT6IY-pROVAAhW.js +1 -0
  175. package/dist/client/assets/wardleyDiagram-YWT4CUSO-qeHmPLyL.js +78 -0
  176. package/dist/client/assets/{xml-NvwpIFDy.js → xml-DbXNss3s.js} +1 -1
  177. package/dist/client/assets/{xsl-CDjXvY9w.js → xsl-B7_dgqqc.js} +1 -1
  178. package/dist/client/assets/xychartDiagram-2RQKCTM6-NN2Et7YL.js +7 -0
  179. package/dist/client/assets/{yaml-Bh-LPjcH.js → yaml-DSyIApdJ.js} +1 -1
  180. package/dist/client/index.html +2 -5
  181. package/dist/{mcp-DKlThflu.mjs → mcp-BWeWR4yd.mjs} +3 -1
  182. package/dist/plan-format-f62JWFI-.mjs +117 -0
  183. package/dist/{server-lock-CAdqFSfl.mjs → server-lock-Dj9fG9md.mjs} +279 -132
  184. package/dist/update-check-DAPFCqpF.mjs +129 -0
  185. package/package.json +15 -5
  186. package/dist/cli-agent-Cfuy9fLC.mjs +0 -179
  187. package/dist/cli-agent-CtNivjQW.mjs +0 -179
  188. package/dist/cli-agent-DZBvZoXZ.mjs +0 -179
  189. package/dist/cli-agent-WzJPMH52.mjs +0 -174
  190. package/dist/cli-agent-uYUoCA4X.mjs +0 -179
  191. package/dist/client/assets/index-CZZd7iTh.js +0 -3887
  192. package/dist/client/assets/index-CkC-EvWo.css +0 -1
  193. package/dist/mcp-BQmH_By-.mjs +0 -119
  194. package/dist/mcp-CLgzC8_a.mjs +0 -119
  195. package/dist/mcp-D9E2HQZ9.mjs +0 -119
  196. package/dist/mcp-LVbLV17O.mjs +0 -119
  197. package/dist/mcp-nMwTAut3.mjs +0 -119
  198. package/dist/server-lock-BbiF7u3F.mjs +0 -493
  199. package/dist/server-lock-Bx9ixiF6.mjs +0 -575
  200. package/dist/server-lock-DIkImokU.mjs +0 -487
  201. package/dist/server-lock-DbxpfF3j.mjs +0 -575
  202. /package/dist/client/assets/{abap-CkQ0tsz-.js → abap-cWYhO34H.js} +0 -0
  203. /package/dist/client/assets/{actionscript-3-DHm0ZfF4.js → actionscript-3-B80ndJsD.js} +0 -0
  204. /package/dist/client/assets/{ada-DK_ua92Z.js → ada-C0C0SoYB.js} +0 -0
  205. /package/dist/client/assets/{andromeeda-D2ccw8v4.js → andromeeda-Ch2kWS7s.js} +0 -0
  206. /package/dist/client/assets/{apache-DXtpFu7o.js → apache-DBwBm4bB.js} +0 -0
  207. /package/dist/client/assets/{apex-DlnRRWpl.js → apex-FcV7UxQi.js} +0 -0
  208. /package/dist/client/assets/{applescript-CShk62ad.js → applescript-hQs8VNpS.js} +0 -0
  209. /package/dist/client/assets/{ara-CWN-FDkm.js → ara-BOadBOZg.js} +0 -0
  210. /package/dist/client/assets/{asciidoc-Bg-PM9Vn.js → asciidoc-BsfqiBck.js} +0 -0
  211. /package/dist/client/assets/{asm-DKOhR24S.js → asm-C2vUqzqz.js} +0 -0
  212. /package/dist/client/assets/{aurora-x-DpaEirL8.js → aurora-x-BHLqzt7j.js} +0 -0
  213. /package/dist/client/assets/{awk-3ibKHffv.js → awk-D4e-eeZT.js} +0 -0
  214. /package/dist/client/assets/{ayu-dark-ursiSH5k.js → ayu-dark-DCotN_ZT.js} +0 -0
  215. /package/dist/client/assets/{ayu-light-DMjlrKmx.js → ayu-light-CKB6o_im.js} +0 -0
  216. /package/dist/client/assets/{ayu-mirage-DZEMb75h.js → ayu-mirage-B38lW-C5.js} +0 -0
  217. /package/dist/client/assets/{ballerina-xx7VNGxs.js → ballerina-BzvFqbha.js} +0 -0
  218. /package/dist/client/assets/{bat-DGYsZ5wR.js → bat-CFyw2ERn.js} +0 -0
  219. /package/dist/client/assets/{beancount-D_ATR1UV.js → beancount-a6Gsu5WX.js} +0 -0
  220. /package/dist/client/assets/{berry-BqYMORfH.js → berry-C2jyz4Ah.js} +0 -0
  221. /package/dist/client/assets/{bibtex-CQTBDajW.js → bibtex-Dyet-B83.js} +0 -0
  222. /package/dist/client/assets/{bicep-CfNtxPrK.js → bicep-CghvUIvy.js} +0 -0
  223. /package/dist/client/assets/{bird2-C0dUPe3I.js → bird2-nHigvs-R.js} +0 -0
  224. /package/dist/client/assets/{c3-BUwZ5e3k.js → c3-9ZrcmAmr.js} +0 -0
  225. /package/dist/client/assets/{cadence-C3OKDYrr.js → cadence-BPDgYNWy.js} +0 -0
  226. /package/dist/client/assets/{catppuccin-frappe-CMnRXVZP.js → catppuccin-frappe-BY_2ofjk.js} +0 -0
  227. /package/dist/client/assets/{catppuccin-latte-CqhXSjCC.js → catppuccin-latte-D6GXPBmA.js} +0 -0
  228. /package/dist/client/assets/{catppuccin-macchiato-B6kneE0C.js → catppuccin-macchiato-BuW8_cyo.js} +0 -0
  229. /package/dist/client/assets/{catppuccin-mocha-CP8YZpvj.js → catppuccin-mocha-znex9DPe.js} +0 -0
  230. /package/dist/client/assets/{clarity-Crxe8gSr.js → clarity-Cmao5KSK.js} +0 -0
  231. /package/dist/client/assets/{clojure-BuzOUPMr.js → clojure-D2dPuTkk.js} +0 -0
  232. /package/dist/client/assets/{cmake-CCpZLdLI.js → cmake-DaqQuj4A.js} +0 -0
  233. /package/dist/client/assets/{codeowners-Bo1VYpHz.js → codeowners-B748jmvc.js} +0 -0
  234. /package/dist/client/assets/{codeql-Db01hzHE.js → codeql-CXb_8gHR.js} +0 -0
  235. /package/dist/client/assets/{common-lisp-CMlJIc6Y.js → common-lisp-UZn9nc_5.js} +0 -0
  236. /package/dist/client/assets/{coq-BlzBYp3f.js → coq-DtO6yBIf.js} +0 -0
  237. /package/dist/client/assets/{csharp-kfNshAa7.js → csharp-C3YlYcLV.js} +0 -0
  238. /package/dist/client/assets/{csv-Dr8ri5Nd.js → csv-BBwiVr0X.js} +0 -0
  239. /package/dist/client/assets/{cue-jXfhyOwY.js → cue-ScBFXY5f.js} +0 -0
  240. /package/dist/client/assets/{cypher-CayLeiaR.js → cypher-CGStaC1X.js} +0 -0
  241. /package/dist/client/assets/{d-BMZQ0mAy.js → d-CuTCIvnX.js} +0 -0
  242. /package/dist/client/assets/{dark-plus-B__q47Nr.js → dark-plus-BvePH-r3.js} +0 -0
  243. /package/dist/client/assets/{dart-gMOEMyuz.js → dart-CqEodbm2.js} +0 -0
  244. /package/dist/client/assets/{dax-C3t5LadU.js → dax-UdE5S0Dh.js} +0 -0
  245. /package/dist/client/assets/{desktop-DmhXLwLR.js → desktop-DfmsfHJA.js} +0 -0
  246. /package/dist/client/assets/{diff-togOXQX6.js → diff-CW0RWGd9.js} +0 -0
  247. /package/dist/client/assets/{docker--6N01olR.js → docker-B1qtW4ha.js} +0 -0
  248. /package/dist/client/assets/{dotenv-Bq930Yx8.js → dotenv-BUzOh1k8.js} +0 -0
  249. /package/dist/client/assets/{dracula-CBkSghcw.js → dracula-kJQjC_Gy.js} +0 -0
  250. /package/dist/client/assets/{dracula-soft-CCSYeoxL.js → dracula-soft-B3XhEjCl.js} +0 -0
  251. /package/dist/client/assets/{dream-maker-BiMf3DHl.js → dream-maker-CBvk18nW.js} +0 -0
  252. /package/dist/client/assets/{emacs-lisp-Dhht7he2.js → emacs-lisp-DvSgLhDc.js} +0 -0
  253. /package/dist/client/assets/{everforest-dark-NqSz4oHS.js → everforest-dark-u0zLn75q.js} +0 -0
  254. /package/dist/client/assets/{everforest-light-a3Yzs_lT.js → everforest-light-D3syIoAR.js} +0 -0
  255. /package/dist/client/assets/{fennel-BExc9TZf.js → fennel-C-1kWlGf.js} +0 -0
  256. /package/dist/client/assets/{fish-C2p-AJmZ.js → fish-CCpdtwqS.js} +0 -0
  257. /package/dist/client/assets/{fluent-CeYzs8Qy.js → fluent-BLg032oD.js} +0 -0
  258. /package/dist/client/assets/{fortran-free-form-BIQEK2Jw.js → fortran-free-form-Br4jRtSc.js} +0 -0
  259. /package/dist/client/assets/{gdscript-B5JQPh_-.js → gdscript-hSAZM1eh.js} +0 -0
  260. /package/dist/client/assets/{gdshader-vVjJ_Qr8.js → gdshader-D4XNAEs9.js} +0 -0
  261. /package/dist/client/assets/{genie-DGl5rqC8.js → genie-DmN-ICAN.js} +0 -0
  262. /package/dist/client/assets/{gherkin-CY_VLUnD.js → gherkin-cuHHmRcX.js} +0 -0
  263. /package/dist/client/assets/{github-dark-ClO4AStc.js → github-dark-CEBLh0--.js} +0 -0
  264. /package/dist/client/assets/{github-dark-default-CM0dPco8.js → github-dark-default-Dq0Z4Xai.js} +0 -0
  265. /package/dist/client/assets/{github-dark-dimmed-Cb6xxpXY.js → github-dark-dimmed-D90YmS4w.js} +0 -0
  266. /package/dist/client/assets/{github-dark-high-contrast-BpYtx2-M.js → github-dark-high-contrast-B6BWv1iC.js} +0 -0
  267. /package/dist/client/assets/{github-light-default-BmnCWL6x.js → github-light-default-DsPWmj4v.js} +0 -0
  268. /package/dist/client/assets/{github-light-high-contrast-rpagNF5O.js → github-light-high-contrast-C7aLb3kp.js} +0 -0
  269. /package/dist/client/assets/{github-light-CeOVFgf_.js → github-light-lXSVSq5T.js} +0 -0
  270. /package/dist/client/assets/{gleam-BarIG9kQ.js → gleam-D8VhqPDN.js} +0 -0
  271. /package/dist/client/assets/{gn-DhYsTPYc.js → gn-BjGRlX27.js} +0 -0
  272. /package/dist/client/assets/{gnuplot-CucLlTBc.js → gnuplot-BZCXuE4q.js} +0 -0
  273. /package/dist/client/assets/{go-toi-uWvg.js → go-BJpWXZEs.js} +0 -0
  274. /package/dist/client/assets/{groovy-Ds4dis_k.js → groovy-D9w3Dn-z.js} +0 -0
  275. /package/dist/client/assets/{gruvbox-dark-hard-aPiW8pQh.js → gruvbox-dark-hard-_deksD0U.js} +0 -0
  276. /package/dist/client/assets/{gruvbox-dark-medium-2HJCvzsS.js → gruvbox-dark-medium-CT31284j.js} +0 -0
  277. /package/dist/client/assets/{gruvbox-dark-soft-Da97ODX9.js → gruvbox-dark-soft-BZuEUCbj.js} +0 -0
  278. /package/dist/client/assets/{gruvbox-light-hard-BXlEipFe.js → gruvbox-light-hard-dxv4n_ZS.js} +0 -0
  279. /package/dist/client/assets/{gruvbox-light-medium-Cun-GjGk.js → gruvbox-light-medium-BsW_fVfR.js} +0 -0
  280. /package/dist/client/assets/{gruvbox-light-soft-Bd3qiiSw.js → gruvbox-light-soft-BiGy99gD.js} +0 -0
  281. /package/dist/client/assets/{haskell-B_jvz5Po.js → haskell-DqDdKEgk.js} +0 -0
  282. /package/dist/client/assets/{haxe-BRbuo5UO.js → haxe-DMg0jS3a.js} +0 -0
  283. /package/dist/client/assets/{hcl-BzNUCtsK.js → hcl-SZIZUK_X.js} +0 -0
  284. /package/dist/client/assets/{hjson-BxVo3sS2.js → hjson-DF1VspiE.js} +0 -0
  285. /package/dist/client/assets/{hlsl-C8Wes1nf.js → hlsl-DPe9L0Dt.js} +0 -0
  286. /package/dist/client/assets/{horizon-D54PjLAY.js → horizon-Ngud75Kn.js} +0 -0
  287. /package/dist/client/assets/{horizon-bright-D9RccdIr.js → horizon-bright-Cnljd0XD.js} +0 -0
  288. /package/dist/client/assets/{houston-CMu1N_4K.js → houston-BenZxlMr.js} +0 -0
  289. /package/dist/client/assets/{hy-dMrJKKU_.js → hy-B0f-B1mH.js} +0 -0
  290. /package/dist/client/assets/{imba-DG_oO8tg.js → imba-Bq3j1cW5.js} +0 -0
  291. /package/dist/client/assets/{ini-geHw79Aj.js → ini-BayloP2U.js} +0 -0
  292. /package/dist/client/assets/{json5-CDUsNqz6.js → json5-CRSfdIrY.js} +0 -0
  293. /package/dist/client/assets/{jsonc-D48xcwyD.js → jsonc-y4g9DQPx.js} +0 -0
  294. /package/dist/client/assets/{jsonl-cpIYDXEe.js → jsonl-D2paVn9r.js} +0 -0
  295. /package/dist/client/assets/{jsonnet-Da1lhszN.js → jsonnet-CLH27mtz.js} +0 -0
  296. /package/dist/client/assets/{jssm-B8BLrEGL.js → jssm-CPPYaqX-.js} +0 -0
  297. /package/dist/client/assets/{kanagawa-dragon-2Sk8Rx83.js → kanagawa-dragon-Bt5mhLu1.js} +0 -0
  298. /package/dist/client/assets/{kanagawa-lotus-CQVSWfJp.js → kanagawa-lotus-FGA3pRJk.js} +0 -0
  299. /package/dist/client/assets/{kanagawa-wave-BKWNioLX.js → kanagawa-wave-DnLVZPAD.js} +0 -0
  300. /package/dist/client/assets/{kdl-B-2tSRmp.js → kdl-D1BQjjnw.js} +0 -0
  301. /package/dist/client/assets/{kotlin-DepsEzh3.js → kotlin-ndCaDnf0.js} +0 -0
  302. /package/dist/client/assets/{kusto-BcCtXIb8.js → kusto-D-4nbkD3.js} +0 -0
  303. /package/dist/client/assets/{laserwave-BUqkO46F.js → laserwave-D2qAZkaG.js} +0 -0
  304. /package/dist/client/assets/{lean-CNO9ffpX.js → lean-YmtAsB0p.js} +0 -0
  305. /package/dist/client/assets/{less-Dj0T9pWC.js → less-DyHub3ow.js} +0 -0
  306. /package/dist/client/assets/{light-plus-C9Hnuc1T.js → light-plus-CP13QUvm.js} +0 -0
  307. /package/dist/client/assets/{llvm-DbV9b66l.js → llvm-DCcqrY0D.js} +0 -0
  308. /package/dist/client/assets/{log-BMblmPyH.js → log-DWAAMNbe.js} +0 -0
  309. /package/dist/client/assets/{logo-B78qMxws.js → logo-ClQMGkNL.js} +0 -0
  310. /package/dist/client/assets/{luau-BUd8xsdE.js → luau-CmcXFvLt.js} +0 -0
  311. /package/dist/client/assets/{make-Ev-6cJrd.js → make-DcfJyUzm.js} +0 -0
  312. /package/dist/client/assets/{markdown-CS4LI6sN.js → markdown-DXxotAxn.js} +0 -0
  313. /package/dist/client/assets/{material-theme-5rA1zn0D.js → material-theme-JFoPTBSr.js} +0 -0
  314. /package/dist/client/assets/{material-theme-darker-DGOEiL65.js → material-theme-darker-BHaqZ19v.js} +0 -0
  315. /package/dist/client/assets/{material-theme-lighter-CULfFRX0.js → material-theme-lighter-CGnnS2bs.js} +0 -0
  316. /package/dist/client/assets/{material-theme-ocean-LQZ9CleP.js → material-theme-ocean-Drhewjqf.js} +0 -0
  317. /package/dist/client/assets/{material-theme-palenight-Di_f5oks.js → material-theme-palenight-DbUypmpb.js} +0 -0
  318. /package/dist/client/assets/{matlab-C0qEtAx9.js → matlab-BlfbTznR.js} +0 -0
  319. /package/dist/client/assets/{mdx-CzvnbLXb.js → mdx-H8fyPQd8.js} +0 -0
  320. /package/dist/client/assets/{mermaid-BPquZaKt.js → mermaid-CWAV2dmV.js} +0 -0
  321. /package/dist/client/assets/{min-dark-rkuIbtJV.js → min-dark-BEelvXUB.js} +0 -0
  322. /package/dist/client/assets/{min-light-Dj_HOGsr.js → min-light-CTPBOACR.js} +0 -0
  323. /package/dist/client/assets/{mipsasm-k5DQoS6j.js → mipsasm-BWYFAm2s.js} +0 -0
  324. /package/dist/client/assets/{mojo-sTTDuNnR.js → mojo-DZcPJEIN.js} +0 -0
  325. /package/dist/client/assets/{monokai-D5p-u2ij.js → monokai-DQlZXbkg.js} +0 -0
  326. /package/dist/client/assets/{moonbit-j5ChFtBC.js → moonbit-6eslNlJL.js} +0 -0
  327. /package/dist/client/assets/{move-Dj0Gmn6R.js → move-DxFc9-QU.js} +0 -0
  328. /package/dist/client/assets/{narrat-DJgMz2oW.js → narrat-gv38dJOT.js} +0 -0
  329. /package/dist/client/assets/{nextflow-groovy-eDLuZ_oi.js → nextflow-groovy-DV4H3hok.js} +0 -0
  330. /package/dist/client/assets/{night-owl-CEdtPl3A.js → night-owl-BLcrYhaC.js} +0 -0
  331. /package/dist/client/assets/{night-owl-light-GdSldewm.js → night-owl-light-BG-tBJOz.js} +0 -0
  332. /package/dist/client/assets/{nix-CxIPS3pb.js → nix-NlOz3HGM.js} +0 -0
  333. /package/dist/client/assets/{nord-3WFAz5H9.js → nord-Dm2F5_QB.js} +0 -0
  334. /package/dist/client/assets/{nushell-GDfBmoBU.js → nushell-D86Z8aFF.js} +0 -0
  335. /package/dist/client/assets/{objective-c-MFQ_MGUa.js → objective-c-iuJvUjCv.js} +0 -0
  336. /package/dist/client/assets/{objective-cpp-Y83LcBE7.js → objective-cpp-C62JvfMT.js} +0 -0
  337. /package/dist/client/assets/{ocaml-FxYfaJxu.js → ocaml-Rbryo9lg.js} +0 -0
  338. /package/dist/client/assets/{odin-CprihY3z.js → odin-CfaOVNP_.js} +0 -0
  339. /package/dist/client/assets/{one-dark-pro-BB6mpmmA.js → one-dark-pro-DC9SHdX0.js} +0 -0
  340. /package/dist/client/assets/{one-light-BYLoxNtg.js → one-light-Doo39p0H.js} +0 -0
  341. /package/dist/client/assets/{openscad-Bc1FbpOi.js → openscad-DCRhgheK.js} +0 -0
  342. /package/dist/client/assets/{pascal-Bb4d8cUL.js → pascal-CTesTHtV.js} +0 -0
  343. /package/dist/client/assets/{pierre-dark-cF19s-RM.js → pierre-dark-Gtc9M_gS.js} +0 -0
  344. /package/dist/client/assets/{pierre-light-BdpX8OpD.js → pierre-light-EDxMHmhf.js} +0 -0
  345. /package/dist/client/assets/{pkl-tM25Sq0w.js → pkl-BjUMjQkt.js} +0 -0
  346. /package/dist/client/assets/{plastic-D3k2Xp_5.js → plastic-B3CcSpon.js} +0 -0
  347. /package/dist/client/assets/{plsql-CLEfFhSj.js → plsql-BgKEZSSo.js} +0 -0
  348. /package/dist/client/assets/{po-CaAzdrrh.js → po-C5s_CVt8.js} +0 -0
  349. /package/dist/client/assets/{poimandres-bPTvT4Da.js → poimandres-BsK70ma5.js} +0 -0
  350. /package/dist/client/assets/{polar-TG7aTGdK.js → polar-y4hX8qM0.js} +0 -0
  351. /package/dist/client/assets/{postcss-8kqfa9f0.js → postcss-BHCm7Qae.js} +0 -0
  352. /package/dist/client/assets/{powerquery-CtSu1Nfd.js → powerquery-C2vMt8aZ.js} +0 -0
  353. /package/dist/client/assets/{powershell-87lLBHMy.js → powershell-DEcjmg1c.js} +0 -0
  354. /package/dist/client/assets/{prisma-Dnf694DZ.js → prisma-Ba-Tcxy5.js} +0 -0
  355. /package/dist/client/assets/{prolog-C0XbHTWa.js → prolog-B4o261Jv.js} +0 -0
  356. /package/dist/client/assets/{proto-BZP-HLIb.js → proto-o3IZKiMR.js} +0 -0
  357. /package/dist/client/assets/{puppet-CJYWgYC5.js → puppet-BC0dqZ31.js} +0 -0
  358. /package/dist/client/assets/{purescript-B95Q6BAd.js → purescript-CLDw1y5G.js} +0 -0
  359. /package/dist/client/assets/{python-CqTCsi7P.js → python-D1NFioGW.js} +0 -0
  360. /package/dist/client/assets/{qmldir-DlIL4hls.js → qmldir-B7i9e9Vn.js} +0 -0
  361. /package/dist/client/assets/{qss-BW72E0q_.js → qss-DIe6oKX8.js} +0 -0
  362. /package/dist/client/assets/{racket-CCAimEsC.js → racket-CAOC_yyk.js} +0 -0
  363. /package/dist/client/assets/{raku-mSuqElkM.js → raku-Ccua3vW6.js} +0 -0
  364. /package/dist/client/assets/{red-C-DA4yBI.js → red-DtDH206x.js} +0 -0
  365. /package/dist/client/assets/{reg-CGwMBJ2c.js → reg-CAYZT9kE.js} +0 -0
  366. /package/dist/client/assets/{rel-JwrbYgX4.js → rel-cvB6F5nO.js} +0 -0
  367. /package/dist/client/assets/{riscv-CoYyE75b.js → riscv-SYGtE4EN.js} +0 -0
  368. /package/dist/client/assets/{ron-C5wrhnmJ.js → ron-CRt869SB.js} +0 -0
  369. /package/dist/client/assets/{rose-pine-DlrMPYYd.js → rose-pine-CjBfSJ5b.js} +0 -0
  370. /package/dist/client/assets/{rose-pine-dawn-DBHFtZD2.js → rose-pine-dawn-C0xwy7iJ.js} +0 -0
  371. /package/dist/client/assets/{rose-pine-moon-BBfrpe5q.js → rose-pine-moon-BSNy9Ty0.js} +0 -0
  372. /package/dist/client/assets/{rosmsg-DlimTR9t.js → rosmsg-BwrfJ66Z.js} +0 -0
  373. /package/dist/client/assets/{rust-Cxnkxx3h.js → rust-DCKTTPxN.js} +0 -0
  374. /package/dist/client/assets/{sass-DEyqUIpU.js → sass-CorcwySt.js} +0 -0
  375. /package/dist/client/assets/{scala-i3RN3yAN.js → scala-DlI-OmYj.js} +0 -0
  376. /package/dist/client/assets/{scheme-BcKdjYkO.js → scheme-CcNEGkZH.js} +0 -0
  377. /package/dist/client/assets/{sdbl-Be_XWc3n.js → sdbl-AL_m-RO_.js} +0 -0
  378. /package/dist/client/assets/{slack-dark-3PDtm74m.js → slack-dark-rMhrfPm6.js} +0 -0
  379. /package/dist/client/assets/{slack-ochin-CwiuFRJD.js → slack-ochin-9w8X74hU.js} +0 -0
  380. /package/dist/client/assets/{smalltalk-BdBlL6ks.js → smalltalk-Y2ogrfbk.js} +0 -0
  381. /package/dist/client/assets/{snazzy-light-Dyqc8Is2.js → snazzy-light-DHr9ygpP.js} +0 -0
  382. /package/dist/client/assets/{solarized-dark-DZiTscIX.js → solarized-dark-UPcKev9Q.js} +0 -0
  383. /package/dist/client/assets/{solarized-light-DBcfxrfG.js → solarized-light-DUsjROvl.js} +0 -0
  384. /package/dist/client/assets/{solidity-DH-yUSWc.js → solidity-Bfbm9QZQ.js} +0 -0
  385. /package/dist/client/assets/{splunk-CIjc4kxs.js → splunk-6DJA2XFa.js} +0 -0
  386. /package/dist/client/assets/{ssh-config-BKVd4drG.js → ssh-config-CbQtdF-u.js} +0 -0
  387. /package/dist/client/assets/{stylus-tqAsmugv.js → stylus-B_iq45Sj.js} +0 -0
  388. /package/dist/client/assets/{swift-CdDBQ3_Z.js → swift-C7KIgN-p.js} +0 -0
  389. /package/dist/client/assets/{synthwave-84-CbbRs73K.js → synthwave-84-lf_Wt0K_.js} +0 -0
  390. /package/dist/client/assets/{system-verilog-CBqEY-9v.js → system-verilog-C3G9wERd.js} +0 -0
  391. /package/dist/client/assets/{systemd-khRP5zwK.js → systemd-CwxGrojj.js} +0 -0
  392. /package/dist/client/assets/{talonscript-D_lneH-6.js → talonscript-DqnSo1EO.js} +0 -0
  393. /package/dist/client/assets/{tasl-CLymawGe.js → tasl-T8ethp86.js} +0 -0
  394. /package/dist/client/assets/{tcl-BRY1S87U.js → tcl-Ccl366o0.js} +0 -0
  395. /package/dist/client/assets/{terraform-BqohMo2f.js → terraform-BBf6ZI_0.js} +0 -0
  396. /package/dist/client/assets/{tokyo-night-BWUONxad.js → tokyo-night-BF5ata25.js} +0 -0
  397. /package/dist/client/assets/{toml-C4c85Lwg.js → toml-C5Z_BR4r.js} +0 -0
  398. /package/dist/client/assets/{tsv-DsZq9HBQ.js → tsv-rX8sUJs7.js} +0 -0
  399. /package/dist/client/assets/{turtle-Dpr8zLlw.js → turtle-BM-MG8WY.js} +0 -0
  400. /package/dist/client/assets/{typespec-DcqgATB-.js → typespec-C5gKydvR.js} +0 -0
  401. /package/dist/client/assets/{typst-CeVxmuPg.js → typst--_5VchrY.js} +0 -0
  402. /package/dist/client/assets/{v-C2Qsh2-1.js → v-C6PwpmTC.js} +0 -0
  403. /package/dist/client/assets/{vala-CvZ3yL7c.js → vala-4pTaceSj.js} +0 -0
  404. /package/dist/client/assets/{vb-B_p4qDw4.js → vb-DhoR5A6S.js} +0 -0
  405. /package/dist/client/assets/{verilog-D8MGjxD7.js → verilog-B9f_1lPn.js} +0 -0
  406. /package/dist/client/assets/{vesper-DossXiE1.js → vesper-CmBi_iCT.js} +0 -0
  407. /package/dist/client/assets/{vhdl-MLyYh_Gz.js → vhdl-Cy4RAmOe.js} +0 -0
  408. /package/dist/client/assets/{viml-BuYIArcz.js → viml-CFQW-CcD.js} +0 -0
  409. /package/dist/client/assets/{vitesse-black-C3kc1227.js → vitesse-black-CE8RoBJP.js} +0 -0
  410. /package/dist/client/assets/{vitesse-dark-kWOu5V36.js → vitesse-dark-BpF142p_.js} +0 -0
  411. /package/dist/client/assets/{vitesse-light-DMtiL8bK.js → vitesse-light-CMwxPBrg.js} +0 -0
  412. /package/dist/client/assets/{vyper-CNqG09-_.js → vyper-BW5zj9bE.js} +0 -0
  413. /package/dist/client/assets/{wasm-BsQDV7Y0.js → wasm-BigxSLnm.js} +0 -0
  414. /package/dist/client/assets/{wasm-DFVlQlgd.js → wasm-CSccDuAq.js} +0 -0
  415. /package/dist/client/assets/{wenyan-BmOQge13.js → wenyan-BZzM1SQT.js} +0 -0
  416. /package/dist/client/assets/{wgsl-CgWrmvIg.js → wgsl-xBPHbLlq.js} +0 -0
  417. /package/dist/client/assets/{wikitext-DkwckXCk.js → wikitext-BXJWUNJK.js} +0 -0
  418. /package/dist/client/assets/{wit-CtyVFGe7.js → wit-2GnUs67P.js} +0 -0
  419. /package/dist/client/assets/{wolfram-CXE4FrKz.js → wolfram-DIFDmw-g.js} +0 -0
  420. /package/dist/client/assets/{zenscript-DH15wJt0.js → zenscript-DQaUUjbx.js} +0 -0
  421. /package/dist/client/assets/{zig-CqXN9brD.js → zig-BDepRvBi.js} +0 -0
package/dist/cli.mjs CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { C as revertHunk, S as listRepoFiles, _ as getRepoRoot, a as extractPlanLines, b as gitAddFile, c as formatComments, d as getFileContent, f as getGitDiffAsync, g as getRepoName, h as getProjectStorageDir, i as writeServerLock, l as getBranchName, m as getMergeStatus, o as formatPlanReview, p as getHunkHistory, r as removeServerLock, s as sectionTitleForLine, u as getCustomGitDiffAsync, v as getTabSizeForFiles, w as isSafePath, x as isGitRepo, y as getUntrackedFilePathsAsync } from "./server-lock-CAdqFSfl.mjs";
2
+ import { C as toSafeRelativePath, S as isSafePath, _ as getUntrackedFilePathsAsync, a as formatComments, b as listRepoFiles, c as getFileContent, d as getMergeStatus, f as getProjectStorageDir, g as getTabSizeForFiles, h as getShowDiff, i as writeServerLock, l as getGitDiffAsync, m as getRepoRoot, o as getBranchName, p as getRepoName, r as removeServerLock, s as getCustomGitDiffAsync, u as getHunkHistory, v as gitAddFile, x as revertHunk, y as isGitRepo } from "./server-lock-Dj9fG9md.mjs";
3
+ import { n as formatPlanReview, r as sectionTitleForLine, t as extractPlanLines } from "./plan-format-f62JWFI-.mjs";
4
+ import { execFile, execFileSync, spawn } from "node:child_process";
3
5
  import { fileURLToPath } from "node:url";
4
- import { dirname, extname, join, resolve } from "node:path";
6
+ import { dirname, extname, isAbsolute, join, resolve } from "node:path";
5
7
  import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
6
8
  import getPort from "get-port";
7
- import { parseArgs } from "node:util";
8
- import { execFileSync } from "node:child_process";
9
+ import { parseArgs, promisify } from "node:util";
9
10
  import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
10
11
  import { homedir } from "node:os";
11
12
  import { Hono } from "hono";
12
13
  import { streamSSE } from "hono/streaming";
13
14
  import { serve } from "@hono/node-server";
15
+ import { stdout } from "node:process";
14
16
  //#region src/lib/diff-options.ts
15
17
  const DEFAULTS$1 = {
16
18
  revisions: [],
@@ -47,7 +49,11 @@ const DEFAULTS$1 = {
47
49
  help: false,
48
50
  version: false,
49
51
  includeUnstaged: true,
50
- includeUntracked: true
52
+ includeUntracked: true,
53
+ tui: false,
54
+ gpu: false,
55
+ showMode: false,
56
+ showRevspecs: []
51
57
  };
52
58
  /**
53
59
  * All git-diff option definitions for `node:util.parseArgs`.
@@ -172,6 +178,15 @@ const DIFFING_OPTIONS = {
172
178
  type: "boolean",
173
179
  default: false
174
180
  },
181
+ tui: {
182
+ type: "boolean",
183
+ default: false
184
+ },
185
+ gpu: {
186
+ type: "boolean",
187
+ default: false
188
+ },
189
+ "gh-pr": { type: "string" },
175
190
  help: {
176
191
  type: "boolean",
177
192
  short: "h"
@@ -208,18 +223,37 @@ Output Modes:
208
223
  - Terminal mode: when output is a pipe, redirect, or non-TTY
209
224
  - Web mode: when output is a TTY (interactive terminal)
210
225
 
211
- Force a mode with --web or --terminal flags.
226
+ Force a mode with --web, --terminal, or --tui flags.
227
+ --tui is experimental and opt-in. It opens a native-Rust terminal
228
+ interface that mirrors the web dashboard. Falls back to git diff
229
+ output if the environment cannot support a TUI (piped stdin, CI,
230
+ missing binary).
231
+
232
+ Subcommands:
233
+ show <revspec>... Drop-in for 'git show'. Renders commit metadata
234
+ (subject, author, date, message body) above the
235
+ diff in the web UI. Accepts single commits,
236
+ ranges (a..b, a...b), tags, and multiple SHAs.
237
+ Use '--' to separate revspecs from pathspecs.
212
238
 
213
239
  Examples:
214
240
  diffing Review uncommitted changes (web UI)
215
241
  diffing --staged Review staged changes
216
242
  diffing HEAD~3 Review last 3 commits
217
243
  diffing main..feature Compare branches
244
+ diffing show HEAD Review a single commit's metadata + diff
245
+ diffing show HEAD~3..HEAD Review the last 3 commits as a series
246
+ diffing show HEAD -- src/ Limit a commit review to a directory
218
247
  diffing --diff-algorithm=patience src/ Diff with patience algorithm
219
248
  diffing -U10 Diff with 10 context lines
220
249
  diffing --word-diff=color Word-level diff in color
221
250
  diffing -b -w Ignore whitespace changes
222
- diffing --host 0.0.0.0 Allow other machines on the LAN to review`);
251
+ diffing --host 0.0.0.0 Allow other machines on the LAN to review
252
+ diffing --tui Open the experimental TUI (native Rust)
253
+
254
+ Note: 'diffing <sha>' runs 'git diff <sha>' (working tree vs <sha>); it
255
+ is not the same as 'diffing show <sha>'. Use 'show' when you want to
256
+ review a commit's changes.`);
223
257
  }
224
258
  function printGitDiffHelp() {
225
259
  console.log(`
@@ -463,7 +497,8 @@ function parseDiffOptions(rawArgs) {
463
497
  }
464
498
  for (let i = dashDashIdx + 1; i < rawArgs.length; i++) opts.pathspecs.push(rawArgs[i]);
465
499
  } else for (const p of positionals) opts.revisions.push(p);
466
- if (values.web) opts.outputMode = "web";
500
+ if (values.tui) opts.outputMode = "tui";
501
+ else if (values.web) opts.outputMode = "web";
467
502
  else if (values.terminal) opts.outputMode = "terminal";
468
503
  if (process.stdout.isTTY && opts.outputMode === "auto") opts.outputMode = "web";
469
504
  else if (!process.stdout.isTTY && opts.outputMode === "auto") opts.outputMode = "terminal";
@@ -544,6 +579,21 @@ function buildGitDiffArgs(opts) {
544
579
  }
545
580
  return args;
546
581
  }
582
+ /**
583
+ * Transform a `parseDiffOptions(...)` result into a "show mode" variant:
584
+ * moves any parsed revisions into `showRevspecs`, clears `revisions` (so the
585
+ * existing diff-engine path does not try to `git diff` them), and sets the
586
+ * `showMode` flag. Pathspecs and every other flag survive unchanged.
587
+ *
588
+ * Returns the same object for callsite ergonomics — the mutation is in-place
589
+ * because `parseDiffOptions` always returns a fresh object.
590
+ */
591
+ function intoShowMode(opts) {
592
+ opts.showRevspecs = opts.revisions;
593
+ opts.revisions = [];
594
+ opts.showMode = true;
595
+ return opts;
596
+ }
547
597
  //#endregion
548
598
  //#region src/lib/diff-engine.ts
549
599
  const MAX_BUFFER = 50 * 1024 * 1024;
@@ -552,10 +602,11 @@ const MAX_BUFFER = 50 * 1024 * 1024;
552
602
  * or a "custom" mode (specific revisions / pathspecs).
553
603
  *
554
604
  * Custom mode is used when the user provides explicit revisions or pathspecs
555
- * beyond just toggling staged/untracked.
605
+ * beyond just toggling staged/untracked. `showMode` is *also* a custom mode
606
+ * — the "Show staged / Show untracked" toggles don't apply to a commit view.
556
607
  */
557
608
  function isCustomMode$1(opts) {
558
- return opts.revisions.length > 0 || opts.pathspecs.length > 0;
609
+ return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
559
610
  }
560
611
  function parseFilePaths(patch) {
561
612
  const paths = /* @__PURE__ */ new Set();
@@ -602,8 +653,24 @@ function parseBinaryFiles(patch, untrackedFiles) {
602
653
  }
603
654
  /**
604
655
  * Execute a diff asynchronously — used by the server.
656
+ *
657
+ * Show mode bypasses the normal `git diff` path entirely: it resolves
658
+ * revspecs to a commit list and fetches per-commit metadata + patches in
659
+ * one `git log` call. The concatenated patch is returned in the same shape
660
+ * the existing UI pipeline expects (so file tree, binary detection, and
661
+ * tab-size detection all keep working unchanged); the per-commit data
662
+ * rides along on `commits` for the metadata banners.
605
663
  */
606
664
  async function executeDiffAsync(opts) {
665
+ if (opts.showMode) {
666
+ const { commits, patch, truncated } = await getShowDiff(opts.showRevspecs, opts.pathspecs);
667
+ return {
668
+ patch,
669
+ args: [],
670
+ commits,
671
+ truncated
672
+ };
673
+ }
607
674
  if (isCustomMode$1(opts)) {
608
675
  const args = buildGitDiffArgs(opts);
609
676
  return {
@@ -623,10 +690,10 @@ async function executeDiffAsync(opts) {
623
690
  * Execute a diff and produce the full enriched result used by the web API.
624
691
  */
625
692
  async function executeDiffWithMeta(opts) {
626
- const { patch } = await executeDiffAsync(opts);
693
+ const { patch, commits, truncated } = await executeDiffAsync(opts);
627
694
  const repoName = getRepoName();
628
695
  const branch = getBranchName();
629
- const untrackedFiles = opts.includeUntracked ? await getUntrackedFilePathsAsync() : [];
696
+ const untrackedFiles = opts.includeUntracked && !opts.showMode ? await getUntrackedFilePathsAsync() : [];
630
697
  const binaryFiles = parseBinaryFiles(patch, new Set(untrackedFiles));
631
698
  const filePaths = parseFilePaths(patch);
632
699
  return {
@@ -636,14 +703,19 @@ async function executeDiffWithMeta(opts) {
636
703
  tabSizeMap: getTabSizeForFiles(filePaths),
637
704
  untrackedFiles,
638
705
  repoName,
639
- branch
706
+ branch,
707
+ ...commits ? { commits } : {},
708
+ ...truncated ? { truncated } : {}
640
709
  };
641
710
  }
642
711
  /**
643
712
  * Run the diff in terminal mode: forward to git diff with full flags,
644
- * output to stdout. Behaves identically to `git diff`.
713
+ * output to stdout. Behaves identically to `git diff`. In show mode this
714
+ * delegates to `git show` instead so `diffing show <rev>... --terminal`
715
+ * is a drop-in for `git show <rev>...`.
645
716
  */
646
717
  function runTerminalDiff(opts) {
718
+ if (opts.showMode) return runTerminalShow(opts);
647
719
  const args = buildGitDiffArgs(opts);
648
720
  const enforceDefaults = !opts.outputFormat && !opts.quiet;
649
721
  const finalArgs = [];
@@ -664,6 +736,30 @@ function runTerminalDiff(opts) {
664
736
  }
665
737
  }
666
738
  /**
739
+ * Terminal mode for `diffing show`: spawn `git show` with the user's revspecs
740
+ * and pathspecs, inherit stdio, propagate exit code. We pass `--no-ext-diff`
741
+ * so the output is a clean unified diff regardless of the user's git config
742
+ * (matches the web-mode pipeline), but otherwise hand everything to git.
743
+ */
744
+ function runTerminalShow(opts) {
745
+ const args = [
746
+ "show",
747
+ "--no-ext-diff",
748
+ ...opts.showRevspecs
749
+ ];
750
+ if (opts.pathspecs.length > 0) args.push("--", ...opts.pathspecs);
751
+ try {
752
+ execFileSync("git", args, {
753
+ encoding: "utf-8",
754
+ maxBuffer: MAX_BUFFER,
755
+ stdio: "inherit"
756
+ });
757
+ return 0;
758
+ } catch (err) {
759
+ return err.status ?? 1;
760
+ }
761
+ }
762
+ /**
667
763
  * Validate the environment: check that we're in a git repo.
668
764
  * Returns an error message or null.
669
765
  */
@@ -1112,7 +1208,10 @@ const DEFAULTS = {
1112
1208
  expandContextByDefault: false,
1113
1209
  collapsedContextThreshold: 10,
1114
1210
  expansionLineCount: 20,
1115
- haptics: true
1211
+ haptics: true,
1212
+ sounds: true,
1213
+ uiFont: null,
1214
+ monoFont: null
1116
1215
  };
1117
1216
  function loadSettings() {
1118
1217
  try {
@@ -1139,9 +1238,15 @@ function saveSettings(settings) {
1139
1238
  var FileCommentStore = class {
1140
1239
  dirPath;
1141
1240
  filePath;
1142
- constructor(customRepoRoot) {
1143
- if (customRepoRoot) this.dirPath = join(customRepoRoot, ".diffing");
1144
- else this.dirPath = getProjectStorageDir();
1241
+ /**
1242
+ * @param storageDir Absolute directory to persist `comments.json` in.
1243
+ * Defaults to the per-repo storage dir under `~/.diffing` — comments are
1244
+ * NEVER written inside the reviewed (consumer) repo, so a consumer project
1245
+ * stays free of any diffing-specific artifacts. The override exists only so
1246
+ * tests can point at a throwaway temp dir.
1247
+ */
1248
+ constructor(storageDir) {
1249
+ this.dirPath = storageDir ?? getProjectStorageDir();
1145
1250
  this.filePath = join(this.dirPath, "comments.json");
1146
1251
  }
1147
1252
  async getAll() {
@@ -1261,9 +1366,15 @@ function applyUpsert(plans, input, now) {
1261
1366
  var FilePlanStore = class {
1262
1367
  dirPath;
1263
1368
  filePath;
1264
- constructor(customRepoRoot) {
1265
- if (customRepoRoot) this.dirPath = join(customRepoRoot, ".diffing");
1266
- else this.dirPath = getProjectStorageDir();
1369
+ /**
1370
+ * @param storageDir Absolute directory to persist `plans.json` in. Defaults
1371
+ * to the per-repo storage dir under `~/.diffing`, the same place comments
1372
+ * and media live — plans are NEVER written inside the reviewed (consumer)
1373
+ * repo, so a consumer project stays free of any diffing-specific artifacts.
1374
+ * The override exists only so tests can point at a throwaway temp dir.
1375
+ */
1376
+ constructor(storageDir) {
1377
+ this.dirPath = storageDir ?? getProjectStorageDir();
1267
1378
  this.filePath = join(this.dirPath, "plans.json");
1268
1379
  }
1269
1380
  async getAll() {
@@ -1389,6 +1500,95 @@ var FilePlanStore = class {
1389
1500
  }
1390
1501
  };
1391
1502
  //#endregion
1503
+ //#region src/lib/state.ts
1504
+ var FileUiStateStore = class {
1505
+ dirPath;
1506
+ filePath;
1507
+ constructor(storageDir) {
1508
+ this.dirPath = storageDir ?? getProjectStorageDir();
1509
+ this.filePath = join(this.dirPath, "ui-state.json");
1510
+ }
1511
+ async getAll() {
1512
+ try {
1513
+ const data = await readFile(this.filePath, "utf-8");
1514
+ return JSON.parse(data);
1515
+ } catch {
1516
+ return {};
1517
+ }
1518
+ }
1519
+ async setAll(state) {
1520
+ await this.save(state);
1521
+ }
1522
+ async save(state) {
1523
+ try {
1524
+ await mkdir(this.dirPath, { recursive: true });
1525
+ try {
1526
+ const repoRoot = getRepoRoot();
1527
+ await writeFile(join(this.dirPath, "repo_path.txt"), repoRoot, "utf-8");
1528
+ } catch {}
1529
+ await writeFile(this.filePath, JSON.stringify(state, null, 2), "utf-8");
1530
+ } catch (err) {
1531
+ console.error("Failed to save UI state to file:", err);
1532
+ }
1533
+ }
1534
+ };
1535
+ //#endregion
1536
+ //#region src/lib/editor-launcher.ts
1537
+ function resolveEditorCommand(editor, absolutePath, platform = process.platform, env = process.env) {
1538
+ switch (editor) {
1539
+ case "vscode": return guiEditorCommand("code", absolutePath, platform);
1540
+ case "zed": return guiEditorCommand("zed", absolutePath, platform);
1541
+ case "vim": return terminalEditorCommand("vim", absolutePath, platform, env);
1542
+ case "neovim": return terminalEditorCommand("nvim", absolutePath, platform, env);
1543
+ default: return null;
1544
+ }
1545
+ }
1546
+ function guiEditorCommand(bin, absolutePath, platform) {
1547
+ if (platform === "win32") return {
1548
+ cmd: `${bin}.cmd`,
1549
+ args: [absolutePath]
1550
+ };
1551
+ return {
1552
+ cmd: bin,
1553
+ args: [absolutePath]
1554
+ };
1555
+ }
1556
+ function terminalEditorCommand(bin, absolutePath, platform, env) {
1557
+ if (platform === "darwin") return {
1558
+ cmd: "osascript",
1559
+ args: ["-e", `tell application "Terminal" to do script "${bin} \\"${absolutePath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}\\""`]
1560
+ };
1561
+ if (platform === "win32") return {
1562
+ cmd: "cmd.exe",
1563
+ args: [
1564
+ "/c",
1565
+ "start",
1566
+ "\"\"",
1567
+ "cmd.exe",
1568
+ "/k",
1569
+ bin,
1570
+ absolutePath
1571
+ ]
1572
+ };
1573
+ const term = env.TERMINAL?.trim();
1574
+ if (term) return {
1575
+ cmd: term,
1576
+ args: [
1577
+ "-e",
1578
+ bin,
1579
+ absolutePath
1580
+ ]
1581
+ };
1582
+ return {
1583
+ cmd: "x-terminal-emulator",
1584
+ args: [
1585
+ "-e",
1586
+ bin,
1587
+ absolutePath
1588
+ ]
1589
+ };
1590
+ }
1591
+ //#endregion
1392
1592
  //#region src/lib/review-session.ts
1393
1593
  var ReviewSession = class {
1394
1594
  round = 0;
@@ -1452,7 +1652,8 @@ var ReviewSession = class {
1452
1652
  sentAt: input.sentAt,
1453
1653
  commentXml: input.commentXml,
1454
1654
  openCount: input.openCount,
1455
- comments: input.comments
1655
+ comments: input.comments,
1656
+ decision: input.decision
1456
1657
  };
1457
1658
  this.lastPayload = payload;
1458
1659
  const waiters = [...this.waiters];
@@ -1580,6 +1781,483 @@ var PlanReviewSession = class {
1580
1781
  }
1581
1782
  };
1582
1783
  //#endregion
1784
+ //#region src/lib/pr-session.ts
1785
+ var FilePrSessionStore = class {
1786
+ dirPath;
1787
+ filePath;
1788
+ constructor(storageDir) {
1789
+ this.dirPath = storageDir ?? getProjectStorageDir();
1790
+ this.filePath = join(this.dirPath, "pr-session.json");
1791
+ }
1792
+ async get() {
1793
+ try {
1794
+ const data = await readFile(this.filePath, "utf-8");
1795
+ return JSON.parse(data);
1796
+ } catch {
1797
+ return null;
1798
+ }
1799
+ }
1800
+ async save(session) {
1801
+ try {
1802
+ await mkdir(this.dirPath, { recursive: true });
1803
+ await writeFile(this.filePath, JSON.stringify(session, null, 2), "utf-8");
1804
+ } catch (err) {
1805
+ console.error("Failed to save pr-session to file:", err);
1806
+ }
1807
+ }
1808
+ async set(session) {
1809
+ await this.save(session);
1810
+ }
1811
+ async update(fields) {
1812
+ const current = await this.get();
1813
+ if (!current) return null;
1814
+ const next = {
1815
+ ...current,
1816
+ ...fields
1817
+ };
1818
+ await this.save(next);
1819
+ return next;
1820
+ }
1821
+ async clear() {
1822
+ try {
1823
+ const { unlink } = await import("node:fs/promises");
1824
+ await unlink(this.filePath);
1825
+ } catch {}
1826
+ }
1827
+ };
1828
+ //#endregion
1829
+ //#region src/lib/github.ts
1830
+ const execFileAsync = promisify(execFile);
1831
+ /**
1832
+ * Probe the `gh` CLI: is it on `$PATH`, and is the user authenticated?
1833
+ * Never throws — returns a permissive `available: false` on any failure.
1834
+ */
1835
+ async function detectGhCli() {
1836
+ try {
1837
+ const { stdout } = await execFileAsync("gh", ["--version"], { encoding: "utf-8" });
1838
+ const version = stdout.split("\n")[0]?.trim() || void 0;
1839
+ try {
1840
+ const result = await execFileAsync("gh", ["auth", "status"], { encoding: "utf-8" });
1841
+ return {
1842
+ available: true,
1843
+ authenticated: true,
1844
+ version,
1845
+ user: parseGhAuthStatusUser(`${result.stdout ?? ""}\n${result.stderr ?? ""}`)
1846
+ };
1847
+ } catch {
1848
+ return {
1849
+ available: true,
1850
+ authenticated: false,
1851
+ version
1852
+ };
1853
+ }
1854
+ } catch {
1855
+ return {
1856
+ available: false,
1857
+ authenticated: false
1858
+ };
1859
+ }
1860
+ }
1861
+ /**
1862
+ * Extract the GitHub username from `gh auth status` output. Exported for
1863
+ * unit tests — see `__tests__/github-auth.test.ts`.
1864
+ *
1865
+ * Real-world samples:
1866
+ * `✓ Logged in to github.com account octocat (keyring)` (gh 2.40+)
1867
+ * `Logged in to github.com as octocat (oauth_token)` (older gh)
1868
+ * ` ✓ Logged in to github.com as octocat (~/.config/gh/hosts.yml)`
1869
+ */
1870
+ function parseGhAuthStatusUser(output) {
1871
+ const account = /Logged in to [^\s]+ account\s+([^\s(]+)/.exec(output);
1872
+ if (account) return account[1];
1873
+ return /Logged in to [^\s]+ as\s+([^\s(]+)/.exec(output)?.[1];
1874
+ }
1875
+ /** Read the most-preferred GitHub token from the env. */
1876
+ function readGithubToken() {
1877
+ const env = process.env;
1878
+ return env.GH_TOKEN || env.GITHUB_TOKEN || env.GITHUB_API_TOKEN || null;
1879
+ }
1880
+ function parsePrRef(input, cwdRepo) {
1881
+ const trimmed = input.trim();
1882
+ const urlMatch = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/i.exec(trimmed);
1883
+ if (urlMatch) return {
1884
+ owner: urlMatch[1],
1885
+ repo: urlMatch[2],
1886
+ pullNumber: Number(urlMatch[3]),
1887
+ ref: trimmed
1888
+ };
1889
+ const shorthand = /^([^/\s]+)\/([^#\s]+)#(\d+)$/.exec(trimmed);
1890
+ if (shorthand) return {
1891
+ owner: shorthand[1],
1892
+ repo: shorthand[2],
1893
+ pullNumber: Number(shorthand[3]),
1894
+ ref: trimmed
1895
+ };
1896
+ const bare = /^#?(\d+)$/.exec(trimmed);
1897
+ if (bare && cwdRepo) return {
1898
+ owner: cwdRepo.owner,
1899
+ repo: cwdRepo.repo,
1900
+ pullNumber: Number(bare[1]),
1901
+ ref: trimmed
1902
+ };
1903
+ if (bare) throw new Error(`Cannot resolve bare PR number "${trimmed}" — run from inside the target repo, or pass a full URL or \`owner/repo#1234\`.`);
1904
+ throw new Error(`Unrecognised PR ref: ${trimmed}`);
1905
+ }
1906
+ /**
1907
+ * Best-effort: derive `{ owner, repo }` for the current working directory
1908
+ * from `git remote get-url origin`. Falls back to throwing.
1909
+ */
1910
+ async function detectCwdRepo() {
1911
+ try {
1912
+ const { stdout } = await execFileAsync("git", [
1913
+ "remote",
1914
+ "get-url",
1915
+ "origin"
1916
+ ], { encoding: "utf-8" });
1917
+ const url = stdout.trim();
1918
+ const m = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\s*$/.exec(url);
1919
+ if (m) return {
1920
+ owner: m[1],
1921
+ repo: m[2]
1922
+ };
1923
+ } catch {}
1924
+ return null;
1925
+ }
1926
+ /**
1927
+ * Fetch PR metadata + diff + existing review comments using `gh`.
1928
+ * Throws with a user-readable message on failure.
1929
+ */
1930
+ async function fetchPrMetadataViaGh(resolved) {
1931
+ const args = ["-R", `${resolved.owner}/${resolved.repo}`];
1932
+ let metaJson;
1933
+ try {
1934
+ const { stdout } = await execFileAsync("gh", [
1935
+ "pr",
1936
+ "view",
1937
+ String(resolved.pullNumber),
1938
+ ...args,
1939
+ "--json",
1940
+ "number,title,url,author,baseRefOid,headRefOid,additions,deletions,changedFiles,headRepositoryOwner,headRepository"
1941
+ ], {
1942
+ encoding: "utf-8",
1943
+ maxBuffer: 20 * 1024 * 1024
1944
+ });
1945
+ metaJson = JSON.parse(stdout);
1946
+ } catch (err) {
1947
+ const stderr = err?.stderr || err?.message || "unknown error";
1948
+ throw new Error(`gh pr view failed: ${stderr.trim()}`);
1949
+ }
1950
+ let diff = "";
1951
+ try {
1952
+ const { stdout } = await execFileAsync("gh", [
1953
+ "pr",
1954
+ "diff",
1955
+ String(resolved.pullNumber),
1956
+ ...args
1957
+ ], {
1958
+ encoding: "utf-8",
1959
+ maxBuffer: 50 * 1024 * 1024
1960
+ });
1961
+ diff = stdout;
1962
+ } catch (err) {
1963
+ const stderr = err?.stderr || err?.message || "unknown error";
1964
+ throw new Error(`gh pr diff failed: ${stderr.trim()}`);
1965
+ }
1966
+ const headOwner = metaJson.headRepositoryOwner?.login || metaJson.headRepository?.owner?.login || resolved.owner;
1967
+ const repoName = metaJson.headRepository?.name || resolved.repo;
1968
+ const author = metaJson.author ? {
1969
+ login: metaJson.author.login,
1970
+ avatarUrl: metaJson.author.avatarUrl
1971
+ } : null;
1972
+ const existingComments = await fetchExistingCommentsViaGh(resolved);
1973
+ return {
1974
+ number: metaJson.number,
1975
+ title: metaJson.title,
1976
+ url: metaJson.url,
1977
+ author,
1978
+ baseSha: metaJson.baseRefOid,
1979
+ headSha: metaJson.headRefOid,
1980
+ additions: metaJson.additions,
1981
+ deletions: metaJson.deletions,
1982
+ changedFiles: metaJson.changedFiles,
1983
+ owner: headOwner,
1984
+ repo: repoName,
1985
+ diff,
1986
+ existingComments
1987
+ };
1988
+ }
1989
+ async function fetchExistingCommentsViaGh(resolved) {
1990
+ `${resolved.owner}${resolved.repo}`;
1991
+ const endpoint = `repos/${resolved.owner}/${resolved.repo}/pulls/${resolved.pullNumber}/comments`;
1992
+ const reviewsEndpoint = `repos/${resolved.owner}/${resolved.repo}/pulls/${resolved.pullNumber}/reviews`;
1993
+ let reviews = [];
1994
+ try {
1995
+ const { stdout } = await execFileAsync("gh", [
1996
+ "api",
1997
+ reviewsEndpoint,
1998
+ "--paginate"
1999
+ ], {
2000
+ encoding: "utf-8",
2001
+ maxBuffer: 20 * 1024 * 1024
2002
+ });
2003
+ const all = JSON.parse(stdout);
2004
+ if (Array.isArray(all)) reviews = all;
2005
+ } catch {}
2006
+ reviews.sort((a, b) => a.submitted_at < b.submitted_at ? 1 : -1);
2007
+ const recentReviews = reviews.slice(0, 50);
2008
+ const reviewStateById = /* @__PURE__ */ new Map();
2009
+ for (const r of recentReviews) reviewStateById.set(r.id, r.state);
2010
+ let commentsRaw = [];
2011
+ try {
2012
+ const { stdout } = await execFileAsync("gh", [
2013
+ "api",
2014
+ `${endpoint}?per_page=100`,
2015
+ "--paginate"
2016
+ ], {
2017
+ encoding: "utf-8",
2018
+ maxBuffer: 20 * 1024 * 1024
2019
+ });
2020
+ const parsed = JSON.parse(stdout);
2021
+ if (Array.isArray(parsed)) commentsRaw = parsed;
2022
+ } catch {
2023
+ return [];
2024
+ }
2025
+ const byId = /* @__PURE__ */ new Map();
2026
+ for (const c of commentsRaw) byId.set(c.id, c);
2027
+ const replyByParent = /* @__PURE__ */ new Map();
2028
+ for (const c of commentsRaw) if (c.in_reply_to_id && byId.has(c.in_reply_to_id)) {
2029
+ const list = replyByParent.get(c.in_reply_to_id) ?? [];
2030
+ list.push(c);
2031
+ replyByParent.set(c.in_reply_to_id, list);
2032
+ }
2033
+ const tops = [];
2034
+ for (const c of commentsRaw) {
2035
+ if (c.in_reply_to_id) continue;
2036
+ const state = c.pull_request_review_id ? reviewStateById.get(c.pull_request_review_id) ?? null : null;
2037
+ tops.push({
2038
+ id: c.id,
2039
+ author: c.user ? {
2040
+ login: c.user.login,
2041
+ avatarUrl: c.user.avatar_url
2042
+ } : null,
2043
+ body: c.body ?? "",
2044
+ path: c.path,
2045
+ line: typeof c.line === "number" ? c.line : null,
2046
+ side: c.side === "LEFT" || c.side === "RIGHT" ? c.side : null,
2047
+ createdAt: c.created_at,
2048
+ updatedAt: c.updated_at,
2049
+ state,
2050
+ replies: (replyByParent.get(c.id) ?? []).map((r) => ({
2051
+ id: r.id,
2052
+ author: r.user ? {
2053
+ login: r.user.login,
2054
+ avatarUrl: r.user.avatar_url
2055
+ } : null,
2056
+ body: r.body ?? "",
2057
+ createdAt: r.created_at,
2058
+ updatedAt: r.updated_at
2059
+ })),
2060
+ isOutdated: typeof c.position === "number" && c.position === null ? false : false
2061
+ });
2062
+ }
2063
+ tops.sort((a, b) => {
2064
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
2065
+ return (a.line ?? 0) - (b.line ?? 0);
2066
+ });
2067
+ return tops;
2068
+ }
2069
+ /**
2070
+ * POST the review to GitHub. Prefers `gh api` (uses the user's existing
2071
+ * `gh auth`); falls back to a direct HTTP call with `$GITHUB_TOKEN` when
2072
+ * `gh` is unavailable.
2073
+ */
2074
+ async function submitReview(input) {
2075
+ const gh = await detectGhCli();
2076
+ if (gh.available && gh.authenticated) return submitViaGh(input);
2077
+ const token = readGithubToken();
2078
+ if (token) return submitViaToken(input, token);
2079
+ return {
2080
+ ok: false,
2081
+ authSource: "none",
2082
+ error: "Cannot submit: no `gh` CLI on $PATH (or not authenticated) and no $GITHUB_TOKEN env var. Run `gh auth login` or set $GITHUB_TOKEN."
2083
+ };
2084
+ }
2085
+ /**
2086
+ * Map our `PrDecision` to the GitHub REST event string. Exported so the
2087
+ * test suite can pin the mapping (including the `rejected → REQUEST_CHANGES`
2088
+ * quirk, which exists because GitHub has no REJECT event).
2089
+ */
2090
+ function decisionToEvent(decision) {
2091
+ if (decision === "approve") return "APPROVE";
2092
+ if (decision === "request-changes") return "REQUEST_CHANGES";
2093
+ return "COMMENT";
2094
+ }
2095
+ /**
2096
+ * Expand multi-line `ReviewComment`s into N single-line `gh`-shaped entries
2097
+ * with `[part N/M]` prefixes. Exported for the test suite — the body of the
2098
+ * caller (`buildReviewPayload`) is otherwise private.
2099
+ *
2100
+ * Skips: non-`open` comments, file-level comments (`lineNumber === 0`).
2101
+ * Strips the `a/` / `b/` prefix from the file path (GitHub expects PR-relative
2102
+ * paths in the `path` field).
2103
+ */
2104
+ function expandMultiLineComments(comments) {
2105
+ const flat = [];
2106
+ for (const c of comments) {
2107
+ if (c.status !== "open") continue;
2108
+ if (c.lineNumber === 0) continue;
2109
+ const path = stripBPrefix(c.filePath);
2110
+ const start = c.startLineNumber && c.startLineNumber < c.lineNumber ? c.startLineNumber : c.lineNumber;
2111
+ const end = c.lineNumber;
2112
+ const span = end - start + 1;
2113
+ if (span > 1) for (let i = 0; i < span; i++) flat.push({
2114
+ path,
2115
+ line: start + i,
2116
+ side: "RIGHT",
2117
+ body: `[part ${i + 1}/${span}]\n${c.body}`
2118
+ });
2119
+ else flat.push({
2120
+ path,
2121
+ line: end,
2122
+ side: "RIGHT",
2123
+ body: c.body
2124
+ });
2125
+ }
2126
+ return flat;
2127
+ }
2128
+ function buildReviewPayload(input) {
2129
+ return {
2130
+ body: input.body,
2131
+ event: decisionToEvent(input.decision ?? "comment"),
2132
+ comments: expandMultiLineComments(input.comments ?? [])
2133
+ };
2134
+ }
2135
+ function stripBPrefix(path) {
2136
+ if (path.startsWith("b/")) return path.slice(2);
2137
+ if (path.startsWith("a/")) return path.slice(2);
2138
+ return path;
2139
+ }
2140
+ async function submitViaGh(input) {
2141
+ const payload = buildReviewPayload(input);
2142
+ `${input.resolved.owner}${input.resolved.repo}`;
2143
+ const args = [
2144
+ "api",
2145
+ "--method",
2146
+ "POST",
2147
+ `repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`,
2148
+ "-H",
2149
+ "Accept: application/vnd.github+json",
2150
+ "--input",
2151
+ "-"
2152
+ ];
2153
+ try {
2154
+ const { stdout } = await execFileAsync("gh", args, {
2155
+ encoding: "utf-8",
2156
+ maxBuffer: 20 * 1024 * 1024,
2157
+ input: JSON.stringify(payload)
2158
+ });
2159
+ const result = JSON.parse(stdout);
2160
+ return {
2161
+ ok: true,
2162
+ authSource: "gh",
2163
+ reviewId: result.id,
2164
+ reviewUrl: result.html_url
2165
+ };
2166
+ } catch (err) {
2167
+ const stdout = err?.stdout || "";
2168
+ const stderr = err?.stderr || err?.message || "unknown error";
2169
+ let parsed = null;
2170
+ try {
2171
+ parsed = JSON.parse(stdout);
2172
+ } catch {}
2173
+ return {
2174
+ ok: false,
2175
+ authSource: "gh",
2176
+ error: parsed?.message || stderr.trim()
2177
+ };
2178
+ }
2179
+ }
2180
+ async function submitViaToken(input, token) {
2181
+ const payload = buildReviewPayload(input);
2182
+ const url = `https://api.github.com/repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`;
2183
+ try {
2184
+ const res = await fetch(url, {
2185
+ method: "POST",
2186
+ headers: {
2187
+ Authorization: `Bearer ${token}`,
2188
+ Accept: "application/vnd.github+json",
2189
+ "X-GitHub-Api-Version": "2022-11-28",
2190
+ "Content-Type": "application/json",
2191
+ "User-Agent": "diffing-cli"
2192
+ },
2193
+ body: JSON.stringify(payload)
2194
+ });
2195
+ if (!res.ok) {
2196
+ const errBody = await res.text().catch(() => "");
2197
+ return {
2198
+ ok: false,
2199
+ authSource: "token",
2200
+ error: `HTTP ${res.status}: ${errBody.slice(0, 500)}`
2201
+ };
2202
+ }
2203
+ const result = await res.json();
2204
+ return {
2205
+ ok: true,
2206
+ authSource: "token",
2207
+ reviewId: result.id,
2208
+ reviewUrl: result.html_url
2209
+ };
2210
+ } catch (err) {
2211
+ return {
2212
+ ok: false,
2213
+ authSource: "token",
2214
+ error: err?.message || "Network error"
2215
+ };
2216
+ }
2217
+ }
2218
+ async function buildPrSession(ref) {
2219
+ const meta = await fetchPrMetadataViaGh(parsePrRef(ref, await detectCwdRepo() ?? void 0));
2220
+ return {
2221
+ ref,
2222
+ owner: meta.owner,
2223
+ repo: meta.repo,
2224
+ pullNumber: meta.number,
2225
+ baseSha: meta.baseSha,
2226
+ headSha: meta.headSha,
2227
+ title: meta.title,
2228
+ url: meta.url,
2229
+ author: meta.author,
2230
+ additions: meta.additions,
2231
+ deletions: meta.deletions,
2232
+ changedFiles: meta.changedFiles,
2233
+ diff: meta.diff,
2234
+ comments: [],
2235
+ existingComments: meta.existingComments
2236
+ };
2237
+ }
2238
+ /** Refresh `diff` + `existingComments` + head SHA in an existing session. */
2239
+ async function refreshPrSession(session) {
2240
+ const meta = await fetchPrMetadataViaGh({
2241
+ owner: session.owner,
2242
+ repo: session.repo,
2243
+ pullNumber: session.pullNumber,
2244
+ ref: session.ref
2245
+ });
2246
+ return {
2247
+ ...session,
2248
+ baseSha: meta.baseSha,
2249
+ headSha: meta.headSha,
2250
+ title: meta.title,
2251
+ url: meta.url,
2252
+ author: meta.author,
2253
+ additions: meta.additions,
2254
+ deletions: meta.deletions,
2255
+ changedFiles: meta.changedFiles,
2256
+ diff: meta.diff,
2257
+ existingComments: meta.existingComments
2258
+ };
2259
+ }
2260
+ //#endregion
1583
2261
  //#region src/server.ts
1584
2262
  const MIME_TYPES = {
1585
2263
  ".html": "text/html",
@@ -1597,13 +2275,15 @@ const MIME_TYPES = {
1597
2275
  ".avif": "image/avif"
1598
2276
  };
1599
2277
  function isCustomMode(opts) {
1600
- return opts.revisions.length > 0 || opts.pathspecs.length > 0;
2278
+ return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
1601
2279
  }
1602
- function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
2280
+ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore, prSessionStore) {
1603
2281
  const app = new Hono();
1604
2282
  const customMode = isCustomMode(diffOpts);
1605
2283
  const store = commentStore ?? new FileCommentStore();
1606
2284
  const plans = planStore ?? new FilePlanStore();
2285
+ const prStore = prSessionStore ?? new FilePrSessionStore();
2286
+ const uiStateStore = new FileUiStateStore();
1607
2287
  const viewedFiles = /* @__PURE__ */ new Set();
1608
2288
  const activeClients = /* @__PURE__ */ new Set();
1609
2289
  const broadcast = (event, data) => {
@@ -1624,6 +2304,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1624
2304
  mkdirSync(storageDir, { recursive: true });
1625
2305
  let commentsDebounce = null;
1626
2306
  let plansDebounce = null;
2307
+ let prSessionDebounce = null;
1627
2308
  watch(storageDir, (_eventType, filename) => {
1628
2309
  if (!filename) return;
1629
2310
  if (filename.startsWith("comments.json")) {
@@ -1632,6 +2313,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1632
2313
  } else if (filename.startsWith("plans.json")) {
1633
2314
  if (plansDebounce) clearTimeout(plansDebounce);
1634
2315
  plansDebounce = setTimeout(() => broadcast("plans", Date.now().toString()), 120);
2316
+ } else if (filename.startsWith("pr-session.json")) {
2317
+ if (prSessionDebounce) clearTimeout(prSessionDebounce);
2318
+ prSessionDebounce = setTimeout(() => broadcast("pr-session", Date.now().toString()), 120);
1635
2319
  }
1636
2320
  }).unref();
1637
2321
  } catch (err) {
@@ -1692,11 +2376,40 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1692
2376
  app.get("/api/diff", async (c) => {
1693
2377
  const staged = c.req.query("staged") === "true";
1694
2378
  const untracked = c.req.query("untracked") === "true";
1695
- const result = await executeDiffWithMeta(customMode ? diffOpts : {
2379
+ const prSession = await prStore.get();
2380
+ if (prSession) {
2381
+ const binaryFiles = [];
2382
+ const filePaths = [];
2383
+ for (const line of prSession.diff.split("\n")) {
2384
+ const m = /^diff --git a\/.+ b\/(.+)$/.exec(line);
2385
+ if (m) filePaths.push(m[1]);
2386
+ }
2387
+ return c.json({
2388
+ patch: prSession.diff,
2389
+ repoName: prSession.repo,
2390
+ branch: `#${prSession.pullNumber}`,
2391
+ customMode: true,
2392
+ binaryFiles,
2393
+ tabSizeMap: {},
2394
+ untrackedFiles: [],
2395
+ prMode: true,
2396
+ prRef: prSession.ref,
2397
+ prOwner: prSession.owner,
2398
+ prRepo: prSession.repo,
2399
+ prPullNumber: prSession.pullNumber,
2400
+ prUrl: prSession.url,
2401
+ prTitle: prSession.title,
2402
+ prAuthor: prSession.author,
2403
+ prHeadSha: prSession.headSha,
2404
+ prBaseSha: prSession.baseSha
2405
+ });
2406
+ }
2407
+ const optsForDiff = customMode ? diffOpts : {
1696
2408
  ...diffOpts,
1697
2409
  staged,
1698
2410
  includeUntracked: untracked
1699
- });
2411
+ };
2412
+ const result = await executeDiffWithMeta(optsForDiff);
1700
2413
  return c.json({
1701
2414
  patch: result.patch,
1702
2415
  repoName: result.repoName,
@@ -1704,7 +2417,10 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1704
2417
  customMode,
1705
2418
  binaryFiles: result.binaryFiles,
1706
2419
  tabSizeMap: result.tabSizeMap,
1707
- untrackedFiles: result.untrackedFiles
2420
+ untrackedFiles: result.untrackedFiles,
2421
+ showMode: optsForDiff.showMode || void 0,
2422
+ commits: result.commits,
2423
+ truncated: result.truncated
1708
2424
  });
1709
2425
  });
1710
2426
  app.get("/api/file-content", (c) => {
@@ -1737,14 +2453,20 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1737
2453
  if (!filePath) return c.json({ error: "filePath is required" }, 400);
1738
2454
  try {
1739
2455
  const root = getRepoRoot();
1740
- if (!isSafePath(filePath, root)) return c.json({ error: "Forbidden file path" }, 403);
1741
- const absolutePath = resolve(root, filePath);
1742
- const { exec } = await import("node:child_process");
1743
- if (editor === "vscode") exec(`code "${absolutePath}"`);
1744
- else if (editor === "zed") exec(`zed "${absolutePath}"`);
1745
- else if (editor === "vim") exec(`osascript -e 'tell application "Terminal" to do script "vim \\"${absolutePath}\\""'`);
1746
- else if (editor === "neovim") exec(`osascript -e 'tell application "Terminal" to do script "nvim \\"${absolutePath}\\""'`);
1747
- else await (await import("open")).default(absolutePath);
2456
+ const relPath = toSafeRelativePath(filePath, root);
2457
+ if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
2458
+ const absolutePath = resolve(root, relPath);
2459
+ if (editor && editor !== "default") {
2460
+ const command = resolveEditorCommand(editor, absolutePath);
2461
+ if (command) {
2462
+ const { execFile } = await import("node:child_process");
2463
+ execFile(command.cmd, command.args, (err) => {
2464
+ if (err) console.error(`Failed to launch ${editor} for ${absolutePath}: ${err.message}`);
2465
+ });
2466
+ return c.json({ ok: true });
2467
+ }
2468
+ }
2469
+ await (await import("open")).default(absolutePath);
1748
2470
  return c.json({ ok: true });
1749
2471
  } catch (err) {
1750
2472
  return c.json({ error: `Failed to open file: ${err.message}` }, 500);
@@ -1812,8 +2534,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1812
2534
  const { filePath, hunkIndex } = await c.req.json();
1813
2535
  if (!filePath || typeof hunkIndex !== "number") return c.json({ error: "Missing filePath or hunkIndex" }, 400);
1814
2536
  try {
1815
- if (!isSafePath(filePath, getRepoRoot())) return c.json({ error: "Forbidden file path" }, 403);
1816
- revertHunk(filePath, hunkIndex);
2537
+ const relPath = toSafeRelativePath(filePath, getRepoRoot());
2538
+ if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
2539
+ revertHunk(relPath, hunkIndex);
1817
2540
  return c.json({ ok: true });
1818
2541
  } catch (err) {
1819
2542
  const stderr = typeof err?.stderr === "string" ? err.stderr : err?.stderr instanceof Buffer ? err.stderr.toString("utf-8") : "";
@@ -1826,8 +2549,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1826
2549
  const deletionCount = Number(c.req.query("deletionCount"));
1827
2550
  if (!filePath || Number.isNaN(deletionStart) || Number.isNaN(deletionCount)) return c.json({ error: "Missing or invalid parameters" }, 400);
1828
2551
  try {
1829
- if (!isSafePath(filePath, getRepoRoot())) return c.json({ error: "Forbidden file path" }, 403);
1830
- const history = getHunkHistory(filePath, deletionStart, deletionCount, diffOpts.revisions[0] || "HEAD");
2552
+ const relPath = toSafeRelativePath(filePath, getRepoRoot());
2553
+ if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
2554
+ const history = getHunkHistory(relPath, deletionStart, deletionCount, diffOpts.showMode ? diffOpts.showRevspecs[diffOpts.showRevspecs.length - 1] || "HEAD" : diffOpts.revisions[0] || "HEAD");
1831
2555
  return c.json(history);
1832
2556
  } catch (err) {
1833
2557
  return c.json({ error: err.message || "Failed to fetch hunk history" }, 500);
@@ -1838,10 +2562,11 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1838
2562
  if (!filePath || typeof content !== "string") return c.json({ error: "Missing filePath or content" }, 400);
1839
2563
  try {
1840
2564
  const root = getRepoRoot();
1841
- if (!isSafePath(filePath, root)) return c.json({ error: "Forbidden file path" }, 403);
1842
- await writeFile(resolve(root, filePath), content, "utf-8");
2565
+ const relPath = toSafeRelativePath(filePath, root);
2566
+ if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
2567
+ await writeFile(resolve(root, relPath), content, "utf-8");
1843
2568
  if (gitAdd) try {
1844
- gitAddFile(filePath);
2569
+ gitAddFile(relPath);
1845
2570
  } catch (err) {
1846
2571
  return c.json({
1847
2572
  ok: true,
@@ -1860,6 +2585,19 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1860
2585
  const settings = saveSettings(await c.req.json());
1861
2586
  return c.json(settings);
1862
2587
  });
2588
+ app.get("/api/ui-state", async (c) => {
2589
+ return c.json(await uiStateStore.getAll());
2590
+ });
2591
+ app.put("/api/ui-state", async (c) => {
2592
+ const body = await c.req.json();
2593
+ const merged = {
2594
+ ...await uiStateStore.getAll(),
2595
+ ...body
2596
+ };
2597
+ for (const key of Object.keys(body)) if (body[key] === null || body[key] === void 0) delete merged[key];
2598
+ await uiStateStore.setAll(merged);
2599
+ return c.json(merged);
2600
+ });
1863
2601
  app.get("/api/viewed", (c) => {
1864
2602
  return c.json([...viewedFiles]);
1865
2603
  });
@@ -1940,8 +2678,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1940
2678
  const suggestion = match[1];
1941
2679
  try {
1942
2680
  const root = getRepoRoot();
1943
- if (!isSafePath(comment.filePath, root)) return c.json({ error: "Forbidden file path" }, 403);
1944
- const absolutePath = resolve(root, comment.filePath);
2681
+ const relPath = toSafeRelativePath(comment.filePath, root);
2682
+ if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
2683
+ const absolutePath = resolve(root, relPath);
1945
2684
  const lines = (await readFile(absolutePath, "utf-8")).split(/\r?\n/);
1946
2685
  const lineIdx = comment.lineNumber - 1;
1947
2686
  if (lineIdx < 0 || lineIdx >= lines.length) return c.json({ error: "Comment line number out of range" }, 400);
@@ -1958,21 +2697,211 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
1958
2697
  if (!await store.remove(id)) return c.json({ error: "Comment not found" }, 404);
1959
2698
  return c.json({ ok: true });
1960
2699
  });
2700
+ /** Shared helper: 404 with a stable shape so the client knows "not in PR mode". */
2701
+ const notInPrMode = (c) => c.json({
2702
+ error: "Not in PR review mode",
2703
+ prMode: false
2704
+ }, 404);
2705
+ app.get("/api/gh/session", async (c) => {
2706
+ const session = await prStore.get();
2707
+ if (!session) return notInPrMode(c);
2708
+ return c.json({
2709
+ prMode: true,
2710
+ ref: session.ref,
2711
+ owner: session.owner,
2712
+ repo: session.repo,
2713
+ pullNumber: session.pullNumber,
2714
+ baseSha: session.baseSha,
2715
+ headSha: session.headSha,
2716
+ title: session.title,
2717
+ url: session.url,
2718
+ author: session.author,
2719
+ additions: session.additions,
2720
+ deletions: session.deletions,
2721
+ changedFiles: session.changedFiles,
2722
+ existingComments: session.existingComments,
2723
+ submittedAt: session.submittedAt,
2724
+ submittedReviewId: session.submittedReviewId,
2725
+ submittedReviewUrl: session.submittedReviewUrl,
2726
+ authSource: session.authSource
2727
+ });
2728
+ });
2729
+ /** Re-fetch PR metadata (head SHA, diff, existing comments) and persist. */
2730
+ app.post("/api/gh/pr/refresh", async (c) => {
2731
+ const session = await prStore.get();
2732
+ if (!session) return notInPrMode(c);
2733
+ try {
2734
+ const refreshed = await refreshPrSession(session);
2735
+ await prStore.set(refreshed);
2736
+ return c.json({
2737
+ ok: true,
2738
+ headSha: refreshed.headSha
2739
+ });
2740
+ } catch (err) {
2741
+ return c.json({ error: err?.message ?? "Refresh failed" }, 500);
2742
+ }
2743
+ });
2744
+ /** Initialize a PR session from a ref like `1234`, `o/r#42`, or a GitHub URL. */
2745
+ app.post("/api/gh/pr/init", async (c) => {
2746
+ const body = await c.req.json().catch(() => ({}));
2747
+ const ref = typeof body?.ref === "string" ? body.ref : "";
2748
+ if (!ref.trim()) return c.json({ error: "ref is required" }, 400);
2749
+ try {
2750
+ parsePrRef(ref, await detectCwdRepo() ?? void 0);
2751
+ const session = await buildPrSession(ref);
2752
+ await prStore.set(session);
2753
+ return c.json({
2754
+ ok: true,
2755
+ ref: session.ref,
2756
+ owner: session.owner,
2757
+ repo: session.repo,
2758
+ pullNumber: session.pullNumber,
2759
+ url: session.url
2760
+ });
2761
+ } catch (err) {
2762
+ return c.json({ error: err?.message ?? "Failed to initialise PR session" }, 500);
2763
+ }
2764
+ });
2765
+ app.get("/api/gh/pr-session/comments", async (c) => {
2766
+ const session = await prStore.get();
2767
+ if (!session) return notInPrMode(c);
2768
+ return c.json(session.comments ?? []);
2769
+ });
2770
+ app.post("/api/gh/pr-session/comments", async (c) => {
2771
+ const body = await c.req.json();
2772
+ const session = await prStore.get();
2773
+ if (!session) return notInPrMode(c);
2774
+ const comment = {
2775
+ id: crypto.randomUUID(),
2776
+ filePath: body.filePath,
2777
+ side: body.side,
2778
+ lineNumber: body.lineNumber,
2779
+ startLineNumber: body.startLineNumber,
2780
+ lineContent: body.lineContent,
2781
+ body: body.body,
2782
+ status: "open",
2783
+ createdAt: Date.now(),
2784
+ replies: []
2785
+ };
2786
+ const next = {
2787
+ ...session,
2788
+ comments: [...session.comments ?? [], comment]
2789
+ };
2790
+ await prStore.set(next);
2791
+ return c.json(comment, 201);
2792
+ });
2793
+ app.put("/api/gh/pr-session/comments/:id", async (c) => {
2794
+ const id = c.req.param("id");
2795
+ const { body, status } = await c.req.json();
2796
+ const session = await prStore.get();
2797
+ if (!session) return notInPrMode(c);
2798
+ const comments = (session.comments ?? []).map((cm) => cm.id === id ? {
2799
+ ...cm,
2800
+ body: body ?? cm.body,
2801
+ status: status ?? cm.status
2802
+ } : cm);
2803
+ await prStore.set({
2804
+ ...session,
2805
+ comments
2806
+ });
2807
+ const updated = comments.find((cm) => cm.id === id) ?? null;
2808
+ if (!updated) return c.json({ error: "Comment not found" }, 404);
2809
+ return c.json(updated);
2810
+ });
2811
+ app.delete("/api/gh/pr-session/comments/:id", async (c) => {
2812
+ const id = c.req.param("id");
2813
+ const session = await prStore.get();
2814
+ if (!session) return notInPrMode(c);
2815
+ const comments = (session.comments ?? []).filter((cm) => cm.id !== id);
2816
+ await prStore.set({
2817
+ ...session,
2818
+ comments
2819
+ });
2820
+ return c.json({ ok: true });
2821
+ });
2822
+ app.post("/api/gh/pr-session/comments/:id/replies", async (c) => {
2823
+ const id = c.req.param("id");
2824
+ const { body, role, model } = await c.req.json();
2825
+ const session = await prStore.get();
2826
+ if (!session) return notInPrMode(c);
2827
+ const comments = (session.comments ?? []).map((cm) => {
2828
+ if (cm.id !== id) return cm;
2829
+ const reply = {
2830
+ id: crypto.randomUUID(),
2831
+ body,
2832
+ createdAt: Date.now(),
2833
+ role: role || (model ? "agent" : "user"),
2834
+ model: model || void 0
2835
+ };
2836
+ return {
2837
+ ...cm,
2838
+ replies: [...cm.replies ?? [], reply]
2839
+ };
2840
+ });
2841
+ await prStore.set({
2842
+ ...session,
2843
+ comments
2844
+ });
2845
+ return c.json(comments.find((cm) => cm.id === id));
2846
+ });
2847
+ /** Submit the current PR session's review (new comments + verdict + body) to GitHub. */
2848
+ app.post("/api/gh/submit", async (c) => {
2849
+ const session = await prStore.get();
2850
+ if (!session) return notInPrMode(c);
2851
+ const body = await c.req.json().catch(() => ({}));
2852
+ const decision = body.decision;
2853
+ if (decision !== "approve" && decision !== "comment" && decision !== "request-changes") return c.json({ error: "decision must be one of: approve, comment, request-changes" }, 400);
2854
+ const generalBody = typeof body.body === "string" ? body.body : "";
2855
+ const dryRun = body.dryRun === true;
2856
+ const result = await submitReview({
2857
+ resolved: {
2858
+ owner: session.owner,
2859
+ repo: session.repo,
2860
+ pullNumber: session.pullNumber,
2861
+ ref: session.ref
2862
+ },
2863
+ decision,
2864
+ body: generalBody,
2865
+ comments: session.comments ?? []
2866
+ });
2867
+ if (result.ok && !dryRun) {
2868
+ const next = {
2869
+ ...session,
2870
+ submittedAt: Date.now(),
2871
+ submittedReviewId: result.reviewId,
2872
+ submittedReviewUrl: result.reviewUrl,
2873
+ authSource: result.authSource
2874
+ };
2875
+ await prStore.set(next);
2876
+ }
2877
+ return c.json({
2878
+ ok: result.ok,
2879
+ reviewId: result.reviewId,
2880
+ reviewUrl: result.reviewUrl,
2881
+ failedComments: result.failedComments ?? 0,
2882
+ authSource: result.authSource,
2883
+ error: result.error,
2884
+ dryRun
2885
+ });
2886
+ });
1961
2887
  app.post("/api/review/send", async (c) => {
1962
2888
  const body = await c.req.json().catch(() => ({}));
1963
2889
  const generalComment = typeof body?.generalComment === "string" ? body.generalComment : void 0;
2890
+ const decision = body?.decision === "approved" || body?.decision === "changes-requested" || body?.decision === "rejected" ? body.decision : void 0;
1964
2891
  const all = await store.getAll();
1965
2892
  const openCount = all.filter((x) => x.status === "open").length;
1966
2893
  const payload = reviewSession.send({
1967
2894
  sentAt: Date.now(),
1968
- commentXml: formatComments(all, generalComment),
2895
+ commentXml: formatComments(all, generalComment, decision),
1969
2896
  openCount,
1970
- comments: all
2897
+ comments: all,
2898
+ decision
1971
2899
  });
1972
2900
  return c.json({
1973
2901
  ok: true,
1974
2902
  round: payload.round,
1975
2903
  openCount: payload.openCount,
2904
+ decision: payload.decision,
1976
2905
  waiters: reviewSession.snapshot().waiters
1977
2906
  });
1978
2907
  });
@@ -2182,6 +3111,44 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
2182
3111
  });
2183
3112
  return app;
2184
3113
  }
3114
+ /**
3115
+ * Newest mtime (epoch ms) across everything that counts as project activity:
3116
+ * comments.json, plans.json, and any attachment (media) file. Plans and media
3117
+ * extend a project's life exactly like comments do, so retention is uniform
3118
+ * across all three. Returns null when none of them exist yet.
3119
+ */
3120
+ async function newestActivityMs(projectDir) {
3121
+ const candidates = [];
3122
+ const safeStat = async (p) => {
3123
+ try {
3124
+ return (await stat(p)).mtimeMs;
3125
+ } catch {
3126
+ return null;
3127
+ }
3128
+ };
3129
+ for (const name of [
3130
+ "comments.json",
3131
+ "plans.json",
3132
+ "pr-session.json"
3133
+ ]) {
3134
+ const file = join(projectDir, name);
3135
+ if (!existsSync(file)) continue;
3136
+ const m = await safeStat(file);
3137
+ if (m !== null) candidates.push(m);
3138
+ }
3139
+ const attachmentsDir = join(projectDir, "attachments");
3140
+ if (existsSync(attachmentsDir)) {
3141
+ const dirM = await safeStat(attachmentsDir);
3142
+ if (dirM !== null) candidates.push(dirM);
3143
+ try {
3144
+ for (const file of await readdir(attachmentsDir)) {
3145
+ const m = await safeStat(join(attachmentsDir, file));
3146
+ if (m !== null) candidates.push(m);
3147
+ }
3148
+ } catch {}
3149
+ }
3150
+ return candidates.length ? Math.max(...candidates) : null;
3151
+ }
2185
3152
  async function cleanupStaleProjects() {
2186
3153
  const baseDir = join(homedir(), ".diffing");
2187
3154
  if (!existsSync(baseDir)) return;
@@ -2192,15 +3159,15 @@ async function cleanupStaleProjects() {
2192
3159
  for (const entry of entries) if (entry.isDirectory()) {
2193
3160
  const projectDir = join(baseDir, entry.name);
2194
3161
  const repoPathFile = join(projectDir, "repo_path.txt");
2195
- const commentsFile = join(projectDir, "comments.json");
2196
3162
  let shouldDelete = false;
2197
3163
  if (existsSync(repoPathFile)) try {
2198
3164
  const repoPath = (await readFile(repoPathFile, "utf-8")).trim();
2199
3165
  if (!repoPath || !existsSync(repoPath)) shouldDelete = true;
2200
3166
  } catch {}
2201
- if (!shouldDelete && existsSync(commentsFile)) try {
2202
- if (now - (await stat(commentsFile)).mtimeMs > STALE_TIME) shouldDelete = true;
2203
- } catch {}
3167
+ if (!shouldDelete) {
3168
+ const newest = await newestActivityMs(projectDir);
3169
+ if (newest !== null && now - newest > STALE_TIME) shouldDelete = true;
3170
+ }
2204
3171
  if (shouldDelete) await rm(projectDir, {
2205
3172
  recursive: true,
2206
3173
  force: true
@@ -2214,20 +3181,484 @@ function startServer(options) {
2214
3181
  cleanupStaleProjects().catch((err) => {
2215
3182
  console.error("Failed to clean up stale projects:", err);
2216
3183
  });
3184
+ let prMode = false;
3185
+ const prReady = (async () => {
3186
+ if (!options.prRef) return;
3187
+ console.error(`Building PR session for ${options.prRef}...`);
3188
+ try {
3189
+ const store = new FilePrSessionStore();
3190
+ const existing = await store.get();
3191
+ if (!existing || existing.ref !== options.prRef) {
3192
+ const session = await buildPrSession(options.prRef);
3193
+ await store.set(session);
3194
+ }
3195
+ prMode = true;
3196
+ } catch (err) {
3197
+ console.error(`[pr-session] failed to build session for ${options.prRef}: ${err?.message ?? err}`);
3198
+ }
3199
+ })();
2217
3200
  const app = createApp(options.clientDir, options.diffOpts);
2218
3201
  return new Promise((resolve) => {
2219
- serve({
2220
- fetch: app.fetch,
2221
- port: options.port,
2222
- hostname: options.host
2223
- }, (info) => {
2224
- resolve({ port: info.port });
3202
+ prReady.then(() => {
3203
+ serve({
3204
+ fetch: app.fetch,
3205
+ port: options.port,
3206
+ hostname: options.host
3207
+ }, (info) => {
3208
+ resolve({
3209
+ port: info.port,
3210
+ prMode
3211
+ });
3212
+ });
2225
3213
  });
2226
3214
  });
2227
3215
  }
2228
3216
  //#endregion
3217
+ //#region src/lib/startup-display.ts
3218
+ const R = "\x1B[0m";
3219
+ const B = "\x1B[1m";
3220
+ const HIDE = "\x1B[?25l";
3221
+ const SHOW = "\x1B[?25h";
3222
+ const CLR = "\x1B[2K\r";
3223
+ const GLITCH_CHARS = "░▒▓█▄▀■□▸▹◆◇";
3224
+ const c = (n) => `\x1b[38;5;${n}m`;
3225
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
3226
+ const up = (n) => `\x1b[${n}A`;
3227
+ const PALETTES = [
3228
+ {
3229
+ base: c(51),
3230
+ text: c(159),
3231
+ faint: c(30),
3232
+ glow: B + c(87),
3233
+ rain: c(37)
3234
+ },
3235
+ {
3236
+ base: c(82),
3237
+ text: c(155),
3238
+ faint: c(22),
3239
+ glow: B + c(118),
3240
+ rain: c(34)
3241
+ },
3242
+ {
3243
+ base: c(207),
3244
+ text: c(219),
3245
+ faint: c(96),
3246
+ glow: B + c(213),
3247
+ rain: c(128)
3248
+ },
3249
+ {
3250
+ base: c(220),
3251
+ text: c(229),
3252
+ faint: c(136),
3253
+ glow: B + c(226),
3254
+ rain: c(178)
3255
+ },
3256
+ {
3257
+ base: c(75),
3258
+ text: c(153),
3259
+ faint: c(25),
3260
+ glow: B + c(81),
3261
+ rain: c(39)
3262
+ },
3263
+ {
3264
+ base: c(214),
3265
+ text: c(223),
3266
+ faint: c(130),
3267
+ glow: B + c(208),
3268
+ rain: c(172)
3269
+ }
3270
+ ];
3271
+ const QUOTES = [
3272
+ {
3273
+ text: "The best code is no code at all.",
3274
+ author: "Jeff Atwood"
3275
+ },
3276
+ {
3277
+ text: "Programs must be written for people to read, and only incidentally for machines to execute.",
3278
+ author: "Abelson & Sussman"
3279
+ },
3280
+ {
3281
+ text: "Make it work, make it right, make it fast.",
3282
+ author: "Kent Beck"
3283
+ },
3284
+ {
3285
+ text: "Simplicity is the soul of efficiency.",
3286
+ author: "Austin Freeman"
3287
+ },
3288
+ {
3289
+ text: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
3290
+ author: "Martin Fowler"
3291
+ },
3292
+ {
3293
+ text: "First, solve the problem. Then, write the code.",
3294
+ author: "John Johnson"
3295
+ },
3296
+ {
3297
+ text: "Code is like humor. When you have to explain it, it's bad.",
3298
+ author: "Cory House"
3299
+ },
3300
+ {
3301
+ text: "Before software can be reusable it first has to be usable.",
3302
+ author: "Ralph Johnson"
3303
+ },
3304
+ {
3305
+ text: "Clean code always looks like it was written by someone who cares.",
3306
+ author: "Robert C. Martin"
3307
+ },
3308
+ {
3309
+ text: "The most dangerous phrase is \"we've always done it this way\".",
3310
+ author: "Grace Hopper"
3311
+ },
3312
+ {
3313
+ text: "Architecture is the decisions you wish you could get right early in a project.",
3314
+ author: "Martin Fowler"
3315
+ },
3316
+ {
3317
+ text: "The best error message is the one that never shows up.",
3318
+ author: "Thomas Fuchs"
3319
+ },
3320
+ {
3321
+ text: "It works on my machine.",
3322
+ author: "Every developer, always"
3323
+ },
3324
+ {
3325
+ text: "git blame yourself.",
3326
+ author: "The terminal"
3327
+ },
3328
+ {
3329
+ text: "There are only two hard things in CS: cache invalidation, naming things, and off-by-one errors.",
3330
+ author: "Unknown"
3331
+ },
3332
+ {
3333
+ text: "Debugging: being the detective in a crime movie where you're also the murderer.",
3334
+ author: "Unknown"
3335
+ },
3336
+ {
3337
+ text: "The code you wrote six months ago was written by an idiot.",
3338
+ author: "Unknown"
3339
+ },
3340
+ {
3341
+ text: "sudo make me a sandwich.",
3342
+ author: "xkcd"
3343
+ },
3344
+ {
3345
+ text: "I don't always test my code, but when I do, I do it in production.",
3346
+ author: "Unknown"
3347
+ },
3348
+ {
3349
+ text: "A QA engineer walks into a bar. Orders 0 beers. Orders 999999 beers. Orders NULL beers. Walks in through the window.",
3350
+ author: "Unknown"
3351
+ },
3352
+ {
3353
+ text: "To understand recursion, you must first understand recursion.",
3354
+ author: "Unknown"
3355
+ },
3356
+ {
3357
+ text: "The code review is the product.",
3358
+ author: "diffing"
3359
+ },
3360
+ {
3361
+ text: "Your abstractions are just someone else's bugs.",
3362
+ author: "Unknown"
3363
+ },
3364
+ {
3365
+ text: "Every rewrite is a confession that you didn't understand the problem the first time.",
3366
+ author: "Unknown"
3367
+ },
3368
+ {
3369
+ text: "The best PR is no PR — ship it in the design.",
3370
+ author: "Unknown"
3371
+ },
3372
+ {
3373
+ text: "You don't own your code. Your code owns you.",
3374
+ author: "Unknown"
3375
+ },
3376
+ {
3377
+ text: "A diff is a conversation. What story does yours tell?",
3378
+ author: "Unknown"
3379
+ },
3380
+ {
3381
+ text: "The senior engineer's superpower: knowing which shortcuts will haunt you.",
3382
+ author: "Unknown"
3383
+ },
3384
+ {
3385
+ text: "Production is the only real test environment.",
3386
+ author: "Site Reliability truth"
3387
+ },
3388
+ {
3389
+ text: "Tech debt is just deferred thinking.",
3390
+ author: "Unknown"
3391
+ },
3392
+ {
3393
+ text: "Comments lie. Code never does.",
3394
+ author: "Ron Jeffries"
3395
+ }
3396
+ ];
3397
+ function wrapText(text, width) {
3398
+ const words = text.split(" ");
3399
+ const lines = [];
3400
+ let cur = "";
3401
+ for (const word of words) {
3402
+ if (!cur) {
3403
+ cur = word;
3404
+ continue;
3405
+ }
3406
+ if (cur.length + 1 + word.length <= width) cur += " " + word;
3407
+ else {
3408
+ lines.push(cur);
3409
+ cur = word;
3410
+ }
3411
+ }
3412
+ if (cur) lines.push(cur);
3413
+ return lines;
3414
+ }
3415
+ function makeBox(quote, pal) {
3416
+ const termWidth = stdout.columns ?? 80;
3417
+ const maxContent = Math.min(64, termWidth - 8);
3418
+ const fullText = `"${quote.text}"`;
3419
+ const authorStr = `— ${quote.author}`;
3420
+ const textLines = wrapText(fullText, maxContent);
3421
+ return {
3422
+ textLines,
3423
+ authorStr,
3424
+ innerWidth: Math.max(...textLines.map((l) => l.length), authorStr.length, 28) + 4,
3425
+ pal
3426
+ };
3427
+ }
3428
+ function topBorder(b, borderCol = b.pal.base) {
3429
+ return `${borderCol}╭${"─".repeat(b.innerWidth)}╮${R}`;
3430
+ }
3431
+ function bottomBorder(b, borderCol = b.pal.base) {
3432
+ return `${borderCol}╰${"─".repeat(b.innerWidth)}╯${R}`;
3433
+ }
3434
+ function contentRow(b, text, borderCol = b.pal.base, textCol = b.pal.text) {
3435
+ return `${borderCol}│${R} ${textCol}${text}${R}${" ".repeat(b.innerWidth - 4 - text.length)} ${borderCol}│${R}`;
3436
+ }
3437
+ function authorRow(b, borderCol = b.pal.base) {
3438
+ const leading = " ".repeat(b.innerWidth - 4 - b.authorStr.length);
3439
+ return `${borderCol}│${R} ${b.pal.faint}${leading}${b.authorStr}${R} ${borderCol}│${R}`;
3440
+ }
3441
+ function allLines(b, borderCol = b.pal.base) {
3442
+ return [
3443
+ topBorder(b, borderCol),
3444
+ ...b.textLines.map((l) => contentRow(b, l, borderCol)),
3445
+ authorRow(b, borderCol),
3446
+ bottomBorder(b, borderCol)
3447
+ ];
3448
+ }
3449
+ function printBox(b) {
3450
+ for (const line of allLines(b)) stdout.write(line + "\n");
3451
+ }
3452
+ const animTypewriter = async (b) => {
3453
+ stdout.write(topBorder(b) + "\n");
3454
+ for (const line of b.textLines) {
3455
+ stdout.write(`${b.pal.base}│${R} `);
3456
+ for (let i = 0; i < line.length; i++) {
3457
+ const ch = line[i];
3458
+ stdout.write(`${b.pal.text}${ch}${b.pal.glow}█${R}`);
3459
+ await sleep(25);
3460
+ stdout.write("\b \b");
3461
+ }
3462
+ for (let blink = 0; blink < 2; blink++) {
3463
+ stdout.write(`${b.pal.glow}█${R}`);
3464
+ await sleep(100);
3465
+ stdout.write("\b \b");
3466
+ await sleep(100);
3467
+ }
3468
+ stdout.write(" ".repeat(b.innerWidth - 4 - line.length) + ` ${b.pal.base}│${R}\n`);
3469
+ }
3470
+ stdout.write(authorRow(b) + "\n");
3471
+ stdout.write(bottomBorder(b) + "\n");
3472
+ };
3473
+ const animWaveReveal = async (b) => {
3474
+ stdout.write(topBorder(b) + "\n");
3475
+ for (const line of b.textLines) {
3476
+ const half = Math.ceil(line.length / 2);
3477
+ stdout.write(contentRow(b, line.slice(0, half).padEnd(line.length), b.pal.base, b.pal.faint) + "\n");
3478
+ await sleep(130);
3479
+ stdout.write(up(1) + CLR + contentRow(b, line) + "\n");
3480
+ await sleep(60);
3481
+ }
3482
+ stdout.write(authorRow(b) + "\n");
3483
+ stdout.write(bottomBorder(b) + "\n");
3484
+ };
3485
+ const animSlide = async (b) => {
3486
+ const lines = allLines(b);
3487
+ for (let i = 0; i < lines.length; i++) stdout.write("\n");
3488
+ stdout.write(up(lines.length));
3489
+ for (let step = 0; step < 5; step++) {
3490
+ const pad = " ".repeat(Math.floor(12 * (1 - step / 4)));
3491
+ for (const line of lines) stdout.write(CLR + pad + line + "\n");
3492
+ if (step < 4) {
3493
+ stdout.write(up(lines.length));
3494
+ await sleep(80);
3495
+ }
3496
+ }
3497
+ };
3498
+ const animPulseBorder = async (b) => {
3499
+ const stages = [
3500
+ allLines(b, b.pal.faint),
3501
+ allLines(b, b.pal.base),
3502
+ allLines(b, b.pal.glow),
3503
+ allLines(b, b.pal.base)
3504
+ ];
3505
+ for (let s = 0; s < stages.length; s++) {
3506
+ if (s > 0) stdout.write(up(stages[0].length));
3507
+ for (const line of stages[s]) stdout.write((s > 0 ? CLR : "") + line + "\n");
3508
+ if (s < stages.length - 1) await sleep(180);
3509
+ }
3510
+ };
3511
+ const animGlitch = async (b) => {
3512
+ const noiseRates = [
3513
+ .25,
3514
+ .12,
3515
+ .04,
3516
+ 0
3517
+ ];
3518
+ const totalLines = b.textLines.length + 3;
3519
+ for (let f = 0; f < noiseRates.length; f++) {
3520
+ if (f > 0) stdout.write(up(totalLines));
3521
+ const rate = noiseRates[f];
3522
+ const clr = f > 0 ? CLR : "";
3523
+ const jitter = rate > 0 ? Math.floor(Math.random() * 3) : 0;
3524
+ const pad = " ".repeat(jitter);
3525
+ stdout.write(clr + pad + topBorder(b) + "\n");
3526
+ for (const line of b.textLines) if (rate === 0) stdout.write(clr + contentRow(b, line) + "\n");
3527
+ else {
3528
+ let inner = "";
3529
+ for (const ch of line) if (Math.random() < rate) {
3530
+ const charColor = Math.random() < .3 ? b.pal.glow : b.pal.base;
3531
+ inner += `${charColor}${GLITCH_CHARS[Math.floor(Math.random() * 12)]}${b.pal.text}`;
3532
+ } else inner += ch;
3533
+ const fill = " ".repeat(b.innerWidth - 4 - line.length);
3534
+ stdout.write(clr + pad + `${b.pal.base}│${R} ${b.pal.text}${inner}${R}${fill} ${b.pal.base}│${R}\n`);
3535
+ }
3536
+ stdout.write(clr + pad + authorRow(b) + "\n");
3537
+ stdout.write(clr + pad + bottomBorder(b) + "\n");
3538
+ if (f < noiseRates.length - 1) await sleep(120);
3539
+ }
3540
+ };
3541
+ const animMatrixRain = async (b) => {
3542
+ const width = b.innerWidth + 2;
3543
+ const RAIN_HEIGHT = 6;
3544
+ const streams = Array.from({ length: RAIN_HEIGHT }, () => Array(width).fill(" "));
3545
+ const chars = "日ハミヒーウシナモニサワツオリアホマケメエカキムユラセネスタヌヘ0123456789";
3546
+ for (let frame = 0; frame < 9; frame++) {
3547
+ for (let y = RAIN_HEIGHT - 1; y > 0; y--) streams[y] = [...streams[y - 1]];
3548
+ streams[0] = Array(width).fill(" ").map(() => {
3549
+ if (Math.random() < .25) return { char: chars[Math.floor(Math.random() * 42)] };
3550
+ return " ";
3551
+ });
3552
+ if (frame > 0) stdout.write(up(RAIN_HEIGHT));
3553
+ for (let y = 0; y < RAIN_HEIGHT; y++) {
3554
+ let line = "";
3555
+ for (let x = 0; x < width; x++) {
3556
+ const cell = streams[y][x];
3557
+ if (cell === " ") line += " ";
3558
+ else {
3559
+ const typedCell = cell;
3560
+ const color = y === 0 ? b.pal.glow : y <= 2 ? b.pal.text : y <= 4 ? b.pal.base : b.pal.faint;
3561
+ line += `${color}${typedCell.char}${R}`;
3562
+ }
3563
+ }
3564
+ stdout.write((frame > 0 ? CLR : "") + line + "\n");
3565
+ }
3566
+ await sleep(90);
3567
+ }
3568
+ stdout.write(up(RAIN_HEIGHT));
3569
+ for (let y = 0; y < RAIN_HEIGHT; y++) stdout.write(CLR + "\n");
3570
+ stdout.write(up(RAIN_HEIGHT));
3571
+ printBox(b);
3572
+ };
3573
+ const ANIMS = [
3574
+ animTypewriter,
3575
+ animWaveReveal,
3576
+ animSlide,
3577
+ animPulseBorder,
3578
+ animGlitch,
3579
+ animMatrixRain
3580
+ ];
3581
+ function playStartupDisplay() {
3582
+ if (!stdout.isTTY) return Promise.resolve();
3583
+ return (async () => {
3584
+ try {
3585
+ const quote = QUOTES[Math.floor(Math.random() * QUOTES.length)];
3586
+ const pal = PALETTES[Math.floor(Math.random() * PALETTES.length)];
3587
+ const b = makeBox(quote, pal);
3588
+ const anim = ANIMS[Math.floor(Math.random() * ANIMS.length)];
3589
+ stdout.write("\n" + HIDE);
3590
+ await anim(b);
3591
+ stdout.write(SHOW);
3592
+ } catch {
3593
+ try {
3594
+ stdout.write(SHOW);
3595
+ } catch {}
3596
+ }
3597
+ })();
3598
+ }
3599
+ //#endregion
3600
+ //#region src/lib/find-tui-binary.ts
3601
+ /**
3602
+ * Locate the `diffing-tui` native binary. Looks, in order:
3603
+ * 1. Sibling of the calling module (`dist/diffing-tui[.exe]` after build).
3604
+ * 2. `bin/diffing-tui[.exe]` next to the package root.
3605
+ * 3. `target/release/diffing-tui[.exe]` next to the package root
3606
+ * (cargo release build).
3607
+ * 4. `target/debug/diffing-tui[.exe]` next to the package root
3608
+ * (cargo debug build — the common case during development, especially
3609
+ * on Windows where release builds are slow).
3610
+ * 5. `$PATH` lookup via `which` / `where`.
3611
+ *
3612
+ * Returns the absolute path of the first match, or `null` if none are found.
3613
+ *
3614
+ * `callerUrl` is the `import.meta.url` of the caller — pass `import.meta.url`
3615
+ * from `cli.ts`. Exposed as a parameter so unit tests can pin the search
3616
+ * root to a known location instead of depending on the test runner's CWD.
3617
+ */
3618
+ function findTuiBinary$1(callerUrl) {
3619
+ const ext = process.platform === "win32" ? ".exe" : "";
3620
+ const here = dirname(fileURLToPath(callerUrl));
3621
+ const candidates = [
3622
+ resolve(here, `diffing-tui${ext}`),
3623
+ resolve(here, "..", "bin", `diffing-tui${ext}`),
3624
+ resolve(here, "..", "target", "release", `diffing-tui${ext}`),
3625
+ resolve(here, "..", "..", "target", "release", `diffing-tui${ext}`),
3626
+ resolve(here, "..", "target", "debug", `diffing-tui${ext}`),
3627
+ resolve(here, "..", "..", "target", "debug", `diffing-tui${ext}`)
3628
+ ];
3629
+ for (const c of candidates) if (existsSync(c)) return c;
3630
+ try {
3631
+ const out = execFileSync(process.platform === "win32" ? "where" : "which", ["diffing-tui"], {
3632
+ encoding: "utf-8",
3633
+ stdio: [
3634
+ "ignore",
3635
+ "pipe",
3636
+ "ignore"
3637
+ ]
3638
+ }).trim();
3639
+ if (out) {
3640
+ const first = out.split(/\r?\n/)[0]?.trim();
3641
+ if (first && isAbsolute(first)) return first;
3642
+ }
3643
+ } catch {}
3644
+ return null;
3645
+ }
3646
+ //#endregion
2229
3647
  //#region src/cli.ts
2230
3648
  const args = process.argv.slice(2);
3649
+ let prRef = null;
3650
+ let ghPrConsumed = 0;
3651
+ if (args[0]?.startsWith("gh pr ") === true) {
3652
+ const rest = args[0].slice(6).trim();
3653
+ if (rest) {
3654
+ prRef = rest;
3655
+ ghPrConsumed = 1;
3656
+ }
3657
+ } else if (args[0] === "gh" && args[1] === "pr" && args[2] !== void 0) {
3658
+ prRef = args[2];
3659
+ ghPrConsumed = 3;
3660
+ }
3661
+ if (ghPrConsumed > 0) args.splice(0, ghPrConsumed);
2231
3662
  if (new Set([
2232
3663
  "await-review",
2233
3664
  "reply",
@@ -2235,17 +3666,31 @@ if (new Set([
2235
3666
  "comments",
2236
3667
  "url",
2237
3668
  "mcp",
2238
- "plan"
3669
+ "plan",
3670
+ "update",
3671
+ "gh"
2239
3672
  ]).has(args[0])) {
2240
3673
  if (args[0] === "mcp") {
2241
- const { startMcpServer } = await import("./mcp-DKlThflu.mjs");
3674
+ const { startMcpServer } = await import("./mcp-BWeWR4yd.mjs");
2242
3675
  await startMcpServer();
2243
3676
  await new Promise(() => {});
2244
3677
  }
2245
- const { runSubcommand } = await import("./cli-agent-Vbs-ePHv.mjs");
3678
+ const { runSubcommand } = await import("./cli-agent-KxNAFIPf.mjs");
2246
3679
  process.exit(await runSubcommand(args[0], args.slice(1)));
2247
3680
  }
3681
+ let showSubcommand = false;
3682
+ if (args[0] === "show") {
3683
+ showSubcommand = true;
3684
+ args.shift();
3685
+ }
2248
3686
  const opts = parseDiffOptions(args);
3687
+ if (showSubcommand) {
3688
+ if (opts.revisions.length === 0 && !opts.help && !opts.version) {
3689
+ console.error("Usage: diffing show <revspec>... [-- <pathspec>...]");
3690
+ process.exit(5);
3691
+ }
3692
+ intoShowMode(opts);
3693
+ }
2249
3694
  if (opts.help) {
2250
3695
  printHelp();
2251
3696
  process.exit(0);
@@ -2261,19 +3706,33 @@ if (envErr) {
2261
3706
  console.error(envErr);
2262
3707
  process.exit(1);
2263
3708
  }
3709
+ if (opts.outputMode === "tui") {
3710
+ const tuiResult = await launchTui(args);
3711
+ process.exit(tuiResult);
3712
+ }
2264
3713
  if (opts.outputMode === "terminal") {
2265
3714
  const exitCode = runTerminalDiff(opts);
2266
3715
  process.exit(exitCode);
2267
3716
  }
3717
+ const __pkgDir = dirname(fileURLToPath(import.meta.url));
3718
+ const currentVersion = JSON.parse(readFileSync(resolve(__pkgDir, "..", "package.json"), "utf-8")).version;
3719
+ const updateCheckPromise = (async () => {
3720
+ try {
3721
+ const { checkForUpdates } = await import("./update-check-DAPFCqpF.mjs");
3722
+ return await checkForUpdates(currentVersion);
3723
+ } catch {
3724
+ return null;
3725
+ }
3726
+ })();
2268
3727
  const port = await getPort(opts.port ? { port: opts.port } : void 0);
2269
3728
  const host = opts.host;
2270
- const clientDir = resolve(dirname(fileURLToPath(import.meta.url)), "client");
2271
- const { existsSync: existsSync$1 } = await import("node:fs");
2272
- const { port: actualPort } = await startServer({
3729
+ const clientDir = resolve(__pkgDir, "client");
3730
+ const { port: actualPort, prMode } = await startServer({
2273
3731
  port,
2274
3732
  host,
2275
- clientDir: existsSync$1(clientDir) ? clientDir : resolve(process.cwd(), "dist/client"),
2276
- diffOpts: opts
3733
+ clientDir: existsSync(clientDir) ? clientDir : resolve(process.cwd(), "dist/client"),
3734
+ diffOpts: opts,
3735
+ prRef: prRef ?? void 0
2277
3736
  });
2278
3737
  const localUrl = `http://${host}:${actualPort}`;
2279
3738
  try {
@@ -2283,18 +3742,26 @@ try {
2283
3742
  } catch {
2284
3743
  repoRoot = process.cwd();
2285
3744
  }
2286
- const __pkgDir = dirname(fileURLToPath(import.meta.url));
2287
- const version = JSON.parse(readFileSync(resolve(__pkgDir, "..", "package.json"), "utf-8")).version;
2288
3745
  writeServerLock({
2289
3746
  port: actualPort,
2290
3747
  host,
2291
3748
  pid: process.pid,
2292
3749
  repoRoot,
2293
3750
  startedAt: Date.now(),
2294
- version
3751
+ version: currentVersion,
3752
+ mode: prMode ? "gh-pr" : "web",
3753
+ prRef: prMode ? prRef ?? void 0 : void 0
2295
3754
  });
2296
3755
  } catch {}
2297
3756
  console.log(`diffing server running at ${localUrl}`);
3757
+ await playStartupDisplay();
3758
+ try {
3759
+ const updateInfo = await updateCheckPromise;
3760
+ if (updateInfo?.hasUpdate) {
3761
+ const { printUpdateDisclaimer } = await import("./update-check-DAPFCqpF.mjs");
3762
+ printUpdateDisclaimer(currentVersion, updateInfo.latestVersion);
3763
+ }
3764
+ } catch {}
2298
3765
  if (!opts.noOpen) {
2299
3766
  const settings = loadSettings();
2300
3767
  const openUrl = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${actualPort}`;
@@ -2311,5 +3778,64 @@ const shutdown = () => {
2311
3778
  };
2312
3779
  process.on("SIGINT", shutdown);
2313
3780
  process.on("SIGTERM", shutdown);
3781
+ /**
3782
+ * Wrapper around `findTuiBinary` that passes this script's `import.meta.url`
3783
+ * so the search paths anchor to the bundled CLI's directory. The real
3784
+ * implementation lives in `lib/find-tui-binary.ts` and is unit-tested there.
3785
+ */
3786
+ function findTuiBinary() {
3787
+ return findTuiBinary$1(import.meta.url);
3788
+ }
3789
+ /**
3790
+ * Fall back to default `git diff` output when the TUI cannot run.
3791
+ * Re-parses `args` so the terminal output exactly matches `diffing` (no flag)
3792
+ * in a non-TTY context.
3793
+ */
3794
+ function runTerminalFallback(args) {
3795
+ const terminalOpts = parseDiffOptions(args.filter((a) => a !== "--tui"));
3796
+ terminalOpts.outputMode = "terminal";
3797
+ terminalOpts.tui = false;
3798
+ return runTerminalDiff(terminalOpts);
3799
+ }
3800
+ /**
3801
+ * Launch the native-Rust TUI binary as a child process. Returns the process
3802
+ * exit code. If the TUI cannot start (no TTY, missing binary), prints a
3803
+ * single stderr line and falls back to the default `git diff` output.
3804
+ */
3805
+ async function launchTui(args) {
3806
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
3807
+ console.error("diffing --tui requires a TTY; falling back to git diff");
3808
+ return runTerminalFallback(args);
3809
+ }
3810
+ const bin = findTuiBinary();
3811
+ if (!bin) {
3812
+ console.error("diffing-tui binary not found; build it with `pnpm build:tui`; falling back to git diff");
3813
+ return runTerminalFallback(args);
3814
+ }
3815
+ const forwarded = args.filter((a) => a !== "--tui");
3816
+ let repoRoot;
3817
+ try {
3818
+ repoRoot = getRepoRoot();
3819
+ } catch {
3820
+ repoRoot = process.cwd();
3821
+ }
3822
+ console.log(`diffing TUI starting (binary: ${bin})`);
3823
+ await playStartupDisplay();
3824
+ return new Promise((resolveP) => {
3825
+ const child = spawn(bin, [
3826
+ "--repo",
3827
+ repoRoot,
3828
+ ...forwarded
3829
+ ], {
3830
+ stdio: "inherit",
3831
+ env: process.env
3832
+ });
3833
+ child.on("exit", (code) => resolveP(code ?? 0));
3834
+ child.on("error", (err) => {
3835
+ console.error(`diffing-tui failed to start: ${err.message}; falling back to git diff`);
3836
+ resolveP(runTerminalFallback(args));
3837
+ });
3838
+ });
3839
+ }
2314
3840
  //#endregion
2315
- export {};
3841
+ export { findTuiBinary };