@opentig/cli 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +14 -14
  3. package/dist/bin.mjs +25 -10
  4. package/dist/chunks/{cli-L6lvCZdX.mjs → cli-gyjD1t0O.mjs} +611 -268
  5. package/dist/chunks/network-settings-CdFeKFRB.mjs +142 -0
  6. package/dist/chunks/{server-Cz0gvydn.mjs → server-DMk8-Xxt.mjs} +557 -105
  7. package/dist/chunks/{service-manager-Jyx-MWo-.mjs → service-manager-BxSmV_8Z.mjs} +88 -14
  8. package/dist/client/assets/App-Bfw_TX8q.js +132 -0
  9. package/dist/client/assets/CreatePullRequestDialog-DyPd6SKH.js +2 -0
  10. package/dist/client/assets/DiffWorkspace-DCo89dF1.js +7 -0
  11. package/dist/client/assets/EditableFileViewer-Dotntfij.js +1 -0
  12. package/dist/client/assets/EditableFileViewer-svutjHZv.css +1 -0
  13. package/dist/client/assets/ExternalLink-jdtpN_yL.js +1 -0
  14. package/dist/client/assets/{File-BPxtFxHC.js → File-DHnVtIhS.js} +1 -1
  15. package/dist/client/assets/FilesView-D6f1hPVy.js +1 -0
  16. package/dist/client/assets/GitHubAvatar-B6vyoVlh.js +1 -0
  17. package/dist/client/assets/HistoryView-D9RhzCPY.js +1 -0
  18. package/dist/client/assets/HtmlFileViewer-Bwmj5umg.js +2 -0
  19. package/dist/client/assets/{IconAlertTriangle-C1OX2bTH.js → IconAlertTriangle-B9iDJKvP.js} +1 -1
  20. package/dist/client/assets/IconCheck-C-G-IA13.js +1 -0
  21. package/dist/client/assets/IconChevronDown-DU5i40lD.js +1 -0
  22. package/dist/client/assets/{IconEdit-BP5Emwzw.js → IconEdit-CZe-EJxX.js} +1 -1
  23. package/dist/client/assets/{IconFolderSymlink-BNWI0eXB.js → IconFolderSymlink-DkLEuUb6.js} +1 -1
  24. package/dist/client/assets/IconGitMerge-D9KEj1cE.js +1 -0
  25. package/dist/client/assets/IconGitPullRequest-ClaUqyVa.js +1 -0
  26. package/dist/client/assets/{IconLoader4-Bk6G4Bls.js → IconLoader4-D2VN7u1V.js} +1 -1
  27. package/dist/client/assets/{IconPlus-DUFNSK_i.js → IconPlus-DJCoD3wt.js} +1 -1
  28. package/dist/client/assets/{IconSearch-ZL3YVslZ.js → IconSearch-1VQ4P4kW.js} +1 -1
  29. package/dist/client/assets/{IconSelector-BGwY84R8.js → IconSelector-BVMU1PEt.js} +1 -1
  30. package/dist/client/assets/ImageFileViewer-3izbFVTw.css +1 -0
  31. package/dist/client/assets/ImageFileViewer-CdFLl_in.js +1 -0
  32. package/dist/client/assets/LinkedMarkdown-Bnc2OA63.js +1 -0
  33. package/dist/client/assets/LinkedText-BD6K3d1t.js +1 -0
  34. package/dist/client/assets/LocalRefsDialog-Bl-fddap.js +1 -0
  35. package/dist/client/assets/MarkdownFileViewer-NUAoQmv5.js +2 -0
  36. package/dist/client/assets/PairingPage-CqrxpC3_.js +1 -0
  37. package/dist/client/assets/PierreDiffViewer-CDfQ32e0.js +1 -0
  38. package/dist/client/assets/{PierreEditBoundary-Bt0q7SXc.js → PierreEditBoundary-DAdYNYZ3.js} +1 -1
  39. package/dist/client/assets/{PierreWorkerPool-DMFigp0u.js → PierreWorkerPool-mIDzTMB9.js} +3 -3
  40. package/dist/client/assets/PullRequestDiff-n1NZFHKN.js +1 -0
  41. package/dist/client/assets/PullRequestViewer-u89zl169.js +2 -0
  42. package/dist/client/assets/PullRequestsView-D6BqvB4E.js +1 -0
  43. package/dist/client/assets/QuickOpenDialog-BbEifGRi.js +1 -0
  44. package/dist/client/assets/RepositoryProjectsDialog-EGAsXL-Z.js +1 -0
  45. package/dist/client/assets/{SearchView-BYz1K1Ta.js → SearchView-BMz7qOdc.js} +1 -1
  46. package/dist/client/assets/SettingsDialog-DmD7HvhX.js +9 -0
  47. package/dist/client/assets/Viewer-CDj_vo6w.js +3 -0
  48. package/dist/client/assets/{abnfDiagram-YUKMZFIV-CpKAN_8n.js → abnfDiagram-YUKMZFIV-It6tW-4v.js} +1 -1
  49. package/dist/client/assets/{architecture-WOLXFQ4H-DrizVopY.js → architecture-WOLXFQ4H-CaOaChND.js} +1 -1
  50. package/dist/client/assets/{architectureDiagram-47P4ROYG-gvUijB3H.js → architectureDiagram-47P4ROYG-COtncecA.js} +1 -1
  51. package/dist/client/assets/{blockDiagram-E7TT5TSB-CdcXmnf_.js → blockDiagram-E7TT5TSB-NU4wFrfP.js} +1 -1
  52. package/dist/client/assets/{c4Diagram-CRA5TL53-DtoD1jKt.js → c4Diagram-CRA5TL53-0xzM4S8q.js} +1 -1
  53. package/dist/client/assets/channel-QED-4ACa.js +1 -0
  54. package/dist/client/assets/checkbox-Dh4T0bCe.js +1 -0
  55. package/dist/client/assets/{chunk-2BW5OAIV-Bxqf2dFQ.js → chunk-2BW5OAIV-BQMxlSNI.js} +1 -1
  56. package/dist/client/assets/{chunk-3YJQHVM4-BhzXJiq0.js → chunk-3YJQHVM4-CdP15KtA.js} +1 -1
  57. package/dist/client/assets/{chunk-4EA7E6EY-CvEILf8c.js → chunk-4EA7E6EY-xRPCCZLU.js} +1 -1
  58. package/dist/client/assets/{chunk-7TKQ45FW-pTEdzVz3.js → chunk-7TKQ45FW-Cr_UAObV.js} +1 -1
  59. package/dist/client/assets/{chunk-AW2ZBBNX-LVROkcXk.js → chunk-AW2ZBBNX-DBqqxF2h.js} +1 -1
  60. package/dist/client/assets/{chunk-DBDB3WZW-FCGrB-q8.js → chunk-DBDB3WZW-BonEIRP2.js} +1 -1
  61. package/dist/client/assets/{chunk-E2ZNV5FY-B3rGXPn-.js → chunk-E2ZNV5FY-DnKvxSiA.js} +1 -1
  62. package/dist/client/assets/{chunk-EU5HNXII-D6a_Ly_W.js → chunk-EU5HNXII-DLK0K3sR.js} +1 -1
  63. package/dist/client/assets/{chunk-FVRAUYC3-B5hy0Ket.js → chunk-FVRAUYC3-DmjKoYAh.js} +1 -1
  64. package/dist/client/assets/{chunk-HJ2JQQFS-CKHFz1Wl.js → chunk-HJ2JQQFS-AF4phse0.js} +1 -1
  65. package/dist/client/assets/{chunk-HTAEGDNF-CtwDuO27.js → chunk-HTAEGDNF-CG4pBnUZ.js} +1 -1
  66. package/dist/client/assets/{chunk-KQW6MTUR-BN3uxLYf.js → chunk-KQW6MTUR-Dd87yj5r.js} +1 -1
  67. package/dist/client/assets/{chunk-NTY3LDVX-CfXSvyIC.js → chunk-NTY3LDVX-OolU2PaP.js} +1 -1
  68. package/dist/client/assets/{chunk-VPRB5NB3-LAggcSji.js → chunk-VPRB5NB3-Dnr3edxV.js} +3 -3
  69. package/dist/client/assets/{chunk-XC4XBNZT-BksIPWQU.js → chunk-XC4XBNZT-CIqGRxkD.js} +1 -1
  70. package/dist/client/assets/{classDiagram-v2-K4WV4PDN-DH78Maqm.js → classDiagram-v2-K4WV4PDN-CPGawm8n.js} +1 -1
  71. package/dist/client/assets/createReactComponent-Ctub7J3a.js +1 -0
  72. package/dist/client/assets/{cynefin-EF2NZ3EQ-DozKlDV1.js → cynefin-EF2NZ3EQ-OA2R9WCH.js} +1 -1
  73. package/dist/client/assets/{cynefinDiagram-3GCD6N5R-A1yS7NKN.js → cynefinDiagram-3GCD6N5R-DqCVtoKt.js} +1 -1
  74. package/dist/client/assets/{dagre-W4DXFKR2-DRJ1mmbe.js → dagre-W4DXFKR2-kc3-tkAQ.js} +1 -1
  75. package/dist/client/assets/{diagram-2UJZ2QOL-B1sI2b2o.js → diagram-2UJZ2QOL-DoBZKT3s.js} +1 -1
  76. package/dist/client/assets/{diagram-OVF4WLC6-DLE1o4-P.js → diagram-OVF4WLC6-DGkyha_F.js} +1 -1
  77. package/dist/client/assets/{diagram-PFPMY2P6-UtIEsv14.js → diagram-PFPMY2P6-DpRDDgdV.js} +1 -1
  78. package/dist/client/assets/{diagram-UMYRVEAY-BwTyzVap.js → diagram-UMYRVEAY-DlsfhYv3.js} +1 -1
  79. package/dist/client/assets/{diagram-YEKJPTXX-DNPUQqu0.js → diagram-YEKJPTXX-BIhRZzBM.js} +1 -1
  80. package/dist/client/assets/{diagram-ZIFT7M5P-ClXykQ3I.js → diagram-ZIFT7M5P-mweJcgBZ.js} +1 -1
  81. package/dist/client/assets/diff-parse.worker-C1IFRYY9.js +13 -0
  82. package/dist/client/assets/{ebnfDiagram-VR2GEFS7-Dm8VAviu.js → ebnfDiagram-VR2GEFS7-Dn5zyIbZ.js} +1 -1
  83. package/dist/client/assets/{elk-IJKZMXRS-CEEoPARn.js → elk-IJKZMXRS-eBPlf1-o.js} +1 -1
  84. package/dist/client/assets/{erDiagram-O2IAPWRE-CaDE4obX.js → erDiagram-O2IAPWRE-WEE519yZ.js} +1 -1
  85. package/dist/client/assets/{eventmodeling-K75KTNOO-PjYgMkPC.js → eventmodeling-K75KTNOO-BgE_VmNy.js} +1 -1
  86. package/dist/client/assets/external-url-IUS7RmYD.js +1 -0
  87. package/dist/client/assets/flowDiagram-OXPTDLAJ-CHQYfkMg.js +1 -0
  88. package/dist/client/assets/{ganttDiagram-R7TSEDQI-CmkBarBi.js → ganttDiagram-R7TSEDQI-D_1KyJZU.js} +1 -1
  89. package/dist/client/assets/{gitGraph-VSP46ZUC-DeotTdHj.js → gitGraph-VSP46ZUC-CEbT7DA4.js} +1 -1
  90. package/dist/client/assets/{gitGraphDiagram-XJZIOB7I-uV1hPaZT.js → gitGraphDiagram-XJZIOB7I-QQyYc1gM.js} +1 -1
  91. package/dist/client/assets/index-DsKvWsTs.js +47 -0
  92. package/dist/client/assets/index-kyWCnbkb.css +2 -0
  93. package/dist/client/assets/{info-OHQRW6UA-BQDsGvSl.js → info-OHQRW6UA-ChHAnveo.js} +1 -1
  94. package/dist/client/assets/{infoDiagram-5W2HQ5XZ-CDfEPsgZ.js → infoDiagram-5W2HQ5XZ-BaxAE7UA.js} +1 -1
  95. package/dist/client/assets/{ishikawaDiagram-K3B6WC7H-Bb7PQGe6.js → ishikawaDiagram-K3B6WC7H-vhUflBE_.js} +1 -1
  96. package/dist/client/assets/{journeyDiagram-COXZFDF6-BDythglw.js → journeyDiagram-COXZFDF6-C8WWosVa.js} +1 -1
  97. package/dist/client/assets/{kanban-definition-P3RFRI5V-CvY5kXhh.js → kanban-definition-P3RFRI5V-CU6W9Yi3.js} +1 -1
  98. package/dist/client/assets/katex-CQfTPFh0.js +1 -0
  99. package/dist/client/assets/{line-99p2yFnB.js → line-Axq4vVX-.js} +1 -1
  100. package/dist/client/assets/{mermaid-parser.core-BjZYtoJ8.js → mermaid-parser.core-DNeaCX12.js} +3 -3
  101. package/dist/client/assets/{mermaid.core-DRcXnUox.js → mermaid.core-Dtt7rXw0.js} +4 -4
  102. package/dist/client/assets/{mindmap-definition-LZFPQGKD-t9LoCpnW.js → mindmap-definition-LZFPQGKD-D_8ZqQot.js} +1 -1
  103. package/dist/client/assets/{packet-JDAUHWVQ-BlR-5V7K.js → packet-JDAUHWVQ-6z7ujmGF.js} +1 -1
  104. package/dist/client/assets/{pegDiagram-BGZESJAR-CWeGI-T9.js → pegDiagram-BGZESJAR-3ujyk-bt.js} +1 -1
  105. package/dist/client/assets/{pie-XZMESJXO-BkvYHWgl.js → pie-XZMESJXO-DvpLy2DB.js} +1 -1
  106. package/dist/client/assets/{pieDiagram-CAPJLFHJ-CY5PPIr3.js → pieDiagram-CAPJLFHJ-BPMPWyc7.js} +1 -1
  107. package/dist/client/assets/{quadrantDiagram-GDMTTRAM-kqj-z271.js → quadrantDiagram-GDMTTRAM-BVUCR_lf.js} +1 -1
  108. package/dist/client/assets/{radar-ABXABTNO-DuEtYatv.js → radar-ABXABTNO-lu14X99R.js} +1 -1
  109. package/dist/client/assets/{railroad-I3PHUGI6-CVlnj2j9.js → railroad-I3PHUGI6-DI_WtDmt.js} +1 -1
  110. package/dist/client/assets/{railroad-abnf-LNEFI6M7-9Qiczw4d.js → railroad-abnf-LNEFI6M7-CmWAKZ2T.js} +1 -1
  111. package/dist/client/assets/{railroad-ebnf-DWYD2UWJ-BNL7TMEg.js → railroad-ebnf-DWYD2UWJ-BWKFU_UH.js} +1 -1
  112. package/dist/client/assets/{railroad-peg-JR7BVNR7-DdpRxgk6.js → railroad-peg-JR7BVNR7-9wkgzxMB.js} +1 -1
  113. package/dist/client/assets/{railroadDiagram-SM67HX2B-CDXvYNqW.js → railroadDiagram-SM67HX2B-4JnRbxl_.js} +1 -1
  114. package/dist/client/assets/{render-markdown-DF9Fy7MD.js → render-markdown-CQi2yKYQ.js} +2 -2
  115. package/dist/client/assets/{requirementDiagram-X7JNWC4A-BDdhqnZA.js → requirementDiagram-X7JNWC4A-CVJiZZsi.js} +1 -1
  116. package/dist/client/assets/{sankeyDiagram-UM26HJRW-Rkyk5oEf.js → sankeyDiagram-UM26HJRW-BmlWQdkv.js} +1 -1
  117. package/dist/client/assets/select-Dq3KDX2O.js +1 -0
  118. package/dist/client/assets/{sequenceDiagram-ZO4K6R2Y-Coj9ap7h.js → sequenceDiagram-ZO4K6R2Y-zUx3LmPj.js} +1 -1
  119. package/dist/client/assets/{stateDiagram-v2-TBUQTH76-BucWlahR.js → stateDiagram-v2-TBUQTH76-o_ci-2bu.js} +1 -1
  120. package/dist/client/assets/{swimlanes-N4OXWK64-tEECcFP9.js → swimlanes-N4OXWK64-BlI0GC5-.js} +1 -1
  121. package/dist/client/assets/swimlanesDiagram-K3J5GTZL-BEeJLN8d.js +8 -0
  122. package/dist/client/assets/tanstack-CVAt_k_T.js +1 -0
  123. package/dist/client/assets/{timeline-definition-YOQAKGHF-XvIbGO4d.js → timeline-definition-YOQAKGHF-DLnvF__o.js} +1 -1
  124. package/dist/client/assets/{treeView-D4JQ5SDB-DuhJpfXe.js → treeView-D4JQ5SDB-DPuMLQhB.js} +1 -1
  125. package/dist/client/assets/{treemap-SAJKECNS-CLiUWfZh.js → treemap-SAJKECNS-DjxqxJ8-.js} +1 -1
  126. package/dist/client/assets/{usecaseDiagram-VIAY4XPW-BuwkpV9w.js → usecaseDiagram-VIAY4XPW-BtnFNltN.js} +1 -1
  127. package/dist/client/assets/utils-D0T3A-kS.js +1 -0
  128. package/dist/client/assets/{vennDiagram-BLWOH2XV-kfza5Rxq.js → vennDiagram-BLWOH2XV-CAiF2FKz.js} +1 -1
  129. package/dist/client/assets/viewer-tabs-DkkqN9MD.js +1 -0
  130. package/dist/client/assets/{wardley-7MLQ67FV-CcbU_Ykg.js → wardley-7MLQ67FV-DAWySamq.js} +1 -1
  131. package/dist/client/assets/{wardleyDiagram-YMQ3BMBF-DnxlEg3a.js → wardleyDiagram-YMQ3BMBF-DiBCclEL.js} +1 -1
  132. package/dist/client/assets/{xychartDiagram-TAQBALBS-BzgtwtvK.js → xychartDiagram-TAQBALBS-B0NgfXz6.js} +1 -1
  133. package/dist/client/index.html +6 -4
  134. package/dist/dev.mjs +6 -6
  135. package/dist/manifest.json +1 -1
  136. package/dist/server.mjs +1 -1
  137. package/dist/service-update.mjs +11 -3
  138. package/dist/utility.mjs +25 -4
  139. package/package.json +4 -4
  140. package/dist/client/assets/App-DEIZhWJG.js +0 -132
  141. package/dist/client/assets/CreatePullRequestDialog-BceOUbYK.js +0 -2
  142. package/dist/client/assets/DiffWorkspace-BMtV4MMc.js +0 -6
  143. package/dist/client/assets/EditableFileViewer-B44TSuFu.css +0 -1
  144. package/dist/client/assets/EditableFileViewer-BE3AsnVa.js +0 -1
  145. package/dist/client/assets/FilesView-DoVhRfi1.js +0 -1
  146. package/dist/client/assets/GitHubAvatar-RiePq1Yf.js +0 -1
  147. package/dist/client/assets/HistoryView-Dl8uqmKe.js +0 -1
  148. package/dist/client/assets/HtmlFileViewer-xfjY1Swc.js +0 -2
  149. package/dist/client/assets/IconCheck-BPhxw_LS.js +0 -1
  150. package/dist/client/assets/IconChevronDown-C3FplJL8.js +0 -1
  151. package/dist/client/assets/ImageFileViewer-BJBOmpsf.css +0 -1
  152. package/dist/client/assets/ImageFileViewer-DXaIo7_d.js +0 -1
  153. package/dist/client/assets/LocalRefsDialog-C5J3kgTe.js +0 -1
  154. package/dist/client/assets/MarkdownFileViewer-18mvm0f2.js +0 -2
  155. package/dist/client/assets/PairingPage-BEUj3t0V.js +0 -1
  156. package/dist/client/assets/PierreDiffViewer-p29h7lW_.js +0 -1
  157. package/dist/client/assets/PullRequestDiff-C17JA4ko.js +0 -1
  158. package/dist/client/assets/PullRequestViewer-DaMtkESD.js +0 -2
  159. package/dist/client/assets/PullRequestsView-44f03VLC.js +0 -1
  160. package/dist/client/assets/QuickOpenDialog-Bi-L68PU.js +0 -1
  161. package/dist/client/assets/RepositoryProjectsDialog-DFGn74fe.js +0 -1
  162. package/dist/client/assets/SettingsDialog-CruoIiyN.js +0 -9
  163. package/dist/client/assets/Viewer-3WKpwRzo.js +0 -3
  164. package/dist/client/assets/channel-BqKQxg-1.js +0 -1
  165. package/dist/client/assets/checkbox-MDl2t9_3.js +0 -1
  166. package/dist/client/assets/createReactComponent-BfrD_oQK.js +0 -1
  167. package/dist/client/assets/flowDiagram-OXPTDLAJ-DSokwYKA.js +0 -1
  168. package/dist/client/assets/index-CoZCJqTQ.js +0 -47
  169. package/dist/client/assets/index-DUERTTRS.css +0 -2
  170. package/dist/client/assets/katex-C6e6EqXB.js +0 -1
  171. package/dist/client/assets/select-pZpCy7qn.js +0 -1
  172. package/dist/client/assets/swimlanesDiagram-K3J5GTZL-DEFKR6Vf.js +0 -8
  173. package/dist/client/assets/tanstack-CYi5knVJ.js +0 -1
  174. package/dist/client/assets/viewer-tabs-MMNrJqGE.js +0 -1
@@ -1,12 +1,13 @@
1
+ import { a as normalizePairingOrigin, c as redactSensitiveText, i as loadStartupNetwork, n as applyNetworkPatch, o as backupPathFor, r as isLoopbackHost, s as writeFileAtomically } from "./network-settings-CdFeKFRB.mjs";
1
2
  import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
2
- import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
3
- import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
4
- import fs, { appendFileSync, closeSync, constants, createReadStream, createWriteStream, openSync, readFileSync, readSync, statSync, watch, writeFileSync } from "node:fs";
5
3
  import { access, appendFile, chmod, cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
6
4
  import path, { delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
5
+ import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
6
+ import fs, { appendFileSync, closeSync, constants, createReadStream, createWriteStream, openSync, readFileSync, readSync, statSync, watch, writeFileSync } from "node:fs";
7
7
  import os, { constants as constants$1, homedir, networkInterfaces } from "node:os";
8
8
  import { fileURLToPath, pathToFileURL } from "node:url";
9
- import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlCharacters } from "node:util";
9
+ import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
10
+ import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlCharacters, types } from "node:util";
10
11
  import { StringDecoder } from "node:string_decoder";
11
12
  import tty from "node:tty";
12
13
  import { scheduler, setImmediate, setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -12931,26 +12932,6 @@ function refine(fn, _params = {}) {
12931
12932
  function superRefine(fn, params) {
12932
12933
  return /* @__PURE__ */ _superRefine(fn, params);
12933
12934
  }
12934
- //#endregion
12935
- //#region ../../src/shared/redaction.ts
12936
- /** Removes credential-shaped values before text reaches logs or clients. */
12937
- function redactSensitiveText(value) {
12938
- return value.replace(/\b(?:github_pat_|gh[opusr]_)[A-Za-z0-9_]{16,}/g, "[redacted]").replace(/\bauthorization\s*[:=]\s*(?:bearer|basic)\s+[^\s,;]+/gi, "authorization=[redacted]").replace(/\b(authorization|cookie|set-cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\b(token|secret|password)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/https?:\/\/[^\s"'<>]+/gi, (candidate) => redactUrl(candidate));
12939
- }
12940
- function redactUrl(candidate) {
12941
- try {
12942
- const url = new URL(candidate);
12943
- if (url.username || url.password) {
12944
- url.username = "[redacted]";
12945
- url.password = "";
12946
- }
12947
- url.search = "";
12948
- url.hash = "";
12949
- return url.toString();
12950
- } catch {
12951
- return "[redacted-url]";
12952
- }
12953
- }
12954
12935
  var EMPTY_AI_USAGE = {
12955
12936
  inputTokens: null,
12956
12937
  outputTokens: null,
@@ -13104,34 +13085,6 @@ function flattenWhitespace$1(value) {
13104
13085
  return flattened.replace(/ {2,}/g, " ").trim();
13105
13086
  }
13106
13087
  //#endregion
13107
- //#region ../../src/main/persistence/atomicWrite.ts
13108
- function backupPathFor(filePath) {
13109
- return `${filePath}.bak`;
13110
- }
13111
- async function writeFileAtomically(filePath, contents, options = {}) {
13112
- if (options.parseJson) JSON.parse(contents);
13113
- const directory = path.dirname(filePath);
13114
- await mkdir(directory, {
13115
- recursive: true,
13116
- ...options.directoryMode === void 0 ? {} : { mode: options.directoryMode }
13117
- });
13118
- const temporary = `${filePath}.${randomUUID()}.tmp`;
13119
- try {
13120
- const handle = await open(temporary, "wx", 384);
13121
- try {
13122
- await handle.writeFile(contents, "utf8");
13123
- await handle.sync();
13124
- } finally {
13125
- await handle.close();
13126
- }
13127
- await rename(temporary, filePath);
13128
- await chmod(filePath, 384);
13129
- } catch (error) {
13130
- await rm(temporary, { force: true });
13131
- throw error;
13132
- }
13133
- }
13134
- //#endregion
13135
13088
  //#region ../../src/main/persistence/AiLogStore.ts
13136
13089
  /**
13137
13090
  * Records a run without ever letting the attempt reach the caller. Diagnostics
@@ -17348,6 +17301,64 @@ function isGitHubHttp(value) {
17348
17301
  }
17349
17302
  }
17350
17303
  //#endregion
17304
+ //#region ../../src/main/git/ReadSingleFlight.ts
17305
+ /** Shares pending attempts only; invalidated results/errors get at most two retries. */
17306
+ var ReadSingleFlight = class {
17307
+ generations = /* @__PURE__ */ new Map();
17308
+ invalidate(scope) {
17309
+ const generation = this.generations.get(scope);
17310
+ if (generation) generation.invalidated = true;
17311
+ this.generations.delete(scope);
17312
+ }
17313
+ clear() {
17314
+ for (const scope of this.generations.keys()) this.invalidate(scope);
17315
+ }
17316
+ async run(scope, key, read, retryInvalidated = true) {
17317
+ for (let attempt = 0; attempt < 3; attempt++) {
17318
+ const { generation, promise } = this.pending(scope, key, read);
17319
+ try {
17320
+ const value = await promise;
17321
+ if (!generation.invalidated || !retryInvalidated) return value;
17322
+ } catch (error) {
17323
+ if (!generation.invalidated || !retryInvalidated) throw error;
17324
+ }
17325
+ }
17326
+ throw new Error("The repository changed repeatedly during the read. Try refreshing again.");
17327
+ }
17328
+ pending(scope, key, read) {
17329
+ let generation = this.generations.get(scope);
17330
+ if (!generation) {
17331
+ generation = {
17332
+ invalidated: false,
17333
+ flights: /* @__PURE__ */ new Map()
17334
+ };
17335
+ this.generations.set(scope, generation);
17336
+ }
17337
+ const existing = generation.flights.get(key);
17338
+ if (existing) return {
17339
+ generation,
17340
+ promise: existing
17341
+ };
17342
+ const current = generation;
17343
+ const release = () => {
17344
+ if (current.flights.get(key) === promise) current.flights.delete(key);
17345
+ if (!current.flights.size && this.generations.get(scope) === current) this.generations.delete(scope);
17346
+ };
17347
+ const promise = Promise.resolve().then(read).then((value) => {
17348
+ release();
17349
+ return value;
17350
+ }, (error) => {
17351
+ release();
17352
+ throw error;
17353
+ });
17354
+ current.flights.set(key, promise);
17355
+ return {
17356
+ generation,
17357
+ promise
17358
+ };
17359
+ }
17360
+ };
17361
+ //#endregion
17351
17362
  //#region ../../src/main/git/GitProcess.ts
17352
17363
  /**
17353
17364
  * Two spellings of the same directory must share one queue. Collapsing case and
@@ -17358,6 +17369,9 @@ function normalizeLockKey(value) {
17358
17369
  return process.platform === "win32" ? unified.toLowerCase() : unified;
17359
17370
  }
17360
17371
  var GitProcess = class {
17372
+ reads = new ReadSingleFlight();
17373
+ repositoryScopes = /* @__PURE__ */ new Map();
17374
+ invalidationListeners = /* @__PURE__ */ new Set();
17361
17375
  writeQueues = /* @__PURE__ */ new Map();
17362
17376
  activeChildren = /* @__PURE__ */ new Set();
17363
17377
  activeControllers = /* @__PURE__ */ new Set();
@@ -17369,6 +17383,32 @@ var GitProcess = class {
17369
17383
  withNetworkContext(cwd, task) {
17370
17384
  return this.networkScope ? this.networkScope(cwd, task) : task();
17371
17385
  }
17386
+ registerRepository(cwd, commonDir) {
17387
+ const key = normalizeLockKey(path.resolve(cwd));
17388
+ const scope = normalizeLockKey(path.resolve(commonDir));
17389
+ if (this.repositoryScopes.get(key) === scope) return;
17390
+ this.invalidate(cwd);
17391
+ this.repositoryScopes.set(key, scope);
17392
+ }
17393
+ unregisterRepository(cwd) {
17394
+ this.invalidate(cwd);
17395
+ this.repositoryScopes.delete(normalizeLockKey(path.resolve(cwd)));
17396
+ }
17397
+ readScope(cwd) {
17398
+ const key = normalizeLockKey(path.resolve(cwd));
17399
+ return this.repositoryScopes.get(key) ?? key;
17400
+ }
17401
+ onInvalidate(listener) {
17402
+ this.invalidationListeners.add(listener);
17403
+ }
17404
+ invalidate(cwd) {
17405
+ const scope = this.readScope(cwd);
17406
+ this.reads.invalidate(scope);
17407
+ for (const listener of this.invalidationListeners) listener(scope);
17408
+ }
17409
+ shareRead(cwd, key, read, retryInvalidated = true) {
17410
+ return this.reads.run(this.readScope(cwd), JSON.stringify([path.resolve(cwd), key]), read, retryInvalidated);
17411
+ }
17372
17412
  run(cwd, args, options) {
17373
17413
  let index = 0;
17374
17414
  while (args[index] === "-c") index += 2;
@@ -17376,13 +17416,28 @@ var GitProcess = class {
17376
17416
  "fetch",
17377
17417
  "push",
17378
17418
  "ls-remote"
17379
- ].includes(args[index] ?? "")) return this.network(cwd, args, options, (next, settings) => this.runDirect(cwd, next, settings));
17419
+ ].includes(args[index] ?? "")) return this.network(cwd, args, options, (next, settings) => this.runDirect(cwd, next, settings, false));
17380
17420
  return this.runDirect(cwd, args, options);
17381
17421
  }
17382
- runDirect(cwd, args, options) {
17422
+ runDirect(cwd, args, options, retryInvalidated = true) {
17383
17423
  const globalArgs = ["--no-pager"];
17384
17424
  if (options.readOnly) globalArgs.push("--no-optional-locks");
17385
- return this.spawnGit(cwd, [...globalArgs, ...args], options);
17425
+ const execute = () => this.spawnGit(cwd, [...globalArgs, ...args], options);
17426
+ if (options.readOnly) {
17427
+ if ("signal" in options && options.signal != null || "onStderr" in options && options.onStderr != null) return execute();
17428
+ const key = createHash("sha256").update(JSON.stringify([
17429
+ args,
17430
+ options,
17431
+ this.authenticationKey?.(cwd)
17432
+ ])).digest("hex");
17433
+ return this.shareRead(cwd, `git:${key}`, execute, retryInvalidated).then((output) => ({
17434
+ ...output,
17435
+ stdout: Buffer.from(output.stdout),
17436
+ stderr: Buffer.from(output.stderr)
17437
+ }));
17438
+ }
17439
+ this.invalidate(cwd);
17440
+ return execute().finally(() => this.invalidate(cwd));
17386
17441
  }
17387
17442
  /**
17388
17443
  * `lockKey` serializes against a queue other than `cwd` without changing the
@@ -17406,9 +17461,11 @@ var GitProcess = class {
17406
17461
  const queued = previous.then(() => current);
17407
17462
  this.writeQueues.set(key, queued);
17408
17463
  await previous;
17464
+ this.invalidate(rawKey);
17409
17465
  try {
17410
17466
  return await task();
17411
17467
  } finally {
17468
+ this.invalidate(rawKey);
17412
17469
  release();
17413
17470
  if (this.writeQueues.get(key) === queued) this.writeQueues.delete(key);
17414
17471
  }
@@ -20525,6 +20582,7 @@ var RepositoryService = class {
20525
20582
  async preparePath(selectedPath) {
20526
20583
  const repository = await this.inspectPath(selectedPath);
20527
20584
  this.repositories.set(repository.id, repository);
20585
+ this.git.registerRepository(repository.path, repository.commonDir);
20528
20586
  return repository;
20529
20587
  }
20530
20588
  async completeSetup(id, validate = () => void 0) {
@@ -20541,6 +20599,7 @@ var RepositoryService = class {
20541
20599
  operation: "relocate-repository",
20542
20600
  message: "The recent repository no longer exists."
20543
20601
  });
20602
+ const previous = this.get(id);
20544
20603
  const repository = await this.inspectPath(selectedPath);
20545
20604
  if (repository.id !== id && this.settings.recentRepositories.some((item) => item.id === repository.id)) throw new GitOperationError({
20546
20605
  code: "INVALID_ARGUMENT",
@@ -20548,14 +20607,19 @@ var RepositoryService = class {
20548
20607
  message: "That folder is already in OpenTig. Remove the old entry instead, or choose a different location."
20549
20608
  });
20550
20609
  await this.settings.relocateRepository(id, repository);
20610
+ this.git.unregisterRepository(previous.path);
20551
20611
  this.repositories.delete(id);
20552
20612
  this.repositories.set(repository.id, repository);
20613
+ this.git.registerRepository(repository.path, repository.commonDir);
20553
20614
  return repository;
20554
20615
  }
20555
20616
  async forget(repositoryKey) {
20556
20617
  const key = normalizeRepositoryKey(repositoryKey);
20557
20618
  const organization = await this.settings.forgetRepository(key);
20558
- for (const [id, repository] of this.repositories) if (normalizeRepositoryKey(repository.commonDir) === key) this.repositories.delete(id);
20619
+ for (const [id, repository] of this.repositories) if (normalizeRepositoryKey(repository.commonDir) === key) {
20620
+ this.git.unregisterRepository(repository.path);
20621
+ this.repositories.delete(id);
20622
+ }
20559
20623
  return organization;
20560
20624
  }
20561
20625
  recent(id) {
@@ -20602,6 +20666,7 @@ var RepositoryService = class {
20602
20666
  commonDir: recent.commonDir
20603
20667
  };
20604
20668
  this.repositories.set(id, hydrated);
20669
+ this.git.registerRepository(hydrated.path, hydrated.commonDir);
20605
20670
  return hydrated;
20606
20671
  }
20607
20672
  throw new GitOperationError({
@@ -20614,6 +20679,16 @@ var RepositoryService = class {
20614
20679
  return this.settings.recentRepositories;
20615
20680
  }
20616
20681
  async status(id, includeStats = true) {
20682
+ const repository = this.get(id);
20683
+ return structuredClone(await this.git.shareRead(repository.path, `status:${includeStats}`, () => this.readStatus(id, includeStats)));
20684
+ }
20685
+ invalidate(id) {
20686
+ this.git.invalidate(this.get(id).path);
20687
+ }
20688
+ invalidateIfKnown(id) {
20689
+ if (typeof id === "string" && (this.repositories.has(id) || this.recent(id))) this.invalidate(id);
20690
+ }
20691
+ async readStatus(id, includeStats) {
20617
20692
  const repository = this.get(id);
20618
20693
  const status = parseStatus((await this.git.run(repository.path, [
20619
20694
  "status",
@@ -20643,7 +20718,10 @@ var RepositoryService = class {
20643
20718
  */
20644
20719
  async forgetWorktree(worktreePath) {
20645
20720
  const target = normalizeWorktreePath$1(worktreePath);
20646
- for (const [id, repository] of this.repositories) if (normalizeWorktreePath$1(repository.path) === target) this.repositories.delete(id);
20721
+ for (const [id, repository] of this.repositories) if (normalizeWorktreePath$1(repository.path) === target) {
20722
+ this.git.unregisterRepository(repository.path);
20723
+ this.repositories.delete(id);
20724
+ }
20647
20725
  return this.settings.forgetWorktreePath(worktreePath);
20648
20726
  }
20649
20727
  /**
@@ -21851,6 +21929,7 @@ var GitHubService = class {
21851
21929
  runner;
21852
21930
  git;
21853
21931
  repositories;
21932
+ reads = new ReadSingleFlight();
21854
21933
  gitAccess;
21855
21934
  accounts;
21856
21935
  constructor(resolver, runner, git, repositories, accounts) {
@@ -21858,9 +21937,37 @@ var GitHubService = class {
21858
21937
  this.git = git;
21859
21938
  this.repositories = repositories;
21860
21939
  this.accounts = accounts ?? new GitHubAccountsService(resolver, runner, git);
21940
+ this.git.onInvalidate((scope) => this.reads.invalidate(scope));
21941
+ }
21942
+ invalidate(repositoryId) {
21943
+ if (repositoryId) this.reads.invalidate(this.git.readScope(this.repositories.get(repositoryId).path));
21944
+ else this.reads.clear();
21945
+ }
21946
+ async shareRead(repositoryId, resource, read) {
21947
+ for (let attempt = 0; attempt < 3; attempt++) {
21948
+ const repository = this.repositories.get(repositoryId);
21949
+ const revision = this.accounts.authRevision;
21950
+ const key = JSON.stringify([
21951
+ repository.path,
21952
+ repository.commonDir,
21953
+ revision,
21954
+ this.accounts.revisionFor(repository),
21955
+ ...resource
21956
+ ]);
21957
+ try {
21958
+ const value = await this.reads.run(this.git.readScope(repository.path), key, read);
21959
+ if (revision === this.accounts.authRevision) return structuredClone(value);
21960
+ } catch (error) {
21961
+ if (revision === this.accounts.authRevision) throw error;
21962
+ }
21963
+ }
21964
+ throw new Error("The GitHub account changed repeatedly during the read. Try refreshing again.");
21861
21965
  }
21862
21966
  async status(forceRefresh = false) {
21863
- if (forceRefresh) this.accounts.refreshAuthentication();
21967
+ if (forceRefresh) {
21968
+ this.invalidate();
21969
+ this.accounts.refreshAuthentication();
21970
+ }
21864
21971
  const detected = await this.accounts.detect(forceRefresh);
21865
21972
  return {
21866
21973
  installed: !!detected.candidate,
@@ -21871,6 +21978,7 @@ var GitHubService = class {
21871
21978
  };
21872
21979
  }
21873
21980
  async repositoryAccount(repositoryId, forceRefresh = false) {
21981
+ if (forceRefresh) this.invalidate(repositoryId);
21874
21982
  const repository = this.repositories.get(repositoryId);
21875
21983
  const info = await this.repositoryInfo(repositoryId);
21876
21984
  const remote = await this.remote(repositoryId);
@@ -21911,9 +22019,23 @@ var GitHubService = class {
21911
22019
  return "";
21912
22020
  }
21913
22021
  }
21914
- async repositoryInfo(repositoryId) {
22022
+ repositoryInfo(repositoryId) {
22023
+ return this.shareRead(repositoryId, [], () => this.readRepositoryInfo(repositoryId));
22024
+ }
22025
+ async readRepositoryInfo(repositoryId) {
22026
+ return (await this.routing(repositoryId)).info;
22027
+ }
22028
+ routing(repositoryId) {
22029
+ return this.shareRead(repositoryId, ["routing"], async () => {
22030
+ const remote = await this.remote(repositoryId);
22031
+ return {
22032
+ remote,
22033
+ info: await this.infoForRemote(remote)
22034
+ };
22035
+ });
22036
+ }
22037
+ async infoForRemote(url) {
21915
22038
  try {
21916
- const url = await this.remote(repositoryId);
21917
22039
  let parsed = parseGitHubRemote(url);
21918
22040
  if (!parsed) {
21919
22041
  const ssh = parseSshRemote(url);
@@ -21946,9 +22068,12 @@ var GitHubService = class {
21946
22068
  };
21947
22069
  }
21948
22070
  }
21949
- async listPullRequests(repositoryId, states) {
22071
+ listPullRequests(repositoryId, states) {
22072
+ return this.shareRead(repositoryId, ["list", [...states].sort()], () => this.readListPullRequests(repositoryId, states));
22073
+ }
22074
+ async readListPullRequests(repositoryId, states) {
21950
22075
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-list");
21951
- const results = await Promise.all(states.map((state) => this.runGh([
22076
+ const results = await Promise.all(states.map((state) => this.runGhRead([
21952
22077
  "pr",
21953
22078
  "list",
21954
22079
  "-R",
@@ -21969,11 +22094,14 @@ var GitHubService = class {
21969
22094
  })));
21970
22095
  return this.withStackMembership(repository.path, nameWithOwner, auth, executable, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
21971
22096
  }
21972
- async findPullRequestForBranch(repositoryId, branchName) {
22097
+ findPullRequestForBranch(repositoryId, branchName) {
22098
+ return this.shareRead(repositoryId, ["branch", branchName], () => this.readFindPullRequestForBranch(repositoryId, branchName));
22099
+ }
22100
+ async readFindPullRequestForBranch(repositoryId, branchName) {
21973
22101
  const operation = "gh-pr-for-branch";
21974
22102
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
21975
22103
  const branch = validateRef(branchName, operation);
21976
- const openPull = sortPullRequestsNewestFirst(parseBranchPullRequestList((await this.runGh([
22104
+ const openPull = sortPullRequestsNewestFirst(parseBranchPullRequestList((await this.runGhRead([
21977
22105
  "pr",
21978
22106
  "list",
21979
22107
  "-R",
@@ -21994,8 +22122,8 @@ var GitHubService = class {
21994
22122
  timeoutMs: NETWORK_TIMEOUT_MS,
21995
22123
  maxOutputBytes: 1048576
21996
22124
  })).stdout, nameWithOwner, branch)).find((pull) => pull.state === "OPEN");
21997
- if (openPull) return openPull;
21998
- return sortPullRequestsNewestFirst(parseBranchPullRequestList((await this.runGh([
22125
+ if (openPull) return (await this.withStackMembership(repository.path, nameWithOwner, auth, executable, [openPull]))[0];
22126
+ const pull = sortPullRequestsNewestFirst(parseBranchPullRequestList((await this.runGhRead([
21999
22127
  "pr",
22000
22128
  "list",
22001
22129
  "-R",
@@ -22015,11 +22143,15 @@ var GitHubService = class {
22015
22143
  operation,
22016
22144
  timeoutMs: NETWORK_TIMEOUT_MS,
22017
22145
  maxOutputBytes: 1048576
22018
- })).stdout, nameWithOwner, branch))[0] ?? null;
22146
+ })).stdout, nameWithOwner, branch))[0];
22147
+ return pull ? (await this.withStackMembership(repository.path, nameWithOwner, auth, executable, [pull]))[0] : null;
22148
+ }
22149
+ getPullRequest(repositoryId, prNumber) {
22150
+ return this.shareRead(repositoryId, ["details", prNumber], () => this.readGetPullRequest(repositoryId, prNumber));
22019
22151
  }
22020
- async getPullRequest(repositoryId, prNumber) {
22152
+ async readGetPullRequest(repositoryId, prNumber) {
22021
22153
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-view");
22022
- const details = parsePullRequestDetails((await this.runGh([
22154
+ const details = parsePullRequestDetails((await this.runGhRead([
22023
22155
  "pr",
22024
22156
  "view",
22025
22157
  String(prNumber),
@@ -22044,7 +22176,7 @@ var GitHubService = class {
22044
22176
  const batch = pulls.slice(start, start + 25);
22045
22177
  try {
22046
22178
  const numbers = batch.map((pr) => pr.number);
22047
- const memberships = parseStackMemberships((await this.runGh([
22179
+ const memberships = parseStackMemberships((await this.runGhRead([
22048
22180
  "api",
22049
22181
  "graphql",
22050
22182
  "-f",
@@ -22074,7 +22206,10 @@ var GitHubService = class {
22074
22206
  }
22075
22207
  return enriched;
22076
22208
  }
22077
- async getPullRequestStack(repositoryId, prNumber) {
22209
+ getPullRequestStack(repositoryId, prNumber) {
22210
+ return this.shareRead(repositoryId, ["stack", prNumber], () => this.readGetPullRequestStack(repositoryId, prNumber));
22211
+ }
22212
+ async readGetPullRequestStack(repositoryId, prNumber) {
22078
22213
  const operation = "gh-pr-stack";
22079
22214
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
22080
22215
  const options = {
@@ -22085,7 +22220,7 @@ var GitHubService = class {
22085
22220
  timeoutMs: NETWORK_TIMEOUT_MS,
22086
22221
  maxOutputBytes: 4194304
22087
22222
  };
22088
- const read = async (endpoint) => this.runGh(["api", endpoint], options);
22223
+ const read = async (endpoint) => this.runGhRead(["api", endpoint], options);
22089
22224
  let listing;
22090
22225
  try {
22091
22226
  listing = await read(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
@@ -22103,9 +22238,12 @@ var GitHubService = class {
22103
22238
  });
22104
22239
  return complete;
22105
22240
  }
22106
- async getPullRequestDiff(repositoryId, prNumber) {
22241
+ getPullRequestDiff(repositoryId, prNumber) {
22242
+ return this.shareRead(repositoryId, ["diff", prNumber], () => this.readGetPullRequestDiff(repositoryId, prNumber));
22243
+ }
22244
+ async readGetPullRequestDiff(repositoryId, prNumber) {
22107
22245
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-diff");
22108
- const patch = (await this.runGh([
22246
+ const patch = (await this.runGhRead([
22109
22247
  "pr",
22110
22248
  "diff",
22111
22249
  String(prNumber),
@@ -22128,7 +22266,10 @@ var GitHubService = class {
22128
22266
  lineCount: patch ? patch.split("\n").length : 0
22129
22267
  };
22130
22268
  }
22131
- async getPullRequestCommitDiff(repositoryId, oid) {
22269
+ getPullRequestCommitDiff(repositoryId, oid) {
22270
+ return this.shareRead(repositoryId, ["commit-diff", oid], () => this.readGetPullRequestCommitDiff(repositoryId, oid));
22271
+ }
22272
+ async readGetPullRequestCommitDiff(repositoryId, oid) {
22132
22273
  const operation = "gh-pr-commit-diff";
22133
22274
  const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
22134
22275
  if (!/^[0-9a-f]{40,64}$/i.test(oid)) throw new GhOperationError({
@@ -22136,7 +22277,7 @@ var GitHubService = class {
22136
22277
  operation,
22137
22278
  message: "The commit is not valid."
22138
22279
  });
22139
- const patch = (await this.runGh([
22280
+ const patch = (await this.runGhRead([
22140
22281
  "api",
22141
22282
  `repos/${nameWithOwner}/commits/${oid}`,
22142
22283
  "--header",
@@ -22215,14 +22356,21 @@ var GitHubService = class {
22215
22356
  ];
22216
22357
  if (input.draft) args.push("--draft");
22217
22358
  verifyAccount();
22218
- const created = parseCreatedPullRequestUrl((await this.runGh(args, {
22219
- auth,
22220
- executable,
22221
- cwd: repository.path,
22222
- operation,
22223
- timeoutMs: 12e4,
22224
- maxOutputBytes: 1048576
22225
- })).stdout);
22359
+ this.invalidate(input.repositoryId);
22360
+ let result;
22361
+ try {
22362
+ result = await this.runGh(args, {
22363
+ auth,
22364
+ executable,
22365
+ cwd: repository.path,
22366
+ operation,
22367
+ timeoutMs: 12e4,
22368
+ maxOutputBytes: 1048576
22369
+ });
22370
+ } finally {
22371
+ this.invalidate(input.repositoryId);
22372
+ }
22373
+ const created = parseCreatedPullRequestUrl(result.stdout);
22226
22374
  if (!created) throw new GhOperationError({
22227
22375
  code: "GH_INVALID_OUTPUT",
22228
22376
  operation,
@@ -22238,13 +22386,12 @@ var GitHubService = class {
22238
22386
  }
22239
22387
  async requireGitHub(repositoryId, operation) {
22240
22388
  const repository = this.repositories.get(repositoryId);
22241
- const info = await this.repositoryInfo(repositoryId);
22389
+ const { remote, info } = await this.routing(repositoryId);
22242
22390
  if (!info.isGitHub || !info.nameWithOwner) throw new GhOperationError({
22243
22391
  code: "GH_NOT_GITHUB_REPO",
22244
22392
  operation,
22245
22393
  message: "The origin remote does not point to a GitHub repository."
22246
22394
  });
22247
- const remote = await this.remote(repositoryId);
22248
22395
  const authenticated = await this.accounts.authenticate(repository, remote, info.nameWithOwner, operation);
22249
22396
  return {
22250
22397
  repository,
@@ -22252,6 +22399,10 @@ var GitHubService = class {
22252
22399
  ...authenticated
22253
22400
  };
22254
22401
  }
22402
+ runGhRead(args, options) {
22403
+ const key = createHash("sha256").update(JSON.stringify([args, options])).digest("hex");
22404
+ return this.reads.run(this.git.readScope(options.cwd), `command:${key}`, () => this.runGh(args, options), false);
22405
+ }
22255
22406
  async runGh(args, options) {
22256
22407
  const result = await this.run(options.executable, args, options);
22257
22408
  if (result.exitCode !== 0) throw classifyFailure({
@@ -22975,6 +23126,11 @@ function normalizeRemoteFetchIntervalSeconds(value) {
22975
23126
  const stepped = Math.round(rounded / 5) * 5;
22976
23127
  return Math.min(300, Math.max(5, stepped));
22977
23128
  }
23129
+ //#endregion
23130
+ //#region ../../src/main/window/desktop-window-size.ts
23131
+ /** Smallest restored desktop width that keeps the toolbar on one row.
23132
+ * Renderer wrapping starts at max-width 1100px in styles/mobile.css. */
23133
+ var DESKTOP_MIN_WINDOW_WIDTH = 1104;
22978
23134
  var defaults = {
22979
23135
  version: 2,
22980
23136
  githubAccounts: {},
@@ -23041,6 +23197,7 @@ var SettingsStore = class {
23041
23197
  loaded = false;
23042
23198
  repositoryAiWriteVersions = /* @__PURE__ */ new Map();
23043
23199
  pendingWrite = Promise.resolve();
23200
+ persistedSnapshot = null;
23044
23201
  backgroundTimers = /* @__PURE__ */ new Map();
23045
23202
  backgroundDirty = /* @__PURE__ */ new Set();
23046
23203
  lastBackgroundWriteError = null;
@@ -23075,6 +23232,7 @@ var SettingsStore = class {
23075
23232
  }
23076
23233
  const backup = await readJsonDocument(backupPathFor(this.filePath));
23077
23234
  if (!backup.ok || backup.raw !== snapshot) await writeFileAtomically(backupPathFor(this.filePath), snapshot, { parseJson: true });
23235
+ this.persistedSnapshot = snapshot;
23078
23236
  }
23079
23237
  get recentRepositories() {
23080
23238
  return [...this.data.recentRepositories];
@@ -23455,8 +23613,11 @@ var SettingsStore = class {
23455
23613
  enqueueWrite() {
23456
23614
  const snapshot = this.snapshot();
23457
23615
  const write = this.pendingWrite.then(async () => {
23616
+ if (snapshot === this.persistedSnapshot) return;
23617
+ this.persistedSnapshot = null;
23458
23618
  await writeFileAtomically(this.filePath, snapshot, { parseJson: true });
23459
23619
  await writeFileAtomically(backupPathFor(this.filePath), snapshot, { parseJson: true });
23620
+ this.persistedSnapshot = snapshot;
23460
23621
  });
23461
23622
  this.pendingWrite = write.catch(() => void 0);
23462
23623
  return write;
@@ -23529,7 +23690,7 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
23529
23690
  const parsedBounds = windowBoundsRecordSchema.safeParse(input.windowBounds);
23530
23691
  const bounds = parsedBounds.success ? parsedBounds.data : null;
23531
23692
  const windowBounds = bounds && typeof bounds.width === "number" && Number.isFinite(bounds.width) && typeof bounds.height === "number" && Number.isFinite(bounds.height) ? {
23532
- width: Math.max(900, bounds.width),
23693
+ width: Math.max(DESKTOP_MIN_WINDOW_WIDTH, bounds.width),
23533
23694
  height: Math.max(600, bounds.height),
23534
23695
  ...typeof bounds.x === "number" && Number.isFinite(bounds.x) ? { x: bounds.x } : {},
23535
23696
  ...typeof bounds.y === "number" && Number.isFinite(bounds.y) ? { y: bounds.y } : {}
@@ -23777,6 +23938,7 @@ var OpenTigRuntime = class {
23777
23938
  }
23778
23939
  publishRepositoryChange(repositoryId, scope) {
23779
23940
  if (this.closePromise) return;
23941
+ this.services.repositories.invalidateIfKnown(repositoryId);
23780
23942
  const elapsed = Date.now() - this.lastNotifiedAt;
23781
23943
  if (elapsed < 1e3) {
23782
23944
  this.trailingChange = this.trailingChange?.repositoryId === repositoryId ? {
@@ -24989,11 +25151,26 @@ function orderingIndexArg(value, operation) {
24989
25151
  //#region ../../src/main/runtime/registerServerCommands.ts
24990
25152
  function registerServerCommands(registry, services, host) {
24991
25153
  const serverDefinitions = new Map(Object.values(OPEN_TIG_SERVER_COMMANDS).map((definition) => [definition.command, definition]));
25154
+ const repositoryMutations = new Set(Object.entries(OPEN_TIG_SERVER_COMMANDS).filter(([method, definition]) => definition.mutation && [
25155
+ "repository.",
25156
+ "index.",
25157
+ "commits.",
25158
+ "refs."
25159
+ ].some((prefix) => method.startsWith(prefix))).map(([, definition]) => definition.command));
24992
25160
  const handleWithContext = (channel, operation, handler) => {
24993
25161
  const definition = serverDefinitions.get(channel);
24994
25162
  if (!definition) throw new Error(`Not a server command: ${channel}`);
24995
25163
  if (definition.operation !== operation) throw new Error(`Operation mismatch for ${channel}: ${operation}`);
24996
- registry.registerHandler(definition, (context, args) => handler(context, ...args));
25164
+ registry.registerHandler(definition, async (context, args) => {
25165
+ const first = args[0];
25166
+ const repositoryId = typeof first === "object" && first !== null && "repositoryId" in first ? first.repositoryId : first;
25167
+ if (repositoryMutations.has(channel)) services.repositories.invalidateIfKnown(repositoryId);
25168
+ try {
25169
+ return await handler(context, ...args);
25170
+ } finally {
25171
+ if (repositoryMutations.has(channel)) services.repositories.invalidateIfKnown(repositoryId);
25172
+ }
25173
+ });
24997
25174
  };
24998
25175
  const handle = (channel, operation, handler) => {
24999
25176
  handleWithContext(channel, operation, (_context, ...args) => handler(...args));
@@ -25470,6 +25647,8 @@ function clipboardImageArg(value) {
25470
25647
  //#endregion
25471
25648
  //#region ../../src/shared/server-protocol.ts
25472
25649
  var OPEN_TIG_PROTOCOL_VERSION = { protocolVersion: 4 }.protocolVersion;
25650
+ /** UTF-8 JSON bytes, including the envelope and escaped strings. */
25651
+ var OPEN_TIG_MAX_SERVER_MESSAGE_BYTES = 4194304;
25473
25652
  //#endregion
25474
25653
  //#region ../../src/shared/application-profile.ts
25475
25654
  function applicationName(profile) {
@@ -26327,7 +26506,11 @@ async function handleRequest(context, request, response) {
26327
26506
  listeningOnLan: !isLoopbackAddress(listener.host),
26328
26507
  listenerHost: listener.host,
26329
26508
  actualPort: listener.port,
26330
- ready: context.isReady()
26509
+ ready: context.isReady(),
26510
+ ...context.webAccess ? {
26511
+ configuration: context.webAccess.getStatus(),
26512
+ recoveryCommand: context.webAccess.getStatus().recoveryCommand
26513
+ } : {}
26331
26514
  });
26332
26515
  }
26333
26516
  if (method === "GET" && rawPath === "/api/auth/sessions") {
@@ -26343,9 +26526,20 @@ async function handleRequest(context, request, response) {
26343
26526
  };
26344
26527
  }) });
26345
26528
  }
26346
- if (method === "POST" && rawPath === "/api/admin/pair") return handleLocalAdminPair(context, request, response);
26529
+ if (method === "POST" && (rawPath === "/api/admin/pair" || rawPath === "/api/admin/status")) return handleLocalAdminRequest(context, request, response, rawPath === "/api/admin/status");
26347
26530
  if (method === "POST" && rawPath.startsWith("/api/")) {
26348
26531
  if (!isAllowedOrigin(request)) return sendJson(response, 403, { error: "Forbidden origin." });
26532
+ if (rawPath === "/api/auth/web-access") {
26533
+ if (!context.auth.authenticate(request.headers)) return sendJson(response, 401, { error: "Authentication required." });
26534
+ if (!context.webAccess) return sendJson(response, 503, { error: "Web access settings are unavailable." });
26535
+ const body = await readJsonObject(request, response);
26536
+ if (!body) return;
26537
+ try {
26538
+ return sendJson(response, 200, await context.webAccess.update(body), { Connection: "close" });
26539
+ } catch (error) {
26540
+ return sendJson(response, 400, { error: error instanceof Error ? error.message : "Could not change web access." }, { Connection: "close" });
26541
+ }
26542
+ }
26349
26543
  if (rawPath === "/api/html-preview") return handleHtmlPreview(context, request, response);
26350
26544
  if (rawPath === "/api/auth/pairing-link") {
26351
26545
  if (!context.auth.authenticate(request.headers)) return sendJson(response, 401, { error: "Authentication required." });
@@ -26354,7 +26548,17 @@ async function handleRequest(context, request, response) {
26354
26548
  code: "WEB_ACCESS_DISABLED"
26355
26549
  });
26356
26550
  if (!context.isReady()) return sendJson(response, 503, { error: "Server is not ready." });
26357
- const url = new URL("/pair", request.headers.origin);
26551
+ const body = request.headers["content-length"] && request.headers["content-length"] !== "0" || request.headers["transfer-encoding"] ? await readJsonObject(request, response) : {};
26552
+ if (!body) return;
26553
+ let endpoint;
26554
+ try {
26555
+ endpoint = body.endpoint === void 0 ? request.headers.origin : normalizePairingOrigin(typeof body.endpoint === "string" ? body.endpoint : "");
26556
+ } catch {
26557
+ return sendJson(response, 400, { error: "Enter a valid pairing endpoint." });
26558
+ }
26559
+ const status = context.webAccess?.getStatus();
26560
+ if (!endpoint || endpoint !== request.headers.origin && !status?.pairingEndpoints.includes(endpoint)) return sendJson(response, 400, { error: "Select an active pairing endpoint or save its domain." });
26561
+ const url = new URL("/pair", endpoint);
26358
26562
  const pairing = context.auth.createPairingToken();
26359
26563
  url.hash = new URLSearchParams({ token: pairing.token }).toString();
26360
26564
  return sendJson(response, 200, {
@@ -26519,14 +26723,20 @@ function isLoopbackAddress(value) {
26519
26723
  const normalized = value.startsWith("::ffff:") ? value.slice(7) : value;
26520
26724
  return normalized === "::1" || normalized.startsWith("127.");
26521
26725
  }
26522
- async function handleLocalAdminPair(context, request, response) {
26726
+ async function handleLocalAdminRequest(context, request, response, statusOnly = false) {
26523
26727
  const admin = context.admin;
26524
26728
  const suppliedToken = request.headers["x-opentig-admin"];
26525
26729
  if (!admin || !isLocalAddress(request.socket.remoteAddress) || typeof suppliedToken !== "string" || !sameSecret(admin.token, suppliedToken)) return sendJson(response, 404, { error: "Not found." });
26526
26730
  const body = await readJsonObject(request, response);
26527
26731
  if (!body) return;
26528
- if (body.instanceId !== admin.instanceId || typeof body.publicOrigin !== "string") return sendJson(response, 409, { error: "OpenTig server identity mismatch." });
26529
- const origin = normalizePublicOrigin(body.publicOrigin);
26732
+ if (body.instanceId !== admin.instanceId) return sendJson(response, 409, { error: "OpenTig server identity mismatch." });
26733
+ if (statusOnly) return sendJson(response, context.isReady() ? 200 : 503, {
26734
+ status: context.isReady() ? "ready" : "not-ready",
26735
+ ...context.identity,
26736
+ instanceId: admin.instanceId,
26737
+ pid: process.pid
26738
+ });
26739
+ const origin = typeof body.publicOrigin === "string" ? normalizePublicOrigin(body.publicOrigin) : null;
26530
26740
  if (!origin) return sendJson(response, 400, { error: "Invalid public origin." });
26531
26741
  if (!context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, {
26532
26742
  error: "Web access is disabled.",
@@ -26684,6 +26894,64 @@ function redactOperationResult(result) {
26684
26894
  };
26685
26895
  }
26686
26896
  //#endregion
26897
+ //#region src/bounded-json.ts
26898
+ /** Stop JSON.stringify before it builds an oversized wire message. */
26899
+ var JsonByteLimitError = class extends Error {};
26900
+ function stringifyBounded(value, maxBytes) {
26901
+ let bytes = 0;
26902
+ let root = true;
26903
+ const populated = /* @__PURE__ */ new WeakSet();
26904
+ const add = (count) => {
26905
+ bytes += count;
26906
+ if (bytes > maxBytes) throw new JsonByteLimitError("JSON byte limit exceeded.");
26907
+ };
26908
+ const text = JSON.stringify(value, function(key, entry) {
26909
+ if (typeof entry === "object" && entry !== null && types.isBoxedPrimitive(entry)) {
26910
+ if (types.isNumberObject(entry)) entry = +entry;
26911
+ else if (types.isStringObject(entry)) entry = String(entry);
26912
+ else if (types.isBooleanObject(entry)) entry = Boolean.prototype.valueOf.call(entry);
26913
+ }
26914
+ const omitted = entry === void 0 || typeof entry === "function" || typeof entry === "symbol";
26915
+ const isRoot = root;
26916
+ root = false;
26917
+ if (omitted && !Array.isArray(this)) return entry;
26918
+ if (!isRoot) {
26919
+ if (populated.has(this)) add(1);
26920
+ else populated.add(this);
26921
+ if (!Array.isArray(this)) add(stringBytes(key, maxBytes - bytes) + 1);
26922
+ }
26923
+ if (typeof entry === "string") add(stringBytes(entry, maxBytes - bytes));
26924
+ else if (entry === null || omitted) add(4);
26925
+ else if (typeof entry === "number") add(Number.isFinite(entry) ? String(entry).length : 4);
26926
+ else if (typeof entry === "boolean") add(entry ? 4 : 5);
26927
+ else if (typeof entry === "object") {
26928
+ add(2);
26929
+ populated.delete(entry);
26930
+ }
26931
+ return entry;
26932
+ });
26933
+ if (text === void 0) throw new TypeError("Expected a JSON value.");
26934
+ return {
26935
+ text,
26936
+ bytes
26937
+ };
26938
+ }
26939
+ function stringBytes(value, remaining) {
26940
+ let bytes = Buffer.byteLength(value) + 2;
26941
+ if (bytes > remaining) throw new JsonByteLimitError("JSON byte limit exceeded.");
26942
+ if (!/[\u0000-\u001f"\\\ud800-\udfff]/.test(value)) return bytes;
26943
+ bytes = 2;
26944
+ for (let start = 0; start < value.length;) {
26945
+ let end = Math.min(start + 16384, value.length);
26946
+ const previous = value.charCodeAt(end - 1), next = value.charCodeAt(end);
26947
+ if (previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343) end++;
26948
+ bytes += Buffer.byteLength(JSON.stringify(value.slice(start, end))) - 2;
26949
+ if (bytes > remaining) throw new JsonByteLimitError("JSON byte limit exceeded.");
26950
+ start = end;
26951
+ }
26952
+ return bytes;
26953
+ }
26954
+ //#endregion
26687
26955
  //#region src/websocket.ts
26688
26956
  var NORMAL_FRAME_LIMIT = 1048576;
26689
26957
  var CONTENT_FRAME_LIMIT = 9437184;
@@ -26706,6 +26974,7 @@ var OpenTigWebSocketTransport = class {
26706
26974
  connectionLimit;
26707
26975
  requestRateLimit;
26708
26976
  requestRateWindowMs;
26977
+ measureTransport = process.env.OPENTIG_PERF_LOG === "1";
26709
26978
  heartbeat;
26710
26979
  upgradeHandler;
26711
26980
  closePromise = null;
@@ -26746,6 +27015,10 @@ var OpenTigWebSocketTransport = class {
26746
27015
  this.closePromise ??= this.closeTransport();
26747
27016
  return this.closePromise;
26748
27017
  }
27018
+ /** Keep authentication and runtime alive while clients reconnect to a new bind. */
27019
+ reconnectClients() {
27020
+ for (const socket of this.clients.keys()) socket.terminate();
27021
+ }
26749
27022
  upgrade(request, socket, head) {
26750
27023
  if ((request.url ?? "").split(/[?#]/, 1)[0] !== "/ws") return rejectUpgrade(socket, 404, "Not Found");
26751
27024
  if (!isAllowedOrigin(request)) return rejectUpgrade(socket, 403, "Forbidden");
@@ -26806,6 +27079,7 @@ var OpenTigWebSocketTransport = class {
26806
27079
  if (bytes.byteLength > frameLimit(input.command)) return this.sendProtocolError(socket, input.id, "Command request exceeded the transport limit.");
26807
27080
  if (input.command === IPC.repositoryReadImage) return this.sendProtocolError(socket, input.id, "Image previews use authenticated HTTP.");
26808
27081
  const controller = new AbortController();
27082
+ const startedAt = this.measureTransport ? performance.now() : void 0;
26809
27083
  state.requests.set(input.id, controller);
26810
27084
  const execution = this.options.registry.execute(state.sessionId, input.command, input.args, { signal: controller.signal });
26811
27085
  this.pendingOperations.add(execution);
@@ -26823,6 +27097,10 @@ var OpenTigWebSocketTransport = class {
26823
27097
  });
26824
27098
  } finally {
26825
27099
  state.requests.delete(input.id);
27100
+ if (startedAt !== void 0) this.measure("request", startedAt, {
27101
+ requestBytes: bytes.byteLength,
27102
+ operation: commandDefinition(input.command)?.operation ?? "unknown"
27103
+ });
26826
27104
  }
26827
27105
  }
26828
27106
  sendProtocolError(socket, id, message) {
@@ -26840,21 +27118,93 @@ var OpenTigWebSocketTransport = class {
26840
27118
  });
26841
27119
  }
26842
27120
  broadcast(message) {
26843
- for (const socket of this.clients.keys()) this.send(socket, message);
27121
+ let serialized;
27122
+ for (const socket of this.clients.keys()) {
27123
+ if (!this.canSend(socket)) continue;
27124
+ if (serialized === void 0) serialized = this.serialize(message);
27125
+ this.sendSerialized(socket, serialized);
27126
+ }
26844
27127
  }
26845
27128
  send(socket, message) {
26846
- if (socket.readyState !== WebSocket.OPEN) return;
27129
+ if (this.canSend(socket)) this.sendSerialized(socket, this.serialize(message));
27130
+ }
27131
+ canSend(socket) {
27132
+ if (socket.readyState !== WebSocket.OPEN) return false;
26847
27133
  const state = this.clients.get(socket);
26848
- if (state && !this.options.auth.hasSession(state.sessionId)) return socket.close(1008, "Session expired or revoked");
26849
- const serialized = JSON.stringify(message);
26850
- if (socket.bufferedAmount + Buffer.byteLength(serialized) > MAX_BUFFERED_SEND_BYTES) {
27134
+ if (!state || !this.options.auth.hasSession(state.sessionId)) {
27135
+ socket.close(1008, "Session expired or revoked");
27136
+ return false;
27137
+ }
27138
+ return true;
27139
+ }
27140
+ serialize(message) {
27141
+ const startedAt = this.measureTransport ? performance.now() : void 0;
27142
+ let serialized = null;
27143
+ let outcome = "accepted";
27144
+ try {
27145
+ serialized = stringifyBounded(message, OPEN_TIG_MAX_SERVER_MESSAGE_BYTES);
27146
+ } catch (error) {
27147
+ if (!(error instanceof JsonByteLimitError)) throw error;
27148
+ outcome = "message-too-large";
27149
+ if (message.type === "result") {
27150
+ outcome = "response-too-large";
27151
+ serialized = stringifyBounded({
27152
+ type: "result",
27153
+ id: message.id,
27154
+ result: {
27155
+ ok: false,
27156
+ error: {
27157
+ code: "RESPONSE_TOO_LARGE",
27158
+ operation: "server-protocol",
27159
+ message: "Response exceeded the 4 MiB transport limit. Request a smaller scope.",
27160
+ maxBytes: OPEN_TIG_MAX_SERVER_MESSAGE_BYTES
27161
+ }
27162
+ }
27163
+ }, OPEN_TIG_MAX_SERVER_MESSAGE_BYTES);
27164
+ }
27165
+ }
27166
+ if (startedAt !== void 0) this.measure("serialize", startedAt, {
27167
+ bytes: serialized?.bytes ?? 0,
27168
+ outcome,
27169
+ maxBytes: OPEN_TIG_MAX_SERVER_MESSAGE_BYTES
27170
+ });
27171
+ return serialized;
27172
+ }
27173
+ sendSerialized(socket, serialized) {
27174
+ if (!this.canSend(socket)) return;
27175
+ if (!serialized) {
27176
+ socket.close(1009, "Server message too large");
27177
+ return;
27178
+ }
27179
+ const startedAt = this.measureTransport ? performance.now() : void 0;
27180
+ const bufferedBytes = socket.bufferedAmount;
27181
+ if (bufferedBytes + serialized.bytes > MAX_BUFFERED_SEND_BYTES) {
27182
+ if (startedAt !== void 0) this.measure("send", startedAt, {
27183
+ bytes: serialized.bytes,
27184
+ bufferedBytes,
27185
+ outcome: "backpressure"
27186
+ });
26851
27187
  socket.close(1013, "Client is too slow");
26852
27188
  return;
26853
27189
  }
26854
- socket.send(serialized, (error) => {
27190
+ socket.send(serialized.text, (error) => {
27191
+ if (startedAt !== void 0) this.measure("send", startedAt, {
27192
+ bytes: serialized.bytes,
27193
+ bufferedBytes,
27194
+ outcome: error ? "failed" : "written"
27195
+ });
26855
27196
  if (error) this.options.logger("warn", "WebSocket send failed.");
26856
27197
  });
26857
27198
  }
27199
+ measure(kind, startedAt, data) {
27200
+ try {
27201
+ this.options.logger("info", `WebSocket performance ${JSON.stringify({
27202
+ kind,
27203
+ durationMs: performance.now() - startedAt,
27204
+ ...data
27205
+ })}`);
27206
+ } catch {}
27207
+ }
26858
27208
  checkHeartbeats() {
26859
27209
  for (const [socket, state] of this.clients) {
26860
27210
  if (!this.options.auth.hasSession(state.sessionId)) {
@@ -26944,8 +27294,11 @@ var OpenTigServer = class {
26944
27294
  ready = false;
26945
27295
  startPromise = null;
26946
27296
  stopPromise = null;
27297
+ networkMutation = Promise.resolve();
27298
+ network;
26947
27299
  constructor(options) {
26948
27300
  this.options = options;
27301
+ this.network = options.network;
26949
27302
  this.logger = options.logger ?? (() => void 0);
26950
27303
  this.httpServer = createServer$1(createOpenTigHttpHandler({
26951
27304
  runtime: options.runtime,
@@ -26954,6 +27307,10 @@ var OpenTigServer = class {
26954
27307
  cancelUpdate: () => options.registry.resumeAfterUpdate(),
26955
27308
  clientRoot: options.clientRoot,
26956
27309
  auth: options.auth,
27310
+ webAccess: {
27311
+ getStatus: () => this.getWebAccessStatus(),
27312
+ update: (patch) => this.updateWebAccess(patch)
27313
+ },
26957
27314
  identity: options.identity,
26958
27315
  mode: options.mode ?? "desktop",
26959
27316
  isReady: () => this.ready,
@@ -27003,6 +27360,74 @@ var OpenTigServer = class {
27003
27360
  browserAccessEnabled: this.options.auth.descriptor().browserAccessEnabled
27004
27361
  };
27005
27362
  }
27363
+ getWebAccessStatus() {
27364
+ const address = this.httpServer.address();
27365
+ const port = address?.port ?? this.network.listenerPort;
27366
+ const host = address?.address ?? this.network.listenerHost;
27367
+ const localEndpoint = `http://${formatHost(host === "0.0.0.0" ? "127.0.0.1" : host === "::" ? "::1" : host)}:${port}`;
27368
+ const endpoints = Object.values(networkInterfaces()).flat().filter((entry) => entry && !entry.internal && !entry.address.startsWith("169.254.") && !entry.address.startsWith("fe80:") && (host.includes(":") || entry.family === "IPv4")).map((entry) => `http://${formatHost(entry.address)}:${port}`);
27369
+ const networkEndpoints = [...new Set(endpoints)].sort();
27370
+ const listeningOnLan = !isLoopbackHost(host);
27371
+ return {
27372
+ webAccessEnabled: this.options.auth.descriptor().browserAccessEnabled,
27373
+ lanAccessEnabled: this.network.lanAccessEnabled,
27374
+ publicOrigin: this.network.publicOrigin,
27375
+ listeningOnLan,
27376
+ listenerHost: host,
27377
+ actualPort: port,
27378
+ serverState: this.ready ? "ready" : "restarting",
27379
+ localEndpoint,
27380
+ networkEndpoints,
27381
+ pairingEndpoints: this.network.webAccessEnabled ? [.../* @__PURE__ */ new Set([
27382
+ localEndpoint,
27383
+ ...listeningOnLan ? networkEndpoints.filter((endpoint) => host === "0.0.0.0" || host === "::" || new URL(endpoint).hostname.replace(/[[\]]/g, "") === host) : [],
27384
+ ...this.network.publicOrigin ? [this.network.publicOrigin] : []
27385
+ ])] : [],
27386
+ connectedSessionCount: this.webSockets.connectedSessionCount,
27387
+ restartError: null,
27388
+ ...this.options.recoveryCommand ? { recoveryCommand: this.options.recoveryCommand } : {}
27389
+ };
27390
+ }
27391
+ updateWebAccess(patch) {
27392
+ const operation = this.networkMutation.then(async () => {
27393
+ if (!this.ready || this.stopPromise) throw new Error("Server is not ready.");
27394
+ const previous = this.network;
27395
+ const next = applyNetworkPatch(previous, patch);
27396
+ const rebind = next.listenerHost !== previous.listenerHost || next.listenerPort !== previous.listenerPort;
27397
+ if (rebind && !this.options.registry.pauseForUpdate()) throw new Error("Wait for running operations to finish before changing the listener.");
27398
+ try {
27399
+ if (rebind) await this.rebind(next);
27400
+ try {
27401
+ await this.options.networkSettings.save(next);
27402
+ await this.options.onNetworkChanged?.(next);
27403
+ } catch (error) {
27404
+ if (rebind) await this.rebind(previous);
27405
+ await this.options.networkSettings.save(previous);
27406
+ await this.options.onNetworkChanged?.(previous);
27407
+ throw error;
27408
+ }
27409
+ this.network = next;
27410
+ await this.setBrowserAccessEnabled(next.webAccessEnabled);
27411
+ if (rebind) this.webSockets.reconnectClients();
27412
+ return this.getWebAccessStatus();
27413
+ } finally {
27414
+ if (rebind) this.options.registry.resumeAfterUpdate();
27415
+ }
27416
+ });
27417
+ this.networkMutation = operation.catch(() => void 0);
27418
+ return operation;
27419
+ }
27420
+ async rebind(config) {
27421
+ const previous = this.network;
27422
+ this.httpServer.close();
27423
+ this.httpServer.closeIdleConnections();
27424
+ try {
27425
+ await this.bind(config.listenerHost, config.listenerPort);
27426
+ } catch (error) {
27427
+ await this.bind(previous.listenerHost, previous.listenerPort);
27428
+ throw new Error("Could not use that listening address or port. The previous listener has been restored.", { cause: error });
27429
+ }
27430
+ }
27006
27431
  async setBrowserAccessEnabled(enabled) {
27007
27432
  if (!this.ready) throw new Error("Server is not ready.");
27008
27433
  const pending = this.options.auth.setBrowserAccessEnabled(enabled);
@@ -27023,8 +27448,17 @@ var OpenTigServer = class {
27023
27448
  return this.stopPromise;
27024
27449
  }
27025
27450
  listen() {
27026
- const host = this.options.host ?? "127.0.0.1";
27027
- const port = this.options.port ?? 6767;
27451
+ return this.bind(this.network.listenerHost, this.network.listenerPort).then((address) => {
27452
+ this.network = {
27453
+ ...this.network,
27454
+ listenerHost: address.host,
27455
+ listenerPort: address.port,
27456
+ lanAccessEnabled: this.network.listenerHost === address.host ? this.network.lanAccessEnabled : !isLoopbackHost(address.host)
27457
+ };
27458
+ return address;
27459
+ });
27460
+ }
27461
+ bind(host, port) {
27028
27462
  return new Promise((resolve, reject) => {
27029
27463
  const onError = (error) => reject(error);
27030
27464
  this.httpServer.once("error", onError);
@@ -27043,6 +27477,7 @@ var OpenTigServer = class {
27043
27477
  });
27044
27478
  }
27045
27479
  async stopServer() {
27480
+ await this.networkMutation;
27046
27481
  this.ready = false;
27047
27482
  await this.webSockets.close();
27048
27483
  if (this.httpServer.listening) await new Promise((resolve, reject) => {
@@ -27069,10 +27504,11 @@ function formatHost(host) {
27069
27504
  */
27070
27505
  async function runOpenTigServer(config) {
27071
27506
  if (config.profile !== void 0 && config.profile !== "production" && config.profile !== "dev") throw new Error("Invalid application profile.");
27507
+ const { networkSettings, network } = await loadStartupNetwork(config);
27072
27508
  let transport = null;
27073
27509
  const auth = await OpenTigSessionAuth.open({
27074
27510
  source: config.auth,
27075
- browserAccessEnabled: config.browserAccessEnabled ?? config.mode === "web-access",
27511
+ browserAccessEnabled: network.webAccessEnabled,
27076
27512
  ...config.profile ? { profile: config.profile } : {},
27077
27513
  dataDirectory: config.serverDataPath ?? path.join(path.dirname(config.settingsPath), "server"),
27078
27514
  ...config.secureCookies === void 0 ? {} : { secureCookies: config.secureCookies }
@@ -27101,6 +27537,10 @@ async function runOpenTigServer(config) {
27101
27537
  registerServerCommands(registry, runtime.services, headlessHost);
27102
27538
  registry.setProblemLog(runtime.services.problems);
27103
27539
  transport = new OpenTigServer({
27540
+ network,
27541
+ networkSettings,
27542
+ ...config.recoveryCommand ? { recoveryCommand: config.recoveryCommand } : {},
27543
+ ...config.onNetworkChanged ? { onNetworkChanged: config.onNetworkChanged } : {},
27104
27544
  runtime,
27105
27545
  registry,
27106
27546
  ...config.updates ? { updates: config.updates } : {},
@@ -27127,7 +27567,19 @@ async function runOpenTigServer(config) {
27127
27567
  runtime,
27128
27568
  clientRoot,
27129
27569
  ...address,
27130
- createPairingLink: () => pairingLink(address.origin, transport.createPairingToken()),
27570
+ get host() {
27571
+ return transport.getWebAccessStatus().listenerHost;
27572
+ },
27573
+ get port() {
27574
+ return transport.getWebAccessStatus().actualPort;
27575
+ },
27576
+ get origin() {
27577
+ const status = transport.getWebAccessStatus();
27578
+ return `http://${status.listenerHost.includes(":") ? `[${status.listenerHost}]` : status.listenerHost}:${status.actualPort}`;
27579
+ },
27580
+ getWebAccessStatus: () => transport.getWebAccessStatus(),
27581
+ updateWebAccess: (patch) => transport.updateWebAccess(patch),
27582
+ createPairingLink: () => pairingLink(transport.getWebAccessStatus().localEndpoint, transport.createPairingToken()),
27131
27583
  getStatus: () => transport.getStatus(),
27132
27584
  revokeAllSessions: () => transport.revokeAllSessions(),
27133
27585
  setBrowserAccessEnabled: (enabled) => transport.setBrowserAccessEnabled(enabled),
@@ -27172,4 +27624,4 @@ var headlessHost = {
27172
27624
  revealItem: () => void 0
27173
27625
  };
27174
27626
  //#endregion
27175
- export { preferredServerPort as a, redactSensitiveText as c, applicationName as i, runOpenTigServer as n, normalizeRuntimePlatform as o, OneTimeBootstrapAuthSource as r, writeFileAtomically as s, resolveServerClientRoot as t };
27627
+ export { preferredServerPort as a, applicationName as i, runOpenTigServer as n, normalizeRuntimePlatform as o, OneTimeBootstrapAuthSource as r, resolveServerClientRoot as t };