diffing 0.10.5 → 0.11.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 +77 -32
- package/dist/{cli-agent-BJiATn11.mjs → cli-agent-DkwGxuy2.mjs} +40 -11
- package/dist/{cli-gh-_bkDCxhh.mjs → cli-gh-D0NfKpOp.mjs} +4 -4
- package/dist/cli.mjs +150 -221
- package/dist/client/assets/{angular-html-CBcwKYDC.js → angular-html-Dl53kkiy.js} +1 -1
- package/dist/client/assets/{angular-ts-DRBYUaJA.js → angular-ts-wNJFFkey.js} +1 -1
- package/dist/client/assets/{apl-BM0X8jeL.js → apl-BxtQST-h.js} +1 -1
- package/dist/client/assets/{arc-DTkmmKl3.js → arc-CBmZuzPB.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-Cw75ay7q.js → architectureDiagram-3BPJPVTR-CEci_K9f.js} +1 -1
- package/dist/client/assets/{astro-DKTNkoRZ.js → astro-BpADD5rh.js} +1 -1
- package/dist/client/assets/{blade-DaM7a9Zp.js → blade-B4I5SQmP.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-DAzINMr_.js → blockDiagram-GPEHLZMM-DcKhDtnE.js} +1 -1
- package/dist/client/assets/{c-B3C0Or4o.js → c-C3VtOgg8.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C7SsS28g.js → c4Diagram-AAUBKEIU-CaHozoxF.js} +1 -1
- package/dist/client/assets/channel-Ct4-oSQN.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DHOUGWx8.js → chunk-2J33WTMH-tvuzKhdK.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-D8d2scLX.js → chunk-3OPIFGDE-D_nBoD9w.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-BSC-E5yg.js → chunk-4BX2VUAB-yHPdy1G8.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-27pC5pKn.js → chunk-55IACEB6-yXpL8wjV.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BM60x82y.js → chunk-5ZQYHXKU-DZ5cvCDV.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-CHCF96vE.js → chunk-727SXJPM-CJjgOWmY.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-BoVsvmTa.js → chunk-AQP2D5EJ-CQYQKXSE.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-Iip4uSio.js → chunk-BSJP7CBP-CP_f908b.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DuIWPyay.js → chunk-CSCIHK7Q-tcgA8jR0.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Du6rtd2k.js → chunk-FMBD7UC4-DlpmhBYg.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-BBq105vI.js → chunk-KSCS5N6A-BkfieXpV.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-0DmB5Qp8.js → chunk-L5ZTLDWV-BjVXSZnt.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-CoRsjUfr.js → chunk-LZXEDZCA-CaAWmy4Q.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-tX8cKEls.js → chunk-ND2GUHAM-kWh41zUn.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BoJGgPic.js → chunk-NZK2D7GU-CDrIi44_.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-mlwCWs97.js → chunk-O5CBEL6O-CbNGK3Te.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-BXhKFVX7.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-_EwuC51_.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-B5efww8M.js → chunk-XPW4576I-Cca0fTtG.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C8bdx9R4.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B26wrQuH.js +1 -0
- package/dist/client/assets/{cobol-De9QR_Td.js → cobol-j-6eG2UQ.js} +1 -1
- package/dist/client/assets/{coffee-Ct_GvuHP.js → coffee-CZLH0dtt.js} +1 -1
- package/dist/client/assets/{cose-bilkent-S5V4N54A-ATaJZGF7.js → cose-bilkent-S5V4N54A-DlxESVex.js} +1 -1
- package/dist/client/assets/{cpp-Dw52w9cj.js → cpp-j6Bp24RI.js} +1 -1
- package/dist/client/assets/{crystal-Dx8ADdsd.js → crystal-DW7wcFUs.js} +1 -1
- package/dist/client/assets/{css-CZ13XvDU.js → css-C43BDU2i.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-aDltuYut.js → dagre-BM42HDAG-BaemEJFh.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DGWnHVMJ.js → diagram-2AECGRRQ-BaM-bkJ3.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Dd7eZBKe.js → diagram-5GNKFQAL-BOH04DJO.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DWThXREf.js → diagram-KO2AKTUF-BP5MyqDZ.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CXR4Bjtc.js → diagram-LMA3HP47-Dt4gKMYF.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-D4Cbf0nW.js → diagram-OG6HWLK6-DoT1VshN.js} +1 -1
- package/dist/client/assets/{dist-CUDhrHNw.js → dist-DEAxvC4F.js} +1 -1
- package/dist/client/assets/{edge-CQCjdJKj.js → edge-D9ySFlnD.js} +1 -1
- package/dist/client/assets/{elixir-bkjwPazA.js → elixir-CoJz_LQ5.js} +1 -1
- package/dist/client/assets/{elm-CCweX5Cq.js → elm-CvedWh_e.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BmAyt7SU.js → erDiagram-TEJ5UH35-CMP78FsQ.js} +1 -1
- package/dist/client/assets/{erb-D-Z-KBLp.js → erb-lD_yYZsm.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C6nIFUVc.js → flowDiagram-I6XJVG4X-Dp0s5BFj.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DMlxThBf.js → ganttDiagram-6RSMTGT7-GmSG31U-.js} +1 -1
- package/dist/client/assets/{git-rebase-BdoFdenB.js → git-rebase-DwZABzof.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-JaIFkAIT.js → gitGraphDiagram-PVQCEYII-C1SXShag.js} +1 -1
- package/dist/client/assets/{glimmer-js-CZQrBJKq.js → glimmer-js-CWcs3w1Z.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CBYW46D4.js → glimmer-ts-CrzGN4lJ.js} +1 -1
- package/dist/client/assets/{glsl-wxixKkY_.js → glsl-DRCtwX7h.js} +1 -1
- package/dist/client/assets/{graphql-CqZ0Lo5a.js → graphql-rpmrismS.js} +1 -1
- package/dist/client/assets/{hack-rqWymQRV.js → hack-Ba02RnuQ.js} +1 -1
- package/dist/client/assets/{haml-DFw_IoGM.js → haml-BHBACStm.js} +1 -1
- package/dist/client/assets/{handlebars-3VmTVMYt.js → handlebars-BtlQ1SCa.js} +1 -1
- package/dist/client/assets/{html-C0Li76jB.js → html-BkVU3Dge.js} +1 -1
- package/dist/client/assets/{html-derivative-CO-RQW8y.js → html-derivative-DoK92vwi.js} +1 -1
- package/dist/client/assets/{http-sPrZOeBY.js → http-CJAwKSPH.js} +1 -1
- package/dist/client/assets/{hurl-DUoPe4Lc.js → hurl-kaX5jwuN.js} +1 -1
- package/dist/client/assets/{index-BN_-di_r.js → index-BHeNiC07.js} +261 -199
- package/dist/client/assets/index-D8hOoJCg.css +1 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-B6AZ5SCV.js → infoDiagram-5YYISTIA-B0i3BlNR.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CytMrpo8.js → ishikawaDiagram-YF4QCWOH-BN8sEei4.js} +1 -1
- package/dist/client/assets/{java-Cj0G6QRg.js → java-BvrigIoq.js} +1 -1
- package/dist/client/assets/{javascript-d5SUuyWD.js → javascript-DZkPTYAi.js} +1 -1
- package/dist/client/assets/{jinja-BTw9k0vO.js → jinja-Ceg7ui1S.js} +1 -1
- package/dist/client/assets/{jison-9xSVvR1q.js → jison-iE1GaOyN.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-Bd4jZUYI.js → journeyDiagram-JHISSGLW-CE6O3NhU.js} +1 -1
- package/dist/client/assets/{json-DjAt53ka.js → json-C0j8rEcg.js} +1 -1
- package/dist/client/assets/{jsx-BX5KmQ7d.js → jsx-B7eXljgz.js} +1 -1
- package/dist/client/assets/{julia-CLLx1dHC.js → julia-DMDw0aMP.js} +1 -1
- package/dist/client/assets/{just-C0v80bKe.js → just-BryMKRJ_.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Ds4pXHJa.js → kanban-definition-UN3LZRKU-j-aU6Yzp.js} +1 -1
- package/dist/client/assets/{latex-DY-oQfwO.js → latex-BNC9-sjr.js} +1 -1
- package/dist/client/assets/{line-BbSLfOl6.js → line-CyEqpEDp.js} +1 -1
- package/dist/client/assets/{linear-BMIZJAAR.js → linear-CXeaA9km.js} +1 -1
- package/dist/client/assets/{liquid-DDHDgbZx.js → liquid-D-fQw-oJ.js} +1 -1
- package/dist/client/assets/{lua-CXymerKS.js → lua-CD2aJ_rS.js} +1 -1
- package/dist/client/assets/{marko-DoPFsptl.js → marko-BQ37hu6z.js} +1 -1
- package/dist/client/assets/{mdc-BxSmTphd.js → mdc-B5NUi3zZ.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CcYSvN4q.js → mermaid-parser.core-BxHF_XbX.js} +1 -1
- package/dist/client/assets/{mermaid.core-COTNtqgQ.js → mermaid.core-BUtaf4P1.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BTxraRzn.js → mindmap-definition-RKZ34NQL-D13tPu2o.js} +1 -1
- package/dist/client/assets/{nginx-Bma0jNOT.js → nginx-1W1E03Rg.js} +1 -1
- package/dist/client/assets/{nim-DMBpjygU.js → nim-C5A88bFP.js} +1 -1
- package/dist/client/assets/{perl-CrnrV-AJ.js → perl-BYoP0wNt.js} +1 -1
- package/dist/client/assets/{php-CDYxzX6t.js → php-CFxna09e.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DglRmzer.js → pieDiagram-4H26LBE5-CHL7Ml5w.js} +1 -1
- package/dist/client/assets/{pug-DNewy3GU.js → pug-BR4JgWAk.js} +1 -1
- package/dist/client/assets/{qml-COjcnZ-W.js → qml-D1U1EFEe.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BjSmyES7.js → quadrantDiagram-W4KKPZXB-BnYtv-HY.js} +1 -1
- package/dist/client/assets/{r-CrCaMxLS.js → r-ChE6X060.js} +1 -1
- package/dist/client/assets/{razor-jeuOZIar.js → razor-B5iVfQMe.js} +1 -1
- package/dist/client/assets/{regexp-DSHVUX08.js → regexp-Dz2drUIf.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-B4f430jS.js → requirementDiagram-4Y6WPE33-D8QEGLKB.js} +1 -1
- package/dist/client/assets/{review-export-Cen1WEcX.js → review-export-CvFFbCAB.js} +1 -1
- package/dist/client/assets/{rst-CIDgytzc.js → rst-DZM3RGRs.js} +1 -1
- package/dist/client/assets/{ruby-DtuseoNq.js → ruby-DkXnm-J8.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-TQPPlxc6.js → sankeyDiagram-5OEKKPKP-wVaks6lB.js} +1 -1
- package/dist/client/assets/{sas-C72Theia.js → sas-DMbpc71c.js} +1 -1
- package/dist/client/assets/{scss-ClECtwRk.js → scss-IwVMMp22.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-D1tx07Wg.js → sequenceDiagram-3UESZ5HK-Cg31TB-R.js} +1 -1
- package/dist/client/assets/{shellscript-BCwBsi2k.js → shellscript-CIStAKx3.js} +1 -1
- package/dist/client/assets/{shellsession-BrOddEl5.js → shellsession-uLtzHhlo.js} +1 -1
- package/dist/client/assets/{soy-Dcs0GWFW.js → soy-BkxBYigm.js} +1 -1
- package/dist/client/assets/{sql-BAjA7Gv7.js → sql-Ba9y9EvJ.js} +1 -1
- package/dist/client/assets/{src-CYYhkqTK.js → src-9MKexpFu.js} +1 -1
- package/dist/client/assets/{stata-19ngQtzR.js → stata-CrPCbiIZ.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-BZRCEzb5.js → stateDiagram-AJRCARHV-BbvNjVF6.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-B5BbZkU5.js +1 -0
- package/dist/client/assets/{surrealql-Ce2TBRjE.js → surrealql-Dvip5u_o.js} +1 -1
- package/dist/client/assets/{svelte-DQ_1eGZz.js → svelte-xBLD60Zd.js} +1 -1
- package/dist/client/assets/{templ-Cw2DDxWF.js → templ-CWv68xbP.js} +1 -1
- package/dist/client/assets/{tex-myDSO2eP.js → tex-CHqz-fYs.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CP0OZhBT.js → timeline-definition-PNZ67QCA-CWKkJ3QZ.js} +1 -1
- package/dist/client/assets/{ts-tags-rh39ZwVs.js → ts-tags-DS82EDuE.js} +1 -1
- package/dist/client/assets/{tsx-B0-26jrU.js → tsx-B2GBjp2F.js} +1 -1
- package/dist/client/assets/{twig-CgasHzPb.js → twig-CC8x9pG0.js} +1 -1
- package/dist/client/assets/{typescript-Dq3T0c-m.js → typescript-g7i13lnC.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-DHaKHj0j.js → vennDiagram-CIIHVFJN-OLItKWeg.js} +1 -1
- package/dist/client/assets/{vue-html-Bv2uFkTH.js → vue-html-D9GQtYub.js} +1 -1
- package/dist/client/assets/{vue-CzFVh92-.js → vue-rFbe7luB.js} +1 -1
- package/dist/client/assets/{vue-vine-DzieBk9Y.js → vue-vine-mrNtQtY5.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dky7jxlZ.js → wardleyDiagram-YWT4CUSO-Dy1yfeb4.js} +1 -1
- package/dist/client/assets/{xml-wcdHRHN8.js → xml-CHfMjyAM.js} +1 -1
- package/dist/client/assets/{xsl-CXrwsLG9.js → xsl-BFkNSogJ.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BQ2hG60h.js → xychartDiagram-2RQKCTM6-DfSnfjVL.js} +1 -1
- package/dist/client/assets/{yaml-lO6lK6Xe.js → yaml-BfSgHc04.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/{completions-3UF8uzhx.mjs → completions-BO2DUM-8.mjs} +21 -1
- package/dist/{doctor-BGYZNIYP.mjs → doctor-D0Y3kOY7.mjs} +5 -4
- package/dist/{find-tui-binary-CRpbYXLC.mjs → find-tui-binary-B71xiwWu.mjs} +33 -15
- package/dist/{github-DKT0lJXv.mjs → github-CErBub2y.mjs} +1 -1
- package/dist/{github-CEjtGd41.mjs → github-DBsZ0h_l.mjs} +3 -3
- package/dist/{github-attachments-Cf17YWp4.mjs → github-attachments-plDeB0iE.mjs} +1 -1
- package/dist/{mcp-Dq7J5QOP.mjs → mcp-DtVrMR2S.mjs} +13 -7
- package/dist/native/tui-darwin-arm64/diffing-tui +0 -0
- package/dist/native/tui-darwin-x64/diffing-tui +0 -0
- package/dist/native/tui-linux-arm64-gnu/diffing-tui +0 -0
- package/dist/native/tui-linux-arm64-musl/diffing-tui +0 -0
- package/dist/native/tui-linux-x64-gnu/diffing-tui +0 -0
- package/dist/native/tui-linux-x64-musl/diffing-tui +0 -0
- package/dist/native/tui-win32-x64-msvc/diffing-tui.exe +0 -0
- package/dist/{review-export-BBDWvftT.mjs → review-export-B63eRAVa.mjs} +1 -1
- package/dist/{settings-bwe7yQ8U.mjs → search-BMPY_ZW8.mjs} +117 -97
- package/dist/search-BO9Im7eu.mjs +2 -0
- package/dist/{server-C9i4V36o.mjs → server-j0PnTVX1.mjs} +146 -75
- package/dist/server-lock-CY0_1mcS.mjs +275 -0
- package/dist/session-conflict-C6V9ZKo7.mjs +96 -0
- package/dist/session-manager-CYNchhVA.mjs +159 -0
- package/dist/settings-yK0GYpbn.mjs +60 -0
- package/dist/tui-search-bridge-Bfb2ktdA.mjs +133 -0
- package/package.json +6 -4
- package/dist/client/assets/channel-lsVHTuYR.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-AHUiTM0z.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-D41j4yxb.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-D1Eo3kly.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-bAMsbsfB.js +0 -1
- package/dist/client/assets/index-B42c0TyV.css +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Da-mGMA2.js +0 -1
- package/dist/server-lock-Dd_ss_Il.mjs +0 -153
- /package/dist/{apply-suggestion-DNNsC5gx.mjs → apply-suggestion-CHYnw68I.mjs} +0 -0
- /package/dist/{comment-format-B5AcGcR-.mjs → comment-format-B85NHZnG.mjs} +0 -0
- /package/dist/{diff-fingerprint-C8YO3TTE.mjs → diff-fingerprint-BgR0tu1o.mjs} +0 -0
- /package/dist/{handoff-DPoIyeWn.mjs → handoff-LAFtmnHf.mjs} +0 -0
- /package/dist/{plan-format-BQyx--mp.mjs → plan-format-BwJh5JSE.mjs} +0 -0
- /package/dist/{update-check-DUuHmPeZ.mjs → update-check-Copcz9eb.mjs} +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { d as getProjectStorageDir, p as getRepoRoot } from "./git-Czo96ymM.mjs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
//#region src/lib/server-lock.ts
|
|
6
|
+
const STARTUP_LEASE_STALE_MS = 3e4;
|
|
7
|
+
/**
|
|
8
|
+
* Produce a stable comparison key for the diff a server displays. Runtime-only
|
|
9
|
+
* web options are excluded so `--port`/`--no-open` do not create false scope
|
|
10
|
+
* mismatches between a user-started session and an MCP request.
|
|
11
|
+
*/
|
|
12
|
+
function diffScopeKey(options) {
|
|
13
|
+
const { port: _port, host: _host, noOpen: _noOpen, reuseSession: _reuseSession, replaceSession: _replaceSession, help: _help, version: _version, outputMode: _outputMode, tui: _tui, gpu: _gpu, noExtDiff: _noExtDiff, ...scope } = options;
|
|
14
|
+
return JSON.stringify(scope);
|
|
15
|
+
}
|
|
16
|
+
function lockPath(repoRoot) {
|
|
17
|
+
return join(getProjectStorageDir(repoRoot), "server.json");
|
|
18
|
+
}
|
|
19
|
+
function sessionsPath(repoRoot) {
|
|
20
|
+
return join(getProjectStorageDir(repoRoot), "sessions");
|
|
21
|
+
}
|
|
22
|
+
/** Stable identity for both current and pre-registry lock records. */
|
|
23
|
+
function serverSessionId(lock) {
|
|
24
|
+
return lock.sessionId ?? lock.ownerId ?? `${lock.mode ?? "web"}-${lock.pid}-${lock.startedAt}`;
|
|
25
|
+
}
|
|
26
|
+
function sameServerSession(left, right) {
|
|
27
|
+
if (left.sessionId && right.sessionId) return left.sessionId === right.sessionId;
|
|
28
|
+
return left.pid === right.pid && left.startedAt === right.startedAt && left.port === right.port;
|
|
29
|
+
}
|
|
30
|
+
function sessionPath(lock) {
|
|
31
|
+
return join(sessionsPath(lock.repoRoot), `${encodeURIComponent(serverSessionId(lock))}.json`);
|
|
32
|
+
}
|
|
33
|
+
function writeJsonAtomically(path, value) {
|
|
34
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
35
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
36
|
+
writeFileSync(temporary, JSON.stringify(value, null, 2), "utf-8");
|
|
37
|
+
renameSync(temporary, path);
|
|
38
|
+
}
|
|
39
|
+
function normalizedLock(lock) {
|
|
40
|
+
return lock.sessionId ? lock : {
|
|
41
|
+
...lock,
|
|
42
|
+
sessionId: serverSessionId(lock)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function writeSessionRecord(lock) {
|
|
46
|
+
const normalized = normalizedLock(lock);
|
|
47
|
+
writeJsonAtomically(sessionPath(normalized), normalized);
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register a session and make it the active target for legacy clients. Before
|
|
52
|
+
* replacing server.json, adopt a live pre-registry lock so an upgrade never
|
|
53
|
+
* makes an already-running review disappear from the session manager.
|
|
54
|
+
*/
|
|
55
|
+
function writeServerLock(lock) {
|
|
56
|
+
const current = readServerLock(lock.repoRoot);
|
|
57
|
+
if (current && isLockAlive(current, lock.repoRoot) && !sameServerSession(current, lock)) writeSessionRecord(current);
|
|
58
|
+
const normalized = writeSessionRecord(lock);
|
|
59
|
+
try {
|
|
60
|
+
writeJsonAtomically(lockPath(lock.repoRoot), normalized);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
rmSync(sessionPath(normalized), { force: true });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function readServerLock(repoRoot) {
|
|
67
|
+
try {
|
|
68
|
+
const raw = readFileSync(lockPath(repoRoot), "utf-8");
|
|
69
|
+
const lock = JSON.parse(raw);
|
|
70
|
+
if (typeof lock.port !== "number" || typeof lock.pid !== "number") return null;
|
|
71
|
+
return lock;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Return every live registered session, newest first, and prune stale entries. */
|
|
77
|
+
function listServerLocks(repoRoot) {
|
|
78
|
+
let expectedRepoRoot;
|
|
79
|
+
try {
|
|
80
|
+
expectedRepoRoot = repoRoot ?? getRepoRoot();
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
85
|
+
const addIfLive = (candidate, stalePath) => {
|
|
86
|
+
if (candidate.repoRoot !== expectedRepoRoot || !isLockAlive(candidate, expectedRepoRoot)) {
|
|
87
|
+
if (stalePath) rmSync(stalePath, { force: true });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const normalized = normalizedLock(candidate);
|
|
91
|
+
sessions.set(serverSessionId(normalized), normalized);
|
|
92
|
+
};
|
|
93
|
+
const active = readServerLock(expectedRepoRoot);
|
|
94
|
+
if (active) addIfLive(active);
|
|
95
|
+
const directory = sessionsPath(expectedRepoRoot);
|
|
96
|
+
try {
|
|
97
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
98
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
99
|
+
const path = join(directory, entry.name);
|
|
100
|
+
try {
|
|
101
|
+
const candidate = JSON.parse(readFileSync(path, "utf-8"));
|
|
102
|
+
if (typeof candidate.port !== "number" || typeof candidate.pid !== "number") {
|
|
103
|
+
rmSync(path, { force: true });
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
addIfLive(candidate, path);
|
|
107
|
+
} catch {
|
|
108
|
+
rmSync(path, { force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
return [...sessions.values()].sort((left, right) => right.startedAt - left.startedAt);
|
|
113
|
+
}
|
|
114
|
+
/** Resolve the active session, electing the newest live fallback when needed. */
|
|
115
|
+
function resolveActiveServerLock(repoRoot) {
|
|
116
|
+
let expectedRepoRoot;
|
|
117
|
+
try {
|
|
118
|
+
expectedRepoRoot = repoRoot ?? getRepoRoot();
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const active = readServerLock(expectedRepoRoot);
|
|
123
|
+
if (active && isLockAlive(active, expectedRepoRoot)) {
|
|
124
|
+
const normalized = normalizedLock(active);
|
|
125
|
+
if (!existsSync(sessionPath(normalized))) writeSessionRecord(normalized);
|
|
126
|
+
if (!active.sessionId) writeJsonAtomically(lockPath(expectedRepoRoot), normalized);
|
|
127
|
+
return normalized;
|
|
128
|
+
}
|
|
129
|
+
const fallback = listServerLocks(expectedRepoRoot)[0];
|
|
130
|
+
if (!fallback) {
|
|
131
|
+
removeServerLock(expectedRepoRoot);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return activateServerLock(fallback);
|
|
135
|
+
}
|
|
136
|
+
/** Make an existing registered session the default target for CLI/MCP clients. */
|
|
137
|
+
function activateServerLock(lock) {
|
|
138
|
+
const normalized = writeSessionRecord(lock);
|
|
139
|
+
writeJsonAtomically(lockPath(lock.repoRoot), normalized);
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* True if the process named by the lock is still alive and the lock belongs to
|
|
144
|
+
* this repo. `process.kill(pid, 0)` sends no signal — it just probes existence.
|
|
145
|
+
*/
|
|
146
|
+
function isLockAlive(lock, expectedRepoRoot) {
|
|
147
|
+
try {
|
|
148
|
+
process.kill(lock.pid, 0);
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
return lock.repoRoot === (expectedRepoRoot ?? getRepoRoot());
|
|
154
|
+
} catch {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function removeServerLock(repoRoot) {
|
|
159
|
+
try {
|
|
160
|
+
rmSync(lockPath(repoRoot), { force: true });
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
/** Remove one registry entry and elect another live session if it was active. */
|
|
164
|
+
function removeServerSession(lock) {
|
|
165
|
+
const normalized = normalizedLock(lock);
|
|
166
|
+
const path = sessionPath(normalized);
|
|
167
|
+
try {
|
|
168
|
+
if (sameServerSession(JSON.parse(readFileSync(path, "utf-8")), normalized)) rmSync(path, { force: true });
|
|
169
|
+
} catch {}
|
|
170
|
+
const active = readServerLock(lock.repoRoot);
|
|
171
|
+
if (!active || !sameServerSession(active, normalized)) return;
|
|
172
|
+
const fallback = listServerLocks(lock.repoRoot).find((candidate) => !sameServerSession(candidate, normalized));
|
|
173
|
+
if (fallback) activateServerLock(fallback);
|
|
174
|
+
else removeServerLock(lock.repoRoot);
|
|
175
|
+
}
|
|
176
|
+
/** Unregister live sessions owned by one exact CLI/MCP process identity. */
|
|
177
|
+
function removeServerLockIfOwned(repoRoot, pid, ownerId) {
|
|
178
|
+
const cleanupLease = acquireServerStartupLease(repoRoot, `cleanup-${ownerId}`);
|
|
179
|
+
if (!cleanupLease) return false;
|
|
180
|
+
try {
|
|
181
|
+
const owned = listServerLocks(repoRoot).filter((lock) => lock.pid === pid && lock.ownerId === ownerId);
|
|
182
|
+
if (owned.length === 0) return false;
|
|
183
|
+
for (const lock of owned) removeServerSession(lock);
|
|
184
|
+
return true;
|
|
185
|
+
} finally {
|
|
186
|
+
cleanupLease.release();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Atomically reserve server startup for one repository. The exclusive-create
|
|
191
|
+
* directory is the cross-process serialization point; callers must re-read
|
|
192
|
+
* server.json after acquiring it because another process may have completed
|
|
193
|
+
* startup between their first lock check and this lease acquisition.
|
|
194
|
+
*/
|
|
195
|
+
function acquireServerStartupLease(repoRoot, ownerId, now = Date.now()) {
|
|
196
|
+
const path = join(getProjectStorageDir(repoRoot), "server-startup.lock");
|
|
197
|
+
const recordPath = join(path, "lease.json");
|
|
198
|
+
const ownerMarkerPath = join(path, `owner-${encodeURIComponent(ownerId)}`);
|
|
199
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
200
|
+
const tryCreate = () => {
|
|
201
|
+
try {
|
|
202
|
+
mkdirSync(path, {
|
|
203
|
+
recursive: false,
|
|
204
|
+
mode: 448
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error?.code === "EEXIST") return false;
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
writeFileSync(recordPath, JSON.stringify({
|
|
212
|
+
ownerId,
|
|
213
|
+
createdAt: now,
|
|
214
|
+
pid: process.pid
|
|
215
|
+
}), "utf-8");
|
|
216
|
+
writeFileSync(ownerMarkerPath, "", "utf-8");
|
|
217
|
+
} catch (error) {
|
|
218
|
+
rmSync(path, {
|
|
219
|
+
recursive: true,
|
|
220
|
+
force: true
|
|
221
|
+
});
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
};
|
|
226
|
+
if (!tryCreate()) {
|
|
227
|
+
let reclaim = false;
|
|
228
|
+
let acquiredAfterRace = false;
|
|
229
|
+
try {
|
|
230
|
+
const record = JSON.parse(readFileSync(recordPath, "utf-8"));
|
|
231
|
+
if (typeof record.createdAt !== "number" || typeof record.pid !== "number") throw new Error("Malformed startup lease");
|
|
232
|
+
let ownerIsAlive = true;
|
|
233
|
+
try {
|
|
234
|
+
process.kill(record.pid, 0);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
ownerIsAlive = error?.code !== "ESRCH";
|
|
237
|
+
}
|
|
238
|
+
reclaim = now - record.createdAt > STARTUP_LEASE_STALE_MS && !ownerIsAlive;
|
|
239
|
+
if (!reclaim) return null;
|
|
240
|
+
} catch {
|
|
241
|
+
try {
|
|
242
|
+
reclaim = now - statSync(path).mtimeMs > STARTUP_LEASE_STALE_MS;
|
|
243
|
+
} catch (statError) {
|
|
244
|
+
if (statError?.code !== "ENOENT") return null;
|
|
245
|
+
if (!tryCreate()) return null;
|
|
246
|
+
acquiredAfterRace = true;
|
|
247
|
+
}
|
|
248
|
+
if (!reclaim && !acquiredAfterRace) return null;
|
|
249
|
+
}
|
|
250
|
+
if (reclaim && !acquiredAfterRace) {
|
|
251
|
+
const stalePath = `${path}.stale-${process.pid}-${now}`;
|
|
252
|
+
renameSync(path, stalePath);
|
|
253
|
+
rmSync(stalePath, {
|
|
254
|
+
recursive: true,
|
|
255
|
+
force: true
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (!acquiredAfterRace && !tryCreate()) return null;
|
|
259
|
+
}
|
|
260
|
+
let released = false;
|
|
261
|
+
return {
|
|
262
|
+
ownerId,
|
|
263
|
+
release() {
|
|
264
|
+
if (released) return;
|
|
265
|
+
released = true;
|
|
266
|
+
try {
|
|
267
|
+
unlinkSync(ownerMarkerPath);
|
|
268
|
+
unlinkSync(recordPath);
|
|
269
|
+
rmdirSync(path);
|
|
270
|
+
} catch {}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
export { listServerLocks as a, removeServerLockIfOwned as c, sameServerSession as d, serverSessionId as f, isLockAlive as i, removeServerSession as l, activateServerLock as n, readServerLock as o, writeServerLock as p, diffScopeKey as r, removeServerLock as s, acquireServerStartupLease as t, resolveActiveServerLock as u };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { t as loadSettings } from "./settings-yK0GYpbn.mjs";
|
|
2
|
+
import { l as removeServerSession } from "./server-lock-CY0_1mcS.mjs";
|
|
3
|
+
//#region src/lib/session-conflict.ts
|
|
4
|
+
function sleepMs(ms) {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
}
|
|
7
|
+
/** Loopback-safe review URL for web / gh-pr locks; null for TUI or invalid ports. */
|
|
8
|
+
function existingSessionUrl(lock) {
|
|
9
|
+
const mode = lock.mode ?? "web";
|
|
10
|
+
if (mode === "tui" || !(lock.port > 0)) return null;
|
|
11
|
+
const host = lock.host === "0.0.0.0" ? "127.0.0.1" : lock.host;
|
|
12
|
+
const path = mode === "gh-pr" ? "/gh/pr" : "";
|
|
13
|
+
return `http://${host}:${lock.port}${path}`;
|
|
14
|
+
}
|
|
15
|
+
function defaultClearLock(lock) {
|
|
16
|
+
removeServerSession(lock);
|
|
17
|
+
}
|
|
18
|
+
function pidAlive(pid, kill) {
|
|
19
|
+
try {
|
|
20
|
+
kill(pid, 0);
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Gracefully stop the process that owns `lock`, then clear a matching lockfile.
|
|
28
|
+
* SIGTERM → wait → SIGKILL → wait → throw.
|
|
29
|
+
*/
|
|
30
|
+
async function stopLockOwner(lock, options = {}) {
|
|
31
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
32
|
+
const killDeadlineMs = options.killDeadlineMs ?? 2e3;
|
|
33
|
+
const pollMs = options.pollMs ?? 100;
|
|
34
|
+
const kill = options.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
35
|
+
const sleep = options.sleep ?? sleepMs;
|
|
36
|
+
const now = options.now ?? Date.now;
|
|
37
|
+
const isAlive = options.isAlive ?? ((candidate) => pidAlive(candidate.pid, kill));
|
|
38
|
+
const clearLock = options.clearLock ?? defaultClearLock;
|
|
39
|
+
if (!isAlive(lock)) {
|
|
40
|
+
clearLock(lock);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
kill(lock.pid, "SIGTERM");
|
|
45
|
+
} catch {}
|
|
46
|
+
const termDeadline = now() + timeoutMs;
|
|
47
|
+
while (now() < termDeadline) {
|
|
48
|
+
if (!isAlive(lock)) {
|
|
49
|
+
clearLock(lock);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await sleep(pollMs);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
kill(lock.pid, "SIGKILL");
|
|
56
|
+
} catch {}
|
|
57
|
+
const killDeadline = now() + killDeadlineMs;
|
|
58
|
+
while (now() < killDeadline) {
|
|
59
|
+
if (!isAlive(lock)) {
|
|
60
|
+
clearLock(lock);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await sleep(pollMs);
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Timed out waiting for diffing pid ${lock.pid} to exit after SIGTERM/SIGKILL. End that process manually and try again.`);
|
|
66
|
+
}
|
|
67
|
+
/** Print and optionally open the existing web/gh-pr session; message-only for TUI. */
|
|
68
|
+
async function openExistingSession(lock, options) {
|
|
69
|
+
if ((lock.mode ?? "web") === "tui") {
|
|
70
|
+
console.log(`A diffing TUI session is already open for this repository (pid ${lock.pid}). Use that terminal, or replace it with --replace-session.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const url = existingSessionUrl(lock);
|
|
74
|
+
if (!url) {
|
|
75
|
+
console.log(`A diffing review is already running for this repository (pid ${lock.pid}).`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(`Opening existing diffing session at ${url}`);
|
|
79
|
+
if (options.noOpen) return;
|
|
80
|
+
if (options.openUrl) {
|
|
81
|
+
await options.openUrl(url);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const settings = loadSettings();
|
|
86
|
+
const openModule = await import("open");
|
|
87
|
+
let appName;
|
|
88
|
+
if (settings.browser) appName = openModule.apps[settings.browser] || settings.browser;
|
|
89
|
+
const openOptions = appName ? { app: { name: appName } } : {};
|
|
90
|
+
await openModule.default(url, openOptions);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error("Failed to open browser:", err instanceof Error ? err.message : err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { openExistingSession as n, stopLockOwner as r, existingSessionUrl as t };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { a as listServerLocks, d as sameServerSession, f as serverSessionId, n as activateServerLock, u as resolveActiveServerLock } from "./server-lock-CY0_1mcS.mjs";
|
|
2
|
+
import { n as openExistingSession, r as stopLockOwner, t as existingSessionUrl } from "./session-conflict-C6V9ZKo7.mjs";
|
|
3
|
+
//#region src/lib/session-manager.ts
|
|
4
|
+
const EXIT_OK = 0;
|
|
5
|
+
const EXIT_NOT_FOUND = 4;
|
|
6
|
+
const EXIT_USAGE = 5;
|
|
7
|
+
function scopeLabel(lock) {
|
|
8
|
+
if (lock.prRef) return `PR ${lock.prRef}`;
|
|
9
|
+
if (lock.diffArgs?.length) return lock.diffArgs.join(" ");
|
|
10
|
+
if (!lock.scope) return "working tree";
|
|
11
|
+
try {
|
|
12
|
+
const scope = JSON.parse(lock.scope);
|
|
13
|
+
const parts = [];
|
|
14
|
+
if (scope.showMode) parts.push("show");
|
|
15
|
+
if (scope.staged) parts.push("staged");
|
|
16
|
+
if (scope.revisions?.length) parts.push(scope.revisions.join(" "));
|
|
17
|
+
if (scope.pathspecs?.length) parts.push(`-- ${scope.pathspecs.join(" ")}`);
|
|
18
|
+
return parts.join(" ") || "working tree";
|
|
19
|
+
} catch {
|
|
20
|
+
return "custom diff";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function summarize(lock, active) {
|
|
24
|
+
return {
|
|
25
|
+
id: serverSessionId(lock),
|
|
26
|
+
active: active ? sameServerSession(lock, active) : false,
|
|
27
|
+
mode: lock.mode ?? "web",
|
|
28
|
+
pid: lock.pid,
|
|
29
|
+
startedAt: lock.startedAt,
|
|
30
|
+
url: existingSessionUrl(lock),
|
|
31
|
+
scope: scopeLabel(lock)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function formatAge(startedAt) {
|
|
35
|
+
const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
|
|
36
|
+
if (seconds < 60) return `${seconds}s`;
|
|
37
|
+
const minutes = Math.floor(seconds / 60);
|
|
38
|
+
if (minutes < 60) return `${minutes}m`;
|
|
39
|
+
const hours = Math.floor(minutes / 60);
|
|
40
|
+
if (hours < 24) return `${hours}h`;
|
|
41
|
+
return `${Math.floor(hours / 24)}d`;
|
|
42
|
+
}
|
|
43
|
+
function printSessions(sessions, active) {
|
|
44
|
+
if (sessions.length === 0) {
|
|
45
|
+
console.log("No diffing sessions are running for this repository.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log("ACTIVE ID MODE PID AGE SCOPE / URL");
|
|
49
|
+
for (const lock of sessions) {
|
|
50
|
+
const summary = summarize(lock, active);
|
|
51
|
+
const activeMark = summary.active ? "*" : "";
|
|
52
|
+
const target = summary.url ? `${summary.scope} ${summary.url}` : summary.scope;
|
|
53
|
+
console.log(`${activeMark.padEnd(7)} ${summary.id.slice(0, 8).padEnd(9)} ${summary.mode.padEnd(6)} ${String(summary.pid).padEnd(8)} ${formatAge(summary.startedAt).padEnd(5)} ${target}`);
|
|
54
|
+
}
|
|
55
|
+
console.log("\nUse `diffing sessions use <id>` to retarget agent commands.");
|
|
56
|
+
}
|
|
57
|
+
function findSession(selector, sessions, active) {
|
|
58
|
+
if (selector === "active") {
|
|
59
|
+
if (active) return active;
|
|
60
|
+
throw new Error("No active diffing session is running.");
|
|
61
|
+
}
|
|
62
|
+
const matches = sessions.filter((lock) => serverSessionId(lock).startsWith(selector));
|
|
63
|
+
if (matches.length === 0) throw new Error(`No live diffing session matches ${JSON.stringify(selector)}.`);
|
|
64
|
+
if (matches.length > 1) throw new Error(`Session prefix ${JSON.stringify(selector)} is ambiguous.`);
|
|
65
|
+
return matches[0];
|
|
66
|
+
}
|
|
67
|
+
function usage() {
|
|
68
|
+
console.error(`Usage:
|
|
69
|
+
diffing sessions [list] [--json]
|
|
70
|
+
diffing sessions use <id>
|
|
71
|
+
diffing sessions open [<id>|active] [--no-open]
|
|
72
|
+
diffing sessions stop|kill <id>|active|all`);
|
|
73
|
+
}
|
|
74
|
+
/** Repository-local task manager for every live web, TUI, and PR session. */
|
|
75
|
+
async function runSessionsCommand(args) {
|
|
76
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
77
|
+
usage();
|
|
78
|
+
return EXIT_OK;
|
|
79
|
+
}
|
|
80
|
+
const action = args[0] && !args[0].startsWith("-") ? args[0] : "list";
|
|
81
|
+
const rest = action === "list" && args[0] !== "list" ? args : args.slice(1);
|
|
82
|
+
const sessions = listServerLocks();
|
|
83
|
+
const active = resolveActiveServerLock();
|
|
84
|
+
if (action === "list") {
|
|
85
|
+
if (rest.filter((arg) => arg !== "--json").length > 0) {
|
|
86
|
+
usage();
|
|
87
|
+
return EXIT_USAGE;
|
|
88
|
+
}
|
|
89
|
+
if (rest.includes("--json")) process.stdout.write(JSON.stringify(sessions.map((lock) => summarize(lock, active)), null, 2) + "\n");
|
|
90
|
+
else printSessions(sessions, active);
|
|
91
|
+
return EXIT_OK;
|
|
92
|
+
}
|
|
93
|
+
if (action === "use") {
|
|
94
|
+
if (rest.length !== 1) {
|
|
95
|
+
usage();
|
|
96
|
+
return EXIT_USAGE;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const selected = findSession(rest[0], sessions, active);
|
|
100
|
+
activateServerLock(selected);
|
|
101
|
+
console.log(`Active diffing session set to ${serverSessionId(selected).slice(0, 8)} (${selected.mode ?? "web"}).`);
|
|
102
|
+
return EXIT_OK;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
105
|
+
return EXIT_NOT_FOUND;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (action === "open") {
|
|
109
|
+
const noOpen = rest.includes("--no-open");
|
|
110
|
+
const positionals = rest.filter((arg) => arg !== "--no-open");
|
|
111
|
+
if (positionals.length > 1) {
|
|
112
|
+
usage();
|
|
113
|
+
return EXIT_USAGE;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const selected = findSession(positionals[0] ?? "active", sessions, active);
|
|
117
|
+
activateServerLock(selected);
|
|
118
|
+
await openExistingSession(selected, { noOpen });
|
|
119
|
+
return EXIT_OK;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
122
|
+
return EXIT_NOT_FOUND;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (action === "stop" || action === "kill") {
|
|
126
|
+
if (rest.length !== 1) {
|
|
127
|
+
usage();
|
|
128
|
+
return EXIT_USAGE;
|
|
129
|
+
}
|
|
130
|
+
let targets;
|
|
131
|
+
try {
|
|
132
|
+
targets = rest[0] === "all" ? sessions : [findSession(rest[0], sessions, active)];
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
135
|
+
return EXIT_NOT_FOUND;
|
|
136
|
+
}
|
|
137
|
+
if (targets.length === 0) {
|
|
138
|
+
console.log("No diffing sessions are running for this repository.");
|
|
139
|
+
return EXIT_OK;
|
|
140
|
+
}
|
|
141
|
+
let failed = false;
|
|
142
|
+
for (const target of targets) {
|
|
143
|
+
const id = serverSessionId(target).slice(0, 8);
|
|
144
|
+
try {
|
|
145
|
+
console.error(`Stopping ${id} (${target.mode ?? "web"}, pid ${target.pid})…`);
|
|
146
|
+
await stopLockOwner(target);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
failed = true;
|
|
149
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!failed) console.log(`Stopped ${targets.length} diffing session${targets.length === 1 ? "" : "s"}.`);
|
|
153
|
+
return failed ? 1 : EXIT_OK;
|
|
154
|
+
}
|
|
155
|
+
usage();
|
|
156
|
+
return EXIT_USAGE;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { runSessionsCommand };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/lib/settings.ts
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".config", "diffing");
|
|
6
|
+
const SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
defaultMode: "web",
|
|
9
|
+
staged: true,
|
|
10
|
+
untracked: true,
|
|
11
|
+
diffStyle: "split",
|
|
12
|
+
defaultTabSize: 4,
|
|
13
|
+
theme: "rose-pine",
|
|
14
|
+
editorIDE: "default",
|
|
15
|
+
lineDiffType: "word",
|
|
16
|
+
lineWrap: false,
|
|
17
|
+
diffIndicators: "classic",
|
|
18
|
+
showLineNumbers: true,
|
|
19
|
+
hunkSeparators: "line-info",
|
|
20
|
+
lineHoverHighlight: "both",
|
|
21
|
+
fontSize: 13,
|
|
22
|
+
expandContextByDefault: false,
|
|
23
|
+
collapsedContextThreshold: 10,
|
|
24
|
+
expansionLineCount: 20,
|
|
25
|
+
haptics: true,
|
|
26
|
+
sounds: true,
|
|
27
|
+
uiFont: null,
|
|
28
|
+
monoFont: null,
|
|
29
|
+
density: "comfortable",
|
|
30
|
+
autoCollapseLineThreshold: 400,
|
|
31
|
+
requireViewAllBeforeSend: false,
|
|
32
|
+
showStatusBar: true,
|
|
33
|
+
savedReplies: [],
|
|
34
|
+
ignoreSpaceChange: false,
|
|
35
|
+
ignoreAllSpace: false
|
|
36
|
+
};
|
|
37
|
+
function loadSettings() {
|
|
38
|
+
try {
|
|
39
|
+
const data = readFileSync(SETTINGS_FILE, "utf-8");
|
|
40
|
+
const settings = {
|
|
41
|
+
...DEFAULTS,
|
|
42
|
+
...JSON.parse(data)
|
|
43
|
+
};
|
|
44
|
+
if (settings.defaultMode !== "web" && settings.defaultMode !== "tui") settings.defaultMode = DEFAULTS.defaultMode;
|
|
45
|
+
return settings;
|
|
46
|
+
} catch {
|
|
47
|
+
return { ...DEFAULTS };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function saveSettings(settings) {
|
|
51
|
+
const merged = {
|
|
52
|
+
...loadSettings(),
|
|
53
|
+
...settings
|
|
54
|
+
};
|
|
55
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
56
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2));
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { saveSettings as n, loadSettings as t };
|