@covro/covro 0.6.1-dev.3 → 0.6.1-dev.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 (115) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dist/cli/background.js +10 -7
  4. package/dist/cli/background.js.map +2 -2
  5. package/dist/cli/daemon.js +3 -1
  6. package/dist/cli/daemon.js.map +2 -2
  7. package/dist/cli/launch-environment.js +5 -2
  8. package/dist/cli/launch-environment.js.map +2 -2
  9. package/dist/cli/launchd.js +14 -8
  10. package/dist/cli/launchd.js.map +2 -2
  11. package/dist/cli/runtime-launch.js +108 -0
  12. package/dist/cli/runtime-launch.js.map +7 -0
  13. package/dist/cli/runtime.js +12 -0
  14. package/dist/cli/runtime.js.map +7 -0
  15. package/dist/cli/service.js +7 -4
  16. package/dist/cli/service.js.map +2 -2
  17. package/dist/shared/updates.js.map +2 -2
  18. package/dist/update-worker.cjs +1111 -754
  19. package/dist/updates/manager.js +89 -8
  20. package/dist/updates/manager.js.map +2 -2
  21. package/dist/updates/registry.js +7 -6
  22. package/dist/updates/registry.js.map +2 -2
  23. package/dist/updates/runtime.js +183 -0
  24. package/dist/updates/runtime.js.map +7 -0
  25. package/dist/updates/transaction.js +8 -4
  26. package/dist/updates/transaction.js.map +2 -2
  27. package/dist/updates/worker.js +177 -112
  28. package/dist/updates/worker.js.map +3 -3
  29. package/dist/web/assets/{abnfDiagram-WEYOIA3C-BzVXOBcA.js → abnfDiagram-WEYOIA3C--q3UKu-m.js} +1 -1
  30. package/dist/web/assets/{arc-CyQWAnQd.js → arc-CsDLrA4o.js} +1 -1
  31. package/dist/web/assets/{architecture-WOLXFQ4H-BEoyxwG0.js → architecture-WOLXFQ4H-6TbwoUek.js} +1 -1
  32. package/dist/web/assets/{architectureDiagram-ZHVYKT53-CCXl4OZW.js → architectureDiagram-ZHVYKT53-GNeotXgT.js} +1 -1
  33. package/dist/web/assets/{blockDiagram-RJAE7QA3-D7dGEImG.js → blockDiagram-RJAE7QA3-Ric8Njom.js} +1 -1
  34. package/dist/web/assets/{c4Diagram-XDPPPE54-DV8znYp0.js → c4Diagram-XDPPPE54-CSFzq9_N.js} +1 -1
  35. package/dist/web/assets/channel-M3Agpi7p.js +1 -0
  36. package/dist/web/assets/{chunk-2E7WHVMD-0QrTjfW_.js → chunk-2E7WHVMD-BwjaS8bo.js} +1 -1
  37. package/dist/web/assets/{chunk-4EMNKR2T-D-zlzBAy.js → chunk-4EMNKR2T-V38rR2JO.js} +1 -1
  38. package/dist/web/assets/{chunk-55XCU76K-CVHEMROA.js → chunk-55XCU76K-DjF4oGX1.js} +1 -1
  39. package/dist/web/assets/{chunk-7S6XSTFO-C2rIevGp.js → chunk-7S6XSTFO-5YHKdb3u.js} +1 -1
  40. package/dist/web/assets/{chunk-AQDTSKZB-DYAv-Riy.js → chunk-AQDTSKZB-BGqebfJY.js} +1 -1
  41. package/dist/web/assets/{chunk-DSNLSHJZ-DWI7CHhD.js → chunk-DSNLSHJZ-DoWMycRk.js} +1 -1
  42. package/dist/web/assets/{chunk-HAGFOTED-CO_q1459.js → chunk-HAGFOTED-D23fXc2S.js} +1 -1
  43. package/dist/web/assets/{chunk-HNE4Q36P-CgksCWEg.js → chunk-HNE4Q36P-BIJsRvPs.js} +1 -1
  44. package/dist/web/assets/{chunk-NQOKRVIF-BkpxlXF9.js → chunk-NQOKRVIF-C54OjZSl.js} +1 -1
  45. package/dist/web/assets/{chunk-UHW7PFBP-BMZQKGkg.js → chunk-UHW7PFBP-C0X5au9W.js} +1 -1
  46. package/dist/web/assets/{chunk-VQ5W62QI-DxCsqxIr.js → chunk-VQ5W62QI-C2vDy1Kl.js} +1 -1
  47. package/dist/web/assets/{chunk-WQQL4CDK-Drs4qCbY.js → chunk-WQQL4CDK-CcesvmQO.js} +1 -1
  48. package/dist/web/assets/{chunk-XXDRQBXY--mv0FkSH.js → chunk-XXDRQBXY-EiI0PYcn.js} +1 -1
  49. package/dist/web/assets/{chunk-YZO67HFG-BrXN1Efx.js → chunk-YZO67HFG-BqeaVf9n.js} +1 -1
  50. package/dist/web/assets/{chunk-ZAUX2LTX-DWiVb0QN.js → chunk-ZAUX2LTX-DW6sEeTI.js} +1 -1
  51. package/dist/web/assets/{chunk-ZCHMQSOR-p8m3-BJN.js → chunk-ZCHMQSOR-BrlOBygH.js} +1 -1
  52. package/dist/web/assets/{classDiagram-v2-4MYWR3UA-lnHHmraY.js → classDiagram-v2-4MYWR3UA-DFpCrr_A.js} +1 -1
  53. package/dist/web/assets/{cose-bilkent-JH36ORCC-B1BVV8Wv.js → cose-bilkent-JH36ORCC-CAWPSnN6.js} +1 -1
  54. package/dist/web/assets/{cynefin-EF2NZ3EQ-C4TULYfr.js → cynefin-EF2NZ3EQ-DMeYqL7r.js} +1 -1
  55. package/dist/web/assets/{cynefinDiagram-WF24UDRP-rExRJUWB.js → cynefinDiagram-WF24UDRP-DI-bFsxF.js} +1 -1
  56. package/dist/web/assets/{dagre-NK4GIZVX-DI6KC0Pk.js → dagre-NK4GIZVX-CdoitF7O.js} +1 -1
  57. package/dist/web/assets/{diagram-CBK3MWTC-Don_Jr-E.js → diagram-CBK3MWTC-DVTPnBLF.js} +1 -1
  58. package/dist/web/assets/{diagram-I5XIIV2D-BW1FwPhX.js → diagram-I5XIIV2D-3eS1TE4r.js} +1 -1
  59. package/dist/web/assets/{diagram-ODIL2UCW-DxqQXqCa.js → diagram-ODIL2UCW-cLU1ehlH.js} +1 -1
  60. package/dist/web/assets/{diagram-PFBEJUXE-DeFKndZw.js → diagram-PFBEJUXE-Bd1TAqaL.js} +1 -1
  61. package/dist/web/assets/{diagram-RJA3I4VV-CM31q1uK.js → diagram-RJA3I4VV-CQqVlBOF.js} +1 -1
  62. package/dist/web/assets/{diagram-TYUNFSNR-DTq2AOnu.js → diagram-TYUNFSNR-CCGCiVOv.js} +1 -1
  63. package/dist/web/assets/{dist-V4sbzI-h.js → dist-BE4VEq-n.js} +1 -1
  64. package/dist/web/assets/{ebnfDiagram-V4P3TTPT-DF58HT_y.js → ebnfDiagram-V4P3TTPT-BGOHdBAS.js} +1 -1
  65. package/dist/web/assets/{elk-QONTBCTQ-BwrVYRCb.js → elk-QONTBCTQ-D5OlAFYO.js} +1 -1
  66. package/dist/web/assets/{erDiagram-KYQIGQAA-P1WxHJun.js → erDiagram-KYQIGQAA-DUtlMKNi.js} +1 -1
  67. package/dist/web/assets/{eventmodeling-K75KTNOO-BtccOmRK.js → eventmodeling-K75KTNOO-DXK-D1Z0.js} +1 -1
  68. package/dist/web/assets/flowDiagram-IUGLLWBU-4EVBAMrs.js +1 -0
  69. package/dist/web/assets/{ganttDiagram-R7GPNTJY-D3qJC4vO.js → ganttDiagram-R7GPNTJY-BVGsal-Q.js} +1 -1
  70. package/dist/web/assets/{gitGraph-VSP46ZUC-BVY9Gtih.js → gitGraph-VSP46ZUC-DV6-WqhO.js} +1 -1
  71. package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-CZvvvj3Q.js → gitGraphDiagram-PDYL7XHY-0QK_COI_.js} +1 -1
  72. package/dist/web/assets/{index-B9qAZAx0.js → index-BBPzwzxe.js} +46 -44
  73. package/dist/web/assets/{index-_yn3Nt0h.css → index-CSpioreu.css} +1 -1
  74. package/dist/web/assets/{info-OHQRW6UA-BGqiJ9Yx.js → info-OHQRW6UA-BZipuOqr.js} +1 -1
  75. package/dist/web/assets/{infoDiagram-MVDRKAX5-DxdTuj3n.js → infoDiagram-MVDRKAX5-1db4SZ4k.js} +1 -1
  76. package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-DbhjLHsb.js → ishikawaDiagram-HPEHV2GE-DTrI4oTY.js} +1 -1
  77. package/dist/web/assets/{journeyDiagram-BOSHW7O7-DJROkZUW.js → journeyDiagram-BOSHW7O7-Bk5sPpaD.js} +1 -1
  78. package/dist/web/assets/{kanban-definition-FNSA4HC5-D4HijqrG.js → kanban-definition-FNSA4HC5-smf99L9V.js} +1 -1
  79. package/dist/web/assets/{line-d2EHEr40.js → line-9F_GFNcI.js} +1 -1
  80. package/dist/web/assets/{linear-CnT_m8Aq.js → linear-B80ozVj0.js} +1 -1
  81. package/dist/web/assets/{mermaid-parser.core-BqD0_cdI.js → mermaid-parser.core-DldmXGom.js} +3 -3
  82. package/dist/web/assets/{mermaid.core-Dd1sHHm2.js → mermaid.core-DIZvVrdE.js} +4 -4
  83. package/dist/web/assets/{mindmap-definition-6OY57UG3-KPpq2dAV.js → mindmap-definition-6OY57UG3-CLhpt9eO.js} +1 -1
  84. package/dist/web/assets/{packet-JDAUHWVQ-CXnrw9bC.js → packet-JDAUHWVQ-CL3Q6msP.js} +1 -1
  85. package/dist/web/assets/{pegDiagram-ECIUJNBX-BIXIhs7W.js → pegDiagram-ECIUJNBX-BeBvMY1a.js} +1 -1
  86. package/dist/web/assets/{pie-XZMESJXO-TrDzTbhs.js → pie-XZMESJXO-D_fdN7qT.js} +1 -1
  87. package/dist/web/assets/{pieDiagram-GNMPY74T-D8a4Sfn1.js → pieDiagram-GNMPY74T-DROrBz0o.js} +1 -1
  88. package/dist/web/assets/{quadrantDiagram-D57K4MC3-Bt2I_ygO.js → quadrantDiagram-D57K4MC3-C57SBgx7.js} +1 -1
  89. package/dist/web/assets/{radar-ABXABTNO-BZDG4zL1.js → radar-ABXABTNO-B0-FyoLn.js} +1 -1
  90. package/dist/web/assets/{railroad-I3PHUGI6-CVZI3RsT.js → railroad-I3PHUGI6-_ocCyblF.js} +1 -1
  91. package/dist/web/assets/{railroad-abnf-LNEFI6M7-BLFBfBsa.js → railroad-abnf-LNEFI6M7-Ct1oCv45.js} +1 -1
  92. package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-CKhl-KNN.js → railroad-ebnf-DWYD2UWJ-Dz3MdcvL.js} +1 -1
  93. package/dist/web/assets/{railroad-peg-JR7BVNR7-CaxuMu1-.js → railroad-peg-JR7BVNR7-Ds4ONRXC.js} +1 -1
  94. package/dist/web/assets/{railroadDiagram-Y72NRBL7-BmN9o9yG.js → railroadDiagram-Y72NRBL7-DqS1adHB.js} +1 -1
  95. package/dist/web/assets/{requirementDiagram-EQYSW5RA-CytiXIk0.js → requirementDiagram-EQYSW5RA-BReuC0TK.js} +1 -1
  96. package/dist/web/assets/{sankeyDiagram-SY24JHO5-2oasnbjI.js → sankeyDiagram-SY24JHO5-DDjUr4b9.js} +1 -1
  97. package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CgKYeJhF.js → sequenceDiagram-BLEHNHQU-D71Uv16-.js} +1 -1
  98. package/dist/web/assets/{src-DeJ6DlL6.js → src-DiejhDC1.js} +1 -1
  99. package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-BcBo9Tt9.js → stateDiagram-v2-A6IESKWZ-B8w400YH.js} +1 -1
  100. package/dist/web/assets/{swimlanes-B3ENRKFQ-DHvDY449.js → swimlanes-B3ENRKFQ-Cx8dbIra.js} +1 -1
  101. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-DLuNBE0x.js +8 -0
  102. package/dist/web/assets/{timeline-definition-JXYKGLGB-DHOYx58-.js → timeline-definition-JXYKGLGB-wEhTG9mn.js} +1 -1
  103. package/dist/web/assets/{treeView-D4JQ5SDB-BKzsH1H3.js → treeView-D4JQ5SDB-J1xTBv_o.js} +1 -1
  104. package/dist/web/assets/{treemap-SAJKECNS-Dznq3JYy.js → treemap-SAJKECNS-B-HfL4ut.js} +1 -1
  105. package/dist/web/assets/{usecaseDiagram-WPOLBNNX-B_Qb6GIb.js → usecaseDiagram-WPOLBNNX-Bqvh0bcu.js} +1 -1
  106. package/dist/web/assets/{vennDiagram-Q3Y3IDCW-DDRYcnZj.js → vennDiagram-Q3Y3IDCW-B2TB6cEs.js} +1 -1
  107. package/dist/web/assets/{wardley-7MLQ67FV-CwCK5TZf.js → wardley-7MLQ67FV-BF1eXS8d.js} +1 -1
  108. package/dist/web/assets/{wardleyDiagram-IVAIDKWA-DW68fX4l.js → wardleyDiagram-IVAIDKWA-BZzFLzrU.js} +1 -1
  109. package/dist/web/assets/{xychartDiagram-4URNHDU4-Cdk5RxaR.js → xychartDiagram-4URNHDU4-Dh0JpuLV.js} +1 -1
  110. package/dist/web/index.html +2 -2
  111. package/dist/web/notification-worker.js +33 -0
  112. package/package.json +3 -3
  113. package/dist/web/assets/channel-kBOj6wNa.js +0 -1
  114. package/dist/web/assets/flowDiagram-IUGLLWBU-DnTlbOxw.js +0 -1
  115. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-2cPOFNNL.js +0 -8
package/README.md CHANGED
@@ -32,7 +32,7 @@
32
32
  - 📱 **Follow tasks from any device.** Review changes, handle approvals, answer questions, and check [account quotas](docs/account-usage.md) on desktop or mobile. Tasks keep running when you close the page.
33
33
  - 🗂️ **Keep projects and conversations together.** Start without a project, track parallel tasks, and resume or fork your Codex CLI conversations.
34
34
  - 📖 **Read results while you chat.** Preview documents, diagrams, and code changes beside the conversation on desktop, or full-screen on mobile. Loaded history stays complete as new responses stream in.
35
- - 🔔 **Know when your attention is needed.** Enable browser notifications for completed work, questions, and approvals; keep the page open in a supported desktop browser.
35
+ - 🔔 **Know when your attention is needed.** Enable browser notifications for completed work, questions, and approvals in a supported desktop or Android browser. Keep the page online; notifications are not pushed while it is frozen or closed.
36
36
 
37
37
  Find controls, shortcuts, and supported commands in the [usage guide · 中文](docs/usage.zh-CN.md) and [command guide](docs/slash-commands.md).
38
38
 
@@ -95,7 +95,7 @@ The host computer must stay awake and online. When adding projects remotely, ent
95
95
 
96
96
  ## Updates and release channels
97
97
 
98
- Choose stable, beta, or dev in the update panel or with `covro update`. Managed upgrades support **macOS and Linux npm global installations**; other installations use their original update method. Automatic installation is off by default and requires verified Codex compatibility; unverified combinations need explicit confirmation. Covro does not upgrade Codex CLI. See [updates and recovery](docs/distribution.md).
98
+ Choose stable, beta, or dev in the update panel or with `covro update`. Managed upgrades support **macOS and Linux npm global installations**; other installations use their original update method. Updates can also switch to a supported default Node environment with an existing global Covro installation, including the same Covro version. Automatic installation is off by default and requires verified Codex compatibility; unverified combinations need explicit confirmation. Covro does not upgrade Codex CLI. See [updates and recovery](docs/distribution.md).
99
99
 
100
100
  ## Your computer, your workspace
101
101
 
package/README.zh-CN.md CHANGED
@@ -32,7 +32,7 @@
32
32
  - 📱 **随时随地跟进任务。** 用电脑或手机查看改动、处理审批、回答问题和[账户额度](docs/account-usage.md)。关闭页面,任务仍会继续。
33
33
  - 🗂️ **集中管理项目与会话。** 无需项目即可开始任务,在一个工作台查看并行进展,接续或 Fork Codex CLI 会话。
34
34
  - 📖 **一边看产出,一边讨论。** 电脑上将文档、图表和代码变更与会话并排阅读,手机上使用全屏阅读器。新回复实时到达时,已加载的历史正文保持完整。
35
- - 🔔 **及时收到待办提醒。** 开启浏览器通知,在工作结束、需要回答或审批时收到提醒;需在支持通知的桌面浏览器中保持页面运行。
35
+ - 🔔 **及时收到待办提醒。** 在支持通知的桌面或 Android 浏览器中,接收工作结束、需要回答或审批的提醒。需保持页面在线;页面冻结或关闭期间不推送通知。
36
36
 
37
37
  操作、快捷键和命令用法见[使用指南](docs/usage.zh-CN.md)与[Slash 命令说明](docs/slash-commands.md)。
38
38
 
@@ -95,7 +95,7 @@ pnpm start -- --config ./config.local.json
95
95
 
96
96
  ## 更新与版本渠道
97
97
 
98
- 在更新面板或通过 `covro update` 选择稳定、预览或开发渠道。受管升级支持 **macOS 和 Linux 的 npm 全局安装**,其他安装方式沿用原更新方法。自动安装默认关闭,且只接受已验证的 Codex 组合;未验证组合需明确确认。Covro 不会升级 Codex CLI。详见[更新与恢复](docs/distribution.md)。
98
+ 在更新面板或通过 `covro update` 选择稳定、预览或开发渠道。受管升级支持 **macOS 和 Linux 的 npm 全局安装**,其他安装方式沿用原更新方法。默认 Node 变更后,可切换到已安装全局 Covro 的受支持环境,Covro 同版本也可迁移。自动安装默认关闭,且只接受已验证的 Codex 组合;未验证组合需明确确认。Covro 不会升级 Codex CLI。详见[更新与恢复](docs/distribution.md)。
99
99
 
100
100
  ## 你的电脑,你的工作台
101
101
 
@@ -13,6 +13,7 @@ import {
13
13
  unloadLaunchd
14
14
  } from "./launchd.js";
15
15
  import { readJson } from "../updates/files.js";
16
+ import { launchRuntime } from "./runtime-launch.js";
16
17
  const exec = promisify(execFile);
17
18
  async function lifecycle(location, action) {
18
19
  await mkdir(location.state, { recursive: true, mode: 448 });
@@ -28,17 +29,18 @@ async function lifecycle(location, action) {
28
29
  await release();
29
30
  }
30
31
  }
31
- async function start(location, entry) {
32
+ async function start(location, entry, override) {
32
33
  try {
33
34
  return await control(location, "status");
34
35
  } catch (error) {
35
36
  if (!isNotRunning(error)) throw error;
36
37
  }
37
38
  let child;
39
+ const runtime = await launchRuntime(location, entry, override);
38
40
  const installed = await readJson(
39
41
  join(location.state, "service.json")
40
42
  );
41
- if (process.platform === "darwin" && await startLaunchd(location, entry)) {
43
+ if (process.platform === "darwin" && await startLaunchd(location, runtime.entry, runtime)) {
42
44
  } else if (installed?.platform === "linux") {
43
45
  await exec("systemctl", ["--user", "start", `${installed.label}.service`]);
44
46
  } else {
@@ -48,11 +50,11 @@ async function start(location, entry) {
48
50
  );
49
51
  const log = await open(location.log, "a", 384);
50
52
  try {
51
- child = spawn(process.execPath, [entry, "run", "--config", location.config], {
53
+ child = spawn(runtime.node, [runtime.entry, "run", "--config", location.config], {
52
54
  detached: true,
53
55
  stdio: ["ignore", log.fd, log.fd],
54
56
  windowsHide: true,
55
- env: { ...process.env, COVRO_HOME: location.home }
57
+ env: { ...process.env, PATH: runtime.path, COVRO_HOME: location.home }
56
58
  });
57
59
  await new Promise((resolve, reject) => {
58
60
  child.once("spawn", resolve);
@@ -85,15 +87,16 @@ async function stop(location) {
85
87
  await exec("systemctl", ["--user", "stop", `${installed.label}.service`]);
86
88
  }
87
89
  }
88
- const startBackground = (location, entry) => lifecycle(location, () => start(location, entry));
90
+ const startBackground = (location, entry, runtime) => lifecycle(location, () => start(location, entry, runtime));
89
91
  const stopBackground = (location) => lifecycle(location, () => stop(location));
90
92
  const macOSLoginService = (location, entry, install) => lifecycle(location, async () => {
91
93
  if (install && !await guiSessionAvailable(location))
92
94
  throw new Error("\u542F\u7528\u767B\u5F55\u81EA\u542F\u9700\u8981\u5F53\u524D\u7528\u6237\u5DF2\u767B\u5F55 macOS \u684C\u9762");
93
95
  if (!install && !await loginServiceInstalled(location)) return;
96
+ const runtime = await launchRuntime(location, entry);
94
97
  await stop(location);
95
- await configureLaunchdLogin(location, entry, install);
96
- if (install) await start(location, entry);
98
+ await configureLaunchdLogin(location, runtime.entry, install, runtime);
99
+ if (install) await start(location, runtime.entry, runtime);
97
100
  });
98
101
  export {
99
102
  macOSLoginService,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/background.ts"],
4
- "sourcesContent": ["import { spawn, execFile } from 'node:child_process';\nimport { mkdir, open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { setTimeout } from 'node:timers/promises';\nimport lockfile from 'proper-lockfile';\nimport type { CovroPaths } from './paths.js';\nimport { control, isNotRunning, stopAndWait } from './control.js';\nimport {\n configureLaunchdLogin,\n guiSessionAvailable,\n loginServiceInstalled,\n startLaunchd,\n unloadLaunchd,\n} from './launchd.js';\nimport { readJson } from '../updates/files.js';\n\nconst exec = promisify(execFile);\nasync function lifecycle<T>(location: CovroPaths, action: () => Promise<T>) {\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await lockfile.lock(location.state, {\n lockfilePath: join(location.state, 'lifecycle.lock'),\n stale: 10_000,\n update: 2_000,\n retries: { retries: 280, minTimeout: 250, maxTimeout: 250 },\n });\n try {\n return await action();\n } finally {\n await release();\n }\n}\n\nasync function start(location: CovroPaths, entry: string) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n let child: ReturnType<typeof spawn> | undefined;\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (process.platform === 'darwin' && (await startLaunchd(location, entry))) {\n // launchd owns the process; startup is confirmed through the same private control socket.\n } else if (installed?.platform === 'linux') {\n await exec('systemctl', ['--user', 'start', `${installed.label}.service`]);\n } else {\n if (process.platform === 'darwin')\n console.warn(\n '\u5F53\u524D\u6CA1\u6709 macOS \u684C\u9762\u767B\u5F55\u4F1A\u8BDD\uFF0C\u4F7F\u7528 headless \u540E\u53F0\u542F\u52A8\uFF1B\u7CFB\u7EDF\u94A5\u5319\u4E32\u51ED\u8BC1\u53EF\u80FD\u4E0D\u53EF\u7528\u3002',\n );\n const log = await open(location.log, 'a', 0o600);\n try {\n child = spawn(process.execPath, [entry, 'run', '--config', location.config], {\n detached: true,\n stdio: ['ignore', log.fd, log.fd],\n windowsHide: true,\n env: { ...process.env, COVRO_HOME: location.home },\n });\n await new Promise<void>((resolve, reject) => {\n child!.once('spawn', resolve);\n child!.once('error', reject);\n });\n child.unref();\n } finally {\n await log.close();\n }\n }\n for (let attempt = 0; attempt < 240; attempt++) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n if (child && child.exitCode !== null) throw new Error(`Covro \u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B ${location.log}`);\n await setTimeout(250);\n }\n throw new Error(`Covro \u5C1A\u672A\u786E\u8BA4\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C covro status \u5E76\u67E5\u770B ${location.log}`);\n}\n\nasync function stop(location: CovroPaths) {\n await stopAndWait(location);\n if (process.platform === 'darwin') await unloadLaunchd(location);\n else {\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (installed?.platform === 'linux')\n await exec('systemctl', ['--user', 'stop', `${installed.label}.service`]);\n }\n}\n\nexport const startBackground = (location: CovroPaths, entry: string) =>\n lifecycle(location, () => start(location, entry));\nexport const stopBackground = (location: CovroPaths) => lifecycle(location, () => stop(location));\nexport const macOSLoginService = (location: CovroPaths, entry: string, install: boolean) =>\n lifecycle(location, async () => {\n if (install && !(await guiSessionAvailable(location)))\n throw new Error('\u542F\u7528\u767B\u5F55\u81EA\u542F\u9700\u8981\u5F53\u524D\u7528\u6237\u5DF2\u767B\u5F55 macOS \u684C\u9762');\n if (!install && !(await loginServiceInstalled(location))) return;\n await stop(location);\n await configureLaunchdLogin(location, entry, install);\n if (install) await start(location, entry);\n });\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { spawn, execFile } from 'node:child_process';\nimport { mkdir, open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { setTimeout } from 'node:timers/promises';\nimport lockfile from 'proper-lockfile';\nimport type { CovroPaths } from './paths.js';\nimport { control, isNotRunning, stopAndWait } from './control.js';\nimport {\n configureLaunchdLogin,\n guiSessionAvailable,\n loginServiceInstalled,\n startLaunchd,\n unloadLaunchd,\n} from './launchd.js';\nimport { readJson } from '../updates/files.js';\nimport { launchRuntime, type LaunchRuntime } from './runtime-launch.js';\n\nconst exec = promisify(execFile);\nasync function lifecycle<T>(location: CovroPaths, action: () => Promise<T>) {\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await lockfile.lock(location.state, {\n lockfilePath: join(location.state, 'lifecycle.lock'),\n stale: 10_000,\n update: 2_000,\n retries: { retries: 280, minTimeout: 250, maxTimeout: 250 },\n });\n try {\n return await action();\n } finally {\n await release();\n }\n}\n\nasync function start(location: CovroPaths, entry: string, override?: LaunchRuntime) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n let child: ReturnType<typeof spawn> | undefined;\n const runtime = await launchRuntime(location, entry, override);\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (process.platform === 'darwin' && (await startLaunchd(location, runtime.entry, runtime))) {\n // launchd owns the process; startup is confirmed through the same private control socket.\n } else if (installed?.platform === 'linux') {\n await exec('systemctl', ['--user', 'start', `${installed.label}.service`]);\n } else {\n if (process.platform === 'darwin')\n console.warn(\n '\u5F53\u524D\u6CA1\u6709 macOS \u684C\u9762\u767B\u5F55\u4F1A\u8BDD\uFF0C\u4F7F\u7528 headless \u540E\u53F0\u542F\u52A8\uFF1B\u7CFB\u7EDF\u94A5\u5319\u4E32\u51ED\u8BC1\u53EF\u80FD\u4E0D\u53EF\u7528\u3002',\n );\n const log = await open(location.log, 'a', 0o600);\n try {\n child = spawn(runtime.node, [runtime.entry, 'run', '--config', location.config], {\n detached: true,\n stdio: ['ignore', log.fd, log.fd],\n windowsHide: true,\n env: { ...process.env, PATH: runtime.path, COVRO_HOME: location.home },\n });\n await new Promise<void>((resolve, reject) => {\n child!.once('spawn', resolve);\n child!.once('error', reject);\n });\n child.unref();\n } finally {\n await log.close();\n }\n }\n for (let attempt = 0; attempt < 240; attempt++) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n if (child && child.exitCode !== null) throw new Error(`Covro \u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B ${location.log}`);\n await setTimeout(250);\n }\n throw new Error(`Covro \u5C1A\u672A\u786E\u8BA4\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C covro status \u5E76\u67E5\u770B ${location.log}`);\n}\n\nasync function stop(location: CovroPaths) {\n await stopAndWait(location);\n if (process.platform === 'darwin') await unloadLaunchd(location);\n else {\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (installed?.platform === 'linux')\n await exec('systemctl', ['--user', 'stop', `${installed.label}.service`]);\n }\n}\n\nexport const startBackground = (location: CovroPaths, entry: string, runtime?: LaunchRuntime) =>\n lifecycle(location, () => start(location, entry, runtime));\nexport const stopBackground = (location: CovroPaths) => lifecycle(location, () => stop(location));\nexport const macOSLoginService = (location: CovroPaths, entry: string, install: boolean) =>\n lifecycle(location, async () => {\n if (install && !(await guiSessionAvailable(location)))\n throw new Error('\u542F\u7528\u767B\u5F55\u81EA\u542F\u9700\u8981\u5F53\u524D\u7528\u6237\u5DF2\u767B\u5F55 macOS \u684C\u9762');\n if (!install && !(await loginServiceInstalled(location))) return;\n const runtime = await launchRuntime(location, entry);\n await stop(location);\n await configureLaunchdLogin(location, runtime.entry, install, runtime);\n if (install) await start(location, runtime.entry, runtime);\n });\n"],
5
+ "mappings": "AAAA,SAAS,OAAO,gBAAgB;AAChC,SAAS,OAAO,YAAY;AAC5B,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,OAAO,cAAc;AAErB,SAAS,SAAS,cAAc,mBAAmB;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,qBAAyC;AAElD,MAAM,OAAO,UAAU,QAAQ;AAC/B,eAAe,UAAa,UAAsB,QAA0B;AAC1E,QAAM,MAAM,SAAS,OAAO,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC5D,QAAM,UAAU,MAAM,SAAS,KAAK,SAAS,OAAO;AAAA,IAClD,cAAc,KAAK,SAAS,OAAO,gBAAgB;AAAA,IACnD,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,EAAE,SAAS,KAAK,YAAY,KAAK,YAAY,IAAI;AAAA,EAC5D,CAAC;AACD,MAAI;AACF,WAAO,MAAM,OAAO;AAAA,EACtB,UAAE;AACA,UAAM,QAAQ;AAAA,EAChB;AACF;AAEA,eAAe,MAAM,UAAsB,OAAe,UAA0B;AAClF,MAAI;AACF,WAAO,MAAM,QAAQ,UAAU,QAAQ;AAAA,EACzC,SAAS,OAAO;AACd,QAAI,CAAC,aAAa,KAAK,EAAG,OAAM;AAAA,EAClC;AACA,MAAI;AACJ,QAAM,UAAU,MAAM,cAAc,UAAU,OAAO,QAAQ;AAC7D,QAAM,YAAY,MAAM;AAAA,IACtB,KAAK,SAAS,OAAO,cAAc;AAAA,EACrC;AACA,MAAI,QAAQ,aAAa,YAAa,MAAM,aAAa,UAAU,QAAQ,OAAO,OAAO,GAAI;AAAA,EAE7F,WAAW,WAAW,aAAa,SAAS;AAC1C,UAAM,KAAK,aAAa,CAAC,UAAU,SAAS,GAAG,UAAU,KAAK,UAAU,CAAC;AAAA,EAC3E,OAAO;AACL,QAAI,QAAQ,aAAa;AACvB,cAAQ;AAAA,QACN;AAAA,MACF;AACF,UAAM,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,GAAK;AAC/C,QAAI;AACF,cAAQ,MAAM,QAAQ,MAAM,CAAC,QAAQ,OAAO,OAAO,YAAY,SAAS,MAAM,GAAG;AAAA,QAC/E,UAAU;AAAA,QACV,OAAO,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE;AAAA,QAChC,aAAa;AAAA,QACb,KAAK,EAAE,GAAG,QAAQ,KAAK,MAAM,QAAQ,MAAM,YAAY,SAAS,KAAK;AAAA,MACvE,CAAC;AACD,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAO,KAAK,SAAS,OAAO;AAC5B,cAAO,KAAK,SAAS,MAAM;AAAA,MAC7B,CAAC;AACD,YAAM,MAAM;AAAA,IACd,UAAE;AACA,YAAM,IAAI,MAAM;AAAA,IAClB;AAAA,EACF;AACA,WAAS,UAAU,GAAG,UAAU,KAAK,WAAW;AAC9C,QAAI;AACF,aAAO,MAAM,QAAQ,UAAU,QAAQ;AAAA,IACzC,SAAS,OAAO;AACd,UAAI,CAAC,aAAa,KAAK,EAAG,OAAM;AAAA,IAClC;AACA,QAAI,SAAS,MAAM,aAAa,KAAM,OAAM,IAAI,MAAM,0DAAkB,SAAS,GAAG,EAAE;AACtF,UAAM,WAAW,GAAG;AAAA,EACtB;AACA,QAAM,IAAI,MAAM,sGAAqC,SAAS,GAAG,EAAE;AACrE;AAEA,eAAe,KAAK,UAAsB;AACxC,QAAM,YAAY,QAAQ;AAC1B,MAAI,QAAQ,aAAa,SAAU,OAAM,cAAc,QAAQ;AAAA,OAC1D;AACH,UAAM,YAAY,MAAM;AAAA,MACtB,KAAK,SAAS,OAAO,cAAc;AAAA,IACrC;AACA,QAAI,WAAW,aAAa;AAC1B,YAAM,KAAK,aAAa,CAAC,UAAU,QAAQ,GAAG,UAAU,KAAK,UAAU,CAAC;AAAA,EAC5E;AACF;AAEO,MAAM,kBAAkB,CAAC,UAAsB,OAAe,YACnE,UAAU,UAAU,MAAM,MAAM,UAAU,OAAO,OAAO,CAAC;AACpD,MAAM,iBAAiB,CAAC,aAAyB,UAAU,UAAU,MAAM,KAAK,QAAQ,CAAC;AACzF,MAAM,oBAAoB,CAAC,UAAsB,OAAe,YACrE,UAAU,UAAU,YAAY;AAC9B,MAAI,WAAW,CAAE,MAAM,oBAAoB,QAAQ;AACjD,UAAM,IAAI,MAAM,+GAA0B;AAC5C,MAAI,CAAC,WAAW,CAAE,MAAM,sBAAsB,QAAQ,EAAI;AAC1D,QAAM,UAAU,MAAM,cAAc,UAAU,KAAK;AACnD,QAAM,KAAK,QAAQ;AACnB,QAAM,sBAAsB,UAAU,QAAQ,OAAO,SAAS,OAAO;AACrE,MAAI,QAAS,OAAM,MAAM,UAAU,QAAQ,OAAO,OAAO;AAC3D,CAAC;",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { packageInfo, paths } from "./paths.js";
7
7
  import { UpdateManager } from "../updates/manager.js";
8
8
  import { saveJson } from "../updates/files.js";
9
9
  import { doctor, launchContext } from "./doctor.js";
10
+ import { runningRuntime } from "./runtime.js";
10
11
  async function runDaemon(configPath) {
11
12
  const location = paths(configPath);
12
13
  await mkdir(location.state, { recursive: true, mode: 448 });
@@ -36,7 +37,8 @@ async function runDaemon(configPath) {
36
37
  maintenance: runtime?.service.isInMaintenance(),
37
38
  codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,
38
39
  codexReady: runtime?.codex.ready ?? false,
39
- launchContext: context
40
+ launchContext: context,
41
+ runtime: runningRuntime()
40
42
  })
41
43
  );
42
44
  } else if (request.url === "/doctor") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/daemon.ts"],
4
- "sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\nimport { doctor, launchContext } from './doctor.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const context = await launchContext();\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n launchContext: context,\n }),\n );\n } else if (request.url === '/doctor') {\n response.end(JSON.stringify(await doctor()));\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/backup-status') {\n response.end(JSON.stringify(await runtime!.backups?.status()));\n } else if (request.url === '/backup-create') {\n if (!runtime!.backups) throw new Error('\u6B64\u5B9E\u4F8B\u672A\u914D\u7F6E\u5907\u4EFD');\n response.end(\n JSON.stringify(await runtime!.backups.start(input as { clientOperationId: string })),\n );\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\nimport { doctor, launchContext } from './doctor.js';\nimport { runningRuntime } from './runtime.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const context = await launchContext();\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n launchContext: context,\n runtime: runningRuntime(),\n }),\n );\n } else if (request.url === '/doctor') {\n response.end(JSON.stringify(await doctor()));\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/backup-status') {\n response.end(JSON.stringify(await runtime!.backups?.status()));\n } else if (request.url === '/backup-create') {\n if (!runtime!.backups) throw new Error('\u6B64\u5B9E\u4F8B\u672A\u914D\u7F6E\u5907\u4EFD');\n response.end(\n JSON.stringify(await runtime!.backups.start(input as { clientOperationId: string })),\n );\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,14 +2,17 @@ import { createServer } from "node:net";
2
2
  import { chmod, rm } from "node:fs/promises";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { join } from "node:path";
5
- async function environmentBridge(home) {
5
+ async function environmentBridge(home, runtimePath = process.env.PATH) {
6
6
  const path = join(home, `e-${randomBytes(6).toString("hex")}.sock`);
7
7
  const clients = /* @__PURE__ */ new Set();
8
8
  let accept;
9
9
  const consumed = new Promise((resolve) => {
10
10
  accept = resolve;
11
11
  });
12
- const payload = JSON.stringify({ env: { ...process.env, COVRO_HOME: home }, cwd: process.cwd() });
12
+ const payload = JSON.stringify({
13
+ env: { ...process.env, PATH: runtimePath, COVRO_HOME: home },
14
+ cwd: process.cwd()
15
+ });
13
16
  const server = createServer((socket) => {
14
17
  clients.add(socket);
15
18
  socket.setTimeout(5e3, () => socket.destroy());
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/launch-environment.ts"],
4
- "sourcesContent": ["import { createServer, type Socket } from 'node:net';\nimport { chmod, rm } from 'node:fs/promises';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\n\n// The launchd job receives only this socket path. Credentials stay in memory, never in a plist.\nexport async function environmentBridge(home: string) {\n const path = join(home, `e-${randomBytes(6).toString('hex')}.sock`);\n const clients = new Set<Socket>();\n let accept: (() => void) | undefined;\n const consumed = new Promise<void>((resolve) => {\n accept = resolve;\n });\n const payload = JSON.stringify({ env: { ...process.env, COVRO_HOME: home }, cwd: process.cwd() });\n const server = createServer((socket) => {\n clients.add(socket);\n socket.setTimeout(5000, () => socket.destroy());\n socket.on('error', () => {});\n socket.on('close', () => clients.delete(socket));\n socket.end(payload, () => accept?.());\n });\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(path, resolve);\n });\n await chmod(path, 0o600);\n return {\n path,\n async wait() {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n consumed,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error('launchd \u542F\u52A8\u73AF\u5883\u4F20\u9012\u8D85\u65F6\uFF0C\u8BF7\u67E5\u770B Covro \u65E5\u5FD7')),\n 10_000,\n );\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n },\n async close() {\n for (const socket of clients) socket.destroy();\n await new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n );\n await rm(path, { force: true });\n },\n };\n } catch (error) {\n server.close();\n await rm(path, { force: true });\n throw error;\n }\n}\n\n// Standalone Node runner also works when the update worker restores a version predating this fix.\n// execve preserves launchd's process identity and signal handling without an extra supervisor.\nexport const launchRunner = `import { connect } from 'node:net';\nconst [socketPath, entry, config] = process.argv.slice(2);\ntry {\n const input = await new Promise((resolve, reject) => {\n const socket = connect(socketPath);\n let body = '';\n socket.setTimeout(10000, () => socket.destroy(new Error('launch environment timed out')));\n socket.setEncoding('utf8');\n socket.on('data', chunk => {\n body += chunk;\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) socket.destroy(new Error('launch environment too large'));\n });\n socket.on('error', reject);\n socket.on('end', () => { try { resolve(JSON.parse(body)); } catch { reject(new Error('invalid launch environment')); } });\n });\n process.chdir(input.cwd);\n process.execve(process.execPath, [process.execPath, entry, 'run', '--config', config], input.env);\n} catch {\n console.error('Covro launch environment unavailable; restart with covro start.');\n process.exitCode = 1;\n}\n`;\n"],
5
- "mappings": "AAAA,SAAS,oBAAiC;AAC1C,SAAS,OAAO,UAAU;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AAGrB,eAAsB,kBAAkB,MAAc;AACpD,QAAM,OAAO,KAAK,MAAM,KAAK,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC,OAAO;AAClE,QAAM,UAAU,oBAAI,IAAY;AAChC,MAAI;AACJ,QAAM,WAAW,IAAI,QAAc,CAAC,YAAY;AAC9C,aAAS;AAAA,EACX,CAAC;AACD,QAAM,UAAU,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,QAAQ,KAAK,YAAY,KAAK,GAAG,KAAK,QAAQ,IAAI,EAAE,CAAC;AAChG,QAAM,SAAS,aAAa,CAAC,WAAW;AACtC,YAAQ,IAAI,MAAM;AAClB,WAAO,WAAW,KAAM,MAAM,OAAO,QAAQ,CAAC;AAC9C,WAAO,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAC3B,WAAO,GAAG,SAAS,MAAM,QAAQ,OAAO,MAAM,CAAC;AAC/C,WAAO,IAAI,SAAS,MAAM,SAAS,CAAC;AAAA,EACtC,CAAC;AACD,MAAI;AACF,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,aAAO,KAAK,SAAS,MAAM;AAC3B,aAAO,OAAO,MAAM,OAAO;AAAA,IAC7B,CAAC;AACD,UAAM,MAAM,MAAM,GAAK;AACvB,WAAO;AAAA,MACL;AAAA,MACA,MAAM,OAAO;AACX,YAAI;AACJ,YAAI;AACF,gBAAM,QAAQ,KAAK;AAAA,YACjB;AAAA,YACA,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,sBAAQ;AAAA,gBACN,MAAM,OAAO,IAAI,MAAM,qGAA+B,CAAC;AAAA,gBACvD;AAAA,cACF;AAAA,YACF,CAAC;AAAA,UACH,CAAC;AAAA,QACH,UAAE;AACA,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF;AAAA,MACA,MAAM,QAAQ;AACZ,mBAAW,UAAU,QAAS,QAAO,QAAQ;AAC7C,cAAM,IAAI;AAAA,UAAc,CAAC,SAAS,WAChC,OAAO,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAAA,QAC7D;AACA,cAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO,MAAM;AACb,UAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9B,UAAM;AAAA,EACR;AACF;AAIO,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
4
+ "sourcesContent": ["import { createServer, type Socket } from 'node:net';\nimport { chmod, rm } from 'node:fs/promises';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\n\n// The launchd job receives only this socket path. Credentials stay in memory, never in a plist.\nexport async function environmentBridge(home: string, runtimePath = process.env.PATH) {\n const path = join(home, `e-${randomBytes(6).toString('hex')}.sock`);\n const clients = new Set<Socket>();\n let accept: (() => void) | undefined;\n const consumed = new Promise<void>((resolve) => {\n accept = resolve;\n });\n const payload = JSON.stringify({\n env: { ...process.env, PATH: runtimePath, COVRO_HOME: home },\n cwd: process.cwd(),\n });\n const server = createServer((socket) => {\n clients.add(socket);\n socket.setTimeout(5000, () => socket.destroy());\n socket.on('error', () => {});\n socket.on('close', () => clients.delete(socket));\n socket.end(payload, () => accept?.());\n });\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(path, resolve);\n });\n await chmod(path, 0o600);\n return {\n path,\n async wait() {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n consumed,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error('launchd \u542F\u52A8\u73AF\u5883\u4F20\u9012\u8D85\u65F6\uFF0C\u8BF7\u67E5\u770B Covro \u65E5\u5FD7')),\n 10_000,\n );\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n },\n async close() {\n for (const socket of clients) socket.destroy();\n await new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n );\n await rm(path, { force: true });\n },\n };\n } catch (error) {\n server.close();\n await rm(path, { force: true });\n throw error;\n }\n}\n\n// Standalone Node runner also works when the update worker restores a version predating this fix.\n// execve preserves launchd's process identity and signal handling without an extra supervisor.\nexport const launchRunner = `import { connect } from 'node:net';\nconst [socketPath, entry, config] = process.argv.slice(2);\ntry {\n const input = await new Promise((resolve, reject) => {\n const socket = connect(socketPath);\n let body = '';\n socket.setTimeout(10000, () => socket.destroy(new Error('launch environment timed out')));\n socket.setEncoding('utf8');\n socket.on('data', chunk => {\n body += chunk;\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) socket.destroy(new Error('launch environment too large'));\n });\n socket.on('error', reject);\n socket.on('end', () => { try { resolve(JSON.parse(body)); } catch { reject(new Error('invalid launch environment')); } });\n });\n process.chdir(input.cwd);\n process.execve(process.execPath, [process.execPath, entry, 'run', '--config', config], input.env);\n} catch {\n console.error('Covro launch environment unavailable; restart with covro start.');\n process.exitCode = 1;\n}\n`;\n"],
5
+ "mappings": "AAAA,SAAS,oBAAiC;AAC1C,SAAS,OAAO,UAAU;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AAGrB,eAAsB,kBAAkB,MAAc,cAAc,QAAQ,IAAI,MAAM;AACpF,QAAM,OAAO,KAAK,MAAM,KAAK,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC,OAAO;AAClE,QAAM,UAAU,oBAAI,IAAY;AAChC,MAAI;AACJ,QAAM,WAAW,IAAI,QAAc,CAAC,YAAY;AAC9C,aAAS;AAAA,EACX,CAAC;AACD,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B,KAAK,EAAE,GAAG,QAAQ,KAAK,MAAM,aAAa,YAAY,KAAK;AAAA,IAC3D,KAAK,QAAQ,IAAI;AAAA,EACnB,CAAC;AACD,QAAM,SAAS,aAAa,CAAC,WAAW;AACtC,YAAQ,IAAI,MAAM;AAClB,WAAO,WAAW,KAAM,MAAM,OAAO,QAAQ,CAAC;AAC9C,WAAO,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAC3B,WAAO,GAAG,SAAS,MAAM,QAAQ,OAAO,MAAM,CAAC;AAC/C,WAAO,IAAI,SAAS,MAAM,SAAS,CAAC;AAAA,EACtC,CAAC;AACD,MAAI;AACF,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,aAAO,KAAK,SAAS,MAAM;AAC3B,aAAO,OAAO,MAAM,OAAO;AAAA,IAC7B,CAAC;AACD,UAAM,MAAM,MAAM,GAAK;AACvB,WAAO;AAAA,MACL;AAAA,MACA,MAAM,OAAO;AACX,YAAI;AACJ,YAAI;AACF,gBAAM,QAAQ,KAAK;AAAA,YACjB;AAAA,YACA,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,sBAAQ;AAAA,gBACN,MAAM,OAAO,IAAI,MAAM,qGAA+B,CAAC;AAAA,gBACvD;AAAA,cACF;AAAA,YACF,CAAC;AAAA,UACH,CAAC;AAAA,QACH,UAAE;AACA,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF;AAAA,MACA,MAAM,QAAQ;AACZ,mBAAW,UAAU,QAAS,QAAO,QAAQ;AAC7C,cAAM,IAAI;AAAA,UAAc,CAAC,SAAS,WAChC,OAAO,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAAA,QAC7D;AACA,cAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO,MAAM;AACb,UAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9B,UAAM;AAAA,EACR;AACF;AAIO,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -55,10 +55,10 @@ async function loginRegistration(location) {
55
55
  async function loginServiceInstalled(location) {
56
56
  return Boolean(await loginRegistration(location));
57
57
  }
58
- function launchdPlist(location, args, login) {
58
+ function launchdPlist(location, args, login, path = process.env.PATH ?? "/usr/bin:/bin") {
59
59
  const { label } = launchdPaths(location, login ? `dev.covro.${location.key}` : void 0);
60
60
  const environment = {
61
- PATH: process.env.PATH ?? "/usr/bin:/bin",
61
+ PATH: path,
62
62
  COVRO_HOME: location.home,
63
63
  ...process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}
64
64
  };
@@ -94,7 +94,7 @@ async function unloadLaunchd(location) {
94
94
  }
95
95
  await rm(temporary, { force: true });
96
96
  }
97
- async function configureLaunchdLogin(location, entry, install) {
97
+ async function configureLaunchdLogin(location, entry, install, runtime) {
98
98
  const { label, login } = launchdPaths(location, `dev.covro.${location.key}`);
99
99
  const previous = await loginRegistration(location);
100
100
  if (install) {
@@ -103,7 +103,12 @@ async function configureLaunchdLogin(location, entry, install) {
103
103
  await mkdir(dirname(login), { recursive: true });
104
104
  await writeFile(
105
105
  login,
106
- launchdPlist(location, [process.execPath, entry, "run", "--config", location.config], true),
106
+ launchdPlist(
107
+ location,
108
+ [runtime?.node ?? process.execPath, entry, "run", "--config", location.config],
109
+ true,
110
+ runtime?.path
111
+ ),
107
112
  { mode: 384 }
108
113
  );
109
114
  await saveJson(join(location.state, "service.json"), { platform: "darwin", label });
@@ -115,7 +120,7 @@ async function configureLaunchdLogin(location, entry, install) {
115
120
  await rm(join(location.state, "service.json"), { force: true });
116
121
  }
117
122
  }
118
- async function startLaunchd(location, entry) {
123
+ async function startLaunchd(location, entry, runtime) {
119
124
  const installed = await loginRegistration(location);
120
125
  const { domain, target, temporary, login } = launchdPaths(location, installed?.label);
121
126
  if (!await guiSessionAvailable(location)) {
@@ -128,7 +133,7 @@ async function startLaunchd(location, entry) {
128
133
  await launchctl(["bootstrap", domain, login]);
129
134
  return true;
130
135
  }
131
- const bridge = await environmentBridge(location.home);
136
+ const bridge = await environmentBridge(location.home, runtime?.path);
132
137
  try {
133
138
  const runner = join(location.state, "launch-runner.mjs");
134
139
  await writeFile(runner, launchRunner, { mode: 384 });
@@ -136,8 +141,9 @@ async function startLaunchd(location, entry) {
136
141
  temporary,
137
142
  launchdPlist(
138
143
  location,
139
- [process.execPath, runner, bridge.path, entry, location.config],
140
- false
144
+ [runtime?.node ?? process.execPath, runner, bridge.path, entry, location.config],
145
+ false,
146
+ runtime?.path
141
147
  ),
142
148
  { mode: 384 }
143
149
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/launchd.ts"],
4
- "sourcesContent": ["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport type { CovroPaths } from './paths.js';\nimport { environmentBridge, launchRunner } from './launch-environment.js';\nimport { readJson, saveJson } from '../updates/files.js';\n\nconst exec = promisify(execFile);\nconst launchctl = (args: string[]) =>\n exec('/bin/launchctl', args, { timeout: 5000, maxBuffer: 2 * 1024 * 1024 });\nconst xml = (value: string) =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n\nfunction failureDetail(error: unknown) {\n const failure = error as { killed?: boolean; stderr?: string };\n if (failure.killed) return 'launchctl \u8BF7\u6C42\u8D85\u65F6';\n return (failure.stderr?.trim() || (error instanceof Error ? error.message : '\u672A\u77E5\u9519\u8BEF')).slice(\n 0,\n 1024,\n );\n}\n\nexport function launchdPaths(\n location: CovroPaths,\n label = `dev.covro.${location.key}.${createHash('sha256').update(location.home).digest('hex').slice(0, 12)}`,\n) {\n const domain = `gui/${process.getuid!()}`;\n return {\n label,\n domain,\n target: `${domain}/${label}`,\n temporary: join(location.state, 'launchd.plist'),\n login: join(homedir(), 'Library/LaunchAgents', `${label}.plist`),\n };\n}\n\nasync function loaded(target: string) {\n try {\n await launchctl(['print', target]);\n return true;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr ?? '';\n if (/Could not find (?:domain|service|specified service)/i.test(stderr)) return false;\n throw new Error(\n `\u65E0\u6CD5\u67E5\u8BE2 launchd ${target}\uFF1A${failureDetail(error)}\uFF1B\u8BF7\u68C0\u67E5\u5F53\u524D\u7528\u6237\u7684 launchd \u6743\u9650`,\n { cause: error },\n );\n }\n}\n\nexport async function guiSessionAvailable(location: CovroPaths) {\n return loaded(launchdPaths(location).domain);\n}\n\nasync function loginRegistration(location: CovroPaths) {\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (installed?.platform !== 'darwin') return undefined;\n // Older releases used a config-only label; keep those installed login services operable.\n if (\n installed.label !== launchdPaths(location).label &&\n installed.label !== `dev.covro.${location.key}`\n )\n throw new Error('\u767B\u5F55\u670D\u52A1\u8EAB\u4EFD\u4E0E\u5F53\u524D\u914D\u7F6E\u4E0D\u5339\u914D');\n return installed;\n}\n\nexport async function loginServiceInstalled(location: CovroPaths) {\n return Boolean(await loginRegistration(location));\n}\n\nexport function launchdPlist(location: CovroPaths, args: string[], login: boolean) {\n const { label } = launchdPaths(location, login ? `dev.covro.${location.key}` : undefined);\n const environment = {\n PATH: process.env.PATH ?? '/usr/bin:/bin',\n COVRO_HOME: location.home,\n ...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),\n };\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\"><dict>\n<key>Label</key><string>${label}</string>\n<key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join('')}</array>\n<key>LimitLoadToSessionType</key><string>Aqua</string>\n<key>RunAtLoad</key><true/>\n${login ? '<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>30</integer>' : ''}\n<key>EnvironmentVariables</key><dict>${Object.entries(environment)\n .map(([key, value]) => `<key>${key}</key><string>${xml(value)}</string>`)\n .join('')}</dict>\n<key>StandardOutPath</key><string>${xml(location.log)}</string>\n<key>StandardErrorPath</key><string>${xml(location.log)}</string>\n</dict></plist>\n`;\n}\n\nexport async function unloadLaunchd(location: CovroPaths) {\n const { target, temporary } = launchdPaths(location);\n const installed = await loginRegistration(location);\n const targets = new Set([\n target,\n ...(installed ? [launchdPaths(location, installed.label).target] : []),\n ]);\n for (const serviceTarget of targets) {\n if (await loaded(serviceTarget)) {\n try {\n await launchctl(['bootout', serviceTarget]);\n } catch (error) {\n if (await loaded(serviceTarget)) throw error;\n }\n }\n }\n await rm(temporary, { force: true });\n}\n\nexport async function configureLaunchdLogin(location: CovroPaths, entry: string, install: boolean) {\n // Preserve the installed label so older CLIs can still uninstall it after a rollback.\n const { label, login } = launchdPaths(location, `dev.covro.${location.key}`);\n const previous = await loginRegistration(location);\n if (install) {\n if (!(await guiSessionAvailable(location)))\n throw new Error('\u542F\u7528\u767B\u5F55\u81EA\u542F\u9700\u8981\u5F53\u524D\u7528\u6237\u5DF2\u767B\u5F55 macOS \u684C\u9762');\n await mkdir(dirname(login), { recursive: true });\n await writeFile(\n login,\n launchdPlist(location, [process.execPath, entry, 'run', '--config', location.config], true),\n { mode: 0o600 },\n );\n await saveJson(join(location.state, 'service.json'), { platform: 'darwin', label });\n if (previous && previous.label !== label)\n await rm(launchdPaths(location, previous.label).login, { force: true });\n } else {\n if (previous) await rm(launchdPaths(location, previous.label).login, { force: true });\n await rm(login, { force: true });\n await rm(join(location.state, 'service.json'), { force: true });\n }\n}\n\n/** Caller holds the per-instance lifecycle lock and has verified the daemon is stopped. */\nexport async function startLaunchd(location: CovroPaths, entry: string) {\n const installed = await loginRegistration(location);\n const { domain, target, temporary, login } = launchdPaths(location, installed?.label);\n if (!(await guiSessionAvailable(location))) {\n if (installed) throw new Error('\u5F53\u524D\u6CA1\u6709 macOS \u684C\u9762\u767B\u5F55\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u542F\u52A8 GUI \u767B\u5F55\u670D\u52A1');\n return false;\n }\n await unloadLaunchd(location);\n if (installed) {\n await launchctl(['enable', target]);\n await launchctl(['bootstrap', domain, login]);\n return true;\n }\n const bridge = await environmentBridge(location.home);\n try {\n const runner = join(location.state, 'launch-runner.mjs');\n await writeFile(runner, launchRunner, { mode: 0o600 });\n await writeFile(\n temporary,\n launchdPlist(\n location,\n [process.execPath, runner, bridge.path, entry, location.config],\n false,\n ),\n { mode: 0o600 },\n );\n await launchctl(['enable', target]);\n await launchctl(['bootstrap', domain, temporary]);\n await bridge.wait();\n return true;\n } catch (error) {\n let cleanup = '';\n try {\n await unloadLaunchd(location);\n } catch (cleanupError) {\n cleanup = `\uFF1B\u6E05\u7406\u4EFB\u52A1\u5931\u8D25\uFF1A${failureDetail(cleanupError)}`;\n }\n throw new Error(\n `Covro GUI \u540E\u53F0\u542F\u52A8\u5931\u8D25\uFF1A${failureDetail(error)}${cleanup}\uFF1B\u8FDB\u7A0B\u65E5\u5FD7\u4F4D\u4E8E ${location.log}`,\n { cause: error },\n );\n } finally {\n await bridge.close();\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport type { CovroPaths } from './paths.js';\nimport { environmentBridge, launchRunner } from './launch-environment.js';\nimport { readJson, saveJson } from '../updates/files.js';\nimport type { LaunchRuntime } from './runtime-launch.js';\n\nconst exec = promisify(execFile);\nconst launchctl = (args: string[]) =>\n exec('/bin/launchctl', args, { timeout: 5000, maxBuffer: 2 * 1024 * 1024 });\nconst xml = (value: string) =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n\nfunction failureDetail(error: unknown) {\n const failure = error as { killed?: boolean; stderr?: string };\n if (failure.killed) return 'launchctl \u8BF7\u6C42\u8D85\u65F6';\n return (failure.stderr?.trim() || (error instanceof Error ? error.message : '\u672A\u77E5\u9519\u8BEF')).slice(\n 0,\n 1024,\n );\n}\n\nexport function launchdPaths(\n location: CovroPaths,\n label = `dev.covro.${location.key}.${createHash('sha256').update(location.home).digest('hex').slice(0, 12)}`,\n) {\n const domain = `gui/${process.getuid!()}`;\n return {\n label,\n domain,\n target: `${domain}/${label}`,\n temporary: join(location.state, 'launchd.plist'),\n login: join(homedir(), 'Library/LaunchAgents', `${label}.plist`),\n };\n}\n\nasync function loaded(target: string) {\n try {\n await launchctl(['print', target]);\n return true;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr ?? '';\n if (/Could not find (?:domain|service|specified service)/i.test(stderr)) return false;\n throw new Error(\n `\u65E0\u6CD5\u67E5\u8BE2 launchd ${target}\uFF1A${failureDetail(error)}\uFF1B\u8BF7\u68C0\u67E5\u5F53\u524D\u7528\u6237\u7684 launchd \u6743\u9650`,\n { cause: error },\n );\n }\n}\n\nexport async function guiSessionAvailable(location: CovroPaths) {\n return loaded(launchdPaths(location).domain);\n}\n\nasync function loginRegistration(location: CovroPaths) {\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (installed?.platform !== 'darwin') return undefined;\n // Older releases used a config-only label; keep those installed login services operable.\n if (\n installed.label !== launchdPaths(location).label &&\n installed.label !== `dev.covro.${location.key}`\n )\n throw new Error('\u767B\u5F55\u670D\u52A1\u8EAB\u4EFD\u4E0E\u5F53\u524D\u914D\u7F6E\u4E0D\u5339\u914D');\n return installed;\n}\n\nexport async function loginServiceInstalled(location: CovroPaths) {\n return Boolean(await loginRegistration(location));\n}\n\nexport function launchdPlist(\n location: CovroPaths,\n args: string[],\n login: boolean,\n path = process.env.PATH ?? '/usr/bin:/bin',\n) {\n const { label } = launchdPaths(location, login ? `dev.covro.${location.key}` : undefined);\n const environment = {\n PATH: path,\n COVRO_HOME: location.home,\n ...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),\n };\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\"><dict>\n<key>Label</key><string>${label}</string>\n<key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join('')}</array>\n<key>LimitLoadToSessionType</key><string>Aqua</string>\n<key>RunAtLoad</key><true/>\n${login ? '<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>30</integer>' : ''}\n<key>EnvironmentVariables</key><dict>${Object.entries(environment)\n .map(([key, value]) => `<key>${key}</key><string>${xml(value)}</string>`)\n .join('')}</dict>\n<key>StandardOutPath</key><string>${xml(location.log)}</string>\n<key>StandardErrorPath</key><string>${xml(location.log)}</string>\n</dict></plist>\n`;\n}\n\nexport async function unloadLaunchd(location: CovroPaths) {\n const { target, temporary } = launchdPaths(location);\n const installed = await loginRegistration(location);\n const targets = new Set([\n target,\n ...(installed ? [launchdPaths(location, installed.label).target] : []),\n ]);\n for (const serviceTarget of targets) {\n if (await loaded(serviceTarget)) {\n try {\n await launchctl(['bootout', serviceTarget]);\n } catch (error) {\n if (await loaded(serviceTarget)) throw error;\n }\n }\n }\n await rm(temporary, { force: true });\n}\n\nexport async function configureLaunchdLogin(\n location: CovroPaths,\n entry: string,\n install: boolean,\n runtime?: LaunchRuntime,\n) {\n // Preserve the installed label so older CLIs can still uninstall it after a rollback.\n const { label, login } = launchdPaths(location, `dev.covro.${location.key}`);\n const previous = await loginRegistration(location);\n if (install) {\n if (!(await guiSessionAvailable(location)))\n throw new Error('\u542F\u7528\u767B\u5F55\u81EA\u542F\u9700\u8981\u5F53\u524D\u7528\u6237\u5DF2\u767B\u5F55 macOS \u684C\u9762');\n await mkdir(dirname(login), { recursive: true });\n await writeFile(\n login,\n launchdPlist(\n location,\n [runtime?.node ?? process.execPath, entry, 'run', '--config', location.config],\n true,\n runtime?.path,\n ),\n { mode: 0o600 },\n );\n await saveJson(join(location.state, 'service.json'), { platform: 'darwin', label });\n if (previous && previous.label !== label)\n await rm(launchdPaths(location, previous.label).login, { force: true });\n } else {\n if (previous) await rm(launchdPaths(location, previous.label).login, { force: true });\n await rm(login, { force: true });\n await rm(join(location.state, 'service.json'), { force: true });\n }\n}\n\n/** Caller holds the per-instance lifecycle lock and has verified the daemon is stopped. */\nexport async function startLaunchd(location: CovroPaths, entry: string, runtime?: LaunchRuntime) {\n const installed = await loginRegistration(location);\n const { domain, target, temporary, login } = launchdPaths(location, installed?.label);\n if (!(await guiSessionAvailable(location))) {\n if (installed) throw new Error('\u5F53\u524D\u6CA1\u6709 macOS \u684C\u9762\u767B\u5F55\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u542F\u52A8 GUI \u767B\u5F55\u670D\u52A1');\n return false;\n }\n await unloadLaunchd(location);\n if (installed) {\n await launchctl(['enable', target]);\n await launchctl(['bootstrap', domain, login]);\n return true;\n }\n const bridge = await environmentBridge(location.home, runtime?.path);\n try {\n const runner = join(location.state, 'launch-runner.mjs');\n await writeFile(runner, launchRunner, { mode: 0o600 });\n await writeFile(\n temporary,\n launchdPlist(\n location,\n [runtime?.node ?? process.execPath, runner, bridge.path, entry, location.config],\n false,\n runtime?.path,\n ),\n { mode: 0o600 },\n );\n await launchctl(['enable', target]);\n await launchctl(['bootstrap', domain, temporary]);\n await bridge.wait();\n return true;\n } catch (error) {\n let cleanup = '';\n try {\n await unloadLaunchd(location);\n } catch (cleanupError) {\n cleanup = `\uFF1B\u6E05\u7406\u4EFB\u52A1\u5931\u8D25\uFF1A${failureDetail(cleanupError)}`;\n }\n throw new Error(\n `Covro GUI \u540E\u53F0\u542F\u52A8\u5931\u8D25\uFF1A${failureDetail(error)}${cleanup}\uFF1B\u8FDB\u7A0B\u65E5\u5FD7\u4F4D\u4E8E ${location.log}`,\n { cause: error },\n );\n } finally {\n await bridge.close();\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,OAAO,IAAI,iBAAiB;AAErC,SAAS,mBAAmB,oBAAoB;AAChD,SAAS,UAAU,gBAAgB;AAGnC,MAAM,OAAO,UAAU,QAAQ;AAC/B,MAAM,YAAY,CAAC,SACjB,KAAK,kBAAkB,MAAM,EAAE,SAAS,KAAM,WAAW,IAAI,OAAO,KAAK,CAAC;AAC5E,MAAM,MAAM,CAAC,UACX,MACG,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ;AAE7B,SAAS,cAAc,OAAgB;AACrC,QAAM,UAAU;AAChB,MAAI,QAAQ,OAAQ,QAAO;AAC3B,UAAQ,QAAQ,QAAQ,KAAK,MAAM,iBAAiB,QAAQ,MAAM,UAAU,6BAAS;AAAA,IACnF;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,aACd,UACA,QAAQ,aAAa,SAAS,GAAG,IAAI,WAAW,QAAQ,EAAE,OAAO,SAAS,IAAI,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,IAC1G;AACA,QAAM,SAAS,OAAO,QAAQ,OAAQ,CAAC;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,GAAG,MAAM,IAAI,KAAK;AAAA,IAC1B,WAAW,KAAK,SAAS,OAAO,eAAe;AAAA,IAC/C,OAAO,KAAK,QAAQ,GAAG,wBAAwB,GAAG,KAAK,QAAQ;AAAA,EACjE;AACF;AAEA,eAAe,OAAO,QAAgB;AACpC,MAAI;AACF,UAAM,UAAU,CAAC,SAAS,MAAM,CAAC;AACjC,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,SAAU,MAA8B,UAAU;AACxD,QAAI,uDAAuD,KAAK,MAAM,EAAG,QAAO;AAChF,UAAM,IAAI;AAAA,MACR,oCAAgB,MAAM,SAAI,cAAc,KAAK,CAAC;AAAA,MAC9C,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAEA,eAAsB,oBAAoB,UAAsB;AAC9D,SAAO,OAAO,aAAa,QAAQ,EAAE,MAAM;AAC7C;AAEA,eAAe,kBAAkB,UAAsB;AACrD,QAAM,YAAY,MAAM;AAAA,IACtB,KAAK,SAAS,OAAO,cAAc;AAAA,EACrC;AACA,MAAI,WAAW,aAAa,SAAU,QAAO;AAE7C,MACE,UAAU,UAAU,aAAa,QAAQ,EAAE,SAC3C,UAAU,UAAU,aAAa,SAAS,GAAG;AAE7C,UAAM,IAAI,MAAM,sFAAgB;AAClC,SAAO;AACT;AAEA,eAAsB,sBAAsB,UAAsB;AAChE,SAAO,QAAQ,MAAM,kBAAkB,QAAQ,CAAC;AAClD;AAEO,SAAS,aACd,UACA,MACA,OACA,OAAO,QAAQ,IAAI,QAAQ,iBAC3B;AACA,QAAM,EAAE,MAAM,IAAI,aAAa,UAAU,QAAQ,aAAa,SAAS,GAAG,KAAK,MAAS;AACxF,QAAM,cAAc;AAAA,IAClB,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,GAAI,QAAQ,IAAI,aAAa,EAAE,YAAY,QAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,EACzE;AACA,SAAO;AAAA;AAAA;AAAA,0BAGiB,KAAK;AAAA,oCACK,KAAK,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC;AAAA;AAAA;AAAA,EAG5F,QAAQ,uHAAuH,EAAE;AAAA,uCAC5F,OAAO,QAAQ,WAAW,EAC5D,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,QAAQ,GAAG,iBAAiB,IAAI,KAAK,CAAC,WAAW,EACvE,KAAK,EAAE,CAAC;AAAA,oCACuB,IAAI,SAAS,GAAG,CAAC;AAAA,sCACf,IAAI,SAAS,GAAG,CAAC;AAAA;AAAA;AAGvD;AAEA,eAAsB,cAAc,UAAsB;AACxD,QAAM,EAAE,QAAQ,UAAU,IAAI,aAAa,QAAQ;AACnD,QAAM,YAAY,MAAM,kBAAkB,QAAQ;AAClD,QAAM,UAAU,oBAAI,IAAI;AAAA,IACtB;AAAA,IACA,GAAI,YAAY,CAAC,aAAa,UAAU,UAAU,KAAK,EAAE,MAAM,IAAI,CAAC;AAAA,EACtE,CAAC;AACD,aAAW,iBAAiB,SAAS;AACnC,QAAI,MAAM,OAAO,aAAa,GAAG;AAC/B,UAAI;AACF,cAAM,UAAU,CAAC,WAAW,aAAa,CAAC;AAAA,MAC5C,SAAS,OAAO;AACd,YAAI,MAAM,OAAO,aAAa,EAAG,OAAM;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACA,QAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AACrC;AAEA,eAAsB,sBACpB,UACA,OACA,SACA,SACA;AAEA,QAAM,EAAE,OAAO,MAAM,IAAI,aAAa,UAAU,aAAa,SAAS,GAAG,EAAE;AAC3E,QAAM,WAAW,MAAM,kBAAkB,QAAQ;AACjD,MAAI,SAAS;AACX,QAAI,CAAE,MAAM,oBAAoB,QAAQ;AACtC,YAAM,IAAI,MAAM,+GAA0B;AAC5C,UAAM,MAAM,QAAQ,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE;AAAA,QACA,CAAC,SAAS,QAAQ,QAAQ,UAAU,OAAO,OAAO,YAAY,SAAS,MAAM;AAAA,QAC7E;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA,EAAE,MAAM,IAAM;AAAA,IAChB;AACA,UAAM,SAAS,KAAK,SAAS,OAAO,cAAc,GAAG,EAAE,UAAU,UAAU,MAAM,CAAC;AAClF,QAAI,YAAY,SAAS,UAAU;AACjC,YAAM,GAAG,aAAa,UAAU,SAAS,KAAK,EAAE,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,EAC1E,OAAO;AACL,QAAI,SAAU,OAAM,GAAG,aAAa,UAAU,SAAS,KAAK,EAAE,OAAO,EAAE,OAAO,KAAK,CAAC;AACpF,UAAM,GAAG,OAAO,EAAE,OAAO,KAAK,CAAC;AAC/B,UAAM,GAAG,KAAK,SAAS,OAAO,cAAc,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EAChE;AACF;AAGA,eAAsB,aAAa,UAAsB,OAAe,SAAyB;AAC/F,QAAM,YAAY,MAAM,kBAAkB,QAAQ;AAClD,QAAM,EAAE,QAAQ,QAAQ,WAAW,MAAM,IAAI,aAAa,UAAU,WAAW,KAAK;AACpF,MAAI,CAAE,MAAM,oBAAoB,QAAQ,GAAI;AAC1C,QAAI,UAAW,OAAM,IAAI,MAAM,gIAAiC;AAChE,WAAO;AAAA,EACT;AACA,QAAM,cAAc,QAAQ;AAC5B,MAAI,WAAW;AACb,UAAM,UAAU,CAAC,UAAU,MAAM,CAAC;AAClC,UAAM,UAAU,CAAC,aAAa,QAAQ,KAAK,CAAC;AAC5C,WAAO;AAAA,EACT;AACA,QAAM,SAAS,MAAM,kBAAkB,SAAS,MAAM,SAAS,IAAI;AACnE,MAAI;AACF,UAAM,SAAS,KAAK,SAAS,OAAO,mBAAmB;AACvD,UAAM,UAAU,QAAQ,cAAc,EAAE,MAAM,IAAM,CAAC;AACrD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE;AAAA,QACA,CAAC,SAAS,QAAQ,QAAQ,UAAU,QAAQ,OAAO,MAAM,OAAO,SAAS,MAAM;AAAA,QAC/E;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA,EAAE,MAAM,IAAM;AAAA,IAChB;AACA,UAAM,UAAU,CAAC,UAAU,MAAM,CAAC;AAClC,UAAM,UAAU,CAAC,aAAa,QAAQ,SAAS,CAAC;AAChD,UAAM,OAAO,KAAK;AAClB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,UAAU;AACd,QAAI;AACF,YAAM,cAAc,QAAQ;AAAA,IAC9B,SAAS,cAAc;AACrB,gBAAU,mDAAW,cAAc,YAAY,CAAC;AAAA,IAClD;AACA,UAAM,IAAI;AAAA,MACR,uDAAoB,cAAc,KAAK,CAAC,GAAG,OAAO,8CAAW,SAAS,GAAG;AAAA,MACzE,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,108 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { readFile, rm, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { z } from "zod";
7
+ import { launchdPaths } from "./launchd.js";
8
+ import { readJson, saveJson } from "../updates/files.js";
9
+ const exec = promisify(execFile);
10
+ const schema = z.object({ node: z.string().startsWith("/"), entry: z.string().startsWith("/"), path: z.string() }).strict();
11
+ const runtimeFile = (location) => join(location.state, "runtime.json");
12
+ async function launchRuntime(location, entry, override) {
13
+ const saved = override ?? await readJson(runtimeFile(location));
14
+ return saved ? schema.parse(saved) : { node: process.execPath, entry, path: process.env.PATH ?? "/usr/bin:/bin" };
15
+ }
16
+ const systemdQuote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%").replaceAll("$", () => "$$")}"`;
17
+ const xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
18
+ function serviceContent(location, runtime, service) {
19
+ const { content, platform } = service;
20
+ const args = [runtime.node, runtime.entry, "run", "--config", location.config];
21
+ return platform === "darwin" ? content.replace(
22
+ /<key>ProgramArguments<\/key><array>.*?<\/array>/s,
23
+ () => `<key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join("")}</array>`
24
+ ).replace(
25
+ /<key>PATH<\/key><string>[^<]*<\/string>/,
26
+ () => `<key>PATH</key><string>${xml(runtime.path)}</string>`
27
+ ) : content.replace(/^ExecStart=.*$/m, () => `ExecStart=${args.map(systemdQuote).join(" ")}`).replace(/"PATH=(?:\\.|[^"\\])*"/, () => systemdQuote(`PATH=${runtime.path}`));
28
+ }
29
+ async function snapshotLaunch(location, current) {
30
+ const runtime = await readJson(runtimeFile(location));
31
+ const installed = await readJson(
32
+ join(location.state, "service.json")
33
+ );
34
+ if (!installed) {
35
+ if (process.env.INVOCATION_ID || process.env.PM2_HOME || process.env.SUPERVISOR_ENABLED)
36
+ throw new Error("\u68C0\u6D4B\u5230\u5916\u90E8\u8FDB\u7A0B\u7BA1\u7406\u5668\uFF1B\u8BF7\u4F7F\u7528\u539F\u7BA1\u7406\u5668\u4FEE\u6539 Node \u548C Covro \u542F\u52A8\u8DEF\u5F84\u540E\u91CD\u542F\u3002");
37
+ return { runtime };
38
+ }
39
+ if (installed.platform !== process.platform || installed.label !== `dev.covro.${location.key}`)
40
+ throw new Error("\u767B\u5F55\u670D\u52A1\u8EAB\u4EFD\u4E0D\u5339\u914D\uFF1B\u8BF7\u7528\u76EE\u6807 Node \u91CD\u65B0\u5B89\u88C5 Covro \u767B\u5F55\u670D\u52A1\u3002");
41
+ const filename = installed.platform === "darwin" ? launchdPaths(location, installed.label).login : join(homedir(), ".config/systemd/user", `${installed.label}.service`);
42
+ const content = await readFile(filename, "utf8");
43
+ const expected = installed.platform === "darwin" ? `<key>ProgramArguments</key><array>${[current.node, current.entry, "run", "--config", location.config].map((arg) => `<string>${xml(arg)}</string>`).join("")}</array>` : `ExecStart=${[current.node, current.entry, "run", "--config", location.config].map(systemdQuote).join(" ")}`;
44
+ if (!content.includes(expected))
45
+ throw new Error(
46
+ `\u670D\u52A1\u542F\u52A8\u6765\u6E90\u4E0E\u8FD0\u884C\u5B9E\u4F8B\u4E0D\u5339\u914D\uFF1A${filename}\uFF1B\u8BF7\u6838\u5BF9\u81EA\u5B9A\u4E49\u670D\u52A1\u914D\u7F6E\uFF0C\u5F53\u524D\u670D\u52A1\u4FDD\u6301\u8FD0\u884C\u3002`
47
+ );
48
+ if (installed.platform === "linux") {
49
+ const { stdout } = await exec(
50
+ "systemctl",
51
+ ["--user", "show", `${installed.label}.service`, "--property=FragmentPath,DropInPaths"],
52
+ { timeout: 5e3 }
53
+ );
54
+ const properties = Object.fromEntries(
55
+ stdout.trim().split("\n").map((line) => {
56
+ const i = line.indexOf("=");
57
+ return [line.slice(0, i), line.slice(i + 1)];
58
+ })
59
+ );
60
+ if (properties.FragmentPath !== filename || properties.DropInPaths !== "")
61
+ throw new Error(
62
+ "systemd \u5B9E\u9645\u52A0\u8F7D\u6765\u6E90\u4E0E Covro \u670D\u52A1\u6587\u4EF6\u4E0D\u5339\u914D\u6216\u5B58\u5728\u81EA\u5B9A\u4E49 drop-in\uFF1B\u8BF7\u4F7F\u7528\u539F\u90E8\u7F72\u65B9\u5F0F\u66F4\u65B0\u3002"
63
+ );
64
+ }
65
+ return { runtime, service: { filename, content, platform: installed.platform } };
66
+ }
67
+ async function switchLaunch(location, runtime, snapshot) {
68
+ schema.parse(runtime);
69
+ if (/[\r\n]/.test([runtime.node, runtime.entry, runtime.path].join("")))
70
+ throw new Error("\u8FD0\u884C\u73AF\u5883\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u6362\u884C");
71
+ snapshot.target = runtime;
72
+ if (snapshot.service) {
73
+ const { filename, content, platform } = snapshot.service;
74
+ if (await readFile(filename, "utf8") !== content)
75
+ throw new Error("\u670D\u52A1\u6587\u4EF6\u5728\u5347\u7EA7\u671F\u95F4\u88AB\u4FEE\u6539\uFF0C\u62D2\u7EDD\u8986\u76D6\u3002");
76
+ const updated = serviceContent(location, runtime, snapshot.service);
77
+ await writeFile(filename, updated, { mode: 384 });
78
+ if (platform === "linux")
79
+ await exec("systemctl", ["--user", "daemon-reload"], { timeout: 5e3 });
80
+ }
81
+ await saveJson(runtimeFile(location), runtime);
82
+ }
83
+ async function restoreLaunch(location, snapshot) {
84
+ if (snapshot.service) {
85
+ const current2 = await readFile(snapshot.service.filename, "utf8");
86
+ if (current2 !== snapshot.service.content && (!snapshot.target || current2 !== serviceContent(location, snapshot.target, snapshot.service)))
87
+ throw new Error("\u670D\u52A1\u6587\u4EF6\u5DF2\u88AB\u5916\u90E8\u4FEE\u6539\uFF0C\u62D2\u7EDD\u5728\u6062\u590D\u65F6\u8986\u76D6\uFF1B\u8BF7\u4FDD\u7559\u73B0\u573A\u5E76\u6838\u5BF9\u542F\u52A8\u6765\u6E90\u3002");
88
+ await writeFile(snapshot.service.filename, snapshot.service.content, { mode: 384 });
89
+ if (snapshot.service.platform === "linux")
90
+ await exec("systemctl", ["--user", "daemon-reload"], { timeout: 5e3 });
91
+ }
92
+ const current = await readJson(runtimeFile(location));
93
+ if (current && snapshot.target && ![snapshot.runtime, snapshot.target].some(
94
+ (value) => value && value.node === current.node && value.entry === current.entry && value.path === current.path
95
+ ))
96
+ throw new Error("\u5B9E\u4F8B\u542F\u52A8\u6765\u6E90\u5DF2\u88AB\u5916\u90E8\u4FEE\u6539\uFF0C\u62D2\u7EDD\u8986\u76D6\u3002");
97
+ if (snapshot.runtime) await saveJson(runtimeFile(location), snapshot.runtime);
98
+ else await rm(runtimeFile(location), { force: true });
99
+ }
100
+ export {
101
+ launchRuntime,
102
+ restoreLaunch,
103
+ runtimeFile,
104
+ snapshotLaunch,
105
+ switchLaunch,
106
+ systemdQuote
107
+ };
108
+ //# sourceMappingURL=runtime-launch.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cli/runtime-launch.ts"],
4
+ "sourcesContent": ["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport type { CovroPaths } from './paths.js';\nimport { launchdPaths } from './launchd.js';\nimport { readJson, saveJson } from '../updates/files.js';\n\nconst exec = promisify(execFile);\nconst schema = z\n .object({ node: z.string().startsWith('/'), entry: z.string().startsWith('/'), path: z.string() })\n .strict();\nexport type LaunchRuntime = z.infer<typeof schema>;\nexport const runtimeFile = (location: CovroPaths) => join(location.state, 'runtime.json');\nexport async function launchRuntime(location: CovroPaths, entry: string, override?: LaunchRuntime) {\n const saved = override ?? (await readJson(runtimeFile(location)));\n return saved\n ? schema.parse(saved)\n : { node: process.execPath, entry, path: process.env.PATH ?? '/usr/bin:/bin' };\n}\nexport const systemdQuote = (value: string) =>\n `\"${value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll('\"', '\\\\\"')\n .replaceAll('%', '%%')\n .replaceAll('$', () => '$$')}\"`;\nconst xml = (value: string) =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\nexport interface LaunchSnapshot {\n runtime?: LaunchRuntime;\n target?: LaunchRuntime;\n service?: { filename: string; content: string; platform: string };\n}\nfunction serviceContent(\n location: CovroPaths,\n runtime: LaunchRuntime,\n service: NonNullable<LaunchSnapshot['service']>,\n) {\n const { content, platform } = service;\n const args = [runtime.node, runtime.entry, 'run', '--config', location.config];\n return platform === 'darwin'\n ? content\n .replace(\n /<key>ProgramArguments<\\/key><array>.*?<\\/array>/s,\n () =>\n `<key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join('')}</array>`,\n )\n .replace(\n /<key>PATH<\\/key><string>[^<]*<\\/string>/,\n () => `<key>PATH</key><string>${xml(runtime.path)}</string>`,\n )\n : content\n .replace(/^ExecStart=.*$/m, () => `ExecStart=${args.map(systemdQuote).join(' ')}`)\n .replace(/\"PATH=(?:\\\\.|[^\"\\\\])*\"/, () => systemdQuote(`PATH=${runtime.path}`));\n}\n\n/** Read and validate the Covro-owned startup source before stopping anything. */\nexport async function snapshotLaunch(\n location: CovroPaths,\n current: LaunchRuntime,\n): Promise<LaunchSnapshot> {\n const runtime = await readJson<LaunchRuntime>(runtimeFile(location));\n const installed = await readJson<{ platform: string; label: string }>(\n join(location.state, 'service.json'),\n );\n if (!installed) {\n if (process.env.INVOCATION_ID || process.env.PM2_HOME || process.env.SUPERVISOR_ENABLED)\n throw new Error('\u68C0\u6D4B\u5230\u5916\u90E8\u8FDB\u7A0B\u7BA1\u7406\u5668\uFF1B\u8BF7\u4F7F\u7528\u539F\u7BA1\u7406\u5668\u4FEE\u6539 Node \u548C Covro \u542F\u52A8\u8DEF\u5F84\u540E\u91CD\u542F\u3002');\n return { runtime };\n }\n if (installed.platform !== process.platform || installed.label !== `dev.covro.${location.key}`)\n throw new Error('\u767B\u5F55\u670D\u52A1\u8EAB\u4EFD\u4E0D\u5339\u914D\uFF1B\u8BF7\u7528\u76EE\u6807 Node \u91CD\u65B0\u5B89\u88C5 Covro \u767B\u5F55\u670D\u52A1\u3002');\n const filename =\n installed.platform === 'darwin'\n ? launchdPaths(location, installed.label).login\n : join(homedir(), '.config/systemd/user', `${installed.label}.service`);\n const content = await readFile(filename, 'utf8');\n const expected =\n installed.platform === 'darwin'\n ? `<key>ProgramArguments</key><array>${[current.node, current.entry, 'run', '--config', location.config].map((arg) => `<string>${xml(arg)}</string>`).join('')}</array>`\n : `ExecStart=${[current.node, current.entry, 'run', '--config', location.config].map(systemdQuote).join(' ')}`;\n if (!content.includes(expected))\n throw new Error(\n `\u670D\u52A1\u542F\u52A8\u6765\u6E90\u4E0E\u8FD0\u884C\u5B9E\u4F8B\u4E0D\u5339\u914D\uFF1A${filename}\uFF1B\u8BF7\u6838\u5BF9\u81EA\u5B9A\u4E49\u670D\u52A1\u914D\u7F6E\uFF0C\u5F53\u524D\u670D\u52A1\u4FDD\u6301\u8FD0\u884C\u3002`,\n );\n if (installed.platform === 'linux') {\n const { stdout } = await exec(\n 'systemctl',\n ['--user', 'show', `${installed.label}.service`, '--property=FragmentPath,DropInPaths'],\n { timeout: 5000 },\n );\n const properties = Object.fromEntries(\n stdout\n .trim()\n .split('\\n')\n .map((line) => {\n const i = line.indexOf('=');\n return [line.slice(0, i), line.slice(i + 1)];\n }),\n );\n if (properties.FragmentPath !== filename || properties.DropInPaths !== '')\n throw new Error(\n 'systemd \u5B9E\u9645\u52A0\u8F7D\u6765\u6E90\u4E0E Covro \u670D\u52A1\u6587\u4EF6\u4E0D\u5339\u914D\u6216\u5B58\u5728\u81EA\u5B9A\u4E49 drop-in\uFF1B\u8BF7\u4F7F\u7528\u539F\u90E8\u7F72\u65B9\u5F0F\u66F4\u65B0\u3002',\n );\n }\n return { runtime, service: { filename, content, platform: installed.platform } };\n}\n\nexport async function switchLaunch(\n location: CovroPaths,\n runtime: LaunchRuntime,\n snapshot: LaunchSnapshot,\n) {\n schema.parse(runtime);\n if (/[\\r\\n]/.test([runtime.node, runtime.entry, runtime.path].join('')))\n throw new Error('\u8FD0\u884C\u73AF\u5883\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u6362\u884C');\n snapshot.target = runtime;\n if (snapshot.service) {\n const { filename, content, platform } = snapshot.service;\n if ((await readFile(filename, 'utf8')) !== content)\n throw new Error('\u670D\u52A1\u6587\u4EF6\u5728\u5347\u7EA7\u671F\u95F4\u88AB\u4FEE\u6539\uFF0C\u62D2\u7EDD\u8986\u76D6\u3002');\n const updated = serviceContent(location, runtime, snapshot.service);\n await writeFile(filename, updated, { mode: 0o600 });\n if (platform === 'linux')\n await exec('systemctl', ['--user', 'daemon-reload'], { timeout: 5000 });\n }\n await saveJson(runtimeFile(location), runtime);\n}\n\nexport async function restoreLaunch(location: CovroPaths, snapshot: LaunchSnapshot) {\n if (snapshot.service) {\n const current = await readFile(snapshot.service.filename, 'utf8');\n if (\n current !== snapshot.service.content &&\n (!snapshot.target || current !== serviceContent(location, snapshot.target, snapshot.service))\n )\n throw new Error('\u670D\u52A1\u6587\u4EF6\u5DF2\u88AB\u5916\u90E8\u4FEE\u6539\uFF0C\u62D2\u7EDD\u5728\u6062\u590D\u65F6\u8986\u76D6\uFF1B\u8BF7\u4FDD\u7559\u73B0\u573A\u5E76\u6838\u5BF9\u542F\u52A8\u6765\u6E90\u3002');\n await writeFile(snapshot.service.filename, snapshot.service.content, { mode: 0o600 });\n if (snapshot.service.platform === 'linux')\n await exec('systemctl', ['--user', 'daemon-reload'], { timeout: 5000 });\n }\n const current = await readJson<LaunchRuntime>(runtimeFile(location));\n if (\n current &&\n snapshot.target &&\n ![snapshot.runtime, snapshot.target].some(\n (value) =>\n value &&\n value.node === current.node &&\n value.entry === current.entry &&\n value.path === current.path,\n )\n )\n throw new Error('\u5B9E\u4F8B\u542F\u52A8\u6765\u6E90\u5DF2\u88AB\u5916\u90E8\u4FEE\u6539\uFF0C\u62D2\u7EDD\u8986\u76D6\u3002');\n if (snapshot.runtime) await saveJson(runtimeFile(location), snapshot.runtime);\n else await rm(runtimeFile(location), { force: true });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["current"]
7
+ }
@@ -0,0 +1,12 @@
1
+ import { join, resolve } from "node:path";
2
+ import { packageRoot } from "./paths.js";
3
+ const runningRuntime = () => ({
4
+ node: process.execPath,
5
+ nodeVersion: process.versions.node,
6
+ root: resolve(packageRoot),
7
+ entry: join(packageRoot, "bin/covro.mjs")
8
+ });
9
+ export {
10
+ runningRuntime
11
+ };
12
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cli/runtime.ts"],
4
+ "sourcesContent": ["import { join, resolve } from 'node:path';\nimport { packageRoot } from './paths.js';\nimport type { RuntimeIdentity } from '../shared/updates.js';\n\nexport const runningRuntime = (): RuntimeIdentity => ({\n node: process.execPath,\n nodeVersion: process.versions.node,\n root: resolve(packageRoot),\n entry: join(packageRoot, 'bin/covro.mjs'),\n});\n"],
5
+ "mappings": "AAAA,SAAS,MAAM,eAAe;AAC9B,SAAS,mBAAmB;AAGrB,MAAM,iBAAiB,OAAwB;AAAA,EACpD,MAAM,QAAQ;AAAA,EACd,aAAa,QAAQ,SAAS;AAAA,EAC9B,MAAM,QAAQ,WAAW;AAAA,EACzB,OAAO,KAAK,aAAa,eAAe;AAC1C;",
6
+ "names": []
7
+ }
@@ -7,8 +7,8 @@ import { stopAndWait } from "./control.js";
7
7
  import { saveJson } from "../updates/files.js";
8
8
  import { ensureConfig, packageRoot } from "./paths.js";
9
9
  import { macOSLoginService } from "./background.js";
10
+ import { launchRuntime, systemdQuote as unit } from "./runtime-launch.js";
10
11
  const exec = promisify(execFile);
11
- const unit = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
12
12
  async function service(location, install) {
13
13
  if (!["darwin", "linux"].includes(process.platform))
14
14
  throw new Error(
@@ -29,15 +29,18 @@ async function service(location, install) {
29
29
  const filename = join(directory, `${label}.service`);
30
30
  if (install) {
31
31
  await mkdir(directory, { recursive: true });
32
- if (/[\r\n]/.test([entry, location.config, location.home, process.env.PATH].join("")))
32
+ const runtime = await launchRuntime(location, entry);
33
+ if (/[\r\n]/.test(
34
+ [runtime.node, runtime.entry, location.config, location.home, runtime.path].join("")
35
+ ))
33
36
  throw new Error("\u670D\u52A1\u8DEF\u5F84\u548C PATH \u4E0D\u80FD\u5305\u542B\u6362\u884C");
34
37
  await writeFile(
35
38
  filename,
36
39
  `[Unit]
37
40
  Description=Covro
38
41
  [Service]
39
- ExecStart=${[process.execPath, entry, "run", "--config", location.config].map(unit).join(" ")}
40
- Environment=${unit(`COVRO_HOME=${location.home}`)} ${unit(`PATH=${process.env.PATH ?? "/usr/bin:/bin"}`)}
42
+ ExecStart=${[runtime.node, runtime.entry, "run", "--config", location.config].map(unit).join(" ")}
43
+ Environment=${unit(`COVRO_HOME=${location.home}`)} ${unit(`PATH=${runtime.path}`)}
41
44
  Restart=on-failure
42
45
  RestartSec=30
43
46
  [Install]