@opentig/cli 0.1.3 → 0.1.5

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 (103) hide show
  1. package/README.md +73 -17
  2. package/dist/bin.mjs +2 -2
  3. package/dist/chunks/{cli-DaYybBfs.mjs → cli-D_aggpu2.mjs} +232 -57
  4. package/dist/chunks/service-manager-DkpSHxsP.mjs +394 -0
  5. package/dist/client/assets/App-CeDVj5Hh.js +118 -0
  6. package/dist/client/assets/{CreatePullRequestDialog-CGnY6nrw.js → CreatePullRequestDialog-DMHY_u8X.js} +2 -2
  7. package/dist/client/assets/{DiffWorkspace-1ZAh7eFX.js → DiffWorkspace-CeYQ0YtE.js} +1 -1
  8. package/dist/client/assets/{EditableFileViewer-DOnxE6ty.js → EditableFileViewer-BhwE-QMO.js} +1 -1
  9. package/dist/client/assets/{File-DM0fcPqi.js → File-C10_BxOu.js} +1 -1
  10. package/dist/client/assets/{FilesView-BuTZiUd4.js → FilesView-BT4eHbFo.js} +1 -1
  11. package/dist/client/assets/{GitHubAvatar-BIZ8KlZ9.js → GitHubAvatar-Ch_PaCv3.js} +1 -1
  12. package/dist/client/assets/{HistoryView-BTo2h2ee.js → HistoryView-CivGwdxV.js} +1 -1
  13. package/dist/client/assets/{HtmlFileViewer-tEtrcHdG.js → HtmlFileViewer-Cufzaec9.js} +1 -1
  14. package/dist/client/assets/{IconEdit-DtVD7NDP.js → IconEdit-RxH71_V-.js} +1 -1
  15. package/dist/client/assets/{IconFolderPlus-D5cPTAyD.js → IconFolderPlus-DSSB-ikf.js} +1 -1
  16. package/dist/client/assets/{ImageFileViewer-C56bQ88S.js → ImageFileViewer-CFUkcXLH.js} +1 -1
  17. package/dist/client/assets/{LocalRefsDialog-DwhpiSyu.js → LocalRefsDialog-B2jUCKTx.js} +1 -1
  18. package/dist/client/assets/{MarkdownFileViewer-BxQfoNnS.js → MarkdownFileViewer-BLfJ-Vof.js} +2 -2
  19. package/dist/client/assets/{PairingPage-Cu1KraeJ.js → PairingPage-C5Ggk-Z_.js} +1 -1
  20. package/dist/client/assets/{PierreDiffViewer-B9IrAdjM.js → PierreDiffViewer-DYj1ZThr.js} +1 -1
  21. package/dist/client/assets/{PierreEditBoundary-DDJlRbaP.js → PierreEditBoundary-UfvbKDbd.js} +1 -1
  22. package/dist/client/assets/{PierreWorkerPool-BJhmuZSy.js → PierreWorkerPool-7EVoquIB.js} +1 -1
  23. package/dist/client/assets/{PullRequestDiff-Di1_Wv1o.js → PullRequestDiff-DxLTcm6L.js} +1 -1
  24. package/dist/client/assets/{PullRequestViewer-CuH0Ze12.js → PullRequestViewer-CyW39MuF.js} +2 -2
  25. package/dist/client/assets/{PullRequestsView-DRKuy94k.js → PullRequestsView-BHcnarLS.js} +1 -1
  26. package/dist/client/assets/{QuickOpenDialog-DSdrgS2H.js → QuickOpenDialog-CasQU7pk.js} +1 -1
  27. package/dist/client/assets/{RepositoryProjectsDialog-CWcakQRC.js → RepositoryProjectsDialog-VAiZrDlA.js} +1 -1
  28. package/dist/client/assets/{SearchView-Btqv-BZZ.js → SearchView-Bv4bA4UJ.js} +1 -1
  29. package/dist/client/assets/{SettingsDialog-CV7hRbtK.js → SettingsDialog-D33V9Fry.js} +2 -2
  30. package/dist/client/assets/{Viewer-bchsRyMu.js → Viewer-I1lC_oJU.js} +2 -2
  31. package/dist/client/assets/{abnfDiagram-N423BO3Z-D6JAOs-X.js → abnfDiagram-N423BO3Z-DrlQwm7W.js} +1 -1
  32. package/dist/client/assets/{arc-YncG-7Bc.js → arc-B9cXI-QL.js} +1 -1
  33. package/dist/client/assets/{architectureDiagram-T3A2C74G-Ckf8dhax.js → architectureDiagram-T3A2C74G-DZz7ZXbI.js} +1 -1
  34. package/dist/client/assets/{blockDiagram-VBNYF7ZC-CGQa9zHO.js → blockDiagram-VBNYF7ZC-DZFMT63B.js} +1 -1
  35. package/dist/client/assets/{c4Diagram-5PPSVZJV-Ctgat2YZ.js → c4Diagram-5PPSVZJV-BnHIZMZp.js} +1 -1
  36. package/dist/client/assets/channel-CE-pdA-6.js +1 -0
  37. package/dist/client/assets/{checkbox-D5IGxs1d.js → checkbox-BpmQTxn7.js} +1 -1
  38. package/dist/client/assets/{chunk-2GRJ4B5K-CyDC0FQq.js → chunk-2GRJ4B5K-CJOlpI9g.js} +1 -1
  39. package/dist/client/assets/{chunk-2Q5K7J3B-DqYRVN1X.js → chunk-2Q5K7J3B-DhsnoUrJ.js} +1 -1
  40. package/dist/client/assets/{chunk-5RXB4S5H-BtrE-Jqe.js → chunk-5RXB4S5H-Nwsk3OIu.js} +1 -1
  41. package/dist/client/assets/{chunk-5VM5RSS4-CEVyxEEH.js → chunk-5VM5RSS4-Dk9FYU9b.js} +1 -1
  42. package/dist/client/assets/{chunk-6Q2QTUOP-CR_RRfTb.js → chunk-6Q2QTUOP-C8lX8T87.js} +1 -1
  43. package/dist/client/assets/{chunk-GF5L2VYU-Uoc_Vpub.js → chunk-GF5L2VYU-DWHvTG6c.js} +1 -1
  44. package/dist/client/assets/{chunk-JWPE2WC7-BAhDzsCx.js → chunk-JWPE2WC7-BLu2-FJl.js} +1 -1
  45. package/dist/client/assets/{chunk-KBJHAD2P-DkGEUlle.js → chunk-KBJHAD2P-CEOb10I1.js} +1 -1
  46. package/dist/client/assets/{chunk-RYQCIY6F-doFXXAJb.js → chunk-RYQCIY6F-BcdMrb_e.js} +1 -1
  47. package/dist/client/assets/{chunk-XXDRQBXY-B44pCuJx.js → chunk-XXDRQBXY-CzPimvfs.js} +1 -1
  48. package/dist/client/assets/classDiagram-JCYQIIEL-2elbt6qi.js +1 -0
  49. package/dist/client/assets/classDiagram-v2-OCEON4UE-2elbt6qi.js +1 -0
  50. package/dist/client/assets/{cose-bilkent-JH36ORCC-CymnJ1xN.js → cose-bilkent-JH36ORCC-BYfzHUkR.js} +1 -1
  51. package/dist/client/assets/{cynefin-VYW2F7L2-BT5dARHp.js → cynefin-VYW2F7L2-B4mEAleX.js} +1 -1
  52. package/dist/client/assets/{cynefinDiagram-MW4NZA55-DVrhZ1A4.js → cynefinDiagram-MW4NZA55-CioMvT6V.js} +1 -1
  53. package/dist/client/assets/{dagre-VZM6K2ZE-BET0T1KQ.js → dagre-VZM6K2ZE-DWPgHi5i.js} +1 -1
  54. package/dist/client/assets/{diagram-7IWD3JNH-mIl4bvgD.js → diagram-7IWD3JNH-94i9EwB_.js} +1 -1
  55. package/dist/client/assets/{diagram-B4RE2ZJO-DKSi3zok.js → diagram-B4RE2ZJO-BRgMOgy8.js} +1 -1
  56. package/dist/client/assets/{diagram-LBJQPF4R-DFzvqR_V.js → diagram-LBJQPF4R-DHaXTl1T.js} +1 -1
  57. package/dist/client/assets/{diagram-Q27KOJAE-C8UUiNSc.js → diagram-Q27KOJAE-DtLuyYm1.js} +1 -1
  58. package/dist/client/assets/{diagram-UB23O5K3-CYuQnggB.js → diagram-UB23O5K3-D8R3grCW.js} +1 -1
  59. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-orsOZEt3.js → ebnfDiagram-BXEA7PRR-voe5GU7W.js} +1 -1
  60. package/dist/client/assets/{erDiagram-JOGREHBK-BPxNY3Or.js → erDiagram-JOGREHBK-BShMJhmY.js} +1 -1
  61. package/dist/client/assets/{flowDiagram-UKHOOZJN-Dj_tbpt_.js → flowDiagram-UKHOOZJN-DASXB2wV.js} +1 -1
  62. package/dist/client/assets/{ganttDiagram-PKOTCBZU-BhG_1m3O.js → ganttDiagram-PKOTCBZU-Da1ElnJN.js} +1 -1
  63. package/dist/client/assets/{gh-utils-CR5JRkW2.js → gh-utils-D00lyruw.js} +1 -1
  64. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-B6i118FC.js → gitGraphDiagram-DS77QQ5N-DoGRli0p.js} +1 -1
  65. package/dist/client/assets/{index-C_D1kOlX.js → index-BdjG_WtS.js} +2 -2
  66. package/dist/client/assets/{index-CE9Jcx4e.css → index-Uku28hIV.css} +1 -1
  67. package/dist/client/assets/{infoDiagram-6WML65LV-DhHBZSVb.js → infoDiagram-6WML65LV-CxkxF5pn.js} +1 -1
  68. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-6Vd6XOG_.js → ishikawaDiagram-WSZJBQD7-l6dk2-PS.js} +1 -1
  69. package/dist/client/assets/{journeyDiagram-NVQOT4AX-uH71A9mr.js → journeyDiagram-NVQOT4AX-wJY4ZF5o.js} +1 -1
  70. package/dist/client/assets/{kanban-definition-27J2QSJJ-DbxRwp0h.js → kanban-definition-27J2QSJJ-CIcwUmJo.js} +1 -1
  71. package/dist/client/assets/{linear-CFzmLtpp.js → linear-Tf-dXwQ9.js} +1 -1
  72. package/dist/client/assets/{mermaid.core-B_Mv_eHi.js → mermaid.core-DnaJSnLe.js} +4 -4
  73. package/dist/client/assets/{mindmap-definition-FAOFIHXS-DQf1s9U3.js → mindmap-definition-FAOFIHXS-jFdJ2bLh.js} +1 -1
  74. package/dist/client/assets/{pegDiagram-VL7TDLO6-BxFVTcDJ.js → pegDiagram-VL7TDLO6-vq0WE66k.js} +1 -1
  75. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-CCNbxGSk.js → pieDiagram-7S7Q4E2Y-3edKDMpI.js} +1 -1
  76. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-BVhzcmm1.js → quadrantDiagram-CIZ2JOQS-B6M8U4XV.js} +1 -1
  77. package/dist/client/assets/{railroadDiagram-AXF67PYL-D3R2-3Og.js → railroadDiagram-AXF67PYL-C-d5-0YS.js} +1 -1
  78. package/dist/client/assets/{render-markdown-DcHo7ihS.js → render-markdown-GXIpf9CM.js} +1 -1
  79. package/dist/client/assets/{requirementDiagram-LRYGKXZP-B-NkzvnF.js → requirementDiagram-LRYGKXZP-FVzmEbFu.js} +1 -1
  80. package/dist/client/assets/{sankeyDiagram-W5VNT64P-BRpIzFiP.js → sankeyDiagram-W5VNT64P-B-qcUamt.js} +1 -1
  81. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-B7cBRL6x.js → sequenceDiagram-SI44F4Z6-DgFXRLSt.js} +1 -1
  82. package/dist/client/assets/{sizeCapture-X5ZJPWSS-BZ6DbjJ6.js → sizeCapture-X5ZJPWSS-CWi_pMb2.js} +1 -1
  83. package/dist/client/assets/{stateDiagram-OKZ733FA-x8ChPNdG.js → stateDiagram-OKZ733FA-amQwzlgU.js} +1 -1
  84. package/dist/client/assets/{stateDiagram-v2-UEYNNEHI-BlYlvm69.js → stateDiagram-v2-UEYNNEHI-TdV8eIV2.js} +1 -1
  85. package/dist/client/assets/{swimlanes-SLNWSIFB-DR21moSS.js → swimlanes-SLNWSIFB-L88WlaUb.js} +2 -2
  86. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-AWimqvIC.js +8 -0
  87. package/dist/client/assets/{timeline-definition-Z64GVDOM-UlAkswoH.js → timeline-definition-Z64GVDOM-D4TJxxc3.js} +1 -1
  88. package/dist/client/assets/{vennDiagram-T6HMQDX7-91kBYEFr.js → vennDiagram-T6HMQDX7-ClJWOyzQ.js} +1 -1
  89. package/dist/client/assets/{viewer-tabs-BHvOJKLx.js → viewer-tabs-Cp_MC_J3.js} +1 -1
  90. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-CqAHibOn.js → wardleyDiagram-T6FBY63Y-Cxfjpd6V.js} +1 -1
  91. package/dist/client/assets/{xychartDiagram-ELKLHX3M-BMFs9l78.js → xychartDiagram-ELKLHX3M-BYqbTvmt.js} +1 -1
  92. package/dist/client/index.html +2 -2
  93. package/dist/dev.mjs +4 -4
  94. package/dist/manifest.json +1 -1
  95. package/dist/server.mjs +5 -5
  96. package/dist/service-update.mjs +12 -10
  97. package/package.json +1 -1
  98. package/dist/chunks/cli-service-CDd3ZvGV.mjs +0 -237
  99. package/dist/client/assets/App-C7Yf3r-l.js +0 -118
  100. package/dist/client/assets/channel-B31JCSE_.js +0 -1
  101. package/dist/client/assets/classDiagram-JCYQIIEL-C6tU-sLq.js +0 -1
  102. package/dist/client/assets/classDiagram-v2-OCEON4UE-C6tU-sLq.js +0 -1
  103. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-DbXYPHRv.js +0 -8
