pushary 1.9.136 → 1.9.137
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.
- package/CHANGELOG.md +11 -0
- package/dist/bin/pushary-bell-hook.js +3 -3
- package/dist/bin/pushary-claude.js +2 -2
- package/dist/bin/pushary-clean.js +9 -9
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex-session.js +2 -2
- package/dist/bin/pushary-daemon.js +3 -3
- package/dist/bin/pushary-display-hook.js +36 -24
- package/dist/bin/pushary-doctor.js +12 -8
- package/dist/bin/pushary-elicitation-hook.js +3 -3
- package/dist/bin/pushary-gemini-hook.js +3 -3
- package/dist/bin/pushary-hook.js +4 -4
- package/dist/bin/pushary-login.js +1 -1
- package/dist/bin/pushary-notification-hook.js +3 -3
- package/dist/bin/pushary-opencode-hook.js +3 -3
- package/dist/bin/pushary-permission-denied-hook.js +4 -4
- package/dist/bin/pushary-permission-hook.js +4 -4
- package/dist/bin/pushary-post-hook.js +3 -3
- package/dist/bin/pushary-prompt-hook.js +3 -3
- package/dist/bin/pushary-session-end-hook.js +3 -3
- package/dist/bin/pushary-session-start-hook.js +3 -3
- package/dist/bin/pushary-setup.js +11 -9
- package/dist/bin/pushary-status.js +2 -2
- package/dist/bin/pushary-stop-hook.js +3 -3
- package/dist/bin/pushary-stopfailure-hook.js +3 -3
- package/dist/bin/pushary-transcript-register.js +2 -2
- package/dist/bin/pushary-transcripts.js +2 -2
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/{chunk-L7Q57R4J.js → chunk-6WT5OIRM.js} +1 -1
- package/dist/{chunk-EQZB5P6I.js → chunk-7NCI2DGB.js} +2 -2
- package/dist/{chunk-RPFSVNH6.js → chunk-7VKW7OA5.js} +1 -1
- package/dist/{chunk-YURIU333.js → chunk-7XD56GXA.js} +1 -1
- package/dist/chunk-B4UIRMOE.js +31 -0
- package/dist/{chunk-HKJLPVPZ.js → chunk-CMAB4KQG.js} +1 -1
- package/dist/{chunk-FAF5RDWT.js → chunk-MD63J3T7.js} +229 -38
- package/dist/{chunk-O5T53LZ5.js → chunk-R6N6FREL.js} +1 -1
- package/dist/{chunk-J42EAEX3.js → chunk-XR27HPYC.js} +2 -1
- package/dist/{codex-app-cards-switch-Z7TMF2OR.js → codex-app-cards-switch-JU236CG2.js} +3 -3
- package/dist/{codex-desktop-launch-HKXGKL3T.js → codex-desktop-launch-LRQ4LU64.js} +1 -1
- package/dist/src/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-GACQSKNG.js +0 -75
|
@@ -34,9 +34,136 @@ import {
|
|
|
34
34
|
isRecord
|
|
35
35
|
} from "./chunk-DL67HX3D.js";
|
|
36
36
|
|
|
37
|
+
// src/daemon-entry-guard.ts
|
|
38
|
+
import { mkdirSync, readFileSync } from "fs";
|
|
39
|
+
import { basename, dirname, join } from "path";
|
|
40
|
+
var DAEMON_ENTRY_GUARD_SOURCE = [
|
|
41
|
+
"const { existsSync } = require('node:fs')",
|
|
42
|
+
"if (process.argv[1] && !existsSync(process.argv[1])) process.exit(0)",
|
|
43
|
+
""
|
|
44
|
+
].join("\n");
|
|
45
|
+
var DAEMON_ENTRY_GUARD_FILE = "daemon-entry-guard.cjs";
|
|
46
|
+
var NODE_EXECUTABLE = /^node(?:\.exe)?$/i;
|
|
47
|
+
var ENTRY_GUARD_PRELOAD_FLAG = "--require";
|
|
48
|
+
var UNGUARDED = { kind: "unguarded" };
|
|
49
|
+
var daemonEntryGuardPath = (stateDir) => join(stateDir, DAEMON_ENTRY_GUARD_FILE);
|
|
50
|
+
var entryGuardApplies = (platform, execPath) => platform === "darwin" && NODE_EXECUTABLE.test(basename(execPath));
|
|
51
|
+
var holdsDaemonEntryGuard = (path) => {
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(path, "utf8") === DAEMON_ENTRY_GUARD_SOURCE;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var writeDaemonEntryGuardAt = (path) => {
|
|
59
|
+
if (holdsDaemonEntryGuard(path)) return { kind: "in-place" };
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
62
|
+
writeFileAtomic(path, DAEMON_ENTRY_GUARD_SOURCE, 384);
|
|
63
|
+
return { kind: "in-place" };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return { kind: "failed", detail: error instanceof Error ? error.message : String(error) };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var writeDaemonEntryGuard = (input) => {
|
|
69
|
+
if (!entryGuardApplies(input.platform, input.execPath)) return UNGUARDED;
|
|
70
|
+
const path = daemonEntryGuardPath(input.stateDir);
|
|
71
|
+
const written = writeDaemonEntryGuardAt(path);
|
|
72
|
+
if (written.kind === "in-place") return { kind: "guarded", path };
|
|
73
|
+
input.warn(`[pushary] could not write ${path}, so the background service was installed without its uninstall guard: ${written.detail}
|
|
74
|
+
`);
|
|
75
|
+
return UNGUARDED;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/diagnostics/service-runtime.ts
|
|
79
|
+
var unescapeXml = (value) => value.replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
80
|
+
var unquoteSystemdExecutable = (value) => value.replace(/\\(["\\])|%(%)/g, (_match, escaped, percent) => escaped ?? percent ?? "");
|
|
81
|
+
var unquoteSystemdArgument = (value) => value.replace(/\\(["\\])|%(%)|\$(\$)/g, (_match, escaped, percent, dollar) => escaped ?? percent ?? dollar ?? "");
|
|
82
|
+
var readServiceExecPath = (contents, platform) => {
|
|
83
|
+
if (platform === "darwin") {
|
|
84
|
+
const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
|
|
85
|
+
return program ? unescapeXml(program) : null;
|
|
86
|
+
}
|
|
87
|
+
if (platform === "linux") {
|
|
88
|
+
const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
|
|
89
|
+
return exec ? unquoteSystemdExecutable(exec) : null;
|
|
90
|
+
}
|
|
91
|
+
if (platform === "win32") {
|
|
92
|
+
const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
|
|
93
|
+
return command ? unescapeXml(command) : null;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
};
|
|
97
|
+
var readLaunchdProgramArguments = (contents) => {
|
|
98
|
+
const array = contents.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
|
|
99
|
+
return [...array.matchAll(/<string>([^<]*)<\/string>/g)].map((match) => unescapeXml(match[1] ?? ""));
|
|
100
|
+
};
|
|
101
|
+
var systemdExecStartArguments = (contents) => {
|
|
102
|
+
const line = contents.match(/^ExecStart=(.*)$/m)?.[1] ?? "";
|
|
103
|
+
return [...line.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match, index) => (index === 0 ? unquoteSystemdExecutable : unquoteSystemdArgument)(match[1] ?? ""));
|
|
104
|
+
};
|
|
105
|
+
var windowsTaskArguments = (contents) => {
|
|
106
|
+
const args = unescapeXml(contents.match(/<Arguments>([^<]*)<\/Arguments>/)?.[1] ?? "");
|
|
107
|
+
return [...args.matchAll(/"([^"]*)"/g)].map((match) => match[1] ?? "");
|
|
108
|
+
};
|
|
109
|
+
var serviceArguments = (contents, platform) => {
|
|
110
|
+
if (platform === "darwin") return readLaunchdProgramArguments(contents);
|
|
111
|
+
if (platform === "linux") return systemdExecStartArguments(contents);
|
|
112
|
+
if (platform === "win32") return windowsTaskArguments(contents);
|
|
113
|
+
return [];
|
|
114
|
+
};
|
|
115
|
+
var readServiceDaemonEntry = (contents, platform) => serviceArguments(contents, platform).at(-1) ?? null;
|
|
116
|
+
var readServiceEntryGuard = (contents, platform) => {
|
|
117
|
+
if (platform !== "darwin") return null;
|
|
118
|
+
const [, flag, guard] = readLaunchdProgramArguments(contents);
|
|
119
|
+
return flag === ENTRY_GUARD_PRELOAD_FLAG && guard !== void 0 ? guard : null;
|
|
120
|
+
};
|
|
121
|
+
var serviceStartFileChecks = (contents, platform, exists) => {
|
|
122
|
+
const checks = [];
|
|
123
|
+
const entry = readServiceDaemonEntry(contents, platform);
|
|
124
|
+
if (entry !== null) {
|
|
125
|
+
const present = exists(entry);
|
|
126
|
+
checks.push({
|
|
127
|
+
label: "Remote-control service entry",
|
|
128
|
+
passed: present,
|
|
129
|
+
detail: present ? entry : `${entry} is gone, so the background service starts nothing at login; run \`pushary daemon install\` from the Pushary install you use`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const guard = readServiceEntryGuard(contents, platform);
|
|
133
|
+
if (guard !== null && !exists(guard)) {
|
|
134
|
+
checks.push({
|
|
135
|
+
label: "Remote-control service start guard",
|
|
136
|
+
passed: false,
|
|
137
|
+
detail: `${guard} is gone, so the background service cannot start; run \`pushary daemon install\` to restore it`
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return checks;
|
|
141
|
+
};
|
|
142
|
+
var MANAGER_SEGMENTS = [
|
|
143
|
+
[".nvm", "nvm"],
|
|
144
|
+
["nvm", "nvm"],
|
|
145
|
+
["fnm", "fnm"],
|
|
146
|
+
[".fnm", "fnm"],
|
|
147
|
+
[".volta", "volta"],
|
|
148
|
+
["volta", "volta"],
|
|
149
|
+
[".asdf", "asdf"],
|
|
150
|
+
["asdf", "asdf"],
|
|
151
|
+
["Cellar", "homebrew"]
|
|
152
|
+
];
|
|
153
|
+
var versionManagerOf = (execPath) => {
|
|
154
|
+
const segments = execPath.split(/[/\\]/);
|
|
155
|
+
for (const segment of segments) {
|
|
156
|
+
const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
|
|
157
|
+
if (hit) return hit[1];
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching or upgrading Node strands it; run pushary daemon install afterwards.`;
|
|
162
|
+
var describeServiceNodeRisk = (manager) => manager === "homebrew" ? "a Homebrew version folder; brew upgrade strands the service, and `pushary daemon install` repins it to the stable link" : `managed by ${manager}; a version switch strands the service`;
|
|
163
|
+
|
|
37
164
|
// src/daemon-lifecycle.ts
|
|
38
|
-
import { dirname, join, win32 as win32Path } from "path";
|
|
39
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
165
|
+
import { dirname as dirname2, join as join2, win32 as win32Path } from "path";
|
|
166
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
40
167
|
import { createHash, randomUUID } from "crypto";
|
|
41
168
|
import { homedir, uptime, userInfo } from "os";
|
|
42
169
|
import { execFileSync } from "child_process";
|
|
@@ -55,7 +182,7 @@ var remoteProofIsFresh = (timestamp, now) => {
|
|
|
55
182
|
};
|
|
56
183
|
var daemonHasRemoteProof = (status, now = Date.now()) => status.kind === "running" && remoteProofIsFresh(status.lastSuccessfulHeartbeatAt, now) && remoteProofIsFresh(status.lastSuccessfulControlAt, now);
|
|
57
184
|
var readServiceDefinitionFile = (path) => {
|
|
58
|
-
const bytes =
|
|
185
|
+
const bytes = readFileSync2(path);
|
|
59
186
|
const encoding = bytes.length >= 2 && bytes[0] === 255 && bytes[1] === 254 ? "utf16le" : "utf-8";
|
|
60
187
|
return { contents: bytes.toString(encoding), encoding };
|
|
61
188
|
};
|
|
@@ -105,9 +232,9 @@ var daemonServiceManagerAvailable = (options = {}) => {
|
|
|
105
232
|
}
|
|
106
233
|
};
|
|
107
234
|
var DAEMON_STATE_FILE = "daemon-state.json";
|
|
108
|
-
var statePath = (dir) =>
|
|
109
|
-
var lockPath = (dir) =>
|
|
110
|
-
var targetPath = (dir) =>
|
|
235
|
+
var statePath = (dir) => join2(dir, DAEMON_STATE_FILE);
|
|
236
|
+
var lockPath = (dir) => join2(dir, "daemon.lock");
|
|
237
|
+
var targetPath = (dir) => join2(dir, "daemon-target.json");
|
|
111
238
|
var DAEMON_LEASE_RENEW_INTERVAL_MS = 3e4;
|
|
112
239
|
var LOCAL_LEASE_TIMEOUT_MS = DAEMON_LEASE_RENEW_INTERVAL_MS * 3;
|
|
113
240
|
var DAEMON_YIELD_CHECK_INTERVAL_MS = 2e3;
|
|
@@ -117,28 +244,38 @@ var buildDaemonLaunch = (input) => input.siblingEntry ? { command: input.execPat
|
|
|
117
244
|
shell: needsShell(input.daemonBinary, input.platform ?? process.platform)
|
|
118
245
|
};
|
|
119
246
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
120
|
-
var
|
|
247
|
+
var systemdQuotedEnvironment = (value) => `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
248
|
+
var escapeSystemdSpecifiers = (value) => value.replaceAll("%", "%%");
|
|
249
|
+
var systemdQuotedWord = (escaped) => `"${escaped.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
250
|
+
var systemdExecutableWord = (value) => systemdQuotedWord(escapeSystemdSpecifiers(value));
|
|
251
|
+
var systemdArgumentWord = (value) => systemdQuotedWord(escapeSystemdSpecifiers(value).replaceAll("$", "$$$$"));
|
|
252
|
+
var systemdExecLine = (executable, args) => [systemdExecutableWord(executable), ...args.map(systemdArgumentWord)].join(" ");
|
|
253
|
+
var ENTRY_PRESENT_CHECK = "process.exit(require('node:fs').existsSync(process.argv[1]) ? 0 : 1)";
|
|
254
|
+
var guardedLaunchdProgram = (input) => {
|
|
255
|
+
const guard = input.entryGuard ?? UNGUARDED;
|
|
256
|
+
return guard.kind === "guarded" ? [input.execPath, ENTRY_GUARD_PRELOAD_FLAG, guard.path, input.daemonEntry] : [input.execPath, input.daemonEntry];
|
|
257
|
+
};
|
|
121
258
|
var buildDaemonService = (input) => {
|
|
122
259
|
if (input.platform === "darwin") {
|
|
123
260
|
if (!Number.isInteger(input.uid) || input.uid < 0) return null;
|
|
124
261
|
const label = "com.pushary.daemon";
|
|
125
262
|
const domain = `gui/${input.uid}`;
|
|
126
263
|
const target = `${domain}/${label}`;
|
|
127
|
-
const definitionPath =
|
|
264
|
+
const definitionPath = join2(input.homeDir, "Library", "LaunchAgents", `${label}.plist`);
|
|
128
265
|
const contents = `<?xml version="1.0" encoding="UTF-8"?>
|
|
129
266
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
130
267
|
<plist version="1.0">
|
|
131
268
|
<dict>
|
|
132
269
|
<key>Label</key><string>${label}</string>
|
|
133
270
|
<key>ProgramArguments</key>
|
|
134
|
-
<array
|
|
271
|
+
<array>${guardedLaunchdProgram(input).map((argument) => `<string>${xml(argument)}</string>`).join("")}</array>
|
|
135
272
|
<key>WorkingDirectory</key><string>${xml(input.homeDir)}</string>
|
|
136
273
|
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(input.pathEnv)}</string></dict>
|
|
137
274
|
<key>RunAtLoad</key><true/>
|
|
138
275
|
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
|
139
276
|
<key>ThrottleInterval</key><integer>10</integer>
|
|
140
|
-
<key>StandardOutPath</key><string>${xml(
|
|
141
|
-
<key>StandardErrorPath</key><string>${xml(
|
|
277
|
+
<key>StandardOutPath</key><string>${xml(join2(input.stateDir, "daemon.log"))}</string>
|
|
278
|
+
<key>StandardErrorPath</key><string>${xml(join2(input.stateDir, "daemon-error.log"))}</string>
|
|
142
279
|
</dict>
|
|
143
280
|
</plist>
|
|
144
281
|
`;
|
|
@@ -157,6 +294,8 @@ var buildDaemonService = (input) => {
|
|
|
157
294
|
disable: [{ command: "launchctl", args: ["disable", target] }],
|
|
158
295
|
canDisableWhenUnregistered: true,
|
|
159
296
|
uninstall: [{ command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true }],
|
|
297
|
+
platform: input.platform,
|
|
298
|
+
startGuardPath: daemonEntryGuardPath(input.stateDir),
|
|
160
299
|
registrationProbe: {
|
|
161
300
|
command: "launchctl",
|
|
162
301
|
args: ["print", target],
|
|
@@ -177,15 +316,17 @@ var buildDaemonService = (input) => {
|
|
|
177
316
|
}
|
|
178
317
|
if (input.platform === "linux") {
|
|
179
318
|
const unit = "pushary-daemon.service";
|
|
180
|
-
const definitionPath =
|
|
319
|
+
const definitionPath = join2(input.homeDir, ".config", "systemd", "user", unit);
|
|
181
320
|
const contents = `[Unit]
|
|
182
321
|
Description=Pushary phone-to-agent daemon
|
|
183
322
|
After=network-online.target
|
|
323
|
+
ConditionPathExists=${escapeSystemdSpecifiers(input.daemonEntry)}
|
|
184
324
|
|
|
185
325
|
[Service]
|
|
186
326
|
Type=simple
|
|
187
|
-
|
|
188
|
-
|
|
327
|
+
ExecCondition=${systemdExecLine(input.execPath, ["-e", ENTRY_PRESENT_CHECK, input.daemonEntry])}
|
|
328
|
+
ExecStart=${systemdExecLine(input.execPath, [input.daemonEntry])}
|
|
329
|
+
Environment=${systemdQuotedEnvironment(`PATH=${input.pathEnv}`)}
|
|
189
330
|
Restart=on-failure
|
|
190
331
|
RestartSec=10
|
|
191
332
|
KillMode=process
|
|
@@ -216,7 +357,9 @@ WantedBy=default.target
|
|
|
216
357
|
args: ["--user", "is-active", unit],
|
|
217
358
|
negativeOutputPattern: /^(?:inactive|failed|unknown|activating|deactivating)\s*$/m
|
|
218
359
|
},
|
|
219
|
-
reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] }
|
|
360
|
+
reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] },
|
|
361
|
+
platform: input.platform,
|
|
362
|
+
startGuardPath: null
|
|
220
363
|
};
|
|
221
364
|
}
|
|
222
365
|
if (input.platform === "win32") {
|
|
@@ -262,6 +405,8 @@ WantedBy=default.target
|
|
|
262
405
|
stop: [{ command: "schtasks.exe", args: ["/End", "/TN", taskName], ignoreFailure: true }],
|
|
263
406
|
disable: [{ command: "schtasks.exe", args: ["/Change", "/TN", taskName, "/Disable"] }],
|
|
264
407
|
uninstall: [{ command: "schtasks.exe", args: ["/Delete", "/TN", taskName, "/F"], ignoreFailure: true }],
|
|
408
|
+
platform: input.platform,
|
|
409
|
+
startGuardPath: null,
|
|
265
410
|
registrationProbe: {
|
|
266
411
|
command: "powershell.exe",
|
|
267
412
|
args: [
|
|
@@ -297,7 +442,7 @@ WantedBy=default.target
|
|
|
297
442
|
return null;
|
|
298
443
|
};
|
|
299
444
|
var WINDOWS_DAEMON_ENTRY_SEGMENTS = ["dist", "bin", "pushary-daemon.js"];
|
|
300
|
-
var readTextFile = (path) =>
|
|
445
|
+
var readTextFile = (path) => readFileSync2(path, "utf8");
|
|
301
446
|
var windowsPackageDaemonEntries = (globalPrefix) => {
|
|
302
447
|
const root = globalRootFor(globalPrefix, "win32");
|
|
303
448
|
return CLI_PACKAGE_NAMES.map((name) => win32Path.join(globalPackageFolder(root, name, "win32"), ...WINDOWS_DAEMON_ENTRY_SEGMENTS));
|
|
@@ -316,7 +461,8 @@ var serviceNodeFor = (execPath, exists) => {
|
|
|
316
461
|
const linked = homebrewLinkedNodeFor(execPath);
|
|
317
462
|
return linked !== null && exists(linked) ? linked : execPath;
|
|
318
463
|
};
|
|
319
|
-
var
|
|
464
|
+
var serviceExecPathFor = (options) => serviceNodeFor(options.execPath ?? process.execPath, options.entryExists ?? existsSync);
|
|
465
|
+
var resolveDaemonServiceInput = (options) => {
|
|
320
466
|
const platform = options.platform ?? process.platform;
|
|
321
467
|
const exists = options.entryExists ?? existsSync;
|
|
322
468
|
const globalBinary = options.globalBinary ?? resolveGlobalBinary("pushary-daemon");
|
|
@@ -333,18 +479,22 @@ var resolveDaemonServiceDefinition = (options = {}) => {
|
|
|
333
479
|
}
|
|
334
480
|
}
|
|
335
481
|
if (!daemonEntry || !exists(daemonEntry)) return null;
|
|
336
|
-
|
|
337
|
-
return buildDaemonService({
|
|
482
|
+
return {
|
|
338
483
|
platform,
|
|
339
|
-
homeDir,
|
|
484
|
+
homeDir: options.homeDir ?? homedir(),
|
|
340
485
|
stateDir: options.stateDir ?? pusharyDir(),
|
|
341
|
-
execPath:
|
|
486
|
+
execPath: serviceExecPathFor(options),
|
|
342
487
|
daemonEntry,
|
|
343
488
|
pathEnv: options.pathEnv ?? process.env.PATH ?? "",
|
|
344
489
|
uid: options.uid ?? process.getuid?.(),
|
|
345
490
|
entryExists: exists
|
|
346
|
-
}
|
|
491
|
+
};
|
|
347
492
|
};
|
|
493
|
+
var resolveDaemonServiceDefinition = (options = {}) => {
|
|
494
|
+
const input = resolveDaemonServiceInput(options);
|
|
495
|
+
return input ? buildDaemonService(input) : null;
|
|
496
|
+
};
|
|
497
|
+
var daemonEntryGuardPathFor = (options = {}) => entryGuardApplies(options.platform ?? process.platform, serviceExecPathFor(options)) ? daemonEntryGuardPath(options.stateDir ?? pusharyDir()) : null;
|
|
348
498
|
var runDaemonServiceSteps = (steps, run) => {
|
|
349
499
|
for (const step of steps) {
|
|
350
500
|
try {
|
|
@@ -409,7 +559,17 @@ var manageDaemonService = (action, options = {}) => {
|
|
|
409
559
|
if (action === "uninstall" && !daemonServiceManagerAvailable(options)) {
|
|
410
560
|
return { kind: "failed", detail: "Native background-service manager is unavailable" };
|
|
411
561
|
}
|
|
412
|
-
|
|
562
|
+
const applied = applyDaemonService(action, definition, { run: options.run });
|
|
563
|
+
return action === "uninstall" && applied.kind === "ok" ? removeEntryGuardOf(definition) : applied;
|
|
564
|
+
};
|
|
565
|
+
var removeEntryGuardOf = (definition) => {
|
|
566
|
+
if (!definition.startGuardPath) return { kind: "ok" };
|
|
567
|
+
try {
|
|
568
|
+
safeUnlink(definition.startGuardPath);
|
|
569
|
+
return { kind: "ok" };
|
|
570
|
+
} catch (err) {
|
|
571
|
+
return { kind: "failed", detail: `the background service is removed, but its start guard ${definition.startGuardPath} could not be: ${err instanceof Error ? err.message : String(err)}` };
|
|
572
|
+
}
|
|
413
573
|
};
|
|
414
574
|
var nominalDaemonService = (options) => buildDaemonService({
|
|
415
575
|
platform: options.platform ?? process.platform,
|
|
@@ -431,7 +591,7 @@ var readInstalledDaemonService = (options = {}) => {
|
|
|
431
591
|
try {
|
|
432
592
|
return {
|
|
433
593
|
path: definition.definitionPath,
|
|
434
|
-
contents:
|
|
594
|
+
contents: readFileSync2(definition.definitionPath, definition.encoding ?? "utf-8")
|
|
435
595
|
};
|
|
436
596
|
} catch {
|
|
437
597
|
return null;
|
|
@@ -595,7 +755,7 @@ var acquireDaemonLease = (options = {}) => {
|
|
|
595
755
|
const instanceId = randomUUID();
|
|
596
756
|
const startedAt = new Date(now()).toISOString();
|
|
597
757
|
const writeLock = () => {
|
|
598
|
-
|
|
758
|
+
mkdirSync2(dir, { recursive: true });
|
|
599
759
|
writeFileSync(lockPath(dir), JSON.stringify({ instanceId, pid }), { flag: "wx", mode: 384 });
|
|
600
760
|
};
|
|
601
761
|
try {
|
|
@@ -619,7 +779,7 @@ var acquireDaemonLease = (options = {}) => {
|
|
|
619
779
|
if (owner && ownerIsFromThisBoot && (options.isProcessAlive ?? processIsAlive)(owner.pid)) {
|
|
620
780
|
return { kind: "unavailable", detail: "daemon owner is still running or starting" };
|
|
621
781
|
}
|
|
622
|
-
const stale =
|
|
782
|
+
const stale = join2(dir, `daemon.lock.${instanceId}.stale`);
|
|
623
783
|
let ownsReplacement = false;
|
|
624
784
|
try {
|
|
625
785
|
renameSync(lockPath(dir), stale);
|
|
@@ -763,8 +923,16 @@ var stopDaemonRunning = async (options = {}) => {
|
|
|
763
923
|
safeUnlink(lockPath(dir));
|
|
764
924
|
return { kind: "stopped" };
|
|
765
925
|
};
|
|
926
|
+
var writeEntryGuardForService = (input, options) => writeDaemonEntryGuard({
|
|
927
|
+
stateDir: input.stateDir,
|
|
928
|
+
platform: input.platform,
|
|
929
|
+
execPath: input.execPath,
|
|
930
|
+
warn: options.warn ?? ((message) => {
|
|
931
|
+
process.stderr.write(message);
|
|
932
|
+
})
|
|
933
|
+
});
|
|
766
934
|
var DAEMON_LOGIN_REASONS = ["key_rejected", "plan_inactive", "key_missing", "mac_signed_out"];
|
|
767
|
-
var needsLoginPath = (dir) =>
|
|
935
|
+
var needsLoginPath = (dir) => join2(dir, "daemon-needs-login.json");
|
|
768
936
|
var isLoginReason = (value) => DAEMON_LOGIN_REASONS.includes(value);
|
|
769
937
|
var parseDaemonNeedsLogin = (value) => {
|
|
770
938
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -804,7 +972,7 @@ var DAEMON_LOGIN_BLOCK_DESCRIPTIONS = {
|
|
|
804
972
|
var describeDaemonLoginBlock = (record) => DAEMON_LOGIN_BLOCK_DESCRIPTIONS[record.reason](record.at);
|
|
805
973
|
var recoverDaemonAfterLogin = (options = {}) => {
|
|
806
974
|
const cleared = clearDaemonNeedsLogin(options);
|
|
807
|
-
if (!cleared || readControlMode(
|
|
975
|
+
if (!cleared || readControlMode(join2(options.dir ?? pusharyDir(), "config.json")) === "off" || !hasDaemonServiceDefinition(options)) {
|
|
808
976
|
return { cleared, restarted: false, detail: null };
|
|
809
977
|
}
|
|
810
978
|
const started = manageDaemonService("start", options);
|
|
@@ -878,12 +1046,26 @@ var probeDaemonServiceState = (definition, run) => {
|
|
|
878
1046
|
}
|
|
879
1047
|
};
|
|
880
1048
|
var previousEncoding = (snapshot) => snapshot.previousEncoding ?? "utf-8";
|
|
1049
|
+
var launchdEntryGuardArguments = (guard) => `<string>${xml(ENTRY_GUARD_PRELOAD_FLAG)}</string><string>${xml(guard)}</string>`;
|
|
1050
|
+
var restorableContents = (definition, contents) => {
|
|
1051
|
+
const guard = definition.startGuardPath;
|
|
1052
|
+
if (!guard || contents === null || readServiceEntryGuard(contents, definition.platform) !== guard) {
|
|
1053
|
+
return { kind: "ready", contents };
|
|
1054
|
+
}
|
|
1055
|
+
const written = writeDaemonEntryGuardAt(guard);
|
|
1056
|
+
if (written.kind === "in-place") return { kind: "ready", contents };
|
|
1057
|
+
const unguarded = contents.replace(launchdEntryGuardArguments(guard), "");
|
|
1058
|
+
return readServiceEntryGuard(unguarded, definition.platform) === null ? { kind: "ready", contents: unguarded } : { kind: "failed", detail: `could not write the start guard ${guard} the previous service names (${written.detail}), or restore that service without it` };
|
|
1059
|
+
};
|
|
881
1060
|
var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
|
|
1061
|
+
const restorable = restorableContents(definition, snapshot.previousContents);
|
|
1062
|
+
if (restorable.kind !== "ready") return restorable;
|
|
1063
|
+
const previousContents = restorable.contents;
|
|
882
1064
|
const removed = applyDaemonService("uninstall", definition, { run });
|
|
883
1065
|
if (removed.kind !== "ok") return removed;
|
|
884
1066
|
try {
|
|
885
|
-
if (
|
|
886
|
-
else writeFileAtomic(definition.definitionPath,
|
|
1067
|
+
if (previousContents === null) safeUnlink(definition.definitionPath);
|
|
1068
|
+
else writeFileAtomic(definition.definitionPath, previousContents, 384, previousEncoding(snapshot));
|
|
887
1069
|
} catch (err) {
|
|
888
1070
|
return {
|
|
889
1071
|
kind: "failed",
|
|
@@ -892,7 +1074,7 @@ var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
|
|
|
892
1074
|
}
|
|
893
1075
|
if (!snapshot.previousRegistered) {
|
|
894
1076
|
const reload = definition.reloadAfterUninstall;
|
|
895
|
-
if (
|
|
1077
|
+
if (previousContents !== null && reload) {
|
|
896
1078
|
try {
|
|
897
1079
|
run(reload.command, reload.args);
|
|
898
1080
|
} catch (err) {
|
|
@@ -917,12 +1099,12 @@ var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
|
|
|
917
1099
|
}
|
|
918
1100
|
return { kind: "ok" };
|
|
919
1101
|
}
|
|
920
|
-
if (
|
|
1102
|
+
if (previousContents === null) {
|
|
921
1103
|
return { kind: "failed", detail: "the previous registered service had no recoverable definition" };
|
|
922
1104
|
}
|
|
923
1105
|
const restored = applyDaemonService("install", {
|
|
924
1106
|
...definition,
|
|
925
|
-
contents:
|
|
1107
|
+
contents: previousContents,
|
|
926
1108
|
encoding: previousEncoding(snapshot)
|
|
927
1109
|
}, { run });
|
|
928
1110
|
if (restored.kind !== "ok") return restored;
|
|
@@ -971,19 +1153,22 @@ var recoverDaemonServiceTransaction = (definition, path, run) => {
|
|
|
971
1153
|
return { kind: "ok" };
|
|
972
1154
|
};
|
|
973
1155
|
var replaceDaemonService = async (options = {}) => {
|
|
974
|
-
const
|
|
975
|
-
|
|
1156
|
+
const input = resolveDaemonServiceInput(options);
|
|
1157
|
+
const unguarded = input ? buildDaemonService(input) : null;
|
|
1158
|
+
if (!input || !unguarded) {
|
|
976
1159
|
return {
|
|
977
1160
|
kind: "unavailable",
|
|
978
1161
|
detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
|
|
979
1162
|
};
|
|
980
1163
|
}
|
|
981
1164
|
const run = options.run ?? runDaemonServiceCommand;
|
|
982
|
-
const transactionPath =
|
|
983
|
-
const recovered = recoverDaemonServiceTransaction(
|
|
1165
|
+
const transactionPath = join2(options.stateDir ?? options.dir ?? pusharyDir(), "daemon-service-transaction.json");
|
|
1166
|
+
const recovered = recoverDaemonServiceTransaction(unguarded, transactionPath, run);
|
|
984
1167
|
if (recovered.kind !== "ok") {
|
|
985
1168
|
return { kind: "failed", detail: `Could not recover interrupted service replacement: ${recovered.detail}` };
|
|
986
1169
|
}
|
|
1170
|
+
const definition = buildDaemonService({ ...input, entryGuard: writeEntryGuardForService(input, options) });
|
|
1171
|
+
if (!definition) return { kind: "unavailable", detail: "Could not build the daemon service definition for this platform." };
|
|
987
1172
|
let previousContents = null;
|
|
988
1173
|
let previousDefinitionEncoding = "utf-8";
|
|
989
1174
|
try {
|
|
@@ -1088,7 +1273,7 @@ var replaceDaemonService = async (options = {}) => {
|
|
|
1088
1273
|
var launchInstalledDaemon = (options) => {
|
|
1089
1274
|
const entryExists = options.entryExists ?? existsSync;
|
|
1090
1275
|
const argvEntry = process.argv[1];
|
|
1091
|
-
const candidate = options.daemonEntry !== void 0 ? options.daemonEntry : argvEntry ?
|
|
1276
|
+
const candidate = options.daemonEntry !== void 0 ? options.daemonEntry : argvEntry ? join2(dirname2(argvEntry), "pushary-daemon.js") : null;
|
|
1092
1277
|
const siblingEntry = candidate && entryExists(candidate) ? candidate : null;
|
|
1093
1278
|
const platform = options.platform ?? process.platform;
|
|
1094
1279
|
const daemonBinary = options.daemonBinary ?? resolveGlobalBinary("pushary-daemon") ?? (platform === "win32" ? "pushary-daemon.cmd" : "pushary-daemon");
|
|
@@ -1163,6 +1348,11 @@ var waitForDaemonRemoteProof = async (options = {}) => {
|
|
|
1163
1348
|
};
|
|
1164
1349
|
|
|
1165
1350
|
export {
|
|
1351
|
+
readServiceExecPath,
|
|
1352
|
+
serviceStartFileChecks,
|
|
1353
|
+
versionManagerOf,
|
|
1354
|
+
describeVersionManagerPin,
|
|
1355
|
+
describeServiceNodeRisk,
|
|
1166
1356
|
daemonIsServing,
|
|
1167
1357
|
daemonProcessIsThere,
|
|
1168
1358
|
DAEMON_COLD_START_TIMEOUT_MS,
|
|
@@ -1172,6 +1362,7 @@ export {
|
|
|
1172
1362
|
DAEMON_YIELD_CHECK_INTERVAL_MS,
|
|
1173
1363
|
xml,
|
|
1174
1364
|
serviceNodeFor,
|
|
1365
|
+
daemonEntryGuardPathFor,
|
|
1175
1366
|
parseDaemonAction,
|
|
1176
1367
|
manageDaemonService,
|
|
1177
1368
|
hasDaemonServiceDefinition,
|
|
@@ -61,7 +61,8 @@ var describeService = (effects) => {
|
|
|
61
61
|
if (effects.controlMode === "off") return "skip the phone-start background service (--control off)";
|
|
62
62
|
const where = effects.serviceDefinitionPath ?? "the phone-start service definition";
|
|
63
63
|
const condition = effects.controlMode === "on" ? "" : ", when a supported agent and a service manager are present";
|
|
64
|
-
|
|
64
|
+
const guard = effects.serviceStartGuardPath ? ` and its start guard ${effects.serviceStartGuardPath}` : "";
|
|
65
|
+
return `write ${where}${guard} and register it with the OS${condition}`;
|
|
65
66
|
};
|
|
66
67
|
var describeSetupSideEffects = (effects) => [
|
|
67
68
|
`run npm install -g ${effects.globalPackage}@${effects.version} (the hooks run from the global copy; up to two minutes)`,
|
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
turnCodexAppCardsOff,
|
|
23
23
|
turnCodexAppCardsOn,
|
|
24
24
|
whyLaunchctlFailed
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-7NCI2DGB.js";
|
|
26
|
+
import "./chunk-7VKW7OA5.js";
|
|
27
27
|
import "./chunk-XCEN33UP.js";
|
|
28
28
|
import "./chunk-QCGNGEGE.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-MD63J3T7.js";
|
|
30
30
|
import "./chunk-H6CL3T2S.js";
|
|
31
31
|
import "./chunk-WNT4JHK3.js";
|
|
32
32
|
import "./chunk-PNVTPEO5.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-R6N6FREL.js";
|
|
4
4
|
import "../chunk-UP5QXEUU.js";
|
|
5
5
|
import "../chunk-G73ACPR2.js";
|
|
6
6
|
import "../chunk-2R4OVJHS.js";
|
|
7
7
|
import "../chunk-HY4ATVCT.js";
|
|
8
8
|
import "../chunk-YQQGIDBC.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-7XD56GXA.js";
|
|
10
10
|
import "../chunk-NN43KGTN.js";
|
|
11
11
|
import "../chunk-H2RE3IPK.js";
|
|
12
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-6WT5OIRM.js";
|
|
13
13
|
import "../chunk-VAE5JBR5.js";
|
|
14
14
|
import "../chunk-G66WIAST.js";
|
|
15
15
|
import "../chunk-43DT3QWZ.js";
|
|
16
|
-
import "../chunk-
|
|
16
|
+
import "../chunk-MD63J3T7.js";
|
|
17
17
|
import "../chunk-TLK5YLIE.js";
|
|
18
18
|
import {
|
|
19
19
|
fetchModeOverride,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushary",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.137",
|
|
4
4
|
"description": "Human-in-the-loop (HITL) for AI agents: ask a person for approvals or answers by push notification. One command connects Claude Code, Codex, Cursor, Gemini CLI and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
package/dist/chunk-GACQSKNG.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
configFilePath
|
|
3
|
-
} from "./chunk-IKBTG3OT.js";
|
|
4
|
-
import {
|
|
5
|
-
readJsonSafe,
|
|
6
|
-
writeJsonAtomic
|
|
7
|
-
} from "./chunk-TODDEIYG.js";
|
|
8
|
-
import {
|
|
9
|
-
readSetupOutcomeProperties
|
|
10
|
-
} from "./chunk-DL67HX3D.js";
|
|
11
|
-
|
|
12
|
-
// src/diagnostics/service-runtime.ts
|
|
13
|
-
var unescapeXml = (value) => value.replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
14
|
-
var unquoteSystemd = (value) => value.replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("%%", "%");
|
|
15
|
-
var readServiceExecPath = (contents, platform) => {
|
|
16
|
-
if (platform === "darwin") {
|
|
17
|
-
const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
|
|
18
|
-
return program ? unescapeXml(program) : null;
|
|
19
|
-
}
|
|
20
|
-
if (platform === "linux") {
|
|
21
|
-
const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
|
|
22
|
-
return exec ? unquoteSystemd(exec) : null;
|
|
23
|
-
}
|
|
24
|
-
if (platform === "win32") {
|
|
25
|
-
const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
|
|
26
|
-
return command ? unescapeXml(command) : null;
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
};
|
|
30
|
-
var MANAGER_SEGMENTS = [
|
|
31
|
-
[".nvm", "nvm"],
|
|
32
|
-
["nvm", "nvm"],
|
|
33
|
-
["fnm", "fnm"],
|
|
34
|
-
[".fnm", "fnm"],
|
|
35
|
-
[".volta", "volta"],
|
|
36
|
-
["volta", "volta"],
|
|
37
|
-
[".asdf", "asdf"],
|
|
38
|
-
["asdf", "asdf"],
|
|
39
|
-
["Cellar", "homebrew"]
|
|
40
|
-
];
|
|
41
|
-
var versionManagerOf = (execPath) => {
|
|
42
|
-
const segments = execPath.split(/[/\\]/);
|
|
43
|
-
for (const segment of segments) {
|
|
44
|
-
const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
|
|
45
|
-
if (hit) return hit[1];
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
};
|
|
49
|
-
var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching or upgrading Node strands it; run pushary daemon install afterwards.`;
|
|
50
|
-
var describeServiceNodeRisk = (manager) => manager === "homebrew" ? "a Homebrew version folder; brew upgrade strands the service, and `pushary daemon install` repins it to the stable link" : `managed by ${manager}; a version switch strands the service`;
|
|
51
|
-
|
|
52
|
-
// src/setup/failure-record.ts
|
|
53
|
-
import { dirname, join } from "path";
|
|
54
|
-
var failurePath = () => join(dirname(configFilePath()), "last-setup-failure.json");
|
|
55
|
-
var saveSetupFailure = (body, path = failurePath()) => {
|
|
56
|
-
const properties = readSetupOutcomeProperties(body);
|
|
57
|
-
if (properties.exitCode === null || properties.exitCode === 0) return;
|
|
58
|
-
writeJsonAtomic(path, { ...properties, recordedAt: (/* @__PURE__ */ new Date()).toISOString() }, 384);
|
|
59
|
-
};
|
|
60
|
-
var readSetupFailure = (path = failurePath()) => {
|
|
61
|
-
const result = readJsonSafe(path);
|
|
62
|
-
if (result.kind !== "ok" || typeof result.value !== "object" || result.value === null || Array.isArray(result.value)) return null;
|
|
63
|
-
const body = result.value;
|
|
64
|
-
if (!("recordedAt" in body) || typeof body.recordedAt !== "string" || body.recordedAt.length > 30 || !Number.isFinite(Date.parse(body.recordedAt))) return null;
|
|
65
|
-
return { ...readSetupOutcomeProperties({ ...body }), recordedAt: body.recordedAt };
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
saveSetupFailure,
|
|
70
|
-
readSetupFailure,
|
|
71
|
-
readServiceExecPath,
|
|
72
|
-
versionManagerOf,
|
|
73
|
-
describeVersionManagerPin,
|
|
74
|
-
describeServiceNodeRisk
|
|
75
|
-
};
|