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.
- package/README.md +185 -4
- package/dist/{cli-agent-Vbs-ePHv.mjs → cli-agent-KxNAFIPf.mjs} +8 -1
- package/dist/cli-gh-CDGSpndZ.mjs +147 -0
- package/dist/cli.mjs +1594 -68
- package/dist/client/assets/{angular-html-fdYbuxHF.js → angular-html-DbbrJavS.js} +1 -1
- package/dist/client/assets/{angular-ts-D3iFqR-R.js → angular-ts-tCrxb1ht.js} +1 -1
- package/dist/client/assets/{apl-MnBV-iQN.js → apl-DawII0w7.js} +1 -1
- package/dist/client/assets/arc-DIRV7ZXr.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-BM5HxebS.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-3IoH78OV.js +36 -0
- package/dist/client/assets/array-CkF66D10.js +1 -0
- package/dist/client/assets/{astro-BrpK01E7.js → astro-DIyGTFXc.js} +1 -1
- package/dist/client/assets/{blade-CGmyBuwD.js → blade-CuF4Z1mP.js} +1 -1
- package/dist/client/assets/blockDiagram-GPEHLZMM-CFxPndC0.js +132 -0
- package/dist/client/assets/{bsl-Dx5tFn3i.js → bsl-BrHd4RA2.js} +1 -1
- package/dist/client/assets/{c-xQnwtG9Y.js → c-DUrjo9T5.js} +1 -1
- package/dist/client/assets/c4Diagram-AAUBKEIU-CGMBnkiv.js +10 -0
- package/dist/client/assets/{cairo-wCCQTdA9.js → cairo-BDZ6TRZS.js} +1 -1
- package/dist/client/assets/channel-D5AdjEWX.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-CJMBLaXy.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-C9HQWVLl.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-Qqg72GQd.js +1 -0
- package/dist/client/assets/chunk-4EGX6M5U-C70lfA2p.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-DZF8brTR.js +1 -0
- package/dist/client/assets/chunk-5DO6E6H7-PprNUwlI.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-BflVFV5z.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-COn6BRLY.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-CNOjODUR.js +231 -0
- package/dist/client/assets/chunk-BR22UD5L-D8XCYMy9.js +1 -0
- package/dist/client/assets/chunk-BSJP7CBP-PQX4ASCI.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BfmUvL-L.js +124 -0
- package/dist/client/assets/chunk-FHYWG6QK-CcHPrHH1.js +1 -0
- package/dist/client/assets/chunk-FMBD7UC4-DorfwLnQ.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-CXY93mId.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-BjMrSVmH.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-q8jj6Nyb.js +2 -0
- package/dist/client/assets/chunk-MPE355IW-Bf9yHfDS.js +1 -0
- package/dist/client/assets/chunk-MZUSXYTE-CnUIUKI4.js +1 -0
- package/dist/client/assets/chunk-N66VUXT2-ahAf9GvV.js +1 -0
- package/dist/client/assets/chunk-ND2GUHAM-BRRQbYHl.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DHF3V75i.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-Czt1qHrO.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-DEopURak.js +70 -0
- package/dist/client/assets/chunk-PUPMXCY4-D0KKIhut.js +1 -0
- package/dist/client/assets/chunk-QZHKN3VN-BE917fn9.js +1 -0
- package/dist/client/assets/chunk-UIBZB4QT-TYsZ_c8u.js +1 -0
- package/dist/client/assets/chunk-WCWK7LTN-Dj8SfnMU.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-DdoWnPla.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-DocCoKUT.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-CxjhKO7s.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CSScAAQ4.js +1 -0
- package/dist/client/assets/{cobol-ypVzOIQu.js → cobol-BylylR2x.js} +1 -1
- package/dist/client/assets/{coffee-CrtE1uj2.js → coffee-DCKfk8xx.js} +1 -1
- package/dist/client/assets/cose-bilkent-S5V4N54A-Cm0BD5CS.js +1 -0
- package/dist/client/assets/{cpp-DLd4XEKA.js → cpp-CfKCIrGK.js} +1 -1
- package/dist/client/assets/{crystal-CJYvpLt_.js → crystal-v-9RIu3l.js} +1 -1
- package/dist/client/assets/{css-DrX2B9os.js → css-XjlXeGmZ.js} +1 -1
- package/dist/client/assets/cytoscape.esm-B7MQoI9k.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-kqgFUaF1.js +4 -0
- package/dist/client/assets/dagre-DAhKikc0.js +1 -0
- package/dist/client/assets/defaultLocale-B_TxGd0k.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-B3hBvGUc.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-r8zU8C-Y.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-CDWD5zkZ.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CRRfS6RD.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-ChBS6E-K.js +24 -0
- package/dist/client/assets/dist-DUTeKbN7.js +1 -0
- package/dist/client/assets/{edge-BsQYk9OW.js → edge-Cwh953i8.js} +1 -1
- package/dist/client/assets/{elixir-DU_VfGYR.js → elixir-C4MgLzTj.js} +1 -1
- package/dist/client/assets/{elm-W4eOLEDv.js → elm-CFBu8r7i.js} +1 -1
- package/dist/client/assets/erDiagram-TEJ5UH35-_03Fb9RH.js +85 -0
- package/dist/client/assets/{erb-D9FdIJ2e.js → erb-CrCna7R1.js} +1 -1
- package/dist/client/assets/{erlang-CtK0kQ6z.js → erlang-cuhwpmsu.js} +1 -1
- package/dist/client/assets/eventmodeling-FCH6USID-hlXE2E34.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-D9pSaH_8.js +162 -0
- package/dist/client/assets/{fortran-fixed-form-DBM4e0_k.js → fortran-fixed-form-DWr0EnJn.js} +1 -1
- package/dist/client/assets/{fsharp-CmzzGQLe.js → fsharp-CQ5iYSPA.js} +1 -1
- package/dist/client/assets/ganttDiagram-6RSMTGT7-DKBfXY9D.js +292 -0
- package/dist/client/assets/{gdresource-B_wbPqHd.js → gdresource-CN-lsNu5.js} +1 -1
- package/dist/client/assets/{git-commit-1z4D6NzL.js → git-commit-6e79eN7Q.js} +1 -1
- package/dist/client/assets/{git-rebase-BtPCXjbS.js → git-rebase-CExLk2dD.js} +1 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-D_3ZhTam.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-BPQ2kpJA.js +106 -0
- package/dist/client/assets/{glimmer-js-BVfUGMUb.js → glimmer-js-wjBSKaMu.js} +1 -1
- package/dist/client/assets/{glimmer-ts-BZEaHDZL.js → glimmer-ts-CyjMXDgZ.js} +1 -1
- package/dist/client/assets/{glsl-CSiNDuwt.js → glsl-CWOoN8or.js} +1 -1
- package/dist/client/assets/graphlib-DBdw4opr.js +1 -0
- package/dist/client/assets/{graphql-Crf08Rhb.js → graphql-DI5CB58U.js} +1 -1
- package/dist/client/assets/{hack-DUiQuO20.js → hack-Ct_Cm7Pr.js} +1 -1
- package/dist/client/assets/{haml-BtvVTDRG.js → haml-DzAjli8d.js} +1 -1
- package/dist/client/assets/{handlebars-Bu_cdZA_.js → handlebars-BfJB0RYx.js} +1 -1
- package/dist/client/assets/{html-C4cun3zD.js → html-CK8auhK8.js} +1 -1
- package/dist/client/assets/{html-derivative-CQnD80KV.js → html-derivative-B7VntYnF.js} +1 -1
- package/dist/client/assets/{http-DuOJF9Kg.js → http-_Zy71Sv3.js} +1 -1
- package/dist/client/assets/{hurl-DgrdYsYh.js → hurl-VbL4I95z.js} +1 -1
- package/dist/client/assets/{hxml-DC4KwKt2.js → hxml-nwJci6H5.js} +1 -1
- package/dist/client/assets/index-C3p7VKOX.css +1 -0
- package/dist/client/assets/index-Dv9jPmOc.js +3921 -0
- package/dist/client/assets/info-J43DQDTF-CAllC7mK.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-B5RN-4Ke.js +2 -0
- package/dist/client/assets/init-Ce66tIvf.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DpbSX3Zy.js +70 -0
- package/dist/client/assets/{java-D-vtFiv8.js → java-0CC2DnK9.js} +1 -1
- package/dist/client/assets/{javascript-CFYFXvdN.js → javascript-DKSTP7rz.js} +1 -1
- package/dist/client/assets/{jinja-fZvIg2_s.js → jinja-DoGBHNJ6.js} +1 -1
- package/dist/client/assets/{jison-DEzxLq5g.js → jison-Bnl3lPA2.js} +1 -1
- package/dist/client/assets/journeyDiagram-JHISSGLW-B_X4_qPO.js +139 -0
- package/dist/client/assets/{json-QM4VBqMC.js → json-BQ3Ddj6g.js} +1 -1
- package/dist/client/assets/{jsx-DLzUM_cC.js → jsx-C_aFDuFZ.js} +1 -1
- package/dist/client/assets/{julia-Dwt1SIpn.js → julia-nIjp5yMR.js} +1 -1
- package/dist/client/assets/{just-Dwp-hLcL.js → just-zc4yJ46n.js} +1 -1
- package/dist/client/assets/kanban-definition-UN3LZRKU-BSs4C5DW.js +89 -0
- package/dist/client/assets/katex-eRFNlMh0.js +257 -0
- package/dist/client/assets/{latex-CMF1JViz.js → latex-BszBC80j.js} +1 -1
- package/dist/client/assets/line-C9HWVwPi.js +1 -0
- package/dist/client/assets/linear-DatJfpKY.js +1 -0
- package/dist/client/assets/{liquid-czkpOZWY.js → liquid-DglQpTge.js} +1 -1
- package/dist/client/assets/{lua-DE5PD0Vh.js → lua-BcbliFeR.js} +1 -1
- package/dist/client/assets/{marko-DHeEvZiW.js → marko-BPm4M8j-.js} +1 -1
- package/dist/client/assets/{mdc-B941liUQ.js → mdc-BNUj07UF.js} +1 -1
- package/dist/client/assets/mermaid-parser.core-BVEKlqQr.js +4 -0
- package/dist/client/assets/mermaid.core-DVis21V_.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-Cuo7KewF.js +96 -0
- package/dist/client/assets/{nextflow-CApkvBGW.js → nextflow-DSfydIxt.js} +1 -1
- package/dist/client/assets/{nginx-Pb9t8c-r.js → nginx-CSSJwkd6.js} +1 -1
- package/dist/client/assets/{nim-MMUvW_R1.js → nim-nQAskwTR.js} +1 -1
- package/dist/client/assets/ordinal-5ziJS8mo.js +1 -0
- package/dist/client/assets/packet-YPE3B663-Dy58IAiO.js +1 -0
- package/dist/client/assets/path-g3G46IZB.js +1 -0
- package/dist/client/assets/{perl-DXwXRmuC.js → perl-CtH8xYH6.js} +1 -1
- package/dist/client/assets/{php-D42kv59r.js → php-Dcc3T2PH.js} +1 -1
- package/dist/client/assets/pie-LRSECV5Y-FXFFfdz2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-BfpvCRmo.js +30 -0
- package/dist/client/assets/{pug-v0iesski.js → pug-BCLaymlV.js} +1 -1
- package/dist/client/assets/{qml-spxQQ-ei.js → qml-BpKydcOi.js} +1 -1
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-CJY6un0-.js +7 -0
- package/dist/client/assets/{r-CobWTBG9.js → r-BzCB9dpd.js} +1 -1
- package/dist/client/assets/radar-GUYGQ44K-BKTzSQ68.js +1 -0
- package/dist/client/assets/{razor-B-_Ww3MB.js → razor-BWmujqiQ.js} +1 -1
- package/dist/client/assets/{regexp-BJMVs8R0.js → regexp-urCI3YiZ.js} +1 -1
- package/dist/client/assets/requirementDiagram-4Y6WPE33-DIHWguS0.js +84 -0
- package/dist/client/assets/rough.esm-BBrzuQ0N.js +1 -0
- package/dist/client/assets/{rst-BHcaWsY0.js → rst-YTDC9H6u.js} +1 -1
- package/dist/client/assets/{ruby-BCMT6kog.js → ruby-v1IXQAFZ.js} +1 -1
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-DJRPEO_F.js +40 -0
- package/dist/client/assets/{sas-DHfZnd3R.js → sas-KDUG3BSk.js} +1 -1
- package/dist/client/assets/{scss-DjDsJvDO.js → scss-pLIGFpjD.js} +1 -1
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-DX0jVCOU.js +162 -0
- package/dist/client/assets/{shaderlab-DXAhMdPo.js → shaderlab-CbbQwRPF.js} +1 -1
- package/dist/client/assets/{shellscript-B6qaIiU-.js → shellscript-B5ZTyEsK.js} +1 -1
- package/dist/client/assets/{shellsession-ByCeir2b.js → shellsession-DVK3bw4p.js} +1 -1
- package/dist/client/assets/{soy-C6WpnMSF.js → soy-BXgyj2-T.js} +1 -1
- package/dist/client/assets/{sparql-BdcL8cbr.js → sparql-DEoxFJ5d.js} +1 -1
- package/dist/client/assets/{sql-BIvSNLV_.js → sql-G2vZCmw6.js} +1 -1
- package/dist/client/assets/src-D9zkqEEX.js +1 -0
- package/dist/client/assets/{stata-PG9iZ--I.js → stata-C4mEnygx.js} +1 -1
- package/dist/client/assets/stateDiagram-AJRCARHV-AMtMxKgQ.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C89CM-_4.js +1 -0
- package/dist/client/assets/{surrealql-CmvCzG09.js → surrealql-Bf8gsTcP.js} +1 -1
- package/dist/client/assets/{svelte-ChumG88s.js → svelte-C1oIyl9t.js} +1 -1
- package/dist/client/assets/{templ-CmzuRY_a.js → templ-PJcWUuTF.js} +1 -1
- package/dist/client/assets/{tex-DL8t-_eJ.js → tex-B7Fnujsi.js} +1 -1
- package/dist/client/assets/timeline-definition-PNZ67QCA-BZJSqcAg.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DtjHwb6x.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-GqTXzMFU.js +1 -0
- package/dist/client/assets/{ts-tags-BFfE0Egm.js → ts-tags-CAAsFnUt.js} +1 -1
- package/dist/client/assets/{tsx-C1bFyHcE.js → tsx-DAKyTsWW.js} +1 -1
- package/dist/client/assets/{twig-DkIluZEQ.js → twig-Bd4dieZF.js} +1 -1
- package/dist/client/assets/{typescript-Gc86Bc3j.js → typescript-8myByDGd.js} +1 -1
- package/dist/client/assets/vennDiagram-CIIHVFJN-CMjBZNs2.js +34 -0
- package/dist/client/assets/{vue-C88JmL53.js → vue-fBA2B-QU.js} +1 -1
- package/dist/client/assets/{vue-html-Cry4aksC.js → vue-html-DEZECpsR.js} +1 -1
- package/dist/client/assets/{vue-vine-zlDkA3Ui.js → vue-vine-B_gG939R.js} +1 -1
- package/dist/client/assets/wardley-L42UT6IY-pROVAAhW.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-qeHmPLyL.js +78 -0
- package/dist/client/assets/{xml-NvwpIFDy.js → xml-DbXNss3s.js} +1 -1
- package/dist/client/assets/{xsl-CDjXvY9w.js → xsl-B7_dgqqc.js} +1 -1
- package/dist/client/assets/xychartDiagram-2RQKCTM6-NN2Et7YL.js +7 -0
- package/dist/client/assets/{yaml-Bh-LPjcH.js → yaml-DSyIApdJ.js} +1 -1
- package/dist/client/index.html +2 -5
- package/dist/{mcp-DKlThflu.mjs → mcp-BWeWR4yd.mjs} +3 -1
- package/dist/plan-format-f62JWFI-.mjs +117 -0
- package/dist/{server-lock-CAdqFSfl.mjs → server-lock-Dj9fG9md.mjs} +279 -132
- package/dist/update-check-DAPFCqpF.mjs +129 -0
- package/package.json +15 -5
- package/dist/cli-agent-Cfuy9fLC.mjs +0 -179
- package/dist/cli-agent-CtNivjQW.mjs +0 -179
- package/dist/cli-agent-DZBvZoXZ.mjs +0 -179
- package/dist/cli-agent-WzJPMH52.mjs +0 -174
- package/dist/cli-agent-uYUoCA4X.mjs +0 -179
- package/dist/client/assets/index-CZZd7iTh.js +0 -3887
- package/dist/client/assets/index-CkC-EvWo.css +0 -1
- package/dist/mcp-BQmH_By-.mjs +0 -119
- package/dist/mcp-CLgzC8_a.mjs +0 -119
- package/dist/mcp-D9E2HQZ9.mjs +0 -119
- package/dist/mcp-LVbLV17O.mjs +0 -119
- package/dist/mcp-nMwTAut3.mjs +0 -119
- package/dist/server-lock-BbiF7u3F.mjs +0 -493
- package/dist/server-lock-Bx9ixiF6.mjs +0 -575
- package/dist/server-lock-DIkImokU.mjs +0 -487
- package/dist/server-lock-DbxpfF3j.mjs +0 -575
- /package/dist/client/assets/{abap-CkQ0tsz-.js → abap-cWYhO34H.js} +0 -0
- /package/dist/client/assets/{actionscript-3-DHm0ZfF4.js → actionscript-3-B80ndJsD.js} +0 -0
- /package/dist/client/assets/{ada-DK_ua92Z.js → ada-C0C0SoYB.js} +0 -0
- /package/dist/client/assets/{andromeeda-D2ccw8v4.js → andromeeda-Ch2kWS7s.js} +0 -0
- /package/dist/client/assets/{apache-DXtpFu7o.js → apache-DBwBm4bB.js} +0 -0
- /package/dist/client/assets/{apex-DlnRRWpl.js → apex-FcV7UxQi.js} +0 -0
- /package/dist/client/assets/{applescript-CShk62ad.js → applescript-hQs8VNpS.js} +0 -0
- /package/dist/client/assets/{ara-CWN-FDkm.js → ara-BOadBOZg.js} +0 -0
- /package/dist/client/assets/{asciidoc-Bg-PM9Vn.js → asciidoc-BsfqiBck.js} +0 -0
- /package/dist/client/assets/{asm-DKOhR24S.js → asm-C2vUqzqz.js} +0 -0
- /package/dist/client/assets/{aurora-x-DpaEirL8.js → aurora-x-BHLqzt7j.js} +0 -0
- /package/dist/client/assets/{awk-3ibKHffv.js → awk-D4e-eeZT.js} +0 -0
- /package/dist/client/assets/{ayu-dark-ursiSH5k.js → ayu-dark-DCotN_ZT.js} +0 -0
- /package/dist/client/assets/{ayu-light-DMjlrKmx.js → ayu-light-CKB6o_im.js} +0 -0
- /package/dist/client/assets/{ayu-mirage-DZEMb75h.js → ayu-mirage-B38lW-C5.js} +0 -0
- /package/dist/client/assets/{ballerina-xx7VNGxs.js → ballerina-BzvFqbha.js} +0 -0
- /package/dist/client/assets/{bat-DGYsZ5wR.js → bat-CFyw2ERn.js} +0 -0
- /package/dist/client/assets/{beancount-D_ATR1UV.js → beancount-a6Gsu5WX.js} +0 -0
- /package/dist/client/assets/{berry-BqYMORfH.js → berry-C2jyz4Ah.js} +0 -0
- /package/dist/client/assets/{bibtex-CQTBDajW.js → bibtex-Dyet-B83.js} +0 -0
- /package/dist/client/assets/{bicep-CfNtxPrK.js → bicep-CghvUIvy.js} +0 -0
- /package/dist/client/assets/{bird2-C0dUPe3I.js → bird2-nHigvs-R.js} +0 -0
- /package/dist/client/assets/{c3-BUwZ5e3k.js → c3-9ZrcmAmr.js} +0 -0
- /package/dist/client/assets/{cadence-C3OKDYrr.js → cadence-BPDgYNWy.js} +0 -0
- /package/dist/client/assets/{catppuccin-frappe-CMnRXVZP.js → catppuccin-frappe-BY_2ofjk.js} +0 -0
- /package/dist/client/assets/{catppuccin-latte-CqhXSjCC.js → catppuccin-latte-D6GXPBmA.js} +0 -0
- /package/dist/client/assets/{catppuccin-macchiato-B6kneE0C.js → catppuccin-macchiato-BuW8_cyo.js} +0 -0
- /package/dist/client/assets/{catppuccin-mocha-CP8YZpvj.js → catppuccin-mocha-znex9DPe.js} +0 -0
- /package/dist/client/assets/{clarity-Crxe8gSr.js → clarity-Cmao5KSK.js} +0 -0
- /package/dist/client/assets/{clojure-BuzOUPMr.js → clojure-D2dPuTkk.js} +0 -0
- /package/dist/client/assets/{cmake-CCpZLdLI.js → cmake-DaqQuj4A.js} +0 -0
- /package/dist/client/assets/{codeowners-Bo1VYpHz.js → codeowners-B748jmvc.js} +0 -0
- /package/dist/client/assets/{codeql-Db01hzHE.js → codeql-CXb_8gHR.js} +0 -0
- /package/dist/client/assets/{common-lisp-CMlJIc6Y.js → common-lisp-UZn9nc_5.js} +0 -0
- /package/dist/client/assets/{coq-BlzBYp3f.js → coq-DtO6yBIf.js} +0 -0
- /package/dist/client/assets/{csharp-kfNshAa7.js → csharp-C3YlYcLV.js} +0 -0
- /package/dist/client/assets/{csv-Dr8ri5Nd.js → csv-BBwiVr0X.js} +0 -0
- /package/dist/client/assets/{cue-jXfhyOwY.js → cue-ScBFXY5f.js} +0 -0
- /package/dist/client/assets/{cypher-CayLeiaR.js → cypher-CGStaC1X.js} +0 -0
- /package/dist/client/assets/{d-BMZQ0mAy.js → d-CuTCIvnX.js} +0 -0
- /package/dist/client/assets/{dark-plus-B__q47Nr.js → dark-plus-BvePH-r3.js} +0 -0
- /package/dist/client/assets/{dart-gMOEMyuz.js → dart-CqEodbm2.js} +0 -0
- /package/dist/client/assets/{dax-C3t5LadU.js → dax-UdE5S0Dh.js} +0 -0
- /package/dist/client/assets/{desktop-DmhXLwLR.js → desktop-DfmsfHJA.js} +0 -0
- /package/dist/client/assets/{diff-togOXQX6.js → diff-CW0RWGd9.js} +0 -0
- /package/dist/client/assets/{docker--6N01olR.js → docker-B1qtW4ha.js} +0 -0
- /package/dist/client/assets/{dotenv-Bq930Yx8.js → dotenv-BUzOh1k8.js} +0 -0
- /package/dist/client/assets/{dracula-CBkSghcw.js → dracula-kJQjC_Gy.js} +0 -0
- /package/dist/client/assets/{dracula-soft-CCSYeoxL.js → dracula-soft-B3XhEjCl.js} +0 -0
- /package/dist/client/assets/{dream-maker-BiMf3DHl.js → dream-maker-CBvk18nW.js} +0 -0
- /package/dist/client/assets/{emacs-lisp-Dhht7he2.js → emacs-lisp-DvSgLhDc.js} +0 -0
- /package/dist/client/assets/{everforest-dark-NqSz4oHS.js → everforest-dark-u0zLn75q.js} +0 -0
- /package/dist/client/assets/{everforest-light-a3Yzs_lT.js → everforest-light-D3syIoAR.js} +0 -0
- /package/dist/client/assets/{fennel-BExc9TZf.js → fennel-C-1kWlGf.js} +0 -0
- /package/dist/client/assets/{fish-C2p-AJmZ.js → fish-CCpdtwqS.js} +0 -0
- /package/dist/client/assets/{fluent-CeYzs8Qy.js → fluent-BLg032oD.js} +0 -0
- /package/dist/client/assets/{fortran-free-form-BIQEK2Jw.js → fortran-free-form-Br4jRtSc.js} +0 -0
- /package/dist/client/assets/{gdscript-B5JQPh_-.js → gdscript-hSAZM1eh.js} +0 -0
- /package/dist/client/assets/{gdshader-vVjJ_Qr8.js → gdshader-D4XNAEs9.js} +0 -0
- /package/dist/client/assets/{genie-DGl5rqC8.js → genie-DmN-ICAN.js} +0 -0
- /package/dist/client/assets/{gherkin-CY_VLUnD.js → gherkin-cuHHmRcX.js} +0 -0
- /package/dist/client/assets/{github-dark-ClO4AStc.js → github-dark-CEBLh0--.js} +0 -0
- /package/dist/client/assets/{github-dark-default-CM0dPco8.js → github-dark-default-Dq0Z4Xai.js} +0 -0
- /package/dist/client/assets/{github-dark-dimmed-Cb6xxpXY.js → github-dark-dimmed-D90YmS4w.js} +0 -0
- /package/dist/client/assets/{github-dark-high-contrast-BpYtx2-M.js → github-dark-high-contrast-B6BWv1iC.js} +0 -0
- /package/dist/client/assets/{github-light-default-BmnCWL6x.js → github-light-default-DsPWmj4v.js} +0 -0
- /package/dist/client/assets/{github-light-high-contrast-rpagNF5O.js → github-light-high-contrast-C7aLb3kp.js} +0 -0
- /package/dist/client/assets/{github-light-CeOVFgf_.js → github-light-lXSVSq5T.js} +0 -0
- /package/dist/client/assets/{gleam-BarIG9kQ.js → gleam-D8VhqPDN.js} +0 -0
- /package/dist/client/assets/{gn-DhYsTPYc.js → gn-BjGRlX27.js} +0 -0
- /package/dist/client/assets/{gnuplot-CucLlTBc.js → gnuplot-BZCXuE4q.js} +0 -0
- /package/dist/client/assets/{go-toi-uWvg.js → go-BJpWXZEs.js} +0 -0
- /package/dist/client/assets/{groovy-Ds4dis_k.js → groovy-D9w3Dn-z.js} +0 -0
- /package/dist/client/assets/{gruvbox-dark-hard-aPiW8pQh.js → gruvbox-dark-hard-_deksD0U.js} +0 -0
- /package/dist/client/assets/{gruvbox-dark-medium-2HJCvzsS.js → gruvbox-dark-medium-CT31284j.js} +0 -0
- /package/dist/client/assets/{gruvbox-dark-soft-Da97ODX9.js → gruvbox-dark-soft-BZuEUCbj.js} +0 -0
- /package/dist/client/assets/{gruvbox-light-hard-BXlEipFe.js → gruvbox-light-hard-dxv4n_ZS.js} +0 -0
- /package/dist/client/assets/{gruvbox-light-medium-Cun-GjGk.js → gruvbox-light-medium-BsW_fVfR.js} +0 -0
- /package/dist/client/assets/{gruvbox-light-soft-Bd3qiiSw.js → gruvbox-light-soft-BiGy99gD.js} +0 -0
- /package/dist/client/assets/{haskell-B_jvz5Po.js → haskell-DqDdKEgk.js} +0 -0
- /package/dist/client/assets/{haxe-BRbuo5UO.js → haxe-DMg0jS3a.js} +0 -0
- /package/dist/client/assets/{hcl-BzNUCtsK.js → hcl-SZIZUK_X.js} +0 -0
- /package/dist/client/assets/{hjson-BxVo3sS2.js → hjson-DF1VspiE.js} +0 -0
- /package/dist/client/assets/{hlsl-C8Wes1nf.js → hlsl-DPe9L0Dt.js} +0 -0
- /package/dist/client/assets/{horizon-D54PjLAY.js → horizon-Ngud75Kn.js} +0 -0
- /package/dist/client/assets/{horizon-bright-D9RccdIr.js → horizon-bright-Cnljd0XD.js} +0 -0
- /package/dist/client/assets/{houston-CMu1N_4K.js → houston-BenZxlMr.js} +0 -0
- /package/dist/client/assets/{hy-dMrJKKU_.js → hy-B0f-B1mH.js} +0 -0
- /package/dist/client/assets/{imba-DG_oO8tg.js → imba-Bq3j1cW5.js} +0 -0
- /package/dist/client/assets/{ini-geHw79Aj.js → ini-BayloP2U.js} +0 -0
- /package/dist/client/assets/{json5-CDUsNqz6.js → json5-CRSfdIrY.js} +0 -0
- /package/dist/client/assets/{jsonc-D48xcwyD.js → jsonc-y4g9DQPx.js} +0 -0
- /package/dist/client/assets/{jsonl-cpIYDXEe.js → jsonl-D2paVn9r.js} +0 -0
- /package/dist/client/assets/{jsonnet-Da1lhszN.js → jsonnet-CLH27mtz.js} +0 -0
- /package/dist/client/assets/{jssm-B8BLrEGL.js → jssm-CPPYaqX-.js} +0 -0
- /package/dist/client/assets/{kanagawa-dragon-2Sk8Rx83.js → kanagawa-dragon-Bt5mhLu1.js} +0 -0
- /package/dist/client/assets/{kanagawa-lotus-CQVSWfJp.js → kanagawa-lotus-FGA3pRJk.js} +0 -0
- /package/dist/client/assets/{kanagawa-wave-BKWNioLX.js → kanagawa-wave-DnLVZPAD.js} +0 -0
- /package/dist/client/assets/{kdl-B-2tSRmp.js → kdl-D1BQjjnw.js} +0 -0
- /package/dist/client/assets/{kotlin-DepsEzh3.js → kotlin-ndCaDnf0.js} +0 -0
- /package/dist/client/assets/{kusto-BcCtXIb8.js → kusto-D-4nbkD3.js} +0 -0
- /package/dist/client/assets/{laserwave-BUqkO46F.js → laserwave-D2qAZkaG.js} +0 -0
- /package/dist/client/assets/{lean-CNO9ffpX.js → lean-YmtAsB0p.js} +0 -0
- /package/dist/client/assets/{less-Dj0T9pWC.js → less-DyHub3ow.js} +0 -0
- /package/dist/client/assets/{light-plus-C9Hnuc1T.js → light-plus-CP13QUvm.js} +0 -0
- /package/dist/client/assets/{llvm-DbV9b66l.js → llvm-DCcqrY0D.js} +0 -0
- /package/dist/client/assets/{log-BMblmPyH.js → log-DWAAMNbe.js} +0 -0
- /package/dist/client/assets/{logo-B78qMxws.js → logo-ClQMGkNL.js} +0 -0
- /package/dist/client/assets/{luau-BUd8xsdE.js → luau-CmcXFvLt.js} +0 -0
- /package/dist/client/assets/{make-Ev-6cJrd.js → make-DcfJyUzm.js} +0 -0
- /package/dist/client/assets/{markdown-CS4LI6sN.js → markdown-DXxotAxn.js} +0 -0
- /package/dist/client/assets/{material-theme-5rA1zn0D.js → material-theme-JFoPTBSr.js} +0 -0
- /package/dist/client/assets/{material-theme-darker-DGOEiL65.js → material-theme-darker-BHaqZ19v.js} +0 -0
- /package/dist/client/assets/{material-theme-lighter-CULfFRX0.js → material-theme-lighter-CGnnS2bs.js} +0 -0
- /package/dist/client/assets/{material-theme-ocean-LQZ9CleP.js → material-theme-ocean-Drhewjqf.js} +0 -0
- /package/dist/client/assets/{material-theme-palenight-Di_f5oks.js → material-theme-palenight-DbUypmpb.js} +0 -0
- /package/dist/client/assets/{matlab-C0qEtAx9.js → matlab-BlfbTznR.js} +0 -0
- /package/dist/client/assets/{mdx-CzvnbLXb.js → mdx-H8fyPQd8.js} +0 -0
- /package/dist/client/assets/{mermaid-BPquZaKt.js → mermaid-CWAV2dmV.js} +0 -0
- /package/dist/client/assets/{min-dark-rkuIbtJV.js → min-dark-BEelvXUB.js} +0 -0
- /package/dist/client/assets/{min-light-Dj_HOGsr.js → min-light-CTPBOACR.js} +0 -0
- /package/dist/client/assets/{mipsasm-k5DQoS6j.js → mipsasm-BWYFAm2s.js} +0 -0
- /package/dist/client/assets/{mojo-sTTDuNnR.js → mojo-DZcPJEIN.js} +0 -0
- /package/dist/client/assets/{monokai-D5p-u2ij.js → monokai-DQlZXbkg.js} +0 -0
- /package/dist/client/assets/{moonbit-j5ChFtBC.js → moonbit-6eslNlJL.js} +0 -0
- /package/dist/client/assets/{move-Dj0Gmn6R.js → move-DxFc9-QU.js} +0 -0
- /package/dist/client/assets/{narrat-DJgMz2oW.js → narrat-gv38dJOT.js} +0 -0
- /package/dist/client/assets/{nextflow-groovy-eDLuZ_oi.js → nextflow-groovy-DV4H3hok.js} +0 -0
- /package/dist/client/assets/{night-owl-CEdtPl3A.js → night-owl-BLcrYhaC.js} +0 -0
- /package/dist/client/assets/{night-owl-light-GdSldewm.js → night-owl-light-BG-tBJOz.js} +0 -0
- /package/dist/client/assets/{nix-CxIPS3pb.js → nix-NlOz3HGM.js} +0 -0
- /package/dist/client/assets/{nord-3WFAz5H9.js → nord-Dm2F5_QB.js} +0 -0
- /package/dist/client/assets/{nushell-GDfBmoBU.js → nushell-D86Z8aFF.js} +0 -0
- /package/dist/client/assets/{objective-c-MFQ_MGUa.js → objective-c-iuJvUjCv.js} +0 -0
- /package/dist/client/assets/{objective-cpp-Y83LcBE7.js → objective-cpp-C62JvfMT.js} +0 -0
- /package/dist/client/assets/{ocaml-FxYfaJxu.js → ocaml-Rbryo9lg.js} +0 -0
- /package/dist/client/assets/{odin-CprihY3z.js → odin-CfaOVNP_.js} +0 -0
- /package/dist/client/assets/{one-dark-pro-BB6mpmmA.js → one-dark-pro-DC9SHdX0.js} +0 -0
- /package/dist/client/assets/{one-light-BYLoxNtg.js → one-light-Doo39p0H.js} +0 -0
- /package/dist/client/assets/{openscad-Bc1FbpOi.js → openscad-DCRhgheK.js} +0 -0
- /package/dist/client/assets/{pascal-Bb4d8cUL.js → pascal-CTesTHtV.js} +0 -0
- /package/dist/client/assets/{pierre-dark-cF19s-RM.js → pierre-dark-Gtc9M_gS.js} +0 -0
- /package/dist/client/assets/{pierre-light-BdpX8OpD.js → pierre-light-EDxMHmhf.js} +0 -0
- /package/dist/client/assets/{pkl-tM25Sq0w.js → pkl-BjUMjQkt.js} +0 -0
- /package/dist/client/assets/{plastic-D3k2Xp_5.js → plastic-B3CcSpon.js} +0 -0
- /package/dist/client/assets/{plsql-CLEfFhSj.js → plsql-BgKEZSSo.js} +0 -0
- /package/dist/client/assets/{po-CaAzdrrh.js → po-C5s_CVt8.js} +0 -0
- /package/dist/client/assets/{poimandres-bPTvT4Da.js → poimandres-BsK70ma5.js} +0 -0
- /package/dist/client/assets/{polar-TG7aTGdK.js → polar-y4hX8qM0.js} +0 -0
- /package/dist/client/assets/{postcss-8kqfa9f0.js → postcss-BHCm7Qae.js} +0 -0
- /package/dist/client/assets/{powerquery-CtSu1Nfd.js → powerquery-C2vMt8aZ.js} +0 -0
- /package/dist/client/assets/{powershell-87lLBHMy.js → powershell-DEcjmg1c.js} +0 -0
- /package/dist/client/assets/{prisma-Dnf694DZ.js → prisma-Ba-Tcxy5.js} +0 -0
- /package/dist/client/assets/{prolog-C0XbHTWa.js → prolog-B4o261Jv.js} +0 -0
- /package/dist/client/assets/{proto-BZP-HLIb.js → proto-o3IZKiMR.js} +0 -0
- /package/dist/client/assets/{puppet-CJYWgYC5.js → puppet-BC0dqZ31.js} +0 -0
- /package/dist/client/assets/{purescript-B95Q6BAd.js → purescript-CLDw1y5G.js} +0 -0
- /package/dist/client/assets/{python-CqTCsi7P.js → python-D1NFioGW.js} +0 -0
- /package/dist/client/assets/{qmldir-DlIL4hls.js → qmldir-B7i9e9Vn.js} +0 -0
- /package/dist/client/assets/{qss-BW72E0q_.js → qss-DIe6oKX8.js} +0 -0
- /package/dist/client/assets/{racket-CCAimEsC.js → racket-CAOC_yyk.js} +0 -0
- /package/dist/client/assets/{raku-mSuqElkM.js → raku-Ccua3vW6.js} +0 -0
- /package/dist/client/assets/{red-C-DA4yBI.js → red-DtDH206x.js} +0 -0
- /package/dist/client/assets/{reg-CGwMBJ2c.js → reg-CAYZT9kE.js} +0 -0
- /package/dist/client/assets/{rel-JwrbYgX4.js → rel-cvB6F5nO.js} +0 -0
- /package/dist/client/assets/{riscv-CoYyE75b.js → riscv-SYGtE4EN.js} +0 -0
- /package/dist/client/assets/{ron-C5wrhnmJ.js → ron-CRt869SB.js} +0 -0
- /package/dist/client/assets/{rose-pine-DlrMPYYd.js → rose-pine-CjBfSJ5b.js} +0 -0
- /package/dist/client/assets/{rose-pine-dawn-DBHFtZD2.js → rose-pine-dawn-C0xwy7iJ.js} +0 -0
- /package/dist/client/assets/{rose-pine-moon-BBfrpe5q.js → rose-pine-moon-BSNy9Ty0.js} +0 -0
- /package/dist/client/assets/{rosmsg-DlimTR9t.js → rosmsg-BwrfJ66Z.js} +0 -0
- /package/dist/client/assets/{rust-Cxnkxx3h.js → rust-DCKTTPxN.js} +0 -0
- /package/dist/client/assets/{sass-DEyqUIpU.js → sass-CorcwySt.js} +0 -0
- /package/dist/client/assets/{scala-i3RN3yAN.js → scala-DlI-OmYj.js} +0 -0
- /package/dist/client/assets/{scheme-BcKdjYkO.js → scheme-CcNEGkZH.js} +0 -0
- /package/dist/client/assets/{sdbl-Be_XWc3n.js → sdbl-AL_m-RO_.js} +0 -0
- /package/dist/client/assets/{slack-dark-3PDtm74m.js → slack-dark-rMhrfPm6.js} +0 -0
- /package/dist/client/assets/{slack-ochin-CwiuFRJD.js → slack-ochin-9w8X74hU.js} +0 -0
- /package/dist/client/assets/{smalltalk-BdBlL6ks.js → smalltalk-Y2ogrfbk.js} +0 -0
- /package/dist/client/assets/{snazzy-light-Dyqc8Is2.js → snazzy-light-DHr9ygpP.js} +0 -0
- /package/dist/client/assets/{solarized-dark-DZiTscIX.js → solarized-dark-UPcKev9Q.js} +0 -0
- /package/dist/client/assets/{solarized-light-DBcfxrfG.js → solarized-light-DUsjROvl.js} +0 -0
- /package/dist/client/assets/{solidity-DH-yUSWc.js → solidity-Bfbm9QZQ.js} +0 -0
- /package/dist/client/assets/{splunk-CIjc4kxs.js → splunk-6DJA2XFa.js} +0 -0
- /package/dist/client/assets/{ssh-config-BKVd4drG.js → ssh-config-CbQtdF-u.js} +0 -0
- /package/dist/client/assets/{stylus-tqAsmugv.js → stylus-B_iq45Sj.js} +0 -0
- /package/dist/client/assets/{swift-CdDBQ3_Z.js → swift-C7KIgN-p.js} +0 -0
- /package/dist/client/assets/{synthwave-84-CbbRs73K.js → synthwave-84-lf_Wt0K_.js} +0 -0
- /package/dist/client/assets/{system-verilog-CBqEY-9v.js → system-verilog-C3G9wERd.js} +0 -0
- /package/dist/client/assets/{systemd-khRP5zwK.js → systemd-CwxGrojj.js} +0 -0
- /package/dist/client/assets/{talonscript-D_lneH-6.js → talonscript-DqnSo1EO.js} +0 -0
- /package/dist/client/assets/{tasl-CLymawGe.js → tasl-T8ethp86.js} +0 -0
- /package/dist/client/assets/{tcl-BRY1S87U.js → tcl-Ccl366o0.js} +0 -0
- /package/dist/client/assets/{terraform-BqohMo2f.js → terraform-BBf6ZI_0.js} +0 -0
- /package/dist/client/assets/{tokyo-night-BWUONxad.js → tokyo-night-BF5ata25.js} +0 -0
- /package/dist/client/assets/{toml-C4c85Lwg.js → toml-C5Z_BR4r.js} +0 -0
- /package/dist/client/assets/{tsv-DsZq9HBQ.js → tsv-rX8sUJs7.js} +0 -0
- /package/dist/client/assets/{turtle-Dpr8zLlw.js → turtle-BM-MG8WY.js} +0 -0
- /package/dist/client/assets/{typespec-DcqgATB-.js → typespec-C5gKydvR.js} +0 -0
- /package/dist/client/assets/{typst-CeVxmuPg.js → typst--_5VchrY.js} +0 -0
- /package/dist/client/assets/{v-C2Qsh2-1.js → v-C6PwpmTC.js} +0 -0
- /package/dist/client/assets/{vala-CvZ3yL7c.js → vala-4pTaceSj.js} +0 -0
- /package/dist/client/assets/{vb-B_p4qDw4.js → vb-DhoR5A6S.js} +0 -0
- /package/dist/client/assets/{verilog-D8MGjxD7.js → verilog-B9f_1lPn.js} +0 -0
- /package/dist/client/assets/{vesper-DossXiE1.js → vesper-CmBi_iCT.js} +0 -0
- /package/dist/client/assets/{vhdl-MLyYh_Gz.js → vhdl-Cy4RAmOe.js} +0 -0
- /package/dist/client/assets/{viml-BuYIArcz.js → viml-CFQW-CcD.js} +0 -0
- /package/dist/client/assets/{vitesse-black-C3kc1227.js → vitesse-black-CE8RoBJP.js} +0 -0
- /package/dist/client/assets/{vitesse-dark-kWOu5V36.js → vitesse-dark-BpF142p_.js} +0 -0
- /package/dist/client/assets/{vitesse-light-DMtiL8bK.js → vitesse-light-CMwxPBrg.js} +0 -0
- /package/dist/client/assets/{vyper-CNqG09-_.js → vyper-BW5zj9bE.js} +0 -0
- /package/dist/client/assets/{wasm-BsQDV7Y0.js → wasm-BigxSLnm.js} +0 -0
- /package/dist/client/assets/{wasm-DFVlQlgd.js → wasm-CSccDuAq.js} +0 -0
- /package/dist/client/assets/{wenyan-BmOQge13.js → wenyan-BZzM1SQT.js} +0 -0
- /package/dist/client/assets/{wgsl-CgWrmvIg.js → wgsl-xBPHbLlq.js} +0 -0
- /package/dist/client/assets/{wikitext-DkwckXCk.js → wikitext-BXJWUNJK.js} +0 -0
- /package/dist/client/assets/{wit-CtyVFGe7.js → wit-2GnUs67P.js} +0 -0
- /package/dist/client/assets/{wolfram-CXE4FrKz.js → wolfram-DIFDmw-g.js} +0 -0
- /package/dist/client/assets/{zenscript-DH15wJt0.js → zenscript-DQaUUjbx.js} +0 -0
- /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
|
|
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 --
|
|
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.
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
|
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
|
-
|
|
1741
|
-
|
|
1742
|
-
const
|
|
1743
|
-
if (editor
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
-
|
|
1816
|
-
|
|
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
|
-
|
|
1830
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
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(
|
|
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
|
-
|
|
1944
|
-
|
|
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
|
|
2202
|
-
|
|
2203
|
-
|
|
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
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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-
|
|
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-
|
|
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(
|
|
2271
|
-
const {
|
|
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
|
|
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 };
|