package/README.md CHANGED
@@ -1,37 +1,93 @@
1
1
  # OpenTig CLI
2
2
 
3
- Run the same authenticated OpenTig server, runtime, protocol, and production web client used by the Windows desktop application.
3
+ **A focused workspace for reviewing code changes, in your browser.**
4
4
 
5
- OpenTig is local-first: it works with existing Git repositories on the machine running the server. Node.js 24 or later and Git are required.
5
+ Browse files, inspect diffs, edit, stage, and commit on your computer or a remote server. The CLI serves the same UI as the OpenTig desktop app, without Electron.
6
6
 
7
- ## Local use
7
+ [Source](https://github.com/jorgefl8/opentig) · [Documentation](https://github.com/jorgefl8/opentig/blob/main/docs/README.md) · [Report a bug](https://github.com/jorgefl8/opentig/issues)
8
8
 
9
- Pin an exact version for repeatable use:
9
+ ![OpenTig reviewing a TypeScript split diff in a demo repository](https://raw.githubusercontent.com/jorgefl8/opentig/main/docs/images/workspace.png)
10
+
11
+ *The browser UI in the Dev build, reviewing a small demo repository.*
12
+
13
+ > **Early development.** I'm building OpenTig for my own daily workflow and sharing it as it takes shape. Expect bugs, rough edges, and changes. Keep important work backed up and review Git and file operations before confirming them.
14
+
15
+ ## Start locally
16
+
17
+ Works on **macOS, Linux, and Windows** with **Node.js 24+** and **Git** installed.
18
+
19
+ ```bash
20
+ npx --yes @opentig/cli@latest
21
+ ```
22
+
23
+ This starts OpenTig at `127.0.0.1:6767` and opens a one-use browser pairing link. Choose an existing repository in the visual folder picker and start reviewing. Repositories are managed in the UI; you do not need to start a separate server for each one.
24
+
25
+ Prefer Bun's package runner?
26
+
27
+ ```bash
28
+ bunx @opentig/cli@latest
29
+ ```
30
+
31
+ Plain `bunx` launches the Node-shebang executable. **Node.js 24+ is still required**; running OpenTig under the Bun runtime itself is not supported.
32
+
33
+ Or install globally:
10
34
 
11
35
  ```bash
12
- npx --yes @opentig/cli@0.1.0
13
- npx --yes @opentig/cli@0.1.0 serve
14
- bunx @opentig/cli@0.1.0 serve
36
+ npm install -g @opentig/cli@latest
37
+ opentig
15
38
  ```
16
39
 
17
- Plain `bunx` installs and launches the Node-shebang executable. Running OpenTig under the Bun runtime itself is not supported.
40
+ ## What you can do
18
41
 
19
- Run `opentig help` or `opentig --help` for commands, examples, pairing/tunnel guidance, service management, environment variables, and shutdown behavior.
42
+ | | |
43
+ | --- | --- |
44
+ | **Review changes** | Split or unified diffs, staged and unstaged files, and merge-conflict resolution. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#changes-diffs-and-commits) |
45
+ | **Browse and edit** | File tree, tabs, quick edits, find and replace, repository search, and Markdown, image, HTML, and SVG previews. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#files-and-editing) |
46
+ | **Work with Git** | Stage files, commit, pull and push, inspect history, and switch branches and worktrees. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#safe-pull-and-push) |
47
+ | **Organize repositories** | Recent repositories, project groups, and manual relocation without moving files. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#repositories-and-projects) |
48
+ | **Review GitHub PRs** | Browse pull requests, inspect changes, and prepare new PRs through your authenticated `gh` CLI. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#github-pull-requests) |
49
+ | **Use a remote server** | Pair a browser on your laptop or phone and work with repositories on the server. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/web-access.md) |
20
50
 
21
- `opentig` and `opentig start` open the pairing link in the system browser. `opentig serve` keeps browser launch disabled and prints a terminal QR. Use `--no-browser` to suppress presentation in start mode. The CLI starts one project-independent server; add, open, and switch repositories exclusively from the web UI using paths on the server.
51
+ **AI is optional.** Normal Git work needs no AI account or OpenTig account. If you choose to use AI assistance, a supported CLI installed and authenticated on the host can draft commit messages and pull-request descriptions for you to review. The selected CLI may send the supplied context to its provider. OpenTig does not run an autonomous coding agent or create commits on its own. See the [feature guide](https://github.com/jorgefl8/opentig/blob/main/docs/features.md) and [privacy details](https://github.com/jorgefl8/opentig/blob/main/docs/security-and-privacy.md).
52
+
53
+ ## Run on a remote server
54
+
55
+ Start without opening a browser:
56
+
57
+ ```bash
58
+ npx --yes @opentig/cli@latest serve
59
+ ```
60
+
61
+ Server mode is designed for personal access through **Cloudflare Tunnel**, a **private network such as Tailscale**, or an SSH tunnel. The default listener stays on `127.0.0.1:6767`, which a tunnel or HTTPS reverse proxy on the same machine can reach. For direct private-network access, bind to its interface with `--host`; alternatively, keep loopback and use a private HTTPS proxy such as Tailscale Serve. Tunnel and VPN setup are managed separately from OpenTig.
62
+
63
+ Pair your browser with the one-use code printed on the server. The visual folder picker selects **folders on the server**, not on your laptop or phone.
64
+
65
+ After a global installation, run `opentig pair` as the same OS user to generate a fresh five-minute pairing code for the running instance. If you use a custom data directory, pass the same `--home` value.
66
+
67
+ Paired browsers have the host user's filesystem, Git, and configured CLI authority. Keep access private and do not expose the raw HTTP port publicly. See [remote access and pairing](https://github.com/jorgefl8/opentig/blob/main/docs/web-access.md) for SSH and reverse-proxy examples.
68
+
69
+ ## Keep a background server running
70
+
71
+ After installing globally, opt into a background service on Linux, macOS, or Windows:
72
+
73
+ ```bash
74
+ opentig service install --host 127.0.0.1 --port 6767
75
+ opentig service status
76
+ opentig service restart
77
+ ```
22
78
 
23
- The default listener is `127.0.0.1:6767`. If the configured port is busy, OpenTig fails instead of silently changing a reverse-proxy target. Configuration precedence is command line, `OPENTIG_HOST` / `OPENTIG_PORT` / `OPENTIG_HOME`, then defaults. Data lives under `~/.opentig` unless `--home` is supplied. Settings, hash-only sessions, AI history, credential-free runtime identity, a private same-host admin credential, and rotating logs are kept there.
79
+ The service uses a persistent runtime outside the temporary package cache and starts independently of any repository. Running `npx` or `bunx` alone never installs a background service. Use `opentig service uninstall` to remove it. Linux uses systemd with lingering and starts at boot. macOS uses a user LaunchAgent and Windows uses a Task Scheduler task for the current user; these start at login and stop at logout. Keep the machine awake for remote access. This service is independent of the desktop app.
24
80
 
25
- Run `opentig pair --home <same-home>` as the same OS user to mint a new five-minute, one-use code and link for a running server. Open `/pair` on the local, LAN, or tunnel domain, paste the code, and confirm or edit the suggested device name. SIGINT and SIGTERM close clients, watchers, owned Git/AI processes, persistence, and logs; a second signal forces exit.
81
+ Managed services on all three platforms can **download updates and restart from the browser UI**. Those updates replace the service's runtime, without changing the global npm package. For foreground instances, stop the process and start the desired package version. Pin an exact version instead of `latest` for repeatable deployments. See [installation, services, and updates](https://github.com/jorgefl8/opentig/blob/main/docs/getting-started.md).
26
82
 
27
- `npx` and `bunx` never install background persistence. Linux/systemd users can opt in with `opentig service install`, inspect it with `opentig service status`, and remove it with `opentig service uninstall`. The installer stages the exact package outside the temporary npx cache, uses the private OpenTig home as its stable working directory, and enables user lingering. It is never scoped to a repository. Other platforms should use their preferred service manager for now.
83
+ ## Configuration and scope
28
84
 
29
- A non-loopback listener grants authenticated owner sessions the filesystem, Git, GitHub CLI, and configured AI CLI authority of the operating-system account running OpenTig. Use a trusted LAN/VPN, HTTPS reverse proxy, or SSH tunnel with a restrictive firewall. Never expose the raw HTTP port to the public Internet or use unencrypted non-loopback HTTP on an untrusted network.
85
+ Run `opentig --help` for commands and options. Configure `--host`, `--port`, and `--home`, or their `OPENTIG_HOST`, `OPENTIG_PORT`, and `OPENTIG_HOME` environment equivalents. Data is stored under `~/.opentig` by default. An occupied port fails clearly instead of silently changing the address.
30
86
 
31
- Pin versions for production. Upgrade or roll back by stopping the process and running a different immutable version. Docker, built-in TLS, Tailscale/SSH automation, multi-user roles, and self-update are not included.
87
+ OpenTig opens existing repositories. Cloning and initial remote setup remain Git CLI tasks. Built-in TLS, multi-user roles, and automatic SSH or tunnel setup are not included.
32
88
 
33
- Source, documentation, and security guidance: <https://github.com/jorgefl8/opentig>
89
+ The separate [desktop installer](https://github.com/jorgefl8/opentig/releases/latest) is currently available for Windows x64 only. **macOS and Linux users can use this CLI and the browser UI today.**
34
90
 
35
91
  ## License
36
92
 
37
- MIT. Bundled third-party assets retain their own licences; see `THIRD_PARTY_NOTICES.md`.
93
+ [MIT](https://github.com/jorgefl8/opentig/blob/main/LICENSE). Bundled third-party assets retain their own licences; see [third-party notices](https://github.com/jorgefl8/opentig/blob/main/THIRD_PARTY_NOTICES.md).
package/dist/bin.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import process$1 from "node:process";
3
- import { p as parseCliArguments, C as CliUsageError, r as runCli } from "./chunks/cli-DaYybBfs.mjs";
3
+ import { p as parseCliArguments, C as CliUsageError, O as OPEN_TIG_APP_VERSION, r as runCli } from "./chunks/cli-D_aggpu2.mjs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { spawn } from "node:child_process";
6
6
  import { realpath } from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { r as readInstallation, n as newerVersion, p as packageDirectory, O as OPEN_TIG_APP_VERSION } from "./chunks/cli-service-CDd3ZvGV.mjs";
8
+ import { r as readInstallation, n as newerVersion, p as packageDirectory } from "./chunks/service-manager-DkpSHxsP.mjs";
9
9
  import "./server.mjs";
10
10
  import "node:crypto";
11
11
  import "node:string_decoder";
@@ -3,10 +3,11 @@ import process$1 from "node:process";
3
3
  import { w as writeFileAtomically, r as redactSensitiveText, n as normalizeRuntimePlatform, a as applicationName, runOpenTigServer } from "../server.mjs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
- import { r as readInstallation, s as systemctl, S as SERVICE_NAME, a as serviceUnitPath, p as packageDirectory, n as newerVersion, b as atomicWrite, e as execute, c as stableVersion, O as OPEN_TIG_APP_VERSION, m as manageCliService } from "./cli-service-CDd3ZvGV.mjs";
7
- import { randomUUID, createHash, randomBytes } from "node:crypto";
8
- import { realpath, readFile, mkdir, rm, mkdtemp, writeFile, rename, chmod, appendFile, stat } from "node:fs/promises";
6
+ import { realpath, access, mkdir, rm, readFile, cp, rename, mkdtemp, writeFile, chmod, appendFile, stat } from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
9
8
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
9
+ import { e as execute, c as createServiceManager, r as readInstallation, n as newerVersion, a as assertManager, p as packageDirectory, s as saveInstallation, b as atomicWrite, d as stableVersion } from "./service-manager-DkpSHxsP.mjs";
10
+ import { createHash, randomBytes } from "node:crypto";
10
11
  var QrCodeDataType = /* @__PURE__ */ ((QrCodeDataType2) => {
11
12
  QrCodeDataType2[QrCodeDataType2["Border"] = -1] = "Border";
12
13
  QrCodeDataType2[QrCodeDataType2["Data"] = 0] = "Data";
@@ -65,14 +66,14 @@ class QrCode {
65
66
  // error correction level, data codeword bytes, and mask number.
66
67
  // This is a low-level API that most users should not use directly.
67
68
  // A mid-level API is the encodeSegments() function.
68
- constructor(version, ecc, dataCodewords, msk) {
69
- this.version = version;
69
+ constructor(version2, ecc, dataCodewords, msk) {
70
+ this.version = version2;
70
71
  this.ecc = ecc;
71
- if (version < MIN_VERSION || version > MAX_VERSION)
72
+ if (version2 < MIN_VERSION || version2 > MAX_VERSION)
72
73
  throw new RangeError("Version value out of range");
73
74
  if (msk < -1 || msk > 7)
74
75
  throw new RangeError("Mask value out of range");
75
- this.size = version * 4 + 17;
76
+ this.size = version2 * 4 + 17;
76
77
  const row = Array.from({ length: this.size }).fill(false);
77
78
  for (let i = 0; i < this.size; i++) {
78
79
  this.modules.push(row.slice());
@@ -490,10 +491,10 @@ function isNumeric(text) {
490
491
  function isAlphanumeric(text) {
491
492
  return ALPHANUMERIC_REGEX.test(text);
492
493
  }
493
- function getTotalBits(segs, version) {
494
+ function getTotalBits(segs, version2) {
494
495
  let result = 0;
495
496
  for (const seg of segs) {
496
- const ccbits = numCharCountBits(seg.mode, version);
497
+ const ccbits = numCharCountBits(seg.mode, version2);
497
498
  if (seg.numChars >= 1 << ccbits)
498
499
  return Number.POSITIVE_INFINITY;
499
500
  result += 4 + ccbits + seg.bitData.length;
@@ -569,37 +570,37 @@ function encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, b
569
570
  if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) {
570
571
  throw new RangeError("Invalid value");
571
572
  }
572
- let version;
573
+ let version2;
573
574
  let dataUsedBits;
574
- for (version = minVersion; ; version++) {
575
- const dataCapacityBits2 = getNumDataCodewords(version, ecl) * 8;
576
- const usedBits = getTotalBits(segs, version);
575
+ for (version2 = minVersion; ; version2++) {
576
+ const dataCapacityBits2 = getNumDataCodewords(version2, ecl) * 8;
577
+ const usedBits = getTotalBits(segs, version2);
577
578
  if (usedBits <= dataCapacityBits2) {
578
579
  dataUsedBits = usedBits;
579
580
  break;
580
581
  }
581
- if (version >= maxVersion)
582
+ if (version2 >= maxVersion)
582
583
  throw new RangeError("Data too long");
583
584
  }
584
585
  for (const newEcl of [MEDIUM, QUARTILE, HIGH]) {
585
- if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
586
+ if (boostEcl && dataUsedBits <= getNumDataCodewords(version2, newEcl) * 8)
586
587
  ecl = newEcl;
587
588
  }
588
589
  const bb = [];
589
590
  for (const seg of segs) {
590
591
  appendBits(seg.mode[0], 4, bb);
591
- appendBits(seg.numChars, numCharCountBits(seg.mode, version), bb);
592
+ appendBits(seg.numChars, numCharCountBits(seg.mode, version2), bb);
592
593
  for (const b of seg.getData())
593
594
  bb.push(b);
594
595
  }
595
- const dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
596
+ const dataCapacityBits = getNumDataCodewords(version2, ecl) * 8;
596
597
  appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
597
598
  appendBits(0, (8 - bb.length % 8) % 8, bb);
598
599
  for (let padByte = 236; bb.length < dataCapacityBits; padByte ^= 236 ^ 17)
599
600
  appendBits(padByte, 8, bb);
600
601
  const dataCodewords = Array.from({ length: Math.ceil(bb.length / 8) }, () => 0);
601
602
  bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << 7 - (i & 7));
602
- return new QrCode(version, ecl, dataCodewords, mask);
603
+ return new QrCode(version2, ecl, dataCodewords, mask);
603
604
  }
604
605
  function encode(data, options) {
605
606
  const {
@@ -780,7 +781,7 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
780
781
  if (command === "pair" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) {
781
782
  throw new CliUsageError("The pair command only accepts --home.");
782
783
  }
783
- if (command === "service" && serviceAction === null) throw new CliUsageError("The service command requires install, status, or uninstall.");
784
+ if (command === "service" && serviceAction === null) throw new CliUsageError("The service command requires install, status, restart, or uninstall.");
784
785
  if (command === "service" && serviceAction !== "install" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) {
785
786
  throw new CliUsageError(`The service ${serviceAction} command only accepts --home.`);
786
787
  }
@@ -807,15 +808,15 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
807
808
  openBrowser: command === "start" && !noBrowser
808
809
  };
809
810
  }
810
- function cliHelp(version) {
811
- return `OpenTig ${version}
811
+ function cliHelp(version2) {
812
+ return `OpenTig ${version2}
812
813
 
813
814
  Usage:
814
815
  opentig [options]
815
816
  opentig start [options]
816
817
  opentig serve [options]
817
818
  opentig pair [--home <path>]
818
- opentig service <install|status|uninstall> [options]
819
+ opentig service <install|status|restart|uninstall> [options]
819
820
  opentig help
820
821
  opentig version
821
822
 
@@ -823,7 +824,7 @@ Commands:
823
824
  start Start OpenTig and open the one-use pairing link (default)
824
825
  serve Start OpenTig without opening a browser; ideal for servers
825
826
  pair Print a fresh five-minute code/link for a running server
826
- service Explicitly manage startup at boot (Linux/systemd only)
827
+ service Manage a background server (Linux, macOS, Windows)
827
828
  help Show this help
828
829
  version Show the OpenTig version
829
830
 
@@ -858,7 +859,8 @@ Runtime behavior:
858
859
  The configured port is never changed silently. If it is occupied, OpenTig
859
860
  exits and asks you to stop that process or choose another --port. SIGINT and
860
861
  SIGTERM shut the server down cleanly. npx and bunx never install a service;
861
- service installation is an explicit Linux/systemd action.
862
+ service installation is explicit. Linux uses systemd with lingering; macOS
863
+ and Windows start at login and stop at logout.
862
864
 
863
865
  Requirements: Node.js 24 or later and Git. Plain bunx launches the Node shebang;
864
866
  running OpenTig with the Bun runtime itself is not supported.`;
@@ -881,7 +883,7 @@ function isCommand(value) {
881
883
  return value === "start" || value === "serve" || value === "pair" || value === "service" || value === "help" || value === "version";
882
884
  }
883
885
  function isServiceAction(value) {
884
- return value === "install" || value === "status" || value === "uninstall";
886
+ return value === "install" || value === "status" || value === "restart" || value === "uninstall";
885
887
  }
886
888
  function validateHost(value) {
887
889
  const normalized = value.trim();
@@ -898,22 +900,197 @@ function resolvePath(value, currentDirectory, label) {
898
900
  if (!value || value.includes("\0")) throw new CliUsageError(`Invalid ${label}.`);
899
901
  return path.resolve(currentDirectory, value);
900
902
  }
903
+ const version = "0.1.5";
904
+ const packageMetadata = {
905
+ version
906
+ };
907
+ const OPEN_TIG_APP_VERSION = packageMetadata.version;
908
+ async function runServiceNpm(args, cwd) {
909
+ const candidates = [
910
+ process.env.npm_execpath,
911
+ path.join(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
912
+ path.resolve(path.dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js")
913
+ ];
914
+ for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
915
+ if (!directory) continue;
916
+ candidates.push(path.join(directory, "node_modules/npm/bin/npm-cli.js"));
917
+ if (process.platform !== "win32") {
918
+ try {
919
+ candidates.push(await realpath(path.join(directory, "npm")));
920
+ } catch {
921
+ }
922
+ }
923
+ }
924
+ for (const candidate of candidates) {
925
+ if (!candidate || path.basename(candidate) !== "npm-cli.js") continue;
926
+ try {
927
+ await access(candidate);
928
+ } catch {
929
+ continue;
930
+ }
931
+ await execute(process.execPath, [candidate, ...args], { cwd, timeout: 18e4, maxBuffer: 2e6, windowsHide: true });
932
+ return;
933
+ }
934
+ throw new Error("npm could not be located. Install Node.js with npm and run service install from that terminal.");
935
+ }
936
+ async function manageCliService(config, io, managerOverride) {
937
+ const manager = await createServiceManager(config.home);
938
+ const action = config.serviceAction;
939
+ if (!action) throw new Error("A service action is required.");
940
+ const installed = await readInstallation(config.home);
941
+ 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.");
942
+ const definition = await manager.read();
943
+ if (definition && !manager.owns(definition)) throw new Error("Another OpenTig home owns this service. Use its --home before changing or removing it.");
944
+ if (installed) assertManager(installed, manager.kind);
945
+ if (action === "status") {
946
+ if (!definition) {
947
+ io.out("OpenTig service is not installed.");
948
+ return 1;
949
+ }
950
+ const state = await manager.status();
951
+ io.out(`OpenTig service: ${state.running ? "running" : "stopped"} (${state.enabled ? "enabled" : "disabled"}).`);
952
+ io.out(manager.location);
953
+ io.out(manager.startup);
954
+ return state.running && state.enabled ? 0 : 1;
955
+ }
956
+ if (action === "uninstall" && !definition && !installed) {
957
+ io.out("OpenTig service is not installed.");
958
+ return 0;
959
+ }
960
+ await mkdir(path.join(config.home, "service"), { recursive: true, mode: 448 });
961
+ const lock = path.join(config.home, "service/update-lock");
962
+ try {
963
+ await mkdir(lock, { mode: 448 });
964
+ } catch (error) {
965
+ if (error.code === "EEXIST") throw new Error("A service installation or update is already in progress.", { cause: error });
966
+ throw error;
967
+ }
968
+ try {
969
+ if (action === "restart") {
970
+ if (!definition || !installed) throw new Error("OpenTig service is not installed.");
971
+ await manager.restart();
972
+ await waitServiceReady(config.home, installed, manager);
973
+ io.out("OpenTig service restarted.");
974
+ return 0;
975
+ }
976
+ if (action === "uninstall") {
977
+ if (definition) {
978
+ await manager.stop();
979
+ await manager.remove();
980
+ }
981
+ await rm(path.join(config.home, "service"), { recursive: true, force: true });
982
+ io.out("OpenTig service removed. Repository settings and paired sessions were kept.");
983
+ return 0;
984
+ }
985
+ await manager.preflight();
986
+ return await installService(config, manager, installed, definition, io);
987
+ } finally {
988
+ await rm(lock, { recursive: true, force: true });
989
+ }
990
+ }
991
+ async function installService(config, manager, installed, previousDefinition, io) {
992
+ const packageRoot = await findPackageRoot(fileURLToPath(import.meta.url));
993
+ await assertPackageBuild(packageRoot);
994
+ const serviceRoot = path.join(config.home, "service");
995
+ const reuse = installed?.version === OPEN_TIG_APP_VERSION;
996
+ const target = reuse ? packageDirectory(config.home, installed) : path.join(serviceRoot, `app-${OPEN_TIG_APP_VERSION}`);
997
+ const temporary = path.join(serviceRoot, `.install-${process.pid}-${Date.now()}`);
998
+ try {
999
+ if (!reuse) {
1000
+ await mkdir(temporary, { recursive: true, mode: 448 });
1001
+ await cp(path.join(packageRoot, "dist"), path.join(temporary, "dist"), { recursive: true });
1002
+ for (const name of ["package.json", "README.md", "LICENSE", "THIRD_PARTY_NOTICES.md"]) await cp(path.join(packageRoot, name), path.join(temporary, name));
1003
+ await runServiceNpm(["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"], temporary);
1004
+ await rm(target, { recursive: true, force: true });
1005
+ await rename(temporary, target);
1006
+ }
1007
+ } finally {
1008
+ await rm(temporary, { recursive: true, force: true });
1009
+ }
1010
+ await assertPackageBuild(target);
1011
+ const next = { schema: 1, manager: manager.kind, version: OPEN_TIG_APP_VERSION, layout: reuse ? installed.layout : "flat", host: config.host, port: config.port, node: process.execPath, environmentPath: process.env.PATH };
1012
+ const definition = manager.render({ nodeExecutable: next.node, cliEntrypoint: path.join(target, "dist/bin.mjs"), home: config.home, host: next.host, port: next.port, environmentPath: next.environmentPath });
1013
+ try {
1014
+ await manager.write(definition);
1015
+ await saveInstallation(config.home, next);
1016
+ await manager.restart();
1017
+ await waitServiceReady(config.home, next, manager);
1018
+ } catch (error) {
1019
+ if (previousDefinition) {
1020
+ await manager.write(previousDefinition);
1021
+ if (installed) await saveInstallation(config.home, installed);
1022
+ else await rm(path.join(serviceRoot, "installation.json"), { force: true });
1023
+ await manager.restart();
1024
+ } else {
1025
+ await manager.stop();
1026
+ await manager.remove();
1027
+ await rm(path.join(serviceRoot, "installation.json"), { force: true });
1028
+ }
1029
+ throw error;
1030
+ }
1031
+ io.out(`OpenTig service installed and started on ${config.host}:${config.port}.`);
1032
+ io.out(manager.location);
1033
+ io.out(manager.startup);
1034
+ io.out(`Pair another browser with: opentig pair --home ${JSON.stringify(config.home)}`);
1035
+ return 0;
1036
+ }
1037
+ async function waitServiceReady(home, installation, manager) {
1038
+ const host = ["0.0.0.0", "::"].includes(installation.host) ? installation.host === "::" ? "[::1]" : "127.0.0.1" : installation.host.includes(":") ? `[${installation.host}]` : installation.host;
1039
+ for (let i = 0; i < 30; i++) {
1040
+ try {
1041
+ const state = await manager.status();
1042
+ const runtime = JSON.parse(await readFile(path.join(home, "runtime.json"), "utf8"));
1043
+ const response = await fetch(`http://${host}:${installation.port}/readyz`, { signal: AbortSignal.timeout(1e3) });
1044
+ const value = await response.json();
1045
+ if (state.running && (!state.pid || state.pid === runtime.pid) && runtime.appVersion === installation.version && response.ok && value.status === "ready" && value.appVersion === installation.version) return;
1046
+ } catch {
1047
+ }
1048
+ await setTimeout$1(1e3);
1049
+ }
1050
+ throw new Error("The managed service did not become ready. Inspect service status and logs; check Git, Node.js, and the configured port.");
1051
+ }
1052
+ async function assertPackageBuild(packageRoot) {
1053
+ await Promise.all([
1054
+ access(path.join(packageRoot, "dist", "bin.mjs")),
1055
+ access(path.join(packageRoot, "dist", "client", "index.html")),
1056
+ readFile(path.join(packageRoot, "package.json"), "utf8")
1057
+ ]).catch(() => {
1058
+ throw new Error("The OpenTig package is incomplete and cannot be installed as a service.");
1059
+ });
1060
+ }
1061
+ async function findPackageRoot(modulePath) {
1062
+ let directory = path.dirname(modulePath);
1063
+ for (let depth = 0; depth < 4; depth += 1) {
1064
+ try {
1065
+ await Promise.all([
1066
+ access(path.join(directory, "package.json")),
1067
+ access(path.join(directory, "dist", "bin.mjs"))
1068
+ ]);
1069
+ return directory;
1070
+ } catch {
1071
+ }
1072
+ const parent = path.dirname(directory);
1073
+ if (parent === directory) break;
1074
+ directory = parent;
1075
+ }
1076
+ throw new Error("Could not locate the installed OpenTig package.");
1077
+ }
901
1078
  const DESKTOP_UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
902
1079
  const REPOSITORY = "jorgefl8/opentig";
903
1080
  const REGISTRY = "https://registry.npmjs.org";
904
1081
  const MAX_PACKAGE_BYTES = 100 * 1024 * 1024;
905
1082
  class ServiceUpdater {
906
- constructor(version, backend, failedUpdate = false) {
1083
+ constructor(version2, backend, failedUpdate = false) {
907
1084
  this.backend = backend;
908
1085
  this.status = {
909
1086
  phase: backend ? failedUpdate ? "error" : "idle" : "unavailable",
910
- currentVersion: version,
1087
+ currentVersion: version2,
911
1088
  availableVersion: null,
912
1089
  progress: null,
913
1090
  checkedAt: null,
914
1091
  releaseUrl: null,
915
1092
  releaseNotes: null,
916
- message: backend ? failedUpdate ? "The new server could not start. The previous version was restored. Check for updates to retry." : null : "Browser updates require an OpenTig Linux service. Install with opentig service install; temporary CLI and Dev instances are updated from the terminal."
1093
+ message: backend ? failedUpdate ? "The new server could not start. The previous version was restored. Check for updates to retry." : null : "Browser updates require a managed OpenTig CLI service. Install with opentig service install; temporary CLI and Dev instances are updated from the terminal."
917
1094
  };
918
1095
  }
919
1096
  backend;
@@ -992,18 +1169,18 @@ async function json(url) {
992
1169
  async function latestCliRelease() {
993
1170
  const release = await json(`https://api.github.com/repos/${REPOSITORY}/releases/latest`);
994
1171
  if (!release || release.draft || release.prerelease || typeof release.tag_name !== "string") return null;
995
- const version = release.tag_name.replace(/^v/, "");
996
- if (!stableVersion(version) || release.tag_name !== `v${version}`) return null;
997
- const pkg = await json(`${REGISTRY}/@opentig%2fcli/${version}`);
1172
+ const version2 = release.tag_name.replace(/^v/, "");
1173
+ if (!stableVersion(version2) || release.tag_name !== `v${version2}`) return null;
1174
+ const pkg = await json(`${REGISTRY}/@opentig%2fcli/${version2}`);
998
1175
  if (!pkg) return null;
999
1176
  const dist = pkg.dist;
1000
- const tarball = `${REGISTRY}/@opentig/cli/-/cli-${version}.tgz`;
1001
- if (pkg.name !== "@opentig/cli" || pkg.version !== version || dist?.tarball !== tarball || typeof dist.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(dist.integrity)) throw new Error("Invalid CLI release metadata.");
1177
+ const tarball = `${REGISTRY}/@opentig/cli/-/cli-${version2}.tgz`;
1178
+ if (pkg.name !== "@opentig/cli" || pkg.version !== version2 || dist?.tarball !== tarball || typeof dist.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(dist.integrity)) throw new Error("Invalid CLI release metadata.");
1002
1179
  return {
1003
- version,
1180
+ version: version2,
1004
1181
  tarball,
1005
1182
  integrity: dist.integrity,
1006
- url: `https://github.com/${REPOSITORY}/releases/tag/v${version}`,
1183
+ url: `https://github.com/${REPOSITORY}/releases/tag/v${version2}`,
1007
1184
  notes: typeof release.body === "string" ? release.body.slice(0, 64e3) : null
1008
1185
  };
1009
1186
  }
@@ -1041,7 +1218,7 @@ async function stageCliRelease(home, release, progress) {
1041
1218
  await writeFile(tarball, bytes, { mode: 384 });
1042
1219
  const target = path.join(temporary, "app");
1043
1220
  await mkdir(target);
1044
- await execute("npm", ["install", "--prefix", target, "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--registry", REGISTRY, tarball], { timeout: 18e4, maxBuffer: 2e6 });
1221
+ await runServiceNpm(["install", "--prefix", target, "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--registry", REGISTRY, tarball]);
1045
1222
  const pkgRoot = path.join(target, "node_modules/@opentig/cli");
1046
1223
  const pkg = JSON.parse(await readFile(path.join(pkgRoot, "package.json"), "utf8"));
1047
1224
  if (pkg.name !== "@opentig/cli" || pkg.version !== release.version) throw new Error("Installed CLI identity mismatch.");
@@ -1060,53 +1237,50 @@ async function stageCliRelease(home, release, progress) {
1060
1237
  await rm(temporary, { recursive: true, force: true });
1061
1238
  }
1062
1239
  }
1063
- async function createServiceUpdater(home, version, profile, address) {
1240
+ async function createServiceUpdater(home, version2, profile, address) {
1064
1241
  let installation = null;
1242
+ let manager = null;
1065
1243
  try {
1066
- if (profile === "production" && process.platform === "linux" && process.env.INVOCATION_ID) {
1244
+ if (profile === "production") {
1245
+ manager = await createServiceManager(home);
1067
1246
  installation = await readInstallation(home);
1068
- if (!installation || installation.version !== version || installation.host !== address.host || installation.port !== address.port || await systemctl("show", SERVICE_NAME, "--property=FragmentPath", "--value") !== serviceUnitPath() || await realpath(process.argv[1]) !== await realpath(path.join(packageDirectory(home, installation), "dist/bin.mjs")) || await systemctl("show", SERVICE_NAME, "--property=MainPID", "--value") !== String(process.pid)) installation = null;
1247
+ if (installation) assertManager(installation, manager.kind);
1248
+ const definition = await manager.read();
1249
+ if (!installation || !definition || !manager.owns(definition) || installation.version !== version2 || installation.host !== address.host || installation.port !== address.port || await realpath(process.argv[1]) !== await realpath(path.join(packageDirectory(home, installation), "dist/bin.mjs")) || !await manager.ownsProcess()) installation = null;
1069
1250
  }
1070
1251
  } catch {
1071
1252
  installation = null;
1072
1253
  }
1073
- if (!installation) return new ServiceUpdater(version, null);
1254
+ if (!installation || !manager) return new ServiceUpdater(version2, null);
1255
+ const supervisor = manager;
1074
1256
  const current = installation;
1075
1257
  let failedUpdate = false;
1076
1258
  try {
1077
1259
  const result = JSON.parse(await readFile(path.join(home, "service/update-result.json"), "utf8"));
1078
- failedUpdate = result.success === false && result.version === version;
1260
+ failedUpdate = result.success === false && result.version === version2;
1079
1261
  } catch {
1080
1262
  }
1081
- const updater = new ServiceUpdater(version, {
1263
+ const updater = new ServiceUpdater(version2, {
1082
1264
  release: latestCliRelease,
1083
1265
  stage: (release, progress) => stageCliRelease(home, release, progress),
1084
1266
  install: async (release) => {
1085
1267
  const lock = path.join(home, "service/update-lock");
1086
1268
  await mkdir(lock, { mode: 448 });
1087
- const unit = `opentig-update-${randomUUID()}`;
1269
+ let worker;
1088
1270
  try {
1089
- const next = { ...current, version: release.version, layout: "npm" };
1090
- const previousUnit = await readFile(serviceUnitPath(), "utf8");
1271
+ const next = { ...current, manager: supervisor.kind, version: release.version, layout: "npm", environmentPath: process.env.PATH ?? current.environmentPath };
1272
+ const previousUnit = await supervisor.read();
1273
+ if (!previousUnit || !supervisor.owns(previousUnit)) throw new Error("Managed service definition changed.");
1091
1274
  await atomicWrite(path.join(lock, "plan.json"), JSON.stringify({ home, current, next, previousUnit, integrity: release.integrity }));
1092
- await execute("systemd-run", [
1093
- "--user",
1094
- "--collect",
1095
- "--unit",
1096
- unit,
1097
- "--property=Type=exec",
1098
- process.execPath,
1099
- path.join(packageDirectory(home, current), "dist/service-update.mjs"),
1100
- home
1101
- ], { timeout: 15e3 });
1275
+ worker = await supervisor.launchWorker(process.execPath, path.join(packageDirectory(home, current), "dist/service-update.mjs"));
1102
1276
  } catch (error) {
1103
1277
  await rm(lock, { recursive: true, force: true });
1104
1278
  throw error;
1105
1279
  }
1106
1280
  for (let attempt = 0; attempt < 120; attempt++) {
1107
1281
  await setTimeout$1(1e3);
1108
- const state = await systemctl("show", unit, "--property=ActiveState", "--value").catch(() => "inactive");
1109
- if (!["active", "activating", "deactivating"].includes(state)) {
1282
+ if (!await worker.running()) {
1283
+ await worker.cleanup();
1110
1284
  await rm(lock, { recursive: true, force: true });
1111
1285
  throw new Error("Update worker exited without replacing the server.");
1112
1286
  }
@@ -1466,6 +1640,7 @@ function assertGitAvailable() {
1466
1640
  }
1467
1641
  export {
1468
1642
  CliUsageError as C,
1643
+ OPEN_TIG_APP_VERSION as O,
1469
1644
  parseCliArguments as p,
1470
1645
  runCli as r
1471
1646
  };