pushary 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +1943 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/data/SKILL.md +591 -0
  5. package/data/cowork/SKILL.md +77 -0
  6. package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
  7. package/data/cursor-plugin/CHANGELOG.md +70 -0
  8. package/data/cursor-plugin/CONTRIBUTING.md +40 -0
  9. package/data/cursor-plugin/LICENSE +21 -0
  10. package/data/cursor-plugin/README.md +113 -0
  11. package/data/cursor-plugin/SECURITY.md +33 -0
  12. package/data/cursor-plugin/assets/logo.png +0 -0
  13. package/data/cursor-plugin/commands/notify-when-done.md +14 -0
  14. package/data/cursor-plugin/commands/pushary-test.md +13 -0
  15. package/data/cursor-plugin/hooks/hooks.json +75 -0
  16. package/data/cursor-plugin/mcp.json +11 -0
  17. package/data/cursor-plugin/rules/pushary.mdc +40 -0
  18. package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
  19. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
  20. package/data/cursor-plugin/scripts/redaction.mjs +51 -0
  21. package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
  22. package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
  23. package/data/vscode-plugin/.mcp.json +11 -0
  24. package/data/vscode-plugin/CHANGELOG.md +49 -0
  25. package/data/vscode-plugin/CONTRIBUTING.md +40 -0
  26. package/data/vscode-plugin/LICENSE +21 -0
  27. package/data/vscode-plugin/README.md +128 -0
  28. package/data/vscode-plugin/SECURITY.md +56 -0
  29. package/data/vscode-plugin/assets/logo.png +0 -0
  30. package/data/vscode-plugin/commands/notify-when-done.md +14 -0
  31. package/data/vscode-plugin/commands/pushary-test.md +13 -0
  32. package/data/vscode-plugin/hooks/hooks.json +60 -0
  33. package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
  34. package/data/vscode-plugin/scripts/redaction.mjs +51 -0
  35. package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
  36. package/dist/bin/pushary-bell-hook.d.ts +1 -0
  37. package/dist/bin/pushary-bell-hook.js +46 -0
  38. package/dist/bin/pushary-bell.d.ts +1 -0
  39. package/dist/bin/pushary-bell.js +146 -0
  40. package/dist/bin/pushary-claude.d.ts +1 -0
  41. package/dist/bin/pushary-claude.js +1916 -0
  42. package/dist/bin/pushary-clean.d.ts +1 -0
  43. package/dist/bin/pushary-clean.js +1300 -0
  44. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  45. package/dist/bin/pushary-codex-bridge.js +599 -0
  46. package/dist/bin/pushary-codex-hook.d.ts +1 -0
  47. package/dist/bin/pushary-codex-hook.js +886 -0
  48. package/dist/bin/pushary-codex.d.ts +1 -0
  49. package/dist/bin/pushary-codex.js +125 -0
  50. package/dist/bin/pushary-connect.d.ts +1 -0
  51. package/dist/bin/pushary-connect.js +100 -0
  52. package/dist/bin/pushary-cowork.d.ts +1 -0
  53. package/dist/bin/pushary-cowork.js +36 -0
  54. package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
  55. package/dist/bin/pushary-daemon-supervisor.js +27 -0
  56. package/dist/bin/pushary-daemon.d.ts +1 -0
  57. package/dist/bin/pushary-daemon.js +878 -0
  58. package/dist/bin/pushary-disconnect.d.ts +1 -0
  59. package/dist/bin/pushary-disconnect.js +264 -0
  60. package/dist/bin/pushary-doctor.d.ts +1 -0
  61. package/dist/bin/pushary-doctor.js +1433 -0
  62. package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
  63. package/dist/bin/pushary-elicitation-hook.js +149 -0
  64. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  65. package/dist/bin/pushary-gemini-bridge.js +339 -0
  66. package/dist/bin/pushary-gemini-hook.d.ts +1 -0
  67. package/dist/bin/pushary-gemini-hook.js +381 -0
  68. package/dist/bin/pushary-hook.d.ts +1 -0
  69. package/dist/bin/pushary-hook.js +95 -0
  70. package/dist/bin/pushary-login.d.ts +1 -0
  71. package/dist/bin/pushary-login.js +198 -0
  72. package/dist/bin/pushary-logout.d.ts +1 -0
  73. package/dist/bin/pushary-logout.js +147 -0
  74. package/dist/bin/pushary-mcp.d.ts +1 -0
  75. package/dist/bin/pushary-mcp.js +114 -0
  76. package/dist/bin/pushary-mode.d.ts +1 -0
  77. package/dist/bin/pushary-mode.js +130 -0
  78. package/dist/bin/pushary-notification-hook.d.ts +1 -0
  79. package/dist/bin/pushary-notification-hook.js +56 -0
  80. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  81. package/dist/bin/pushary-opencode-hook.js +338 -0
  82. package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
  83. package/dist/bin/pushary-permission-denied-hook.js +60 -0
  84. package/dist/bin/pushary-permission-hook.d.ts +1 -0
  85. package/dist/bin/pushary-permission-hook.js +65 -0
  86. package/dist/bin/pushary-post-hook.d.ts +1 -0
  87. package/dist/bin/pushary-post-hook.js +70 -0
  88. package/dist/bin/pushary-prompt-hook.d.ts +1 -0
  89. package/dist/bin/pushary-prompt-hook.js +61 -0
  90. package/dist/bin/pushary-session-end-hook.d.ts +1 -0
  91. package/dist/bin/pushary-session-end-hook.js +59 -0
  92. package/dist/bin/pushary-session-start-hook.d.ts +1 -0
  93. package/dist/bin/pushary-session-start-hook.js +72 -0
  94. package/dist/bin/pushary-setup.d.ts +1 -0
  95. package/dist/bin/pushary-setup.js +2660 -0
  96. package/dist/bin/pushary-stats.d.ts +1 -0
  97. package/dist/bin/pushary-stats.js +44 -0
  98. package/dist/bin/pushary-status.d.ts +1 -0
  99. package/dist/bin/pushary-status.js +262 -0
  100. package/dist/bin/pushary-stop-hook.d.ts +1 -0
  101. package/dist/bin/pushary-stop-hook.js +63 -0
  102. package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
  103. package/dist/bin/pushary-stopfailure-hook.js +56 -0
  104. package/dist/bin/pushary-suggestions.d.ts +1 -0
  105. package/dist/bin/pushary-suggestions.js +100 -0
  106. package/dist/bin/pushary-transcript-register.d.ts +1 -0
  107. package/dist/bin/pushary-transcript-register.js +40 -0
  108. package/dist/bin/pushary-transcripts.d.ts +1 -0
  109. package/dist/bin/pushary-transcripts.js +77 -0
  110. package/dist/bin/pushary-upgrade.d.ts +1 -0
  111. package/dist/bin/pushary-upgrade.js +599 -0
  112. package/dist/bin/pushary-wait.d.ts +1 -0
  113. package/dist/bin/pushary-wait.js +122 -0
  114. package/dist/bin/pushary.d.ts +1 -0
  115. package/dist/bin/pushary.js +74 -0
  116. package/dist/chunk-2ABTSGFT.js +173 -0
  117. package/dist/chunk-2OY3ZZ5N.js +124 -0
  118. package/dist/chunk-3EGEA4KH.js +44 -0
  119. package/dist/chunk-3EVPNIBE.js +185 -0
  120. package/dist/chunk-5OP5MQI7.js +45 -0
  121. package/dist/chunk-6J2JC2RT.js +149 -0
  122. package/dist/chunk-7QLSKOSU.js +19 -0
  123. package/dist/chunk-7Z7Q4GGS.js +88 -0
  124. package/dist/chunk-A2A2YTMG.js +835 -0
  125. package/dist/chunk-AHV4LNB5.js +377 -0
  126. package/dist/chunk-AXPCESYO.js +197 -0
  127. package/dist/chunk-C2WCPBF7.js +86 -0
  128. package/dist/chunk-CJVVKOSR.js +73 -0
  129. package/dist/chunk-CULUZTWQ.js +2117 -0
  130. package/dist/chunk-CVQYGMSR.js +142 -0
  131. package/dist/chunk-CVWG2N3Y.js +16 -0
  132. package/dist/chunk-D2FX3EPW.js +15 -0
  133. package/dist/chunk-DB3ONZB4.js +44 -0
  134. package/dist/chunk-DE4Y6TCC.js +160 -0
  135. package/dist/chunk-E4FJTUH4.js +249 -0
  136. package/dist/chunk-EPCXUP3P.js +41 -0
  137. package/dist/chunk-EX3GAPA4.js +44 -0
  138. package/dist/chunk-EYK3GSRH.js +143 -0
  139. package/dist/chunk-GMXKITVA.js +70 -0
  140. package/dist/chunk-GPEGOGRJ.js +49 -0
  141. package/dist/chunk-GU3EJCVV.js +43 -0
  142. package/dist/chunk-HQ6G3B5R.js +59 -0
  143. package/dist/chunk-HZVVNGEW.js +22 -0
  144. package/dist/chunk-IPVKH2ST.js +231 -0
  145. package/dist/chunk-J5TIV3J6.js +238 -0
  146. package/dist/chunk-JU6W3XAU.js +64 -0
  147. package/dist/chunk-JYG4B33G.js +227 -0
  148. package/dist/chunk-JZTHGAPA.js +503 -0
  149. package/dist/chunk-KGDWDSNL.js +102 -0
  150. package/dist/chunk-KVQBWI5T.js +99 -0
  151. package/dist/chunk-KZERVKTD.js +16 -0
  152. package/dist/chunk-LXQ5FBBD.js +47 -0
  153. package/dist/chunk-M5ICYGHT.js +138 -0
  154. package/dist/chunk-M5JDZ45W.js +943 -0
  155. package/dist/chunk-MDEPU45E.js +1226 -0
  156. package/dist/chunk-MPWXGONV.js +157 -0
  157. package/dist/chunk-N6IOSZOE.js +185 -0
  158. package/dist/chunk-NGDD6TXY.js +192 -0
  159. package/dist/chunk-NQTHFF4W.js +2830 -0
  160. package/dist/chunk-OKXS7WDZ.js +68 -0
  161. package/dist/chunk-OLQRRFBX.js +21 -0
  162. package/dist/chunk-P7WQEVWA.js +307 -0
  163. package/dist/chunk-POFBE6ZY.js +12 -0
  164. package/dist/chunk-PQ4K74WL.js +30 -0
  165. package/dist/chunk-PUZZELE6.js +37 -0
  166. package/dist/chunk-Q7E4I6UA.js +767 -0
  167. package/dist/chunk-QCGNGEGE.js +15 -0
  168. package/dist/chunk-R7437YI3.js +151 -0
  169. package/dist/chunk-RHR6NARM.js +305 -0
  170. package/dist/chunk-SAXBC4AZ.js +60 -0
  171. package/dist/chunk-SGZJ5LYH.js +76 -0
  172. package/dist/chunk-SIFVFP6T.js +70 -0
  173. package/dist/chunk-SLFABHW3.js +150 -0
  174. package/dist/chunk-T7E75EKA.js +67 -0
  175. package/dist/chunk-TY4JT7W3.js +38 -0
  176. package/dist/chunk-U4TMAANR.js +111 -0
  177. package/dist/chunk-UI36QSBI.js +177 -0
  178. package/dist/chunk-VQKRG3AC.js +339 -0
  179. package/dist/chunk-WE2J62NC.js +1131 -0
  180. package/dist/chunk-XHKBHWLX.js +14 -0
  181. package/dist/chunk-ZSZSSJCV.js +275 -0
  182. package/dist/chunk-ZVSKMPBZ.js +135 -0
  183. package/dist/reapply-CW3MA66V.js +34 -0
  184. package/dist/src/index.d.ts +204 -0
  185. package/dist/src/index.js +64 -0
  186. package/package.json +108 -0
@@ -0,0 +1,1131 @@
1
+ import {
2
+ needsShell,
3
+ spawnClaude
4
+ } from "./chunk-XHKBHWLX.js";
5
+ import {
6
+ readControlMode
7
+ } from "./chunk-P7WQEVWA.js";
8
+ import {
9
+ getMachineId
10
+ } from "./chunk-LXQ5FBBD.js";
11
+ import {
12
+ pusharyDir,
13
+ resolveGlobalBinary
14
+ } from "./chunk-A2A2YTMG.js";
15
+ import {
16
+ readJsonSafe,
17
+ writeFileAtomic,
18
+ writeJsonAtomic,
19
+ writeJsonAtomicDurable
20
+ } from "./chunk-T7E75EKA.js";
21
+ import {
22
+ getApiKey
23
+ } from "./chunk-GPEGOGRJ.js";
24
+ import {
25
+ CLI_PACKAGE_NAMES,
26
+ cmdShimTarget,
27
+ getPackageVersion,
28
+ globalPackageFolder,
29
+ globalRootFor
30
+ } from "./chunk-NQTHFF4W.js";
31
+
32
+ // src/daemon-lifecycle.ts
33
+ import { dirname, join, win32 as win32Path } from "path";
34
+ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "fs";
35
+ import { createHash, randomUUID } from "crypto";
36
+ import { homedir, userInfo } from "os";
37
+ import { execFileSync } from "child_process";
38
+ var daemonNeedsHandoff = (status) => status.kind === "version-mismatch" || status.kind === "configuration-mismatch" || status.kind === "unresponsive" && status.processAlive === true;
39
+ var DAEMON_REMOTE_PROOF_MAX_AGE_MS = 18e4;
40
+ var DAEMON_REMOTE_PROOF_CLOCK_SKEW_MS = 3e4;
41
+ var remoteProofIsFresh = (timestamp, now) => {
42
+ if (timestamp === null) return false;
43
+ const age = now - Date.parse(timestamp);
44
+ return Number.isFinite(age) && age >= -DAEMON_REMOTE_PROOF_CLOCK_SKEW_MS && age < DAEMON_REMOTE_PROOF_MAX_AGE_MS;
45
+ };
46
+ var daemonHasRemoteProof = (status, now = Date.now()) => status.kind === "running" && remoteProofIsFresh(status.lastSuccessfulHeartbeatAt, now) && remoteProofIsFresh(status.lastSuccessfulControlAt, now);
47
+ var readServiceDefinitionFile = (path) => {
48
+ const bytes = readFileSync(path);
49
+ const encoding = bytes.length >= 2 && bytes[0] === 255 && bytes[1] === 254 ? "utf16le" : "utf-8";
50
+ return { contents: bytes.toString(encoding), encoding };
51
+ };
52
+ var runDaemonServiceCommand = (command, args) => execFileSync(command, [...args], {
53
+ encoding: "utf-8",
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ windowsHide: true
56
+ });
57
+ var commandFailureOutput = (error) => {
58
+ const failure = error;
59
+ return [failure.stdout, failure.stderr, failure.message].map((value) => Buffer.isBuffer(value) ? value.toString("utf-8") : typeof value === "string" ? value : "").filter(Boolean).join("\n");
60
+ };
61
+ var isExpectedProbeFailure = (probe, error) => {
62
+ const failure = error;
63
+ const status = typeof failure.status === "number" ? failure.status : typeof failure.code === "number" ? failure.code : null;
64
+ const exitMatches = probe.negativeExitCodes === void 0 || status !== null && probe.negativeExitCodes.includes(status);
65
+ const outputMatches = probe.negativeOutputPattern === void 0 || probe.negativeOutputPattern.test(commandFailureOutput(error));
66
+ return (probe.negativeExitCodes !== void 0 || probe.negativeOutputPattern !== void 0) && exitMatches && outputMatches;
67
+ };
68
+ var daemonServiceManagerAvailable = (options = {}) => {
69
+ const platform = options.platform ?? process.platform;
70
+ const run = options.run ?? runDaemonServiceCommand;
71
+ try {
72
+ if (platform === "darwin") {
73
+ const uid = options.uid ?? process.getuid?.();
74
+ if (uid === void 0) return false;
75
+ run("launchctl", ["print", `gui/${uid}`]);
76
+ return true;
77
+ }
78
+ if (platform === "linux") {
79
+ run("systemctl", ["--user", "show-environment"]);
80
+ return true;
81
+ }
82
+ if (platform === "win32") {
83
+ run("sc.exe", ["query", "Schedule"]);
84
+ run("powershell.exe", [
85
+ "-NoProfile",
86
+ "-NonInteractive",
87
+ "-Command",
88
+ "Get-Command Get-ScheduledTask -ErrorAction Stop | Out-Null"
89
+ ]);
90
+ return true;
91
+ }
92
+ return false;
93
+ } catch {
94
+ return false;
95
+ }
96
+ };
97
+ var statePath = (dir) => join(dir, "daemon-state.json");
98
+ var lockPath = (dir) => join(dir, "daemon.lock");
99
+ var targetPath = (dir) => join(dir, "daemon-target.json");
100
+ var DAEMON_LEASE_RENEW_INTERVAL_MS = 3e4;
101
+ var LOCAL_LEASE_TIMEOUT_MS = DAEMON_LEASE_RENEW_INTERVAL_MS * 3;
102
+ var buildDaemonLaunch = (input) => input.siblingEntry ? { command: input.execPath, args: [input.siblingEntry], shell: false } : {
103
+ command: input.daemonBinary,
104
+ args: [],
105
+ shell: needsShell(input.daemonBinary, input.platform ?? process.platform)
106
+ };
107
+ var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
108
+ var systemdQuote = (value) => `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
109
+ var buildDaemonService = (input) => {
110
+ if (input.platform === "darwin") {
111
+ if (!Number.isInteger(input.uid) || input.uid < 0) return null;
112
+ const label = "com.pushary.daemon";
113
+ const domain = `gui/${input.uid}`;
114
+ const target = `${domain}/${label}`;
115
+ const definitionPath = join(input.homeDir, "Library", "LaunchAgents", `${label}.plist`);
116
+ const contents = `<?xml version="1.0" encoding="UTF-8"?>
117
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
118
+ <plist version="1.0">
119
+ <dict>
120
+ <key>Label</key><string>${label}</string>
121
+ <key>ProgramArguments</key>
122
+ <array><string>${xml(input.execPath)}</string><string>${xml(input.daemonEntry)}</string></array>
123
+ <key>WorkingDirectory</key><string>${xml(input.homeDir)}</string>
124
+ <key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(input.pathEnv)}</string></dict>
125
+ <key>RunAtLoad</key><true/>
126
+ <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
127
+ <key>ThrottleInterval</key><integer>10</integer>
128
+ <key>StandardOutPath</key><string>${xml(join(input.stateDir, "daemon.log"))}</string>
129
+ <key>StandardErrorPath</key><string>${xml(join(input.stateDir, "daemon-error.log"))}</string>
130
+ </dict>
131
+ </plist>
132
+ `;
133
+ const bootstrap = { command: "launchctl", args: ["bootstrap", domain, definitionPath] };
134
+ return {
135
+ definitionPath,
136
+ contents,
137
+ install: [
138
+ { command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true },
139
+ bootstrap,
140
+ { command: "launchctl", args: ["enable", target] },
141
+ { command: "launchctl", args: ["kickstart", "-k", target] }
142
+ ],
143
+ start: [{ command: "launchctl", args: ["kickstart", "-k", target] }],
144
+ stop: [{ command: "launchctl", args: ["kill", "SIGTERM", target], ignoreFailure: true }],
145
+ disable: [{ command: "launchctl", args: ["disable", target] }],
146
+ canDisableWhenUnregistered: true,
147
+ uninstall: [{ command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true }],
148
+ registrationProbe: {
149
+ command: "launchctl",
150
+ args: ["print", target],
151
+ negativeExitCodes: [113]
152
+ },
153
+ enabledProbe: {
154
+ command: "launchctl",
155
+ args: ["print-disabled", domain],
156
+ disabledOutputPattern: new RegExp(`"${label}"\\s*=>\\s*disabled`)
157
+ },
158
+ runningProbe: {
159
+ command: "launchctl",
160
+ args: ["print", target],
161
+ outputPattern: /\bstate = running\b/,
162
+ negativeExitCodes: [113]
163
+ }
164
+ };
165
+ }
166
+ if (input.platform === "linux") {
167
+ const unit = "pushary-daemon.service";
168
+ const definitionPath = join(input.homeDir, ".config", "systemd", "user", unit);
169
+ const contents = `[Unit]
170
+ Description=Pushary phone-to-agent daemon
171
+ After=network-online.target
172
+
173
+ [Service]
174
+ Type=simple
175
+ ExecStart=${systemdQuote(input.execPath)} ${systemdQuote(input.daemonEntry)}
176
+ Environment=${systemdQuote(`PATH=${input.pathEnv}`)}
177
+ Restart=on-failure
178
+ RestartSec=10
179
+
180
+ [Install]
181
+ WantedBy=default.target
182
+ `;
183
+ return {
184
+ definitionPath,
185
+ contents,
186
+ install: [
187
+ { command: "systemctl", args: ["--user", "daemon-reload"] },
188
+ { command: "systemctl", args: ["--user", "enable", "--now", unit] }
189
+ ],
190
+ start: [{ command: "systemctl", args: ["--user", "start", unit] }],
191
+ stop: [{ command: "systemctl", args: ["--user", "stop", unit], ignoreFailure: true }],
192
+ disable: [{ command: "systemctl", args: ["--user", "disable", unit] }],
193
+ uninstall: [
194
+ { command: "systemctl", args: ["--user", "disable", "--now", unit], ignoreFailure: true }
195
+ ],
196
+ registrationProbe: {
197
+ command: "systemctl",
198
+ args: ["--user", "is-enabled", unit],
199
+ negativeOutputPattern: /^(?:disabled|not-found)\s*$/m
200
+ },
201
+ runningProbe: {
202
+ command: "systemctl",
203
+ args: ["--user", "is-active", unit],
204
+ negativeOutputPattern: /^(?:inactive|failed|unknown)\s*$/m
205
+ },
206
+ reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] }
207
+ };
208
+ }
209
+ if (input.platform === "win32") {
210
+ const taskName = "Pushary Daemon";
211
+ const username = process.env.USERNAME?.trim() || userInfo().username;
212
+ const domain = process.env.USERDOMAIN?.trim();
213
+ const userId = input.userId?.trim() || (domain ? `${domain}\\${username}` : username);
214
+ const definitionPath = win32Path.join(input.stateDir, "pushary-daemon-task.xml");
215
+ const supervisorPath = win32Path.join(win32Path.dirname(input.daemonEntry), "pushary-daemon-supervisor.js");
216
+ const supervised = (input.entryExists ?? existsSync)(supervisorPath);
217
+ const args = supervised ? `&quot;${xml(supervisorPath)}&quot; &quot;${xml(input.daemonEntry)}&quot;` : `&quot;${xml(input.daemonEntry)}&quot;`;
218
+ const contents = `\uFEFF<?xml version="1.0" encoding="UTF-16"?>
219
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
220
+ <RegistrationInfo><Description>Pushary phone-to-agent daemon</Description></RegistrationInfo>
221
+ <Triggers><LogonTrigger><UserId>${xml(userId)}</UserId><Enabled>true</Enabled></LogonTrigger></Triggers>
222
+ <Principals><Principal id="Author"><UserId>${xml(userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
223
+ <Settings>
224
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
225
+ <StartWhenAvailable>true</StartWhenAvailable>
226
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
227
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
228
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
229
+ <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>
230
+ </Settings>
231
+ <Actions Context="Author">
232
+ <Exec>
233
+ <Command>${xml(input.execPath)}</Command>
234
+ <Arguments>${args}</Arguments>
235
+ <WorkingDirectory>${xml(input.homeDir)}</WorkingDirectory>
236
+ </Exec>
237
+ </Actions>
238
+ </Task>
239
+ `;
240
+ return {
241
+ definitionPath,
242
+ contents,
243
+ encoding: "utf16le",
244
+ install: [
245
+ { command: "schtasks.exe", args: ["/Create", "/TN", taskName, "/XML", definitionPath, "/F"] },
246
+ { command: "schtasks.exe", args: ["/Run", "/TN", taskName] }
247
+ ],
248
+ start: [{ command: "schtasks.exe", args: ["/Run", "/TN", taskName] }],
249
+ stop: [{ command: "schtasks.exe", args: ["/End", "/TN", taskName], ignoreFailure: true }],
250
+ disable: [{ command: "schtasks.exe", args: ["/Change", "/TN", taskName, "/Disable"] }],
251
+ uninstall: [{ command: "schtasks.exe", args: ["/Delete", "/TN", taskName, "/F"], ignoreFailure: true }],
252
+ registrationProbe: {
253
+ command: "powershell.exe",
254
+ args: [
255
+ "-NoProfile",
256
+ "-NonInteractive",
257
+ "-Command",
258
+ "$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }"
259
+ ],
260
+ negativeExitCodes: [3]
261
+ },
262
+ enabledProbe: {
263
+ command: "powershell.exe",
264
+ args: [
265
+ "-NoProfile",
266
+ "-NonInteractive",
267
+ "-Command",
268
+ "$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }; if ($null -eq $task.Settings -or $null -eq $task.Settings.Enabled) { exit 5 }; if (-not $task.Settings.Enabled) { exit 4 }"
269
+ ],
270
+ negativeExitCodes: [3, 4]
271
+ },
272
+ runningProbe: {
273
+ command: "powershell.exe",
274
+ args: [
275
+ "-NoProfile",
276
+ "-NonInteractive",
277
+ "-Command",
278
+ "$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }; if ($task.State -ne 'Running') { exit 4 }"
279
+ ],
280
+ negativeExitCodes: [3, 4]
281
+ }
282
+ };
283
+ }
284
+ return null;
285
+ };
286
+ var WINDOWS_DAEMON_ENTRY_SEGMENTS = ["dist", "bin", "pushary-daemon.js"];
287
+ var WINDOWS_CMD_SHIM = /\.cmd$/i;
288
+ var readTextFile = (path) => readFileSync(path, "utf8");
289
+ var windowsShimTarget = (shim, readFile) => {
290
+ if (!WINDOWS_CMD_SHIM.test(shim)) return void 0;
291
+ try {
292
+ return cmdShimTarget(win32Path.dirname(shim), readFile(shim));
293
+ } catch {
294
+ return void 0;
295
+ }
296
+ };
297
+ var windowsPackageDaemonEntries = (globalPrefix) => {
298
+ const root = globalRootFor(globalPrefix, "win32");
299
+ return CLI_PACKAGE_NAMES.map((name) => win32Path.join(globalPackageFolder(root, name, "win32"), ...WINDOWS_DAEMON_ENTRY_SEGMENTS));
300
+ };
301
+ var windowsDaemonEntryCandidates = (shim, readFile) => {
302
+ const shimEntry = windowsShimTarget(shim, readFile);
303
+ const packageEntries = windowsPackageDaemonEntries(win32Path.dirname(shim));
304
+ return shimEntry === void 0 ? packageEntries : [shimEntry, ...packageEntries];
305
+ };
306
+ var resolveDaemonServiceDefinition = (options = {}) => {
307
+ const platform = options.platform ?? process.platform;
308
+ const exists = options.entryExists ?? existsSync;
309
+ const globalBinary = options.globalBinary ?? resolveGlobalBinary("pushary-daemon");
310
+ let daemonEntry = options.daemonEntry;
311
+ if (!daemonEntry && globalBinary) {
312
+ if (platform === "win32") {
313
+ daemonEntry = windowsDaemonEntryCandidates(globalBinary, options.readFile ?? readTextFile).find((candidate) => exists(candidate));
314
+ } else {
315
+ try {
316
+ const candidate = (options.realpath ?? realpathSync)(globalBinary);
317
+ if (exists(candidate)) daemonEntry = candidate;
318
+ } catch {
319
+ }
320
+ }
321
+ }
322
+ if (!daemonEntry || !exists(daemonEntry)) return null;
323
+ const homeDir = options.homeDir ?? homedir();
324
+ return buildDaemonService({
325
+ platform,
326
+ homeDir,
327
+ stateDir: options.stateDir ?? pusharyDir(),
328
+ execPath: options.execPath ?? process.execPath,
329
+ daemonEntry,
330
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
331
+ uid: options.uid ?? process.getuid?.(),
332
+ entryExists: exists
333
+ });
334
+ };
335
+ var runDaemonServiceSteps = (steps, run) => {
336
+ for (const step of steps) {
337
+ try {
338
+ run(step.command, step.args);
339
+ } catch (err) {
340
+ if (!step.ignoreFailure) throw err;
341
+ }
342
+ }
343
+ };
344
+ var applyDaemonService = (action, definition, options = {}) => {
345
+ const run = options.run ?? runDaemonServiceCommand;
346
+ try {
347
+ if (action === "install") {
348
+ writeFileAtomic(definition.definitionPath, definition.contents, 384, definition.encoding);
349
+ }
350
+ const steps = action === "uninstall" ? [...definition.stop, ...definition.uninstall] : definition[action];
351
+ runDaemonServiceSteps(steps, run);
352
+ if (action === "uninstall") {
353
+ const state = probeDaemonServiceState(definition, run);
354
+ if (state.kind !== "ok") throw new Error(`Could not verify native background service is absent: ${state.detail}`);
355
+ if (state.registered) throw new Error("Native background service remains registered");
356
+ if (state.running) throw new Error("Native background service remains active");
357
+ safeUnlink(definition.definitionPath);
358
+ const reload = definition.reloadAfterUninstall;
359
+ if (reload) run(reload.command, reload.args);
360
+ }
361
+ return { kind: "ok" };
362
+ } catch (err) {
363
+ return { kind: "failed", detail: err instanceof Error ? err.message : String(err) };
364
+ }
365
+ };
366
+ var DAEMON_ACTIONS = /* @__PURE__ */ new Set([
367
+ "install",
368
+ "start",
369
+ "stop",
370
+ "restart",
371
+ "status",
372
+ "uninstall",
373
+ "foreground"
374
+ ]);
375
+ var parseDaemonAction = (argv) => {
376
+ const args = argv[0] === "daemon" ? argv.slice(1) : argv;
377
+ const action = args[0] ?? "foreground";
378
+ return DAEMON_ACTIONS.has(action) ? action : null;
379
+ };
380
+ var manageDaemonService = (action, options = {}) => {
381
+ const definition = action === "uninstall" || action === "stop" ? buildDaemonService({
382
+ platform: options.platform ?? process.platform,
383
+ homeDir: options.homeDir ?? homedir(),
384
+ stateDir: options.stateDir ?? pusharyDir(),
385
+ execPath: options.execPath ?? process.execPath,
386
+ daemonEntry: options.daemonEntry ?? process.execPath,
387
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
388
+ uid: options.uid ?? process.getuid?.()
389
+ }) : resolveDaemonServiceDefinition(options);
390
+ if (!definition) {
391
+ return {
392
+ kind: "unavailable",
393
+ detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
394
+ };
395
+ }
396
+ if (action === "uninstall" && !daemonServiceManagerAvailable(options)) {
397
+ return { kind: "failed", detail: "Native background-service manager is unavailable" };
398
+ }
399
+ return applyDaemonService(action, definition, { run: options.run });
400
+ };
401
+ var nominalDaemonService = (options) => buildDaemonService({
402
+ platform: options.platform ?? process.platform,
403
+ homeDir: options.homeDir ?? homedir(),
404
+ stateDir: options.stateDir ?? pusharyDir(),
405
+ execPath: options.execPath ?? process.execPath,
406
+ daemonEntry: options.daemonEntry ?? process.execPath,
407
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
408
+ uid: options.uid ?? process.getuid?.()
409
+ });
410
+ var hasDaemonServiceDefinition = (options = {}) => {
411
+ const definition = nominalDaemonService(options);
412
+ return definition !== null && existsSync(definition.definitionPath);
413
+ };
414
+ var daemonServiceDefinitionPath = (options = {}) => nominalDaemonService(options)?.definitionPath ?? null;
415
+ var readInstalledDaemonService = (options = {}) => {
416
+ const definition = nominalDaemonService(options);
417
+ if (!definition) return null;
418
+ try {
419
+ return {
420
+ path: definition.definitionPath,
421
+ contents: readFileSync(definition.definitionPath, definition.encoding ?? "utf-8")
422
+ };
423
+ } catch {
424
+ return null;
425
+ }
426
+ };
427
+ var spawnDetachedDaemon = (plan, options = {}) => {
428
+ const spawnImpl = options.spawnImpl ?? spawnClaude;
429
+ const child = spawnImpl(plan.command, plan.args, {
430
+ detached: true,
431
+ stdio: "ignore",
432
+ windowsHide: true,
433
+ shell: plan.shell
434
+ });
435
+ child.once?.("error", () => {
436
+ });
437
+ child.unref();
438
+ return typeof child.pid === "number" ? child.pid : null;
439
+ };
440
+ var processIsAlive = (pid) => {
441
+ try {
442
+ process.kill(pid, 0);
443
+ return true;
444
+ } catch (err) {
445
+ return err.code === "EPERM";
446
+ }
447
+ };
448
+ var isString = (value) => typeof value === "string" && value.length > 0;
449
+ var isTimestamp = (value) => isString(value) && Number.isFinite(Date.parse(value));
450
+ var daemonConfigurationId = (apiKey) => createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
451
+ var currentConfigurationId = () => {
452
+ try {
453
+ return daemonConfigurationId(getApiKey());
454
+ } catch {
455
+ return null;
456
+ }
457
+ };
458
+ var parseDaemonLastError = (value) => {
459
+ if (typeof value !== "object" || value === null) return null;
460
+ const error = value;
461
+ return isString(error.message) && isTimestamp(error.at) ? { message: error.message, at: error.at } : null;
462
+ };
463
+ var parseDaemonState = (value) => {
464
+ if (typeof value !== "object" || value === null) return null;
465
+ const state = value;
466
+ if (!isString(state.instanceId) || !Number.isInteger(state.pid) || state.pid <= 0 || !isString(state.version) || !(state.configurationId === void 0 || state.configurationId === null || isString(state.configurationId)) || !isString(state.machineId) || !isTimestamp(state.startedAt) || !isTimestamp(state.updatedAt) || !(state.lastSuccessfulHeartbeatAt === null || isTimestamp(state.lastSuccessfulHeartbeatAt)) || !(state.lastSuccessfulControlAt === void 0 || state.lastSuccessfulControlAt === null || isTimestamp(state.lastSuccessfulControlAt))) return null;
467
+ return {
468
+ instanceId: state.instanceId,
469
+ pid: state.pid,
470
+ version: state.version,
471
+ configurationId: state.configurationId ?? null,
472
+ machineId: state.machineId,
473
+ startedAt: state.startedAt,
474
+ updatedAt: state.updatedAt,
475
+ lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt,
476
+ lastSuccessfulControlAt: state.lastSuccessfulControlAt ?? null,
477
+ lastError: parseDaemonLastError(state.lastError)
478
+ };
479
+ };
480
+ var parseLockOwner = (value) => {
481
+ if (typeof value !== "object" || value === null) return null;
482
+ const owner = value;
483
+ return isString(owner.instanceId) && Number.isInteger(owner.pid) && owner.pid > 0 ? { instanceId: owner.instanceId, pid: owner.pid } : null;
484
+ };
485
+ var readDaemonStatus = (options = {}) => {
486
+ const dir = options.dir ?? pusharyDir();
487
+ const installedVersion = options.installedVersion ?? getPackageVersion();
488
+ const state = readJsonSafe(statePath(dir));
489
+ if (state.kind === "missing") {
490
+ return {
491
+ kind: "absent",
492
+ installedVersion,
493
+ machineId: null,
494
+ pid: null,
495
+ processAlive: null,
496
+ daemonVersion: null,
497
+ startedAt: null,
498
+ updatedAt: null,
499
+ lastSuccessfulHeartbeatAt: null,
500
+ lastSuccessfulControlAt: null,
501
+ lastError: null
502
+ };
503
+ }
504
+ const value = state.kind === "ok" ? parseDaemonState(state.value) : null;
505
+ if (value) {
506
+ const alive = (options.isProcessAlive ?? processIsAlive)(value.pid);
507
+ const fresh = (options.now ?? Date.now)() - Date.parse(value.updatedAt) <= LOCAL_LEASE_TIMEOUT_MS;
508
+ const expectedConfigurationId2 = options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
509
+ return {
510
+ kind: !alive || !fresh ? "unresponsive" : value.version !== installedVersion ? "version-mismatch" : expectedConfigurationId2 !== null && value.configurationId !== null && value.configurationId !== expectedConfigurationId2 ? "configuration-mismatch" : "running",
511
+ installedVersion,
512
+ machineId: value.machineId,
513
+ pid: value.pid,
514
+ processAlive: alive,
515
+ daemonVersion: value.version,
516
+ startedAt: value.startedAt,
517
+ updatedAt: value.updatedAt,
518
+ lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt,
519
+ lastSuccessfulControlAt: value.lastSuccessfulControlAt,
520
+ lastError: value.lastError
521
+ };
522
+ }
523
+ return {
524
+ kind: "invalid",
525
+ installedVersion,
526
+ machineId: null,
527
+ pid: null,
528
+ processAlive: null,
529
+ daemonVersion: null,
530
+ startedAt: null,
531
+ updatedAt: null,
532
+ lastSuccessfulHeartbeatAt: null,
533
+ lastSuccessfulControlAt: null,
534
+ lastError: null
535
+ };
536
+ };
537
+ var safeUnlink = (path) => {
538
+ try {
539
+ unlinkSync(path);
540
+ } catch (err) {
541
+ if (err.code !== "ENOENT") throw err;
542
+ }
543
+ };
544
+ var requestDaemonVersion = (version, options = {}) => {
545
+ const dir = options.dir ?? pusharyDir();
546
+ const now = options.now ?? Date.now;
547
+ writeJsonAtomic(targetPath(dir), { version, requestedAt: new Date(now()).toISOString() }, 384);
548
+ };
549
+ var acquireDaemonLease = (options = {}) => {
550
+ const dir = options.dir ?? pusharyDir();
551
+ const installedVersion = options.installedVersion ?? getPackageVersion();
552
+ const machineId = options.machineId ?? getMachineId();
553
+ const pid = options.pid ?? process.pid;
554
+ const now = options.now ?? Date.now;
555
+ const instanceId = randomUUID();
556
+ const startedAt = new Date(now()).toISOString();
557
+ const writeLock = () => {
558
+ mkdirSync(dir, { recursive: true });
559
+ writeFileSync(lockPath(dir), JSON.stringify({ instanceId, pid }), { flag: "wx", mode: 384 });
560
+ };
561
+ try {
562
+ writeLock();
563
+ } catch (err) {
564
+ if (err.code !== "EEXIST") {
565
+ return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
566
+ }
567
+ const status = readDaemonStatus(options);
568
+ if (status.kind === "running" || status.kind === "version-mismatch" || status.kind === "configuration-mismatch") {
569
+ return { kind: "already-running", status };
570
+ }
571
+ const lock = readJsonSafe(lockPath(dir));
572
+ const owner = lock.kind === "ok" ? parseLockOwner(lock.value) : null;
573
+ if (!owner) return { kind: "unavailable", detail: "daemon lock is invalid" };
574
+ if ((options.isProcessAlive ?? processIsAlive)(owner.pid)) {
575
+ return { kind: "unavailable", detail: "daemon owner is still running or starting" };
576
+ }
577
+ const stale = join(dir, `daemon.lock.${instanceId}.stale`);
578
+ let ownsReplacement = false;
579
+ try {
580
+ renameSync(lockPath(dir), stale);
581
+ writeLock();
582
+ ownsReplacement = true;
583
+ safeUnlink(statePath(dir));
584
+ safeUnlink(stale);
585
+ } catch (retryError) {
586
+ if (ownsReplacement) safeUnlink(lockPath(dir));
587
+ try {
588
+ safeUnlink(stale);
589
+ } catch {
590
+ }
591
+ return { kind: "unavailable", detail: retryError instanceof Error ? retryError.message : String(retryError) };
592
+ }
593
+ }
594
+ const state = {
595
+ instanceId,
596
+ pid,
597
+ version: installedVersion,
598
+ configurationId: options.configurationId === void 0 ? currentConfigurationId() : options.configurationId,
599
+ machineId,
600
+ startedAt,
601
+ updatedAt: startedAt,
602
+ lastSuccessfulHeartbeatAt: null,
603
+ lastSuccessfulControlAt: null,
604
+ lastError: null
605
+ };
606
+ try {
607
+ writeJsonAtomic(statePath(dir), state, 384);
608
+ } catch (err) {
609
+ safeUnlink(lockPath(dir));
610
+ return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
611
+ }
612
+ let currentState = state;
613
+ const isOwner = () => {
614
+ const owner = readJsonSafe(lockPath(dir));
615
+ return owner.kind === "ok" && owner.value.instanceId === instanceId;
616
+ };
617
+ const updateState = (next) => {
618
+ if (!isOwner()) return;
619
+ try {
620
+ writeJsonAtomic(statePath(dir), next, 384);
621
+ currentState = next;
622
+ } catch {
623
+ }
624
+ };
625
+ return {
626
+ kind: "acquired",
627
+ lease: {
628
+ isOwner,
629
+ renew: (at = now()) => {
630
+ updateState({ ...currentState, updatedAt: new Date(at).toISOString() });
631
+ },
632
+ recordSuccessfulHeartbeat: (at = now()) => {
633
+ const timestamp = new Date(at).toISOString();
634
+ updateState({
635
+ ...currentState,
636
+ updatedAt: timestamp,
637
+ lastSuccessfulHeartbeatAt: timestamp
638
+ });
639
+ },
640
+ recordSuccessfulControl: (at = now()) => {
641
+ const timestamp = new Date(at).toISOString();
642
+ updateState({
643
+ ...currentState,
644
+ updatedAt: timestamp,
645
+ lastSuccessfulControlAt: timestamp
646
+ });
647
+ },
648
+ recordError: (message, at = now()) => {
649
+ const timestamp = new Date(at).toISOString();
650
+ updateState({
651
+ ...currentState,
652
+ updatedAt: timestamp,
653
+ lastError: { message: message.slice(0, 240), at: timestamp }
654
+ });
655
+ },
656
+ shouldYield: () => {
657
+ if (!isOwner()) return true;
658
+ const target = readJsonSafe(targetPath(dir));
659
+ return target.kind === "ok" && (typeof target.value.version === "string" && target.value.version !== installedVersion || isTimestamp(target.value.requestedAt) && Date.parse(target.value.requestedAt) > Date.parse(startedAt));
660
+ },
661
+ release: () => {
662
+ if (!isOwner()) return;
663
+ safeUnlink(statePath(dir));
664
+ safeUnlink(lockPath(dir));
665
+ }
666
+ }
667
+ };
668
+ };
669
+ var hasConfiguration = () => {
670
+ try {
671
+ getApiKey();
672
+ return true;
673
+ } catch {
674
+ return false;
675
+ }
676
+ };
677
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
678
+ var requestDaemonHandoff = async (initial, options = {}) => {
679
+ const now = options.now ?? Date.now;
680
+ const requestedAt = Math.max(
681
+ now(),
682
+ initial.startedAt === null ? 0 : Date.parse(initial.startedAt) + 1
683
+ );
684
+ requestDaemonVersion(options.installedVersion ?? initial.installedVersion, {
685
+ dir: options.dir,
686
+ now: () => requestedAt
687
+ });
688
+ if (initial.pid === null || initial.processAlive !== true) return initial;
689
+ const sleep = options.sleep ?? wait;
690
+ const pollIntervalMs = options.pollIntervalMs ?? 50;
691
+ const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
692
+ let status = initial;
693
+ for (let attempt = 0; attempt < attempts; attempt++) {
694
+ await sleep(pollIntervalMs);
695
+ status = readDaemonStatus(options);
696
+ if (status.pid !== initial.pid || status.processAlive !== true) break;
697
+ }
698
+ return status;
699
+ };
700
+ var stopDaemonRunning = async (options = {}) => {
701
+ const dir = options.dir ?? pusharyDir();
702
+ const initial = readDaemonStatus(options);
703
+ if (initial.pid === null || initial.processAlive !== true) {
704
+ safeUnlink(statePath(dir));
705
+ safeUnlink(lockPath(dir));
706
+ return { kind: "absent" };
707
+ }
708
+ let status;
709
+ try {
710
+ status = await requestDaemonHandoff(initial, options);
711
+ } catch (err) {
712
+ return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
713
+ }
714
+ if (status.pid === initial.pid && status.processAlive === true) {
715
+ return { kind: "unavailable", detail: "daemon did not stop" };
716
+ }
717
+ safeUnlink(statePath(dir));
718
+ safeUnlink(lockPath(dir));
719
+ return { kind: "stopped" };
720
+ };
721
+ var needsLoginPath = (dir) => join(dir, "daemon-needs-login.json");
722
+ var isLoginReason = (value) => value === "key_rejected" || value === "plan_inactive";
723
+ var parseDaemonNeedsLogin = (value) => {
724
+ if (typeof value !== "object" || value === null) return null;
725
+ const record = value;
726
+ if (!isLoginReason(record.reason) || !isTimestamp(record.at)) return null;
727
+ if (!(record.configurationId === null || isString(record.configurationId))) return null;
728
+ return { reason: record.reason, at: record.at, configurationId: record.configurationId };
729
+ };
730
+ var expectedConfigurationId = (options) => options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
731
+ var writeDaemonNeedsLogin = (reason, options = {}) => {
732
+ const record = {
733
+ reason,
734
+ at: new Date((options.now ?? Date.now)()).toISOString(),
735
+ configurationId: expectedConfigurationId(options)
736
+ };
737
+ writeJsonAtomic(needsLoginPath(options.dir ?? pusharyDir()), record, 384);
738
+ return record;
739
+ };
740
+ var readDaemonNeedsLogin = (options = {}) => {
741
+ const stored = readJsonSafe(needsLoginPath(options.dir ?? pusharyDir()));
742
+ const record = stored.kind === "ok" ? parseDaemonNeedsLogin(stored.value) : null;
743
+ if (!record) return { kind: "none" };
744
+ return record.configurationId === expectedConfigurationId(options) ? { kind: "blocked", record } : { kind: "stale", record };
745
+ };
746
+ var clearDaemonNeedsLogin = (options = {}) => {
747
+ const path = needsLoginPath(options.dir ?? pusharyDir());
748
+ if (!existsSync(path)) return false;
749
+ safeUnlink(path);
750
+ return true;
751
+ };
752
+ var describeDaemonLoginBlock = (record) => record.reason === "key_rejected" ? `this machine's key was rejected at ${record.at}; run \`pushary login\` to sign in again` : `this workspace had no active plan at ${record.at}; pick one at pushary.com/dashboard/settings?tab=billing, then run \`pushary login\``;
753
+ var recoverDaemonAfterLogin = (options = {}) => {
754
+ const cleared = clearDaemonNeedsLogin(options);
755
+ if (!cleared || readControlMode(join(options.dir ?? pusharyDir(), "config.json")) === "off" || !hasDaemonServiceDefinition(options)) {
756
+ return { cleared, restarted: false, detail: null };
757
+ }
758
+ const started = manageDaemonService("start", options);
759
+ return started.kind === "ok" ? { cleared, restarted: true, detail: null } : { cleared, restarted: false, detail: started.detail };
760
+ };
761
+ var deactivateDaemonService = async (action, options) => {
762
+ const platform = options.platform ?? process.platform;
763
+ const supported = ["darwin", "linux", "win32"].includes(platform);
764
+ const managerAvailable = supported && daemonServiceManagerAvailable(options);
765
+ const absenceMustBeProven = options.requireAbsenceProof || hasDaemonServiceDefinition(options);
766
+ const service = supported && !managerAvailable && absenceMustBeProven ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService(action, options) : { kind: "ok" };
767
+ const stopped = await stopDaemonRunning(options);
768
+ const failures = [
769
+ service.kind === "ok" ? null : service.detail,
770
+ stopped.kind === "unavailable" ? stopped.detail : null
771
+ ].filter((detail) => detail !== null);
772
+ return failures.length > 0 ? { kind: "failed", detail: failures.join("; ") } : { kind: "ok" };
773
+ };
774
+ var stopDaemonService = async (options = {}) => deactivateDaemonService("stop", options);
775
+ var disableDaemonService = async (options = {}) => deactivateDaemonService("uninstall", options);
776
+ var parseDaemonServiceTransaction = (value) => {
777
+ if (typeof value !== "object" || value === null) return null;
778
+ const transaction = value;
779
+ if (transaction.version !== 1 || transaction.phase !== "prepared" && transaction.phase !== "committed" || typeof transaction.definitionPath !== "string" || !(transaction.previousContents === null || typeof transaction.previousContents === "string") || !(transaction.previousEncoding === void 0 || typeof transaction.previousEncoding === "string") || typeof transaction.previousRegistered !== "boolean" || !(transaction.previousEnabled === void 0 || typeof transaction.previousEnabled === "boolean") || typeof transaction.previousRunning !== "boolean") return null;
780
+ return {
781
+ ...transaction,
782
+ previousEnabled: typeof transaction.previousEnabled === "boolean" ? transaction.previousEnabled : transaction.previousRegistered
783
+ };
784
+ };
785
+ var probeDaemonServiceState = (definition, run) => {
786
+ let registered = true;
787
+ try {
788
+ run(definition.registrationProbe.command, definition.registrationProbe.args);
789
+ } catch (err) {
790
+ if (!isExpectedProbeFailure(definition.registrationProbe, err)) {
791
+ return {
792
+ kind: "failed",
793
+ detail: `Could not inspect native service registration: ${commandFailureOutput(err) || String(err)}`
794
+ };
795
+ }
796
+ registered = false;
797
+ }
798
+ let enabled = registered;
799
+ const enabledProbe = definition.enabledProbe;
800
+ if (enabledProbe) {
801
+ try {
802
+ const output = run(enabledProbe.command, enabledProbe.args);
803
+ enabled = !(enabledProbe.disabledOutputPattern && typeof output === "string" && enabledProbe.disabledOutputPattern.test(output));
804
+ } catch (err) {
805
+ if (!isExpectedProbeFailure(enabledProbe, err)) {
806
+ return {
807
+ kind: "failed",
808
+ detail: `Could not inspect whether the native service is enabled: ${commandFailureOutput(err) || String(err)}`
809
+ };
810
+ }
811
+ enabled = false;
812
+ }
813
+ }
814
+ try {
815
+ const output = run(definition.runningProbe.command, definition.runningProbe.args);
816
+ const running = definition.runningProbe.outputPattern ? typeof output === "string" && definition.runningProbe.outputPattern.test(output) : true;
817
+ return { kind: "ok", registered, enabled, running };
818
+ } catch (err) {
819
+ if (!isExpectedProbeFailure(definition.runningProbe, err)) {
820
+ return {
821
+ kind: "failed",
822
+ detail: `Could not inspect whether the native service is running: ${commandFailureOutput(err) || String(err)}`
823
+ };
824
+ }
825
+ return { kind: "ok", registered, enabled, running: false };
826
+ }
827
+ };
828
+ var previousEncoding = (snapshot) => snapshot.previousEncoding ?? "utf-8";
829
+ var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
830
+ const removed = applyDaemonService("uninstall", definition, { run });
831
+ if (removed.kind !== "ok") return removed;
832
+ try {
833
+ if (snapshot.previousContents === null) safeUnlink(definition.definitionPath);
834
+ else writeFileAtomic(definition.definitionPath, snapshot.previousContents, 384, previousEncoding(snapshot));
835
+ } catch (err) {
836
+ return {
837
+ kind: "failed",
838
+ detail: `could not restore the previous definition: ${err instanceof Error ? err.message : String(err)}`
839
+ };
840
+ }
841
+ if (!snapshot.previousRegistered) {
842
+ const reload = definition.reloadAfterUninstall;
843
+ if (snapshot.previousContents !== null && reload) {
844
+ try {
845
+ run(reload.command, reload.args);
846
+ } catch (err) {
847
+ return { kind: "failed", detail: `could not reload the previous definition: ${commandFailureOutput(err) || String(err)}` };
848
+ }
849
+ }
850
+ if (snapshot.previousRunning) {
851
+ const started = applyDaemonService("start", definition, { run });
852
+ if (started.kind !== "ok") return started;
853
+ }
854
+ if (!snapshot.previousEnabled && definition.canDisableWhenUnregistered) {
855
+ try {
856
+ runDaemonServiceSteps(definition.disable, run);
857
+ } catch (err) {
858
+ return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
859
+ }
860
+ }
861
+ const state2 = probeDaemonServiceState(definition, run);
862
+ if (state2.kind !== "ok") return { kind: "failed", detail: state2.detail };
863
+ if (state2.registered || state2.enabled !== snapshot.previousEnabled || state2.running !== snapshot.previousRunning) {
864
+ return { kind: "failed", detail: "could not restore the previous unregistered service state" };
865
+ }
866
+ return { kind: "ok" };
867
+ }
868
+ if (snapshot.previousContents === null) {
869
+ return { kind: "failed", detail: "the previous registered service had no recoverable definition" };
870
+ }
871
+ const restored = applyDaemonService("install", {
872
+ ...definition,
873
+ contents: snapshot.previousContents,
874
+ encoding: previousEncoding(snapshot)
875
+ }, { run });
876
+ if (restored.kind !== "ok") return restored;
877
+ if (!snapshot.previousRunning) applyDaemonService("stop", definition, { run });
878
+ if (!snapshot.previousEnabled) {
879
+ try {
880
+ runDaemonServiceSteps(definition.disable, run);
881
+ } catch (err) {
882
+ return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
883
+ }
884
+ }
885
+ const state = probeDaemonServiceState(definition, run);
886
+ if (state.kind !== "ok") return { kind: "failed", detail: state.detail };
887
+ if (!state.registered) return { kind: "failed", detail: "the previous service is no longer registered" };
888
+ if (state.enabled !== snapshot.previousEnabled) {
889
+ return { kind: "failed", detail: "could not restore whether the previous service was enabled" };
890
+ }
891
+ if (state.running !== snapshot.previousRunning) {
892
+ return {
893
+ kind: "failed",
894
+ detail: snapshot.previousRunning ? "the previous service did not restart" : "could not restore the previous stopped state"
895
+ };
896
+ }
897
+ return { kind: "ok" };
898
+ };
899
+ var recoverDaemonServiceTransaction = (definition, path, run) => {
900
+ const stored = readJsonSafe(path);
901
+ if (stored.kind === "missing") return { kind: "ok" };
902
+ if (stored.kind !== "ok") {
903
+ return { kind: "failed", detail: `transaction receipt is ${stored.kind}` };
904
+ }
905
+ const transaction = parseDaemonServiceTransaction(stored.value);
906
+ if (!transaction) return { kind: "failed", detail: "transaction receipt is invalid" };
907
+ if (transaction.definitionPath !== definition.definitionPath) {
908
+ return { kind: "failed", detail: "transaction receipt belongs to a different service definition" };
909
+ }
910
+ if (transaction.phase === "prepared") {
911
+ const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
912
+ if (restored.kind !== "ok") return restored;
913
+ }
914
+ try {
915
+ safeUnlink(path);
916
+ } catch (err) {
917
+ return { kind: "failed", detail: `could not clear transaction receipt: ${err instanceof Error ? err.message : String(err)}` };
918
+ }
919
+ return { kind: "ok" };
920
+ };
921
+ var replaceDaemonService = async (options = {}) => {
922
+ const definition = resolveDaemonServiceDefinition(options);
923
+ if (!definition) {
924
+ return {
925
+ kind: "unavailable",
926
+ detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
927
+ };
928
+ }
929
+ const run = options.run ?? runDaemonServiceCommand;
930
+ const transactionPath = join(options.stateDir ?? options.dir ?? pusharyDir(), "daemon-service-transaction.json");
931
+ const recovered = recoverDaemonServiceTransaction(definition, transactionPath, run);
932
+ if (recovered.kind !== "ok") {
933
+ return { kind: "failed", detail: `Could not recover interrupted service replacement: ${recovered.detail}` };
934
+ }
935
+ let previousContents = null;
936
+ let previousDefinitionEncoding = "utf-8";
937
+ try {
938
+ if (existsSync(definition.definitionPath)) {
939
+ const previous = readServiceDefinitionFile(definition.definitionPath);
940
+ previousContents = previous.contents;
941
+ previousDefinitionEncoding = previous.encoding;
942
+ }
943
+ } catch (err) {
944
+ return { kind: "failed", detail: `Could not snapshot the existing service: ${err instanceof Error ? err.message : String(err)}` };
945
+ }
946
+ const previousState = probeDaemonServiceState(definition, run);
947
+ if (previousState.kind !== "ok") return { kind: "failed", detail: previousState.detail };
948
+ if ((previousState.registered || previousState.running) && previousContents === null) {
949
+ return { kind: "failed", detail: "Could not replace an active native service with no recoverable definition" };
950
+ }
951
+ const transaction = {
952
+ version: 1,
953
+ phase: "prepared",
954
+ definitionPath: definition.definitionPath,
955
+ previousContents,
956
+ previousEncoding: previousDefinitionEncoding,
957
+ previousRegistered: previousState.registered,
958
+ previousEnabled: previousState.enabled,
959
+ previousRunning: previousState.running
960
+ };
961
+ try {
962
+ writeJsonAtomicDurable(transactionPath, transaction, 384);
963
+ } catch (err) {
964
+ return { kind: "failed", detail: `Could not persist the service rollback snapshot: ${err instanceof Error ? err.message : String(err)}` };
965
+ }
966
+ const stopped = await stopDaemonRunning(options);
967
+ if (stopped.kind === "unavailable") {
968
+ try {
969
+ safeUnlink(transactionPath);
970
+ } catch (err) {
971
+ return {
972
+ kind: "failed",
973
+ detail: `${stopped.detail}; could not clear unused rollback evidence: ${err instanceof Error ? err.message : String(err)}`
974
+ };
975
+ }
976
+ return stopped;
977
+ }
978
+ let installed = applyDaemonService("install", definition, { run });
979
+ if (installed.kind === "ok") {
980
+ const sleep = options.sleep ?? wait;
981
+ const pollIntervalMs = Math.max(1, options.pollIntervalMs ?? 50);
982
+ const now = options.now ?? Date.now;
983
+ const deadline = now() + Math.max(0, options.waitTimeoutMs ?? 5e3);
984
+ let state = probeDaemonServiceState(definition, run);
985
+ while (state.kind === "ok" && (!state.registered || !state.enabled || !state.running) && now() < deadline) {
986
+ await sleep(pollIntervalMs);
987
+ state = probeDaemonServiceState(definition, run);
988
+ }
989
+ if (state.kind !== "ok") {
990
+ installed = { kind: "failed", detail: state.detail };
991
+ } else if (!state.registered || !state.enabled || !state.running) {
992
+ installed = {
993
+ kind: "failed",
994
+ detail: !state.registered ? "Service registration verification failed" : !state.enabled ? "Service enable verification failed" : "Service start verification failed"
995
+ };
996
+ }
997
+ }
998
+ if (installed.kind === "ok") {
999
+ try {
1000
+ writeJsonAtomicDurable(transactionPath, { ...transaction, phase: "committed" }, 384);
1001
+ } catch (err) {
1002
+ const detail = `Could not commit the service replacement: ${err instanceof Error ? err.message : String(err)}`;
1003
+ const recovered2 = recoverDaemonServiceTransaction(definition, transactionPath, run);
1004
+ return recovered2.kind === "ok" ? { kind: "failed", detail } : {
1005
+ kind: "failed",
1006
+ detail: `${detail}; recovery failed: ${recovered2.detail}`
1007
+ };
1008
+ }
1009
+ try {
1010
+ safeUnlink(transactionPath);
1011
+ return installed;
1012
+ } catch (err) {
1013
+ return {
1014
+ kind: "failed",
1015
+ detail: `Service replacement committed but its receipt could not be cleared: ${err instanceof Error ? err.message : String(err)}`
1016
+ };
1017
+ }
1018
+ }
1019
+ const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
1020
+ if (restored.kind !== "ok") {
1021
+ return { kind: "failed", detail: `${installed.detail}; rollback failed: ${restored.detail}` };
1022
+ }
1023
+ try {
1024
+ safeUnlink(transactionPath);
1025
+ } catch (err) {
1026
+ return {
1027
+ kind: "failed",
1028
+ detail: `${installed.detail}; previous service restored but recovery evidence could not be cleared: ${err instanceof Error ? err.message : String(err)}`
1029
+ };
1030
+ }
1031
+ return {
1032
+ kind: "failed",
1033
+ detail: `${installed.detail}; ${transaction.previousRegistered ? "previous service restored" : "previous service state restored"}`
1034
+ };
1035
+ };
1036
+ var launchInstalledDaemon = (options) => {
1037
+ const entryExists = options.entryExists ?? existsSync;
1038
+ const argvEntry = process.argv[1];
1039
+ const candidate = options.daemonEntry !== void 0 ? options.daemonEntry : argvEntry ? join(dirname(argvEntry), "pushary-daemon.js") : null;
1040
+ const siblingEntry = candidate && entryExists(candidate) ? candidate : null;
1041
+ const platform = options.platform ?? process.platform;
1042
+ const daemonBinary = options.daemonBinary ?? resolveGlobalBinary("pushary-daemon") ?? (platform === "win32" ? "pushary-daemon.cmd" : "pushary-daemon");
1043
+ const plan = buildDaemonLaunch({
1044
+ siblingEntry,
1045
+ execPath: options.execPath ?? process.execPath,
1046
+ daemonBinary,
1047
+ platform
1048
+ });
1049
+ spawnDetachedDaemon(plan, { spawnImpl: options.spawnImpl });
1050
+ };
1051
+ var ensureDaemonRunning = async (options = {}) => {
1052
+ const initial = readDaemonStatus(options);
1053
+ if (!(options.configured ?? hasConfiguration)()) {
1054
+ return { kind: "not-configured", status: initial, detail: "Pushary is not configured on this machine" };
1055
+ }
1056
+ if (initial.kind === "running") return { kind: "running", status: initial };
1057
+ const loginBlock = readDaemonNeedsLogin(options);
1058
+ if (loginBlock.kind === "blocked") {
1059
+ return { kind: "unavailable", status: initial, detail: describeDaemonLoginBlock(loginBlock.record) };
1060
+ }
1061
+ let handoffStatus;
1062
+ try {
1063
+ handoffStatus = await requestDaemonHandoff(initial, options);
1064
+ } catch (err) {
1065
+ return {
1066
+ kind: "unavailable",
1067
+ status: initial,
1068
+ detail: err instanceof Error ? err.message : String(err)
1069
+ };
1070
+ }
1071
+ const sleep = options.sleep ?? wait;
1072
+ const pollIntervalMs = options.pollIntervalMs ?? 50;
1073
+ const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
1074
+ if (handoffStatus.kind === "running") return { kind: "running", status: handoffStatus };
1075
+ if (daemonNeedsHandoff(handoffStatus)) {
1076
+ return { kind: "unavailable", status: handoffStatus, detail: "unhealthy daemon did not yield" };
1077
+ }
1078
+ try {
1079
+ (options.spawnDaemon ?? (() => launchInstalledDaemon(options)))();
1080
+ } catch (err) {
1081
+ return {
1082
+ kind: "unavailable",
1083
+ status: initial,
1084
+ detail: err instanceof Error ? err.message : String(err)
1085
+ };
1086
+ }
1087
+ let status = initial;
1088
+ for (let attempt = 0; attempt < attempts; attempt++) {
1089
+ status = readDaemonStatus(options);
1090
+ if (status.kind === "running") return { kind: "started", status };
1091
+ await sleep(pollIntervalMs);
1092
+ }
1093
+ return { kind: "unavailable", status, detail: `daemon did not become healthy (${status.kind})` };
1094
+ };
1095
+ var waitForDaemonRemoteProof = async (options = {}) => {
1096
+ const sleep = options.sleep ?? wait;
1097
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
1098
+ const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
1099
+ let status = readDaemonStatus(options);
1100
+ for (let attempt = 0; attempt < attempts; attempt++) {
1101
+ status = readDaemonStatus(options);
1102
+ if (daemonHasRemoteProof(status)) return status;
1103
+ if (attempt + 1 < attempts) await sleep(pollIntervalMs);
1104
+ }
1105
+ return status;
1106
+ };
1107
+
1108
+ export {
1109
+ daemonHasRemoteProof,
1110
+ daemonServiceManagerAvailable,
1111
+ DAEMON_LEASE_RENEW_INTERVAL_MS,
1112
+ parseDaemonAction,
1113
+ manageDaemonService,
1114
+ hasDaemonServiceDefinition,
1115
+ daemonServiceDefinitionPath,
1116
+ readInstalledDaemonService,
1117
+ readDaemonStatus,
1118
+ acquireDaemonLease,
1119
+ requestDaemonHandoff,
1120
+ stopDaemonRunning,
1121
+ writeDaemonNeedsLogin,
1122
+ readDaemonNeedsLogin,
1123
+ clearDaemonNeedsLogin,
1124
+ describeDaemonLoginBlock,
1125
+ recoverDaemonAfterLogin,
1126
+ stopDaemonService,
1127
+ disableDaemonService,
1128
+ replaceDaemonService,
1129
+ ensureDaemonRunning,
1130
+ waitForDaemonRemoteProof
1131
+ };