@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,13 +1,16 @@
1
- import { c as redactSensitiveText, i as applicationName, n as runOpenTigServer, o as normalizeRuntimePlatform, s as writeFileAtomically } from "./server-Cz0gvydn.mjs";
2
- import { a as newerVersion, c as saveInstallation, i as execute, l as stableVersion, n as createServiceManager, o as packageDirectory, r as atomicWrite, s as readInstallation, t as assertManager } from "./service-manager-Jyx-MWo-.mjs";
1
+ import { i as applicationName, n as runOpenTigServer, o as normalizeRuntimePlatform } from "./server-DMk8-Xxt.mjs";
2
+ import { c as redactSensitiveText, i as loadStartupNetwork, s as writeFileAtomically } from "./network-settings-CdFeKFRB.mjs";
3
+ import { a as newerVersion, c as saveInstallation, d as cliCommand, f as diagnostic, h as serviceError, i as execute, l as stableVersion, m as operationError, n as createServiceManager, o as packageDirectory, p as errorCode, r as atomicWrite, s as readInstallation, t as assertManager, u as CliError } from "./service-manager-BxSmV_8Z.mjs";
3
4
  import process$1 from "node:process";
4
- import { execFile, spawn } from "node:child_process";
5
- import { createHash, randomBytes } from "node:crypto";
6
5
  import { access, appendFile, chmod, cp, mkdir, mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
7
6
  import path from "node:path";
7
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import os from "node:os";
9
9
  import { fileURLToPath } from "node:url";
10
+ import { execFile, spawn } from "node:child_process";
10
11
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
+ import { createServer } from "node:net";
13
+ import { lookup } from "node:dns/promises";
11
14
  //#region ../../src/shared/date-format.ts
12
15
  var pad = (value, width = 2) => String(value).padStart(width, "0");
13
16
  function localDate(value) {
@@ -908,6 +911,354 @@ function renderUnicodeCompact(data, options = {}) {
908
911
  return lines.join("\n");
909
912
  }
910
913
  //#endregion
914
+ //#region src/cli-network.ts
915
+ async function cliNetwork(config) {
916
+ try {
917
+ const { network, savedListener } = await loadStartupNetwork({
918
+ settingsPath: path.join(config.home, "settings.json"),
919
+ serverDataPath: path.join(config.home, "server"),
920
+ mode: "web-access",
921
+ host: config.host,
922
+ port: config.port
923
+ });
924
+ return {
925
+ ...network,
926
+ savedListener
927
+ };
928
+ } catch (cause) {
929
+ throw new CliError("ACCESS_SETTINGS_INVALID", "Could not load the saved web access settings.", [
930
+ `Check ${path.join(config.home, "server/web-access.json")} and its permissions.`,
931
+ `To restore startup address and browser access, run ${cliCommand(config.home, "reset-access")} and restart this instance.`,
932
+ "Resetting access preserves paired browsers but removes the saved listener and access switches."
933
+ ], { cause });
934
+ }
935
+ }
936
+ function listenerError(error, config, network, command) {
937
+ const code = errorCode(error);
938
+ const address = `${network.listenerHost.includes(":") ? `[${network.listenerHost}]` : network.listenerHost}:${network.listenerPort}`;
939
+ const context = network.savedListener || network.listenerHost !== config.host || network.listenerPort !== config.port ? [
940
+ "This address is affected by saved Settings → Web access. Saved listener fields override the corresponding startup options.",
941
+ "Change the saved listener in that instance before retrying. If access recovery is needed:",
942
+ `${cliCommand(config.home, "reset-access")} removes saved access switches and listener; restart with the desired options.`
943
+ ] : [
944
+ "Listener selected by startup options, OPENTIG_HOST/OPENTIG_PORT, or defaults.",
945
+ `Choose another port, for example: ${cliCommand(config.home, ...command.split(" "), "--host", config.host, "--port", String(network.listenerPort === 65535 ? 6767 : network.listenerPort + 1))}`,
946
+ "The example port has not been checked for availability."
947
+ ];
948
+ if (code === "EADDRINUSE") return new CliError("PORT_IN_USE", `Port ${network.listenerPort} is already in use (${address}); cannot run ${command}.`, [
949
+ "OpenTig Desktop or another process may be using this address. Close it and retry, or use a different port.",
950
+ ...context,
951
+ "No other application was stopped."
952
+ ], { cause: error });
953
+ if (code === "EADDRNOTAVAIL" || code === "ENOTFOUND" || code === "EAI_AGAIN") return new CliError("HOST_UNAVAILABLE", `Cannot listen on ${address}: this host is not available on this machine.`, ["Use a local listening address, such as 127.0.0.1 for access from this computer.", ...context], { cause: error });
954
+ if (code === "EACCES" || code === "EPERM") return new CliError("LISTENER_PERMISSION_DENIED", `Permission denied when listening on ${address}.`, ["Check OS port restrictions and choose an allowed port. Do not run the server as administrator.", ...context], { cause: error });
955
+ return error;
956
+ }
957
+ /** Same bind semantics as OpenTigServer; advisory only, the real bind still handles races. */
958
+ async function assertListenerAvailable(config, network, command) {
959
+ const socket = createServer();
960
+ try {
961
+ await new Promise((resolve, reject) => {
962
+ socket.once("error", reject);
963
+ socket.listen(network.listenerPort, network.listenerHost, resolve);
964
+ });
965
+ } catch (error) {
966
+ throw listenerError(error, config, network, command);
967
+ } finally {
968
+ if (socket.listening) await new Promise((resolve, reject) => socket.close((error) => error ? reject(error) : resolve()));
969
+ }
970
+ }
971
+ async function sameListener(network, runtime) {
972
+ if (network.listenerPort !== runtime.port) return false;
973
+ if (network.listenerHost === runtime.host) return true;
974
+ try {
975
+ return (await lookup(network.listenerHost)).address === runtime.host;
976
+ } catch {
977
+ return false;
978
+ }
979
+ }
980
+ //#endregion
981
+ //#region src/cli-home.ts
982
+ var RUNTIME_STATE_VERSION = 1;
983
+ function resolveCliPaths(home) {
984
+ const resolved = path.resolve(home);
985
+ const serverData = path.join(resolved, "server");
986
+ const logs = path.join(resolved, "logs");
987
+ return {
988
+ home: resolved,
989
+ settings: path.join(resolved, "settings.json"),
990
+ aiLog: path.join(resolved, "ai-log.jsonl"),
991
+ problems: path.join(resolved, "problems.jsonl"),
992
+ serverData,
993
+ logs,
994
+ serverLog: path.join(logs, "server.log"),
995
+ runtimeState: path.join(resolved, "runtime.json"),
996
+ adminToken: path.join(serverData, "admin-token")
997
+ };
998
+ }
999
+ async function prepareCliHome(paths) {
1000
+ await mkdir(paths.home, {
1001
+ recursive: true,
1002
+ mode: 448
1003
+ });
1004
+ await chmod(paths.home, 448);
1005
+ await Promise.all([mkdir(paths.serverData, {
1006
+ recursive: true,
1007
+ mode: 448
1008
+ }), mkdir(paths.logs, {
1009
+ recursive: true,
1010
+ mode: 448
1011
+ })]);
1012
+ await Promise.all([chmod(paths.serverData, 448), chmod(paths.logs, 448)]);
1013
+ }
1014
+ async function loadOrCreateAdminToken(filePath) {
1015
+ try {
1016
+ return await readAdminToken(filePath);
1017
+ } catch (error) {
1018
+ if (error.code !== "ENOENT") throw error;
1019
+ }
1020
+ const token = randomBytes(32).toString("base64url");
1021
+ await writeFileAtomically(filePath, `${token}\n`, { directoryMode: 448 });
1022
+ return token;
1023
+ }
1024
+ async function readAdminToken(filePath) {
1025
+ const token = (await readFile(filePath, "utf8")).trim();
1026
+ if (!isToken(token)) throw new CliError("ADMIN_CREDENTIAL_INVALID", "OpenTig local admin credential is corrupt.", ["Check the private home permissions and restore this credential from a trusted backup; do not delete the home or copy a credential from another instance."]);
1027
+ await chmod(filePath, 384);
1028
+ return token;
1029
+ }
1030
+ async function writeRuntimeState(filePath, state) {
1031
+ await writeFileAtomically(filePath, `${JSON.stringify({
1032
+ version: RUNTIME_STATE_VERSION,
1033
+ ...state
1034
+ }, null, 2)}\n`, {
1035
+ parseJson: true,
1036
+ directoryMode: 448
1037
+ });
1038
+ }
1039
+ async function readRuntimeState(filePath) {
1040
+ let value;
1041
+ try {
1042
+ value = JSON.parse(await readFile(filePath, "utf8"));
1043
+ } catch (error) {
1044
+ if (error.code === "ENOENT") throw new CliError("RUNTIME_MISSING", "No running OpenTig server was found for this home.", [`Start it with ${cliCommand(path.dirname(filePath), "serve")}`, `Or check ${cliCommand(path.dirname(filePath), "service", "status")}`], { cause: error });
1045
+ throw new CliError("RUNTIME_INVALID", "OpenTig runtime state is unreadable.", [`Check permissions and server logs in ${path.dirname(filePath)}. Do not delete settings or credentials.`], { cause: error });
1046
+ }
1047
+ if (!isRuntimeState(value)) throw new CliError("RUNTIME_INVALID", "OpenTig runtime state is invalid.", [`Inspect ${filePath} and the service status before restarting; do not delete the home.`]);
1048
+ await chmod(filePath, 384);
1049
+ return value;
1050
+ }
1051
+ async function clearRuntimeState(filePath, instanceId) {
1052
+ try {
1053
+ if ((await readRuntimeState(filePath)).instanceId === instanceId) await rm(filePath, { force: true });
1054
+ } catch (error) {
1055
+ if (!(error instanceof CliError && error.code === "RUNTIME_MISSING")) throw error;
1056
+ }
1057
+ }
1058
+ function newInstanceId() {
1059
+ return randomBytes(24).toString("base64url");
1060
+ }
1061
+ function isToken(value) {
1062
+ return /^[A-Za-z0-9_-]{43}$/.test(value);
1063
+ }
1064
+ function isRuntimeState(value) {
1065
+ const state = value;
1066
+ return Boolean(state && state.version === RUNTIME_STATE_VERSION && Number.isSafeInteger(state.pid) && Number(state.pid) > 0 && typeof state.host === "string" && state.host.length > 0 && state.host.length <= 255 && Number.isSafeInteger(state.port) && Number(state.port) > 0 && Number(state.port) <= 65535 && Number.isSafeInteger(state.protocolVersion) && Number(state.protocolVersion) > 0 && typeof state.appVersion === "string" && state.appVersion.length > 0 && state.appVersion.length <= 128 && typeof state.instanceId === "string" && /^[A-Za-z0-9_-]{32}$/.test(state.instanceId) && typeof state.startedAt === "string" && !Number.isNaN(Date.parse(state.startedAt)));
1067
+ }
1068
+ //#endregion
1069
+ //#region src/cli-runtime.ts
1070
+ function publicOrigin(host, port) {
1071
+ const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host === "::" ? "::1" : host;
1072
+ return `http://${displayHost.includes(":") ? `[${displayHost}]` : displayHost}:${port}`;
1073
+ }
1074
+ function isProcessRunning(pid) {
1075
+ try {
1076
+ process.kill(pid, 0);
1077
+ return true;
1078
+ } catch (error) {
1079
+ return error.code === "EPERM";
1080
+ }
1081
+ }
1082
+ /** The admin endpoint proves the listener owns both this home's token and instance ID. */
1083
+ async function probeRuntime(home, runtime, allowLegacy = false) {
1084
+ const paths = resolveCliPaths(home);
1085
+ const state = runtime ?? await readRuntimeState(paths.runtimeState);
1086
+ if (!isProcessRunning(state.pid)) throw new CliError("RUNTIME_STALE", "OpenTig runtime state is stale; its process is not running.", [`Start this instance: ${cliCommand(home, "serve")}`, `Or restart its service: ${cliCommand(home, "service", "restart")}`]);
1087
+ const token = await readAdminToken(paths.adminToken).catch((cause) => {
1088
+ throw new CliError("ADMIN_CREDENTIAL_INVALID", "OpenTig local admin credential is missing or unreadable.", [`Check the private credential and permissions in ${paths.serverData}; do not delete the home or copy another instance's credential.`], { cause });
1089
+ });
1090
+ const response = await fetch(`${publicOrigin(state.host, state.port)}/api/admin/status`, {
1091
+ method: "POST",
1092
+ headers: {
1093
+ "Content-Type": "application/json",
1094
+ "X-OpenTig-Admin": token
1095
+ },
1096
+ body: JSON.stringify({ instanceId: state.instanceId }),
1097
+ signal: AbortSignal.timeout(1e3)
1098
+ });
1099
+ if (response.status === 404 && allowLegacy) {
1100
+ const check = await fetch(`${publicOrigin(state.host, state.port)}/api/admin/pair`, {
1101
+ method: "POST",
1102
+ headers: {
1103
+ "Content-Type": "application/json",
1104
+ "X-OpenTig-Admin": token
1105
+ },
1106
+ body: JSON.stringify({
1107
+ instanceId: state.instanceId,
1108
+ publicOrigin: "invalid-origin"
1109
+ }),
1110
+ signal: AbortSignal.timeout(1e3)
1111
+ });
1112
+ if (check.status === 400 && (await check.json()).error === "Invalid public origin.") {
1113
+ const ready = await fetch(`${publicOrigin(state.host, state.port)}/readyz`, { signal: AbortSignal.timeout(1e3) });
1114
+ const identity = await ready.json();
1115
+ if (ready.ok && identity.status === "ready" && identity.appVersion === state.appVersion && identity.protocolVersion === state.protocolVersion) return {
1116
+ ...state,
1117
+ legacy: true
1118
+ };
1119
+ }
1120
+ }
1121
+ if (!response.ok) throw new CliError("RUNTIME_MISMATCH", "OpenTig runtime state does not match the running server.", [`Inspect this instance: ${cliCommand(home, "service", "status")}`, "Do not stop an unknown process or remove credentials to work around an identity mismatch."]);
1122
+ const identity = await response.json();
1123
+ if (identity.instanceId !== state.instanceId || identity.pid !== state.pid || identity.appVersion !== state.appVersion || identity.protocolVersion !== state.protocolVersion || identity.status !== "ready") throw new CliError("RUNTIME_MISMATCH", "The running server identity or readiness does not match this home.", [`Check ${path.join(home, "logs/server.log")} before restarting this instance.`]);
1124
+ return state;
1125
+ }
1126
+ function assertGitAvailable() {
1127
+ return new Promise((resolve, reject) => {
1128
+ execFile("git", ["--version"], {
1129
+ timeout: 1e4,
1130
+ windowsHide: true
1131
+ }, (error, stdout) => {
1132
+ if (error || !stdout.trim().startsWith("git version ")) reject(new CliError("GIT_UNAVAILABLE", "Git is required and could not be executed from PATH.", ["Install Git or correct PATH in this terminal, then retry.", "If tool locations changed, reinstall the service from this terminal to capture the updated PATH."], { cause: error }));
1133
+ else resolve();
1134
+ });
1135
+ });
1136
+ }
1137
+ //#endregion
1138
+ //#region src/cli-log.ts
1139
+ var MAX_BYTES = 1048576;
1140
+ var BACKUPS = 3;
1141
+ var CliServerLog = class {
1142
+ filePath;
1143
+ queue = Promise.resolve();
1144
+ constructor(filePath) {
1145
+ this.filePath = filePath;
1146
+ }
1147
+ logger = (level, message) => {
1148
+ const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${redactSensitiveText(message)}\n`;
1149
+ this.queue = this.queue.then(async () => {
1150
+ await mkdir(path.dirname(this.filePath), {
1151
+ recursive: true,
1152
+ mode: 448
1153
+ });
1154
+ if (await wouldExceed(this.filePath, Buffer.byteLength(line))) await this.rotate();
1155
+ await appendFile(this.filePath, line, {
1156
+ encoding: "utf8",
1157
+ mode: 384
1158
+ });
1159
+ }).catch(() => void 0);
1160
+ };
1161
+ close() {
1162
+ return this.queue;
1163
+ }
1164
+ async rotate() {
1165
+ await rm(`${this.filePath}.${BACKUPS}`, { force: true });
1166
+ for (let index = 2; index >= 1; index -= 1) await moveIfPresent(`${this.filePath}.${index}`, `${this.filePath}.${index + 1}`);
1167
+ await moveIfPresent(this.filePath, `${this.filePath}.1`);
1168
+ }
1169
+ };
1170
+ async function wouldExceed(filePath, incomingBytes) {
1171
+ try {
1172
+ return (await stat(filePath)).size + incomingBytes > MAX_BYTES;
1173
+ } catch (error) {
1174
+ if (error.code === "ENOENT") return false;
1175
+ throw error;
1176
+ }
1177
+ }
1178
+ async function moveIfPresent(source, target) {
1179
+ try {
1180
+ await rename(source, target);
1181
+ } catch (error) {
1182
+ if (error.code !== "ENOENT") throw error;
1183
+ }
1184
+ }
1185
+ //#endregion
1186
+ //#region src/cli-startup.ts
1187
+ var file = (home) => path.join(home, "service/startup.json");
1188
+ async function readStartup(home) {
1189
+ try {
1190
+ const value = JSON.parse(await readFile(file(home), "utf8"));
1191
+ return value && typeof value.attempt === "string" && typeof value.requestedAt === "string" && [
1192
+ "pending",
1193
+ "starting",
1194
+ "ready",
1195
+ "failed"
1196
+ ].includes(value.phase) ? value : null;
1197
+ } catch (error) {
1198
+ if (errorCode(error) === "ENOENT" || error instanceof SyntaxError) return null;
1199
+ throw error;
1200
+ }
1201
+ }
1202
+ /** One private bounded record, replaced for each installation/restart attempt. */
1203
+ async function requestStartup(home) {
1204
+ const attempt = randomUUID();
1205
+ await atomicWrite(file(home), JSON.stringify({
1206
+ attempt,
1207
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1208
+ phase: "pending"
1209
+ }));
1210
+ return attempt;
1211
+ }
1212
+ async function startupReporter(home) {
1213
+ if (!process.env.OPENTIG_SERVICE_HOME || path.resolve(process.env.OPENTIG_SERVICE_HOME) !== path.resolve(home)) return null;
1214
+ const previous = await readStartup(home);
1215
+ const result = previous?.phase === "pending" ? previous : {
1216
+ attempt: randomUUID(),
1217
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1218
+ phase: "pending"
1219
+ };
1220
+ const save = async (patch, initial = false) => {
1221
+ const current = await readStartup(home);
1222
+ if (current && current.attempt !== result.attempt && (!initial || current.attempt !== previous?.attempt)) return;
1223
+ await atomicWrite(file(home), JSON.stringify({
1224
+ ...result,
1225
+ ...patch,
1226
+ pid: process.pid
1227
+ }));
1228
+ };
1229
+ await save({ phase: "starting" }, true);
1230
+ return {
1231
+ ready: (instanceId) => save({
1232
+ phase: "ready",
1233
+ instanceId
1234
+ }),
1235
+ failed: (error) => save({
1236
+ phase: "failed",
1237
+ code: error instanceof CliError ? error.code : "STARTUP_FAILED",
1238
+ message: diagnostic(error)
1239
+ })
1240
+ };
1241
+ }
1242
+ async function recordCliFailure(home, error) {
1243
+ const log = new CliServerLog(path.join(home, "logs/server.log"));
1244
+ log.logger("error", diagnostic(error));
1245
+ let cause = error instanceof Error ? error.cause : void 0;
1246
+ for (let depth = 0; cause && depth < 4; depth++) {
1247
+ log.logger("error", `Cause: ${cause instanceof Error ? cause.message : "Unknown failure"}`);
1248
+ if (cause instanceof AggregateError) for (const item of cause.errors) log.logger("error", `Recovery detail: ${diagnostic(item)}`);
1249
+ cause = cause instanceof Error ? cause.cause : void 0;
1250
+ }
1251
+ await log.close();
1252
+ }
1253
+ function startupTimeout(home) {
1254
+ return new CliError("STARTUP_TIMEOUT", "The managed service did not become ready before the startup timeout.", [
1255
+ "The cause could not be determined automatically.",
1256
+ `Check: ${cliCommand(home, "service", "status")}`,
1257
+ `Server log: ${path.join(home, "logs/server.log")}`,
1258
+ `Windows startup output: ${path.join(home, "logs/service-stderr.log")}; on Linux/macOS inspect the user supervisor's logs.`
1259
+ ]);
1260
+ }
1261
+ //#endregion
911
1262
  //#region src/browser.ts
912
1263
  function browserCommand(url, platform = process.platform) {
913
1264
  if (platform === "win32") return {
@@ -981,8 +1332,13 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
981
1332
  }
982
1333
  if (!optionsEnded && argument.startsWith("--")) {
983
1334
  const [name, inline] = splitOption(argument);
1335
+ if (![
1336
+ "--host",
1337
+ "--port",
1338
+ "--home"
1339
+ ].includes(name)) throw new CliUsageError(`Unknown option: ${name}. Use --host, --port, or --home.`);
984
1340
  const value = inline ?? args[++index];
985
- if (value === void 0 || value.startsWith("--")) throw new CliUsageError(`Missing value for ${name}.`);
1341
+ if (value === void 0 || value === "" || value.startsWith("-")) throw new CliUsageError(`Missing value for ${name}.`);
986
1342
  if (name === "--host") hostValue = value;
987
1343
  else if (name === "--port") portValue = value;
988
1344
  else if (name === "--home") homeValue = value;
@@ -999,14 +1355,16 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
999
1355
  serviceAction = argument;
1000
1356
  continue;
1001
1357
  }
1002
- throw new CliUsageError(`Unexpected argument: ${argument}. Add and select repositories from the OpenTig web UI.`);
1358
+ if (command === "service" && serviceAction === null) throw new CliUsageError(`Unknown service action: ${argument}. Use install, status, restart, or uninstall.`);
1359
+ throw new CliUsageError(`Unexpected argument: ${argument}. Check the command spelling with opentig --help. Add and select repositories from the OpenTig web UI.`);
1003
1360
  }
1004
- if (command === "pair" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) throw new CliUsageError("The pair command only accepts --home.");
1361
+ if (noBrowser && command !== "start") throw new CliUsageError("--no-browser is only supported by start; serve and services already run without opening a browser.");
1362
+ if ((command === "pair" || command === "reset-access") && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) throw new CliUsageError(`The ${command} command only accepts --home.`);
1005
1363
  if (command === "service" && serviceAction === null) throw new CliUsageError("The service command requires install, status, restart, or uninstall.");
1006
1364
  if (command === "service" && serviceAction !== "install" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) throw new CliUsageError(`The service ${serviceAction} command only accepts --home.`);
1007
- if (command === "pair") return {
1365
+ if (command === "pair" || command === "reset-access" || command === "service" && serviceAction !== "install") return {
1008
1366
  command,
1009
- serviceAction: null,
1367
+ serviceAction,
1010
1368
  host: DEFAULT_SERVER_HOST,
1011
1369
  port: DEFAULT_SERVER_PORT,
1012
1370
  home: resolvePath(homeValue ?? environment.OPENTIG_HOME ?? path.join(homeDirectory, ".opentig"), currentDirectory, "home"),
@@ -1033,6 +1391,7 @@ Usage:
1033
1391
  opentig start [options]
1034
1392
  opentig serve [options]
1035
1393
  opentig pair [--home <path>]
1394
+ opentig reset-access [--home <path>]
1036
1395
  opentig service <install|status|restart|uninstall> [options]
1037
1396
  opentig help
1038
1397
  opentig version
@@ -1041,6 +1400,7 @@ Commands:
1041
1400
  start Start OpenTig and open the one-use pairing link (default)
1042
1401
  serve Start OpenTig without opening a browser; ideal for servers
1043
1402
  pair Print a fresh five-minute code/link for a running server
1403
+ reset-access Restore browser access and startup listener settings on next restart
1044
1404
  service Manage a background server (Linux, macOS, Windows)
1045
1405
  help Show this help
1046
1406
  version Show the OpenTig version
@@ -1073,8 +1433,13 @@ Pairing and remote access:
1073
1433
  a trusted LAN/VPN; pairing is still required.
1074
1434
 
1075
1435
  Runtime behavior:
1436
+ serve runs in this terminal and stops when it closes. service install starts
1437
+ a background server and installs startup persistence; do not run both for
1438
+ the same home. Desktop runs its own server and needs a different port.
1076
1439
  The configured port is never changed silently. If it is occupied, OpenTig
1077
- exits and asks you to stop that process or choose another --port. SIGINT and
1440
+ exits and asks you to stop that process or choose another --port. Saved
1441
+ Settings → Web access take precedence over startup host/port options.
1442
+ Service installation checks the effective address before registering a service. SIGINT and
1078
1443
  SIGTERM shut the server down cleanly. npx and bunx never install a service;
1079
1444
  service installation is explicit. Linux uses systemd with lingering; macOS
1080
1445
  and Windows start at login and stop at logout.
@@ -1097,7 +1462,7 @@ function splitOption(argument) {
1097
1462
  return separator < 0 ? [argument, void 0] : [argument.slice(0, separator), argument.slice(separator + 1)];
1098
1463
  }
1099
1464
  function isCommand(value) {
1100
- return value === "start" || value === "serve" || value === "pair" || value === "service" || value === "help" || value === "version";
1465
+ return value === "start" || value === "serve" || value === "pair" || value === "reset-access" || value === "service" || value === "help" || value === "version";
1101
1466
  }
1102
1467
  function isServiceAction(value) {
1103
1468
  return value === "install" || value === "status" || value === "restart" || value === "uninstall";
@@ -1114,15 +1479,15 @@ function validatePort(value) {
1114
1479
  return port;
1115
1480
  }
1116
1481
  function resolvePath(value, currentDirectory, label) {
1117
- if (!value || value.includes("\0")) throw new CliUsageError(`Invalid ${label}.`);
1482
+ if (!value || /[\0\r\n]/.test(value)) throw new CliUsageError(`Invalid ${label}.`);
1118
1483
  return path.resolve(currentDirectory, value);
1119
1484
  }
1120
1485
  //#endregion
1121
1486
  //#region src/version.ts
1122
1487
  var OPEN_TIG_APP_VERSION = {
1123
1488
  name: "@opentig/cli",
1124
- version: "0.2.3",
1125
- description: "Run the OpenTig local-first Git client in a browser from any Git worktree.",
1489
+ version: "0.2.4",
1490
+ description: "Run OpenTig, a free, open-source Git client, on your repository host and access it through a paired browser.",
1126
1491
  type: "module",
1127
1492
  main: "dist/server.mjs",
1128
1493
  bin: { "opentig": "dist/bin.mjs" },
@@ -1154,7 +1519,7 @@ var OPEN_TIG_APP_VERSION = {
1154
1519
  "cli",
1155
1520
  "headless"
1156
1521
  ],
1157
- author: "OpenTig contributors",
1522
+ author: "OpenTig",
1158
1523
  license: "MIT",
1159
1524
  repository: {
1160
1525
  "type": "git",
@@ -1162,7 +1527,7 @@ var OPEN_TIG_APP_VERSION = {
1162
1527
  "directory": "packages/server"
1163
1528
  },
1164
1529
  bugs: { "url": "https://github.com/jorgefl8/opentig/issues" },
1165
- homepage: "https://github.com/jorgefl8/opentig#readme",
1530
+ homepage: "https://opentig.com/",
1166
1531
  publishConfig: {
1167
1532
  "access": "public",
1168
1533
  "provenance": true
@@ -1176,6 +1541,31 @@ var OPEN_TIG_APP_VERSION = {
1176
1541
  //#region src/service-npm.ts
1177
1542
  /** Run npm's JS entry with Node, never a .cmd file or interpolated shell command. */
1178
1543
  async function runServiceNpm(args, cwd) {
1544
+ const entry = await resolveServiceNpm();
1545
+ try {
1546
+ await execute(process.execPath, [entry, ...args], {
1547
+ cwd,
1548
+ timeout: 18e4,
1549
+ maxBuffer: 2e6,
1550
+ windowsHide: true
1551
+ });
1552
+ } catch (cause) {
1553
+ const code = (cause && typeof cause === "object" && "stderr" in cause && typeof cause.stderr === "string" ? cause.stderr : "").match(/npm (?:error|ERR!) code (E[A-Z0-9]+)/)?.[1];
1554
+ const action = code && [
1555
+ "ENOTFOUND",
1556
+ "EAI_AGAIN",
1557
+ "ETIMEDOUT",
1558
+ "ECONNREFUSED",
1559
+ "ECONNRESET"
1560
+ ].includes(code) ? "npm could not reach the package registry. Check the network and npm proxy/registry configuration." : code === "E401" || code === "E403" ? "npm registry access was denied. Check npm authentication and registry policies." : code === "ENOSPC" ? "npm ran out of disk space. Free space on the service volume and npm cache volume." : code === "EACCES" || code === "EPERM" ? "npm could not write the dependencies. Check permissions for this user and the npm cache." : "Check npm registry connectivity, proxy configuration, available disk space, and permissions.";
1561
+ throw new CliError("DEPENDENCIES_FAILED", `Could not prepare the managed service dependencies with npm${code ? ` (${code})` : ""}.`, [
1562
+ action,
1563
+ "The existing service has not been replaced.",
1564
+ "Correct the npm error recorded in the server log, then retry the installation."
1565
+ ], { cause });
1566
+ }
1567
+ }
1568
+ async function resolveServiceNpm() {
1179
1569
  const candidates = [
1180
1570
  process.env.npm_execpath,
1181
1571
  path.join(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
@@ -1195,37 +1585,47 @@ async function runServiceNpm(args, cwd) {
1195
1585
  } catch {
1196
1586
  continue;
1197
1587
  }
1198
- await execute(process.execPath, [candidate, ...args], {
1199
- cwd,
1200
- timeout: 18e4,
1201
- maxBuffer: 2e6,
1202
- windowsHide: true
1203
- });
1204
- return;
1588
+ return candidate;
1205
1589
  }
1206
- throw new Error("npm could not be located. Install Node.js with npm and run service install from that terminal.");
1590
+ throw new CliError("NPM_UNAVAILABLE", "npm could not be located.", ["Install Node.js with npm and run service install from that terminal."]);
1207
1591
  }
1208
1592
  //#endregion
1209
1593
  //#region src/cli-service.ts
1210
1594
  async function manageCliService(config, io, managerOverride) {
1211
- const manager = managerOverride ?? await createServiceManager(config.home);
1595
+ try {
1596
+ return await manage(config, io, managerOverride ?? await createServiceManager(config.home));
1597
+ } catch (error) {
1598
+ throw serviceError(error, config.home, config.serviceAction ?? "manage");
1599
+ }
1600
+ }
1601
+ async function manage(config, io, manager) {
1212
1602
  const action = config.serviceAction;
1213
- if (!action) throw new Error("A service action is required.");
1603
+ if (!action) throw new CliError("SERVICE_ACTION_REQUIRED", "A service action is required.", ["Use install, status, restart, or uninstall."]);
1214
1604
  const installed = await readInstallation(config.home);
1215
- if (action !== "status" && installed && newerVersion(installed.version, OPEN_TIG_APP_VERSION)) throw new Error("A newer managed service is installed. Use its CLI to administer it; downgrades are not automatic.");
1605
+ if (action !== "status" && installed && newerVersion(installed.version, OPEN_TIG_APP_VERSION)) throw new CliError("SERVICE_VERSION_NEWER", "A newer managed service is installed; downgrades are not automatic.", ["Update the global CLI to that version or later before administering this service.", `Check versions: ${cliCommand(config.home, "--version")}`]);
1216
1606
  const definition = await manager.read();
1217
- if (definition && !manager.owns(definition)) throw new Error("Another OpenTig home owns this service. Use its --home before changing or removing it.");
1607
+ if (definition && !manager.owns(definition)) throw new CliError("SERVICE_HOME_MISMATCH", "Another OpenTig home owns this service.", ["Use the original installation's --home for status, restart, or uninstall. No service was changed.", `Service definition: ${manager.location}`]);
1218
1608
  if (installed) assertManager(installed, manager.kind);
1219
1609
  if (action === "status") {
1220
1610
  if (!definition) {
1221
1611
  io.out("OpenTig service is not installed.");
1612
+ io.out(`Install: ${cliCommand(config.home, "service", "install")}`);
1222
1613
  return 1;
1223
1614
  }
1224
1615
  const state = await manager.status();
1225
- io.out(`OpenTig service: ${state.running ? "running" : "stopped"} (${state.enabled ? "enabled" : "disabled"}).`);
1616
+ let status = state.starting ? "starting" : state.running ? "running" : "stopped";
1617
+ let ready = false;
1618
+ if (state.running) try {
1619
+ await verifiedServiceRuntime(config.home, manager);
1620
+ ready = true;
1621
+ } catch {
1622
+ status = "running, backend not ready or identity not verified";
1623
+ }
1624
+ io.out(`OpenTig service: ${status} (${state.enabled ? "enabled" : "disabled"}).`);
1226
1625
  io.out(manager.location);
1227
1626
  io.out(manager.startup);
1228
- return state.running && state.enabled ? 0 : 1;
1627
+ if (!ready) io.out(`Inspect ${path.join(config.home, "logs/server.log")}; restart with ${cliCommand(config.home, "service", "restart")}`);
1628
+ return ready && state.enabled ? 0 : 1;
1229
1629
  }
1230
1630
  if (action === "uninstall" && !definition && !installed) {
1231
1631
  io.out("OpenTig service is not installed.");
@@ -1238,16 +1638,18 @@ async function manageCliService(config, io, managerOverride) {
1238
1638
  const lock = path.join(config.home, "service/update-lock");
1239
1639
  try {
1240
1640
  await mkdir(lock, { mode: 448 });
1241
- } catch (error) {
1242
- if (error.code === "EEXIST") throw new Error("A service installation or update is already in progress.", { cause: error });
1243
- throw error;
1641
+ } catch (cause) {
1642
+ if (errorCode(cause) === "EEXIST") throw new CliError("SERVICE_BUSY", "A service installation or update is already in progress.", [`Check ${cliCommand(config.home, "service", "status")} and wait for the operation to finish.`, "Do not delete its lock until you have verified that no installation or update is active."], { cause });
1643
+ throw cause;
1244
1644
  }
1245
- try {
1645
+ return withServiceLock(config.home, lock, async () => {
1246
1646
  if (action === "restart") {
1247
- if (!definition || !installed) throw new Error("OpenTig service is not installed.");
1647
+ if (!definition || !installed) throw new CliError("SERVICE_MISSING", "OpenTig service is not installed.", [`Install: ${cliCommand(config.home, "service", "install")}`]);
1648
+ const attempt = await requestStartup(config.home);
1649
+ io.out("Restarting the OpenTig service...");
1248
1650
  await manager.restart();
1249
- await waitServiceReady(config.home, installed, manager);
1250
- io.out("OpenTig service restarted.");
1651
+ const runtime = await waitServiceReady(config.home, installed, manager, attempt);
1652
+ io.out(`OpenTig service restarted. Connection URL: ${publicOrigin(runtime.host, runtime.port)}`);
1251
1653
  return 0;
1252
1654
  }
1253
1655
  if (action === "uninstall") {
@@ -1262,14 +1664,53 @@ async function manageCliService(config, io, managerOverride) {
1262
1664
  io.out("OpenTig service removed. Repository settings and paired sessions were kept.");
1263
1665
  return 0;
1264
1666
  }
1667
+ io.out("Checking service configuration and listening address...");
1668
+ const network = await cliNetwork(config);
1669
+ let ownRuntime = null;
1670
+ try {
1671
+ if (isProcessRunning((await readRuntimeState(resolveCliPaths(config.home).runtimeState)).pid)) {
1672
+ if (definition && installed) ownRuntime = await verifiedServiceRuntime(config.home, manager);
1673
+ else throw new CliError("HOME_ACTIVE", "OpenTig is already running for this home.", [
1674
+ `Pair a browser: ${cliCommand(config.home, "pair")}`,
1675
+ "To install a service for this home, stop the foreground instance in its terminal and retry.",
1676
+ "No service was installed and the existing process was left running."
1677
+ ]);
1678
+ }
1679
+ } catch (error) {
1680
+ if (!(error instanceof CliError && error.code === "RUNTIME_MISSING")) throw error;
1681
+ }
1682
+ if (!ownRuntime || !await sameListener(network, ownRuntime)) try {
1683
+ await assertListenerAvailable(config, network, "service install");
1684
+ } catch (error) {
1685
+ if (error instanceof CliError) throw new CliError(error.code, error.message, [...error.actions, "No service was installed or replaced."], { cause: error });
1686
+ throw error;
1687
+ }
1688
+ await assertGitAvailable();
1689
+ await resolveServiceNpm();
1265
1690
  await manager.preflight();
1266
1691
  return await installService(config, manager, installed, definition, io);
1267
- } finally {
1692
+ });
1693
+ }
1694
+ async function withServiceLock(home, lock, run) {
1695
+ let result = 1;
1696
+ let failed = false;
1697
+ let failure;
1698
+ try {
1699
+ result = await run();
1700
+ } catch (error) {
1701
+ failed = true;
1702
+ failure = error;
1703
+ }
1704
+ try {
1268
1705
  await rm(lock, {
1269
1706
  recursive: true,
1270
1707
  force: true
1271
1708
  });
1709
+ } catch (cleanup) {
1710
+ throw new CliError("SERVICE_LOCK_CLEANUP_FAILED", failed ? diagnostic(serviceError(failure, home, "complete the operation")) : "The service operation completed, but its lock could not be removed.", [`Lock cleanup also failed: check permissions on ${lock}.`, `Inspect ${cliCommand(home, "service", "status")} and verify no operation is active before repairing the lock.`], { cause: failed ? new AggregateError([failure, cleanup], "Service operation and lock cleanup failed.") : cleanup });
1272
1711
  }
1712
+ if (failed) throw failure;
1713
+ return result;
1273
1714
  }
1274
1715
  async function installService(config, manager, installed, previousDefinition, io) {
1275
1716
  const packageRoot = await findPackageRoot(fileURLToPath(import.meta.url));
@@ -1278,6 +1719,7 @@ async function installService(config, manager, installed, previousDefinition, io
1278
1719
  const reuse = installed?.version === OPEN_TIG_APP_VERSION;
1279
1720
  const target = reuse ? packageDirectory(config.home, installed) : path.join(serviceRoot, `app-${OPEN_TIG_APP_VERSION}`);
1280
1721
  const temporary = path.join(serviceRoot, `.install-${process.pid}-${Date.now()}`);
1722
+ io.out("Preparing the managed CLI and dependencies...");
1281
1723
  try {
1282
1724
  if (!reuse) {
1283
1725
  await mkdir(temporary, {
@@ -1329,65 +1771,83 @@ async function installService(config, manager, installed, previousDefinition, io
1329
1771
  port: next.port,
1330
1772
  environmentPath: next.environmentPath
1331
1773
  });
1774
+ let runtime;
1332
1775
  try {
1776
+ io.out("Registering the OpenTig user service...");
1333
1777
  await manager.write(definition);
1334
1778
  await saveInstallation(config.home, next);
1779
+ const attempt = await requestStartup(config.home);
1780
+ io.out("Starting the service and verifying its identity...");
1335
1781
  await manager.restart();
1336
- await waitServiceReady(config.home, next, manager);
1782
+ runtime = await waitServiceReady(config.home, next, manager, attempt);
1337
1783
  } catch (error) {
1338
- if (previousDefinition) {
1339
- await manager.write(previousDefinition);
1340
- if (installed) await saveInstallation(config.home, installed);
1341
- else await rm(path.join(serviceRoot, "installation.json"), { force: true });
1342
- await manager.restart();
1343
- } else {
1344
- await manager.stop();
1345
- await manager.remove();
1346
- await rm(path.join(serviceRoot, "installation.json"), { force: true });
1784
+ const original = serviceError(error, config.home, "install");
1785
+ try {
1786
+ if (previousDefinition) {
1787
+ await manager.write(previousDefinition);
1788
+ if (installed) await saveInstallation(config.home, installed);
1789
+ else await rm(path.join(serviceRoot, "installation.json"), { force: true });
1790
+ const attempt = await requestStartup(config.home);
1791
+ await manager.restart();
1792
+ if (!installed) throw new CliError("RECOVERY_UNVERIFIED", "The previous service definition has no installation metadata.");
1793
+ await waitServiceReady(config.home, installed, manager, attempt);
1794
+ } else {
1795
+ await manager.stop();
1796
+ await manager.remove();
1797
+ await rm(path.join(serviceRoot, "installation.json"), { force: true });
1798
+ }
1799
+ } catch (recovery) {
1800
+ throw new CliError("SERVICE_RECOVERY_FAILED", diagnostic(original), [`Recovery also failed: ${diagnostic(serviceError(recovery, config.home, "restore or remove"))}`, `The final service state is not verified. Inspect ${cliCommand(config.home, "service", "status")} before retrying.`], { cause: new AggregateError([original, recovery], "Installation and recovery both failed.") });
1347
1801
  }
1348
- throw error;
1802
+ throw new CliError(original.code, diagnostic(original), [previousDefinition ? "The previous service was restored and verified." : "The newly registered service was removed. Existing applications were left running."], { cause: original });
1349
1803
  }
1350
- io.out(`OpenTig service installed and started on ${config.host}:${config.port}.`);
1804
+ io.out(`OpenTig service installed and started on ${runtime.host}:${runtime.port}.`);
1805
+ io.out(`Connection URL: ${publicOrigin(runtime.host, runtime.port)}`);
1351
1806
  io.out(manager.location);
1352
1807
  io.out(manager.startup);
1353
- io.out(`Pair another browser with: opentig pair --home ${JSON.stringify(config.home)}`);
1808
+ io.out(`Pair another browser with: ${cliCommand(config.home, "pair")}`);
1354
1809
  return 0;
1355
1810
  }
1356
- async function waitServiceReady(home, installation, manager) {
1357
- const host = ["0.0.0.0", "::"].includes(installation.host) ? installation.host === "::" ? "[::1]" : "127.0.0.1" : installation.host.includes(":") ? `[${installation.host}]` : installation.host;
1811
+ async function verifiedServiceRuntime(home, manager) {
1812
+ const runtime = await readRuntimeState(resolveCliPaths(home).runtimeState);
1813
+ const state = await manager.status();
1814
+ const owned = state.pid ? state.pid === runtime.pid : await manager.ownsRuntime?.(runtime.pid);
1815
+ if (!state.running || !owned) throw new CliError("SERVICE_PROCESS_MISMATCH", "The running OpenTig instance is not verified as this managed service.", [`Check ${cliCommand(home, "service", "status")}. If a foreground instance uses this home, stop it in its terminal before installing the service.`, "No unknown process was stopped."]);
1816
+ return probeRuntime(home, runtime, true);
1817
+ }
1818
+ async function waitServiceReady(home, installation, manager, attempt) {
1358
1819
  for (let i = 0; i < 30; i++) {
1820
+ const startup = await readStartup(home);
1821
+ if (attempt && startup?.attempt === attempt && startup.phase === "failed") throw new CliError(startup.code ?? "STARTUP_FAILED", startup.message ?? "The service failed during startup.");
1359
1822
  try {
1360
- const state = await manager.status();
1361
- const runtime = JSON.parse(await readFile(path.join(home, "runtime.json"), "utf8"));
1362
- const response = await fetch(`http://${host}:${installation.port}/readyz`, { signal: AbortSignal.timeout(1e3) });
1363
- const value = await response.json();
1364
- if (state.running && (!state.pid || state.pid === runtime.pid) && runtime.appVersion === installation.version && response.ok && value.status === "ready" && value.appVersion === installation.version) return;
1823
+ const runtime = await verifiedServiceRuntime(home, manager);
1824
+ if (runtime.appVersion === installation.version && (!attempt || startup?.attempt === attempt && startup.phase === "ready" && startup.pid === runtime.pid && startup.instanceId === runtime.instanceId || runtime.legacy && startup?.attempt === attempt && startup.phase === "pending" && Date.parse(runtime.startedAt) >= Date.parse(startup.requestedAt))) return runtime;
1365
1825
  } catch {}
1366
1826
  await setTimeout$1(1e3);
1367
1827
  }
1368
- throw new Error("The managed service did not become ready. Inspect service status and logs; check Git, Node.js, and the configured port.");
1828
+ throw startupTimeout(home);
1369
1829
  }
1370
1830
  async function assertPackageBuild(packageRoot) {
1371
1831
  await Promise.all([
1372
- access(path.join(packageRoot, "dist", "bin.mjs")),
1373
- access(path.join(packageRoot, "dist", "client", "index.html")),
1832
+ access(path.join(packageRoot, "dist/bin.mjs")),
1833
+ access(path.join(packageRoot, "dist/client/index.html")),
1374
1834
  readFile(path.join(packageRoot, "package.json"), "utf8")
1375
- ]).catch(() => {
1376
- throw new Error("The OpenTig package is incomplete and cannot be installed as a service.");
1835
+ ]).catch((cause) => {
1836
+ throw new CliError("PACKAGE_INCOMPLETE", "The OpenTig package is incomplete and cannot be installed as a service.", ["Reinstall the same CLI version from npm, then retry."], { cause });
1377
1837
  });
1378
1838
  }
1379
- async function findPackageRoot(modulePath) {
1380
- let directory = path.dirname(modulePath);
1381
- for (let depth = 0; depth < 4; depth += 1) {
1839
+ async function findPackageRoot(moduleUrl) {
1840
+ let directory = path.dirname(moduleUrl);
1841
+ for (let depth = 0; depth < 4; depth++) {
1382
1842
  try {
1383
- await Promise.all([access(path.join(directory, "package.json")), access(path.join(directory, "dist", "bin.mjs"))]);
1843
+ await Promise.all([access(path.join(directory, "package.json")), access(path.join(directory, "dist/bin.mjs"))]);
1384
1844
  return directory;
1385
1845
  } catch {}
1386
1846
  const parent = path.dirname(directory);
1387
1847
  if (parent === directory) break;
1388
1848
  directory = parent;
1389
1849
  }
1390
- throw new Error("Could not locate the installed OpenTig package.");
1850
+ throw new CliError("PACKAGE_INCOMPLETE", "Could not locate the installed OpenTig package.", ["Reinstall the CLI from npm, then retry."]);
1391
1851
  }
1392
1852
  //#endregion
1393
1853
  //#region ../../src/shared/desktop-updates.ts
@@ -1673,142 +2133,6 @@ async function createServiceUpdater(home, version, profile, address) {
1673
2133
  }, failedUpdate);
1674
2134
  }
1675
2135
  //#endregion
1676
- //#region src/cli-home.ts
1677
- var RUNTIME_STATE_VERSION = 1;
1678
- function resolveCliPaths(home) {
1679
- const resolved = path.resolve(home);
1680
- const serverData = path.join(resolved, "server");
1681
- const logs = path.join(resolved, "logs");
1682
- return {
1683
- home: resolved,
1684
- settings: path.join(resolved, "settings.json"),
1685
- aiLog: path.join(resolved, "ai-log.jsonl"),
1686
- problems: path.join(resolved, "problems.jsonl"),
1687
- serverData,
1688
- logs,
1689
- serverLog: path.join(logs, "server.log"),
1690
- runtimeState: path.join(resolved, "runtime.json"),
1691
- adminToken: path.join(serverData, "admin-token")
1692
- };
1693
- }
1694
- async function prepareCliHome(paths) {
1695
- await mkdir(paths.home, {
1696
- recursive: true,
1697
- mode: 448
1698
- });
1699
- await chmod(paths.home, 448);
1700
- await Promise.all([mkdir(paths.serverData, {
1701
- recursive: true,
1702
- mode: 448
1703
- }), mkdir(paths.logs, {
1704
- recursive: true,
1705
- mode: 448
1706
- })]);
1707
- await Promise.all([chmod(paths.serverData, 448), chmod(paths.logs, 448)]);
1708
- }
1709
- async function loadOrCreateAdminToken(filePath) {
1710
- try {
1711
- return await readAdminToken(filePath);
1712
- } catch (error) {
1713
- if (error.code !== "ENOENT") throw error;
1714
- }
1715
- const token = randomBytes(32).toString("base64url");
1716
- await writeFileAtomically(filePath, `${token}\n`, { directoryMode: 448 });
1717
- return token;
1718
- }
1719
- async function readAdminToken(filePath) {
1720
- const token = (await readFile(filePath, "utf8")).trim();
1721
- if (!isToken(token)) throw new Error("OpenTig local admin credential is corrupt.");
1722
- await chmod(filePath, 384);
1723
- return token;
1724
- }
1725
- async function writeRuntimeState(filePath, state) {
1726
- await writeFileAtomically(filePath, `${JSON.stringify({
1727
- version: RUNTIME_STATE_VERSION,
1728
- ...state
1729
- }, null, 2)}\n`, {
1730
- parseJson: true,
1731
- directoryMode: 448
1732
- });
1733
- }
1734
- async function readRuntimeState(filePath) {
1735
- let value;
1736
- try {
1737
- value = JSON.parse(await readFile(filePath, "utf8"));
1738
- } catch (error) {
1739
- if (error.code === "ENOENT") throw new Error("No running OpenTig server was found for this home.", { cause: error });
1740
- throw new Error("OpenTig runtime state is unreadable.", { cause: error });
1741
- }
1742
- if (!isRuntimeState(value)) throw new Error("OpenTig runtime state is invalid.");
1743
- await chmod(filePath, 384);
1744
- return value;
1745
- }
1746
- async function clearRuntimeState(filePath, instanceId) {
1747
- try {
1748
- if ((await readRuntimeState(filePath)).instanceId === instanceId) await rm(filePath, { force: true });
1749
- } catch (error) {
1750
- if (!(error instanceof Error && error.message === "No running OpenTig server was found for this home.")) throw error;
1751
- }
1752
- }
1753
- function newInstanceId() {
1754
- return randomBytes(24).toString("base64url");
1755
- }
1756
- function isToken(value) {
1757
- return /^[A-Za-z0-9_-]{43}$/.test(value);
1758
- }
1759
- function isRuntimeState(value) {
1760
- const state = value;
1761
- return Boolean(state && state.version === RUNTIME_STATE_VERSION && Number.isSafeInteger(state.pid) && Number(state.pid) > 0 && typeof state.host === "string" && state.host.length > 0 && state.host.length <= 255 && Number.isSafeInteger(state.port) && Number(state.port) > 0 && Number(state.port) <= 65535 && Number.isSafeInteger(state.protocolVersion) && Number(state.protocolVersion) > 0 && typeof state.appVersion === "string" && state.appVersion.length > 0 && state.appVersion.length <= 128 && typeof state.instanceId === "string" && /^[A-Za-z0-9_-]{32}$/.test(state.instanceId) && typeof state.startedAt === "string" && !Number.isNaN(Date.parse(state.startedAt)));
1762
- }
1763
- //#endregion
1764
- //#region src/cli-log.ts
1765
- var MAX_BYTES = 1048576;
1766
- var BACKUPS = 3;
1767
- var CliServerLog = class {
1768
- filePath;
1769
- queue = Promise.resolve();
1770
- constructor(filePath) {
1771
- this.filePath = filePath;
1772
- }
1773
- logger = (level, message) => {
1774
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${redactSensitiveText(message)}\n`;
1775
- this.queue = this.queue.then(async () => {
1776
- await mkdir(path.dirname(this.filePath), {
1777
- recursive: true,
1778
- mode: 448
1779
- });
1780
- if (await wouldExceed(this.filePath, Buffer.byteLength(line))) await this.rotate();
1781
- await appendFile(this.filePath, line, {
1782
- encoding: "utf8",
1783
- mode: 384
1784
- });
1785
- }).catch(() => void 0);
1786
- };
1787
- close() {
1788
- return this.queue;
1789
- }
1790
- async rotate() {
1791
- await rm(`${this.filePath}.${BACKUPS}`, { force: true });
1792
- for (let index = 2; index >= 1; index -= 1) await moveIfPresent(`${this.filePath}.${index}`, `${this.filePath}.${index + 1}`);
1793
- await moveIfPresent(this.filePath, `${this.filePath}.1`);
1794
- }
1795
- };
1796
- async function wouldExceed(filePath, incomingBytes) {
1797
- try {
1798
- return (await stat(filePath)).size + incomingBytes > MAX_BYTES;
1799
- } catch (error) {
1800
- if (error.code === "ENOENT") return false;
1801
- throw error;
1802
- }
1803
- }
1804
- async function moveIfPresent(source, target) {
1805
- try {
1806
- await rename(source, target);
1807
- } catch (error) {
1808
- if (error.code !== "ENOENT") throw error;
1809
- }
1810
- }
1811
- //#endregion
1812
2136
  //#region src/cli.ts
1813
2137
  var SHUTDOWN_TIMEOUT_MS = 8e3;
1814
2138
  var consoleIo = {
@@ -1816,9 +2140,10 @@ var consoleIo = {
1816
2140
  error: (value) => process$1.stderr.write(`${value}\n`)
1817
2141
  };
1818
2142
  async function runCli(args, environment = process$1.env, io = consoleIo, profile = "production") {
2143
+ let config;
2144
+ let reporter = null;
1819
2145
  try {
1820
- assertNodeVersion();
1821
- const config = parseCliArguments(args, environment);
2146
+ config = parseCliArguments(args, environment);
1822
2147
  if (config.command === "help") {
1823
2148
  io.out(cliHelp(OPEN_TIG_APP_VERSION));
1824
2149
  return 0;
@@ -1827,21 +2152,35 @@ async function runCli(args, environment = process$1.env, io = consoleIo, profile
1827
2152
  io.out(OPEN_TIG_APP_VERSION);
1828
2153
  return 0;
1829
2154
  }
2155
+ if (config.command === "serve" || config.command === "start") reporter = await startupReporter(config.home);
2156
+ assertNodeVersion();
1830
2157
  if (config.command === "service") return await manageCliService(config, io);
2158
+ if (config.command === "reset-access") {
2159
+ await rm(path.join(resolveCliPaths(config.home).serverData, "web-access.json"), { force: true });
2160
+ io.out("Web access settings reset. Restart this OpenTig instance to enable browser access on its startup address and port. Paired devices are preserved.");
2161
+ io.out(`Managed service: ${cliCommand(config.home, "service", "restart")}`);
2162
+ io.out(`Foreground server: stop it in its terminal, then run ${cliCommand(config.home, "serve")}`);
2163
+ return 0;
2164
+ }
1831
2165
  if (config.command === "pair") return await pairRunningServer(config, io, profile);
1832
- return await startCliServer(config, io, profile);
2166
+ return await startCliServer(config, io, profile, reporter);
1833
2167
  } catch (error) {
1834
- const message = redactSensitiveText(error instanceof Error ? error.message : String(error));
1835
- io.error(`OpenTig: ${message}`);
2168
+ const failure = error instanceof CliUsageError || !config ? error : operationError(error, config.home, config.command === "service" ? `run service ${config.serviceAction}` : `run ${config.command}`);
2169
+ if (config && !(error instanceof CliUsageError)) {
2170
+ await reporter?.failed(failure).catch(() => void 0);
2171
+ await recordCliFailure(config.home, failure).catch(() => void 0);
2172
+ }
2173
+ io.error(`OpenTig: ${diagnostic(failure)}`);
1836
2174
  if (error instanceof CliUsageError) io.error("Run \"opentig --help\" for usage.");
1837
2175
  return error instanceof CliUsageError ? error.exitCode : 1;
1838
2176
  }
1839
2177
  }
1840
- async function startCliServer(config, io, profile) {
2178
+ async function startCliServer(config, io, profile, reporter) {
1841
2179
  await assertGitAvailable();
1842
2180
  const paths = resolveCliPaths(config.home);
1843
2181
  await prepareCliHome(paths);
1844
- await refuseActiveRuntime(paths.runtimeState, profile);
2182
+ await refuseActiveRuntime(config, profile);
2183
+ await assertListenerAvailable(config, await cliNetwork(config), config.command);
1845
2184
  const adminToken = await loadOrCreateAdminToken(paths.adminToken);
1846
2185
  const instanceId = newInstanceId();
1847
2186
  const log = new CliServerLog(paths.serverLog);
@@ -1863,29 +2202,40 @@ async function startCliServer(config, io, profile) {
1863
2202
  admin: {
1864
2203
  token: adminToken,
1865
2204
  instanceId
2205
+ },
2206
+ recoveryCommand: profile === "dev" ? "node packages/server/dist/dev.mjs reset-access" : "opentig reset-access",
2207
+ onNetworkChanged: async (network) => {
2208
+ const state = await readRuntimeState(paths.runtimeState);
2209
+ await writeRuntimeState(paths.runtimeState, {
2210
+ ...state,
2211
+ host: network.listenerHost,
2212
+ port: network.listenerPort
2213
+ });
1866
2214
  }
1867
- }, config.port);
2215
+ }, config.port, config);
1868
2216
  await writeRuntimeState(paths.runtimeState, {
1869
2217
  pid: process$1.pid,
1870
- host: config.host,
2218
+ host: server.host,
1871
2219
  port: server.port,
1872
2220
  protocolVersion: server.protocolVersion,
1873
2221
  appVersion: server.appVersion,
1874
2222
  instanceId,
1875
2223
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1876
2224
  });
1877
- log.logger("info", `OpenTig CLI ready on ${publicOrigin(config.host, server.port)}.`);
1878
- const origin = publicOrigin(config.host, server.port);
1879
- const pairing = rewritePairingOrigin(server.createPairingLink(), origin);
2225
+ await reporter?.ready(instanceId);
2226
+ log.logger("info", `OpenTig CLI ready on ${publicOrigin(server.host, server.port)}.`);
2227
+ const origin = publicOrigin(server.host, server.port);
2228
+ const pairing = server.getStatus().browserAccessEnabled ? rewritePairingOrigin(server.createPairingLink(), origin) : null;
1880
2229
  io.out(`${applicationName(profile)} ${server.appVersion} is ready.`);
1881
2230
  if (profile === "dev") io.out(`Dev data directory: ${paths.home}`);
1882
2231
  io.out(`Connection URL: ${origin}`);
1883
- printPairing(pairing, io, config.command === "serve");
1884
- if (!isLoopbackHost(config.host)) {
2232
+ if (pairing) printPairing(pairing, io, config.command === "serve");
2233
+ else io.out("Web access is paused. Use reset-access locally and restart to restore it.");
2234
+ if (!isLoopbackHost(server.host)) {
1885
2235
  io.error("WARNING: This listener grants owner-level access as your OS user. Use only a trusted LAN/VPN or an HTTPS/SSH tunnel; never expose it directly to the public Internet.");
1886
- if (config.host === "0.0.0.0" || config.host === "::") io.error("Replace the loopback host in the printed link with a trusted address of this machine for another device.");
2236
+ if (server.host === "0.0.0.0" || server.host === "::") io.error("Replace the loopback host in the printed link with a trusted address of this machine for another device.");
1887
2237
  }
1888
- if (config.openBrowser) await openSystemBrowser(pairing.url).catch(() => {
2238
+ if (config.openBrowser && pairing) await openSystemBrowser(pairing.url).catch(() => {
1889
2239
  io.error("Could not open the system browser. Open the one-time pairing link shown above manually.");
1890
2240
  });
1891
2241
  updates.start();
@@ -1895,7 +2245,7 @@ async function startCliServer(config, io, profile) {
1895
2245
  await log.close();
1896
2246
  }, io);
1897
2247
  } catch (error) {
1898
- await updates.stop();
2248
+ await updates.stop().catch(() => void 0);
1899
2249
  if (server) await server.close().catch(() => void 0);
1900
2250
  await clearRuntimeState(paths.runtimeState, instanceId).catch(() => void 0);
1901
2251
  await log.close();
@@ -1905,15 +2255,15 @@ async function startCliServer(config, io, profile) {
1905
2255
  async function pairRunningServer(config, io, profile) {
1906
2256
  const paths = resolveCliPaths(config.home);
1907
2257
  const state = await readRuntimeState(paths.runtimeState);
1908
- if (!isProcessRunning(state.pid)) throw new Error("OpenTig runtime state is stale; its process is not running.");
2258
+ if (!isProcessRunning(state.pid)) throw new CliError("RUNTIME_STALE", "OpenTig runtime state is stale; its process is not running.", [`Start ${cliCommand(config.home, "serve")} or restart its managed service.`]);
1909
2259
  const origin = publicOrigin(state.host, state.port);
1910
- const adminOrigin = localAdminOrigin(state);
2260
+ const adminOrigin = publicOrigin(state.host, state.port);
1911
2261
  const ready = await fetch(`${adminOrigin}/readyz`, { signal: AbortSignal.timeout(3e3) });
1912
- if (!ready.ok) throw new Error("The running OpenTig server is not ready.");
2262
+ if (!ready.ok) throw new CliError("SERVER_NOT_READY", "The running OpenTig server is not ready.", [`Check ${cliCommand(config.home, "service", "status")} and retry when startup completes.`]);
1913
2263
  const identity = await ready.json();
1914
- if (identity.status !== "ready" || identity.protocolVersion !== state.protocolVersion || identity.appVersion !== state.appVersion || profile === "dev" && identity.profile !== "dev") throw new Error("OpenTig runtime state does not match the running server.");
2264
+ if (identity.status !== "ready" || identity.protocolVersion !== state.protocolVersion || identity.appVersion !== state.appVersion || profile === "dev" && identity.profile !== "dev") throw new CliError("RUNTIME_MISMATCH", "OpenTig runtime state does not match the running server.", [`Inspect ${cliCommand(config.home, "service", "status")} before restarting this instance.`]);
1915
2265
  const adminToken = await readAdminToken(paths.adminToken).catch(() => {
1916
- throw new Error("OpenTig local admin credential is missing or unreadable.");
2266
+ throw new CliError("ADMIN_CREDENTIAL_INVALID", "OpenTig local admin credential is missing or unreadable.", [`Check the private credential and permissions in ${paths.serverData}. Do not copy credentials from another instance.`]);
1917
2267
  });
1918
2268
  const response = await fetch(`${adminOrigin}/api/admin/pair`, {
1919
2269
  method: "POST",
@@ -1927,32 +2277,50 @@ async function pairRunningServer(config, io, profile) {
1927
2277
  }),
1928
2278
  signal: AbortSignal.timeout(3e3)
1929
2279
  });
1930
- if (!response.ok) throw new Error("The running server refused the local pairing request.");
1931
- printPairing(validatePairing(await response.json(), origin), io, true);
2280
+ if (response.status === 403) throw new CliError("WEB_ACCESS_DISABLED", "Web access is paused; no pairing link was created.", [`Enable browser access in Settings → Web access, or run ${cliCommand(config.home, "reset-access")} and restart this instance.`, "Resetting access restores startup listener settings and preserves paired browsers."]);
2281
+ if (!response.ok) throw new CliError("PAIRING_REFUSED", "The running server refused the local pairing request.", [`Check ${cliCommand(config.home, "service", "status")} and the selected home before retrying.`]);
2282
+ let pairing;
2283
+ try {
2284
+ pairing = validatePairing(await response.json(), origin);
2285
+ } catch (cause) {
2286
+ throw new CliError("PAIRING_INVALID", "The server returned an invalid pairing link.", [`No link was printed. Inspect ${cliCommand(config.home, "service", "status")} and the server log before retrying.`], { cause });
2287
+ }
2288
+ printPairing(pairing, io, true);
1932
2289
  return 0;
1933
2290
  }
1934
- async function startOnConfiguredPort(baseConfig, port) {
2291
+ async function startOnConfiguredPort(baseConfig, port, cliConfig) {
1935
2292
  try {
1936
2293
  return await runOpenTigServer({
1937
2294
  ...baseConfig,
1938
2295
  port
1939
2296
  });
1940
2297
  } catch (error) {
1941
- if (isAddressInUse(error)) throw new Error(`Port ${port} is already in use. Stop that process or select another port with --port.`, { cause: error });
1942
- throw error;
2298
+ const config = cliConfig ?? {
2299
+ home: path.dirname(baseConfig.settingsPath),
2300
+ host: baseConfig.host ?? "127.0.0.1",
2301
+ port
2302
+ };
2303
+ const { network, savedListener } = await loadStartupNetwork({
2304
+ ...baseConfig,
2305
+ port
2306
+ });
2307
+ throw listenerError(error, config, {
2308
+ ...network,
2309
+ savedListener
2310
+ }, cliConfig?.command ?? "serve");
1943
2311
  }
1944
2312
  }
1945
- function publicOrigin(host, port) {
1946
- const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host === "::" ? "::1" : host;
1947
- return `http://${displayHost.includes(":") ? `[${displayHost}]` : displayHost}:${port}`;
1948
- }
1949
- async function refuseActiveRuntime(runtimeStatePath, profile) {
2313
+ async function refuseActiveRuntime(config, profile) {
2314
+ const paths = resolveCliPaths(config.home);
1950
2315
  try {
1951
- const state = await readRuntimeState(runtimeStatePath);
1952
- if (isProcessRunning(state.pid)) throw new Error(`OpenTig is already running for this home (PID ${state.pid}). Use "${profile === "dev" ? "npm run pair:web:dev" : "opentig pair"}" to add a device.`);
1953
- await clearRuntimeState(runtimeStatePath, state.instanceId);
2316
+ const state = await readRuntimeState(paths.runtimeState);
2317
+ if (isProcessRunning(state.pid)) {
2318
+ await probeRuntime(config.home, state);
2319
+ throw new CliError("HOME_ACTIVE", "OpenTig is already running for this home.", [`Pair a browser with ${profile === "dev" ? "npm run pair:web:dev" : cliCommand(config.home, "pair")}`, "To switch a foreground instance to a service, stop it in its terminal first."]);
2320
+ }
2321
+ await clearRuntimeState(paths.runtimeState, state.instanceId);
1954
2322
  } catch (error) {
1955
- if (error instanceof Error && error.message === "No running OpenTig server was found for this home.") return;
2323
+ if (error instanceof CliError && error.code === "RUNTIME_MISSING") return;
1956
2324
  throw error;
1957
2325
  }
1958
2326
  }
@@ -2002,47 +2370,22 @@ function rewritePairingOrigin(pairing, origin) {
2002
2370
  }
2003
2371
  function validatePairing(value, origin) {
2004
2372
  const pairing = value;
2005
- if (!pairing || typeof pairing.url !== "string" || typeof pairing.expiresAt !== "string") throw new Error("The server returned an invalid pairing link.");
2373
+ if (!pairing || typeof pairing.url !== "string" || typeof pairing.expiresAt !== "string") throw new CliError("PAIRING_INVALID", "The server returned an invalid pairing link.", ["No link was printed. Check the selected instance and server logs before retrying."]);
2006
2374
  const url = new URL(pairing.url);
2007
2375
  const token = new URLSearchParams(url.hash.slice(1)).get("token");
2008
2376
  const expiresAt = Date.parse(pairing.expiresAt);
2009
- if (url.origin !== origin || url.pathname !== "/pair" || url.search || !token || !/^[A-Za-z0-9_-]{43}$/.test(token) || expiresAt <= Date.now() || expiresAt > Date.now() + 9e5) throw new Error("The server returned an invalid pairing link.");
2377
+ if (url.origin !== origin || url.username || url.password || url.pathname !== "/pair" || url.search || !token || !/^[A-Za-z0-9_-]{43}$/.test(token) || !Number.isFinite(expiresAt) || expiresAt <= Date.now() || expiresAt > Date.now() + 9e5) throw new CliError("PAIRING_INVALID", "The server returned an invalid pairing link.", ["No link was printed. Check the selected instance and server logs before retrying."]);
2010
2378
  return {
2011
2379
  url: url.href,
2012
2380
  expiresAt: pairing.expiresAt
2013
2381
  };
2014
2382
  }
2015
- function localAdminOrigin(state) {
2016
- return publicOrigin(state.host, state.port);
2017
- }
2018
2383
  function isLoopbackHost(host) {
2019
2384
  return host === "127.0.0.1" || host === "::1" || host.toLowerCase() === "localhost";
2020
2385
  }
2021
- function isAddressInUse(error) {
2022
- return error instanceof Error && "code" in error && error.code === "EADDRINUSE";
2023
- }
2024
- function isProcessRunning(pid) {
2025
- try {
2026
- process$1.kill(pid, 0);
2027
- return true;
2028
- } catch (error) {
2029
- return error.code === "EPERM";
2030
- }
2031
- }
2032
2386
  function assertNodeVersion() {
2033
2387
  const major = Number(process$1.versions.node.split(".", 1)[0]);
2034
- if (!Number.isSafeInteger(major) || major < 24) throw new Error("Node.js 24 or later is required.");
2035
- }
2036
- function assertGitAvailable() {
2037
- return new Promise((resolve, reject) => {
2038
- execFile("git", ["--version"], {
2039
- timeout: 1e4,
2040
- windowsHide: true
2041
- }, (error, stdout) => {
2042
- if (error || !stdout.trim().startsWith("git version ")) reject(/* @__PURE__ */ new Error("Git is required and was not found on PATH."));
2043
- else resolve();
2044
- });
2045
- });
2388
+ if (!Number.isSafeInteger(major) || major < 24) throw new CliError("NODE_UNSUPPORTED", "Node.js 24 or later is required.", ["Install a supported Node.js version, then retry from that terminal."]);
2046
2389
  }
2047
2390
  //#endregion
2048
2391
  export { parseCliArguments as i, OPEN_TIG_APP_VERSION as n, CliUsageError as r, runCli as t };