@pushary/agent-hooks 0.89.3 → 0.89.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +40 -0
  2. package/dist/bin/pushary-bell-hook.js +3 -3
  3. package/dist/bin/pushary-bell.js +9 -10
  4. package/dist/bin/pushary-claude.js +62 -26
  5. package/dist/bin/pushary-clean.js +28 -7
  6. package/dist/bin/pushary-codex-bridge.js +4 -5
  7. package/dist/bin/pushary-codex-hook.js +8 -7
  8. package/dist/bin/pushary-codex.js +6 -4
  9. package/dist/bin/pushary-connect.js +4 -4
  10. package/dist/bin/pushary-daemon.js +141 -21
  11. package/dist/bin/pushary-disconnect.js +3 -4
  12. package/dist/bin/pushary-doctor.js +31 -20
  13. package/dist/bin/pushary-elicitation-hook.js +4 -2
  14. package/dist/bin/pushary-gemini-bridge.js +3 -4
  15. package/dist/bin/pushary-gemini-hook.js +6 -4
  16. package/dist/bin/pushary-hook.js +9 -7
  17. package/dist/bin/pushary-login.js +4 -4
  18. package/dist/bin/pushary-logout.js +5 -6
  19. package/dist/bin/pushary-mode.js +2 -2
  20. package/dist/bin/pushary-notification-hook.js +6 -4
  21. package/dist/bin/pushary-opencode-hook.js +14 -6
  22. package/dist/bin/pushary-permission-denied-hook.js +7 -5
  23. package/dist/bin/pushary-permission-hook.js +7 -5
  24. package/dist/bin/pushary-post-hook.js +6 -4
  25. package/dist/bin/pushary-prompt-hook.js +6 -4
  26. package/dist/bin/pushary-session-end-hook.js +6 -4
  27. package/dist/bin/pushary-session-start-hook.js +6 -4
  28. package/dist/bin/pushary-setup.js +49 -28
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +7 -6
  31. package/dist/bin/pushary-stop-hook.js +6 -4
  32. package/dist/bin/pushary-stopfailure-hook.js +6 -4
  33. package/dist/bin/pushary-suggestions.js +2 -2
  34. package/dist/bin/pushary-upgrade.js +63 -28
  35. package/dist/bin/pushary-wait.js +2 -2
  36. package/dist/bin/pushary.js +1 -1
  37. package/dist/chunk-2HYPZLYY.js +45 -0
  38. package/dist/{chunk-ZYPS2XMT.js → chunk-4ZHQNBDP.js} +1 -1
  39. package/dist/{chunk-B7JE4XHD.js → chunk-7IJYOBHL.js} +1 -1
  40. package/dist/{chunk-NRGRUOAR.js → chunk-FWUEYXSD.js} +4 -0
  41. package/dist/{chunk-HUJAAWNL.js → chunk-GKAKAOKC.js} +10 -3
  42. package/dist/{chunk-GOEXZ5QJ.js → chunk-GPNOHKFO.js} +9 -2
  43. package/dist/{chunk-SQOFGPNX.js → chunk-KJ57OPCC.js} +1 -1
  44. package/dist/{chunk-LGXTBUT4.js → chunk-KOPUTJQC.js} +1 -0
  45. package/dist/{chunk-JT6TIBYH.js → chunk-MZBT2LXW.js} +4 -4
  46. package/dist/{chunk-3UBYTSCY.js → chunk-NFI2K2ZQ.js} +15 -7
  47. package/dist/{chunk-K42SZKYF.js → chunk-NLMSSKES.js} +251 -6
  48. package/dist/{chunk-3O2XPYLF.js → chunk-OBI4YMCT.js} +1 -1
  49. package/dist/{chunk-LUCFN47K.js → chunk-QH4AA7QV.js} +1 -1
  50. package/dist/{chunk-VKVHY5HD.js → chunk-V37ZXMH3.js} +11 -3
  51. package/dist/{chunk-MHBC2XUP.js → chunk-VP7SDXYG.js} +1 -1
  52. package/dist/src/index.js +7 -5
  53. package/package.json +1 -1
  54. package/dist/chunk-GIRT7677.js +0 -11
  55. package/dist/chunk-RN3NOEJF.js +0 -9
  56. package/dist/{chunk-LALHWDWL.js → chunk-W6NPWHBJ.js} +3 -3
@@ -1,4 +1,6 @@
1
1
  // src/live-capabilities.ts
2
+ var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
3
+ var CLAUDE_RESUME_CAPABILITY = "claude-resume";
2
4
  var CODEX_BRIDGE_CAPABILITY = "codex-bridge";
3
5
  var CODEX_RESUME_CAPABILITY = "codex-resume";
4
6
  var GEMINI_BRIDGE_CAPABILITY = "gemini-bridge";
@@ -19,6 +21,8 @@ var geminiLiveCapabilities = (rawVersion) => {
19
21
  };
20
22
 
21
23
  export {
24
+ CLAUDE_BRIDGE_CAPABILITY,
25
+ CLAUDE_RESUME_CAPABILITY,
22
26
  CODEX_BRIDGE_CAPABILITY,
23
27
  CODEX_RESUME_CAPABILITY,
24
28
  geminiLiveCapabilities
@@ -91,7 +91,11 @@ var readPendingCommand = (sessionId, namespace = "") => {
91
91
  unlinkSync(commandFile(sessionId, namespace));
92
92
  return null;
93
93
  }
94
- return { ...value, delivered: value.delivered === true };
94
+ return {
95
+ ...value,
96
+ delivered: value.delivered === true,
97
+ ...value.failed === true ? { failed: true } : {}
98
+ };
95
99
  } catch {
96
100
  return null;
97
101
  }
@@ -106,13 +110,13 @@ var savePendingCommand = (sessionId, command, namespace = "") => {
106
110
  return false;
107
111
  }
108
112
  };
109
- var markPendingCommandDelivered = (sessionId, id, namespace = "") => {
113
+ var updatePendingCommand = (sessionId, id, update, namespace = "") => {
110
114
  const command = readPendingCommand(sessionId, namespace);
111
115
  if (!command || command.id !== id) return false;
112
116
  const file = commandFile(sessionId, namespace);
113
117
  const temporary = `${file}.${process.pid}.tmp`;
114
118
  try {
115
- writeFileSync(temporary, JSON.stringify({ ...command, delivered: true }), { encoding: "utf-8", mode: 384 });
119
+ writeFileSync(temporary, JSON.stringify({ ...command, ...update }), { encoding: "utf-8", mode: 384 });
116
120
  renameSync(temporary, file);
117
121
  return true;
118
122
  } catch {
@@ -123,6 +127,8 @@ var markPendingCommandDelivered = (sessionId, id, namespace = "") => {
123
127
  return false;
124
128
  }
125
129
  };
130
+ var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
131
+ var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
126
132
  var removePendingCommand = (sessionId, id, namespace = "") => {
127
133
  if (readPendingCommand(sessionId, namespace)?.id !== id) return;
128
134
  try {
@@ -178,6 +184,7 @@ export {
178
184
  readPendingCommand,
179
185
  savePendingCommand,
180
186
  markPendingCommandDelivered,
187
+ markPendingCommandFailed,
181
188
  removePendingCommand,
182
189
  isDefaultSession,
183
190
  savePendingQuestion,
@@ -19,8 +19,8 @@ var COMMAND_LIST = [
19
19
  module: "pushary-daemon",
20
20
  summary: "Keep this machine reachable so your phone can START a new session",
21
21
  detail: [
22
- "Leave it running in a project dir; it launches a headless",
23
- "'pushary claude --remote' when you send a prompt from the app."
22
+ "Install the native per-user service for restart-on-login and restart-on-failure.",
23
+ "A bare `pushary daemon` remains foreground mode for diagnostics."
24
24
  ]
25
25
  },
26
26
  {
@@ -114,6 +114,13 @@ var COMMAND_OPTIONS = {
114
114
  claude: [
115
115
  ["--remote", "Start headless and drive it entirely from your phone"],
116
116
  ["-p <prompt>", "Initial prompt"]
117
+ ],
118
+ daemon: [
119
+ ["install", "Install and start the native per-user service"],
120
+ ["start | stop | restart", "Control the installed service"],
121
+ ["status", "Show local daemon health and PID"],
122
+ ["uninstall", "Stop and remove the native service"],
123
+ ["foreground", "Run attached to this terminal for diagnostics"]
117
124
  ]
118
125
  };
119
126
  var SETUP_OPTIONS = [
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderCommandHelp
3
- } from "./chunk-GOEXZ5QJ.js";
3
+ } from "./chunk-GPNOHKFO.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -268,6 +268,7 @@ var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACT
268
268
  var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
269
269
 
270
270
  export {
271
+ HOOK_FALLBACK_MAX,
271
272
  describeToolCall,
272
273
  deriveToolTarget,
273
274
  parseApplyPatchOps,
@@ -17,7 +17,7 @@ import {
17
17
  readLastUserPrompt,
18
18
  repoKeyFor,
19
19
  throttlePass
20
- } from "./chunk-3UBYTSCY.js";
20
+ } from "./chunk-NFI2K2ZQ.js";
21
21
  import {
22
22
  DEFAULT_SESSION,
23
23
  askUser,
@@ -25,7 +25,7 @@ import {
25
25
  pollForAnswer,
26
26
  savePendingQuestion,
27
27
  sendNotification
28
- } from "./chunk-HUJAAWNL.js";
28
+ } from "./chunk-GKAKAOKC.js";
29
29
  import {
30
30
  deriveAction,
31
31
  deriveActionBody,
@@ -33,10 +33,10 @@ import {
33
33
  deriveToolTarget,
34
34
  describeToolCall,
35
35
  scopePathFor
36
- } from "./chunk-LGXTBUT4.js";
36
+ } from "./chunk-KOPUTJQC.js";
37
37
  import {
38
38
  getMachineId
39
- } from "./chunk-RN3NOEJF.js";
39
+ } from "./chunk-2HYPZLYY.js";
40
40
  import {
41
41
  getApiKey,
42
42
  getBaseUrl
@@ -12,7 +12,7 @@ import {
12
12
  savePendingCommand,
13
13
  sendNotification,
14
14
  waitForAnswer
15
- } from "./chunk-HUJAAWNL.js";
15
+ } from "./chunk-GKAKAOKC.js";
16
16
  import {
17
17
  AGENT_IDENTITIES,
18
18
  deriveReceiptMeta,
@@ -26,10 +26,10 @@ import {
26
26
  summarizeStop,
27
27
  taskTitleFrom,
28
28
  toolResultErrorText
29
- } from "./chunk-LGXTBUT4.js";
29
+ } from "./chunk-KOPUTJQC.js";
30
30
  import {
31
31
  getMachineId
32
- } from "./chunk-RN3NOEJF.js";
32
+ } from "./chunk-2HYPZLYY.js";
33
33
  import {
34
34
  withRetry
35
35
  } from "./chunk-BSZYIAZL.js";
@@ -670,7 +670,7 @@ var consumesActivityContext = (agentType) => {
670
670
  return pendingCommandEvents(source).some((event) => event !== "Stop");
671
671
  };
672
672
  var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
673
- var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, machineId, timeoutMs) => {
673
+ var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, machineId, timeoutMs, state = "delivered") => {
674
674
  try {
675
675
  const response = await fetch(`${baseUrl}/api/agent/command/ack`, {
676
676
  method: "POST",
@@ -678,7 +678,13 @@ var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, ma
678
678
  "Content-Type": "application/json",
679
679
  "Authorization": `Bearer ${apiKey}`
680
680
  },
681
- body: JSON.stringify({ commandId, sessionId: sessionId ?? null, machineId, state: "delivered" }),
681
+ body: JSON.stringify({
682
+ commandId,
683
+ sessionId: sessionId ?? null,
684
+ machineId,
685
+ state,
686
+ ...state === "failed" ? { failureCode: "consumer_rejected" } : {}
687
+ }),
682
688
  signal: AbortSignal.timeout(timeoutMs)
683
689
  });
684
690
  return response.ok;
@@ -729,16 +735,18 @@ var reportEvent = async (event, options = {}) => {
729
735
  const result = await res.json();
730
736
  const withoutPending = () => ({ ...result, pendingCommand: void 0, pendingCommandId: void 0 });
731
737
  if (storedCommand) {
738
+ const failed = storedCommand.failed === true;
732
739
  const acknowledged = await acknowledgePendingCommand(
733
740
  apiKey,
734
741
  baseUrl,
735
742
  storedCommand.id,
736
743
  event.sessionId,
737
744
  machineId,
738
- options.timeoutMs ?? 1e4
745
+ options.timeoutMs ?? 1e4,
746
+ failed ? "failed" : "delivered"
739
747
  );
740
748
  if (!acknowledged) return withoutPending();
741
- if (storedCommand.delivered) {
749
+ if (storedCommand.delivered || failed) {
742
750
  removePendingCommand(sessionKey, storedCommand.id, receiptNamespace);
743
751
  return withoutPending();
744
752
  }
@@ -5,17 +5,18 @@ import {
5
5
  import {
6
6
  resolveGlobalBinary
7
7
  } from "./chunk-PZA7G4CN.js";
8
- import {
9
- pusharyDir
10
- } from "./chunk-VKVHY5HD.js";
11
8
  import {
12
9
  getMachineId
13
- } from "./chunk-RN3NOEJF.js";
10
+ } from "./chunk-2HYPZLYY.js";
11
+ import {
12
+ pusharyDir
13
+ } from "./chunk-V37ZXMH3.js";
14
14
  import {
15
15
  getPackageVersion
16
16
  } from "./chunk-DINDL2CF.js";
17
17
  import {
18
18
  readJsonSafe,
19
+ writeFileAtomic,
19
20
  writeJsonAtomic
20
21
  } from "./chunk-6MTNS63X.js";
21
22
  import {
@@ -23,9 +24,11 @@ import {
23
24
  } from "./chunk-2UMNXADU.js";
24
25
 
25
26
  // src/daemon-lifecycle.ts
26
- import { dirname, join } from "path";
27
- import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
27
+ import { dirname, join, win32 as win32Path } from "path";
28
+ import { existsSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "fs";
28
29
  import { createHash, randomUUID } from "crypto";
30
+ import { homedir } from "os";
31
+ import { execFileSync } from "child_process";
29
32
  var daemonNeedsHandoff = (status) => status.kind === "version-mismatch" || status.kind === "configuration-mismatch" || status.kind === "unresponsive" && status.processAlive === true;
30
33
  var statePath = (dir) => join(dir, "daemon-state.json");
31
34
  var lockPath = (dir) => join(dir, "daemon.lock");
@@ -37,6 +40,219 @@ var buildDaemonLaunch = (input) => input.siblingEntry ? { command: input.execPat
37
40
  args: [],
38
41
  shell: needsShell(input.daemonBinary, input.platform ?? process.platform)
39
42
  };
43
+ var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
44
+ var systemdQuote = (value) => `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
45
+ var buildDaemonService = (input) => {
46
+ if (input.platform === "darwin") {
47
+ if (!Number.isInteger(input.uid) || input.uid < 0) return null;
48
+ const label = "com.pushary.daemon";
49
+ const domain = `gui/${input.uid}`;
50
+ const target = `${domain}/${label}`;
51
+ const definitionPath = join(input.homeDir, "Library", "LaunchAgents", `${label}.plist`);
52
+ const contents = `<?xml version="1.0" encoding="UTF-8"?>
53
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
54
+ <plist version="1.0">
55
+ <dict>
56
+ <key>Label</key><string>${label}</string>
57
+ <key>ProgramArguments</key>
58
+ <array><string>${xml(input.execPath)}</string><string>${xml(input.daemonEntry)}</string></array>
59
+ <key>WorkingDirectory</key><string>${xml(input.homeDir)}</string>
60
+ <key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(input.pathEnv)}</string></dict>
61
+ <key>RunAtLoad</key><true/>
62
+ <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
63
+ <key>ThrottleInterval</key><integer>10</integer>
64
+ <key>StandardOutPath</key><string>${xml(join(input.stateDir, "daemon.log"))}</string>
65
+ <key>StandardErrorPath</key><string>${xml(join(input.stateDir, "daemon-error.log"))}</string>
66
+ </dict>
67
+ </plist>
68
+ `;
69
+ const bootstrap = { command: "launchctl", args: ["bootstrap", domain, definitionPath] };
70
+ return {
71
+ definitionPath,
72
+ contents,
73
+ install: [
74
+ { command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true },
75
+ bootstrap,
76
+ { command: "launchctl", args: ["enable", target] },
77
+ { command: "launchctl", args: ["kickstart", "-k", target] }
78
+ ],
79
+ start: [{ command: "launchctl", args: ["kickstart", "-k", target] }],
80
+ stop: [{ command: "launchctl", args: ["kill", "SIGTERM", target], ignoreFailure: true }],
81
+ uninstall: [{ command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true }]
82
+ };
83
+ }
84
+ if (input.platform === "linux") {
85
+ const unit = "pushary-daemon.service";
86
+ const definitionPath = join(input.homeDir, ".config", "systemd", "user", unit);
87
+ const contents = `[Unit]
88
+ Description=Pushary phone-to-agent daemon
89
+ After=network-online.target
90
+
91
+ [Service]
92
+ Type=simple
93
+ ExecStart=${systemdQuote(input.execPath)} ${systemdQuote(input.daemonEntry)}
94
+ WorkingDirectory=${systemdQuote(input.homeDir)}
95
+ Environment=${systemdQuote(`PATH=${input.pathEnv}`)}
96
+ Restart=on-failure
97
+ RestartSec=10
98
+
99
+ [Install]
100
+ WantedBy=default.target
101
+ `;
102
+ return {
103
+ definitionPath,
104
+ contents,
105
+ install: [
106
+ { command: "systemctl", args: ["--user", "daemon-reload"] },
107
+ { command: "systemctl", args: ["--user", "enable", "--now", unit] }
108
+ ],
109
+ start: [{ command: "systemctl", args: ["--user", "start", unit] }],
110
+ stop: [{ command: "systemctl", args: ["--user", "stop", unit], ignoreFailure: true }],
111
+ uninstall: [
112
+ { command: "systemctl", args: ["--user", "disable", "--now", unit], ignoreFailure: true }
113
+ ],
114
+ reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] }
115
+ };
116
+ }
117
+ if (input.platform === "win32") {
118
+ const taskName = "Pushary Daemon";
119
+ const definitionPath = win32Path.join(input.stateDir, "pushary-daemon-task.xml");
120
+ const contents = `<?xml version="1.0" encoding="UTF-8"?>
121
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
122
+ <RegistrationInfo><Description>Pushary phone-to-agent daemon</Description></RegistrationInfo>
123
+ <Triggers><LogonTrigger><Enabled>true</Enabled></LogonTrigger></Triggers>
124
+ <Principals><Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
125
+ <Settings>
126
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
127
+ <StartWhenAvailable>true</StartWhenAvailable>
128
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
129
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
130
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
131
+ <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>
132
+ </Settings>
133
+ <Actions Context="Author">
134
+ <Exec>
135
+ <Command>${xml(input.execPath)}</Command>
136
+ <Arguments>&quot;${xml(input.daemonEntry)}&quot;</Arguments>
137
+ <WorkingDirectory>${xml(input.homeDir)}</WorkingDirectory>
138
+ </Exec>
139
+ </Actions>
140
+ </Task>
141
+ `;
142
+ return {
143
+ definitionPath,
144
+ contents,
145
+ install: [
146
+ { command: "schtasks.exe", args: ["/Create", "/TN", taskName, "/XML", definitionPath, "/F"] },
147
+ { command: "schtasks.exe", args: ["/Run", "/TN", taskName] }
148
+ ],
149
+ start: [{ command: "schtasks.exe", args: ["/Run", "/TN", taskName] }],
150
+ stop: [{ command: "schtasks.exe", args: ["/End", "/TN", taskName], ignoreFailure: true }],
151
+ uninstall: [{ command: "schtasks.exe", args: ["/Delete", "/TN", taskName, "/F"], ignoreFailure: true }]
152
+ };
153
+ }
154
+ return null;
155
+ };
156
+ var resolveDaemonServiceDefinition = (options = {}) => {
157
+ const platform = options.platform ?? process.platform;
158
+ const exists = options.entryExists ?? existsSync;
159
+ const globalBinary = options.globalBinary ?? resolveGlobalBinary("pushary-daemon");
160
+ let daemonEntry = options.daemonEntry;
161
+ if (!daemonEntry && globalBinary) {
162
+ if (platform === "win32") {
163
+ const globalRoot = win32Path.dirname(globalBinary);
164
+ const candidate = win32Path.join(
165
+ globalRoot,
166
+ "node_modules",
167
+ "@pushary",
168
+ "agent-hooks",
169
+ "dist",
170
+ "bin",
171
+ "pushary-daemon.js"
172
+ );
173
+ if (exists(candidate)) daemonEntry = candidate;
174
+ } else {
175
+ try {
176
+ const candidate = (options.realpath ?? realpathSync)(globalBinary);
177
+ if (exists(candidate)) daemonEntry = candidate;
178
+ } catch {
179
+ }
180
+ }
181
+ }
182
+ const argvEntry = options.argvEntry ?? process.argv[1];
183
+ if (!daemonEntry && argvEntry) {
184
+ const path = platform === "win32" ? win32Path : { dirname, join };
185
+ const candidate = path.join(path.dirname(argvEntry), "pushary-daemon.js");
186
+ if (exists(candidate)) daemonEntry = candidate;
187
+ }
188
+ if (!daemonEntry || !exists(daemonEntry)) return null;
189
+ const homeDir = options.homeDir ?? homedir();
190
+ return buildDaemonService({
191
+ platform,
192
+ homeDir,
193
+ stateDir: options.stateDir ?? pusharyDir(),
194
+ execPath: options.execPath ?? process.execPath,
195
+ daemonEntry,
196
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
197
+ uid: options.uid ?? process.getuid?.()
198
+ });
199
+ };
200
+ var applyDaemonService = (action, definition, options = {}) => {
201
+ const run = options.run ?? ((command, args) => {
202
+ execFileSync(command, [...args], { stdio: "ignore", windowsHide: true });
203
+ });
204
+ try {
205
+ if (action === "install") writeFileAtomic(definition.definitionPath, definition.contents, 384);
206
+ const steps = action === "uninstall" ? [...definition.stop, ...definition.uninstall] : definition[action];
207
+ for (const step of steps) {
208
+ try {
209
+ run(step.command, step.args);
210
+ } catch (err) {
211
+ if (!step.ignoreFailure) throw err;
212
+ }
213
+ }
214
+ if (action === "uninstall") {
215
+ safeUnlink(definition.definitionPath);
216
+ const reload = definition.reloadAfterUninstall;
217
+ if (reload) run(reload.command, reload.args);
218
+ }
219
+ return { kind: "ok" };
220
+ } catch (err) {
221
+ return { kind: "failed", detail: err instanceof Error ? err.message : String(err) };
222
+ }
223
+ };
224
+ var DAEMON_ACTIONS = /* @__PURE__ */ new Set([
225
+ "install",
226
+ "start",
227
+ "stop",
228
+ "restart",
229
+ "status",
230
+ "uninstall",
231
+ "foreground"
232
+ ]);
233
+ var parseDaemonAction = (argv) => {
234
+ const args = argv[0] === "daemon" ? argv.slice(1) : argv;
235
+ const action = args[0] ?? "foreground";
236
+ return DAEMON_ACTIONS.has(action) ? action : null;
237
+ };
238
+ var manageDaemonService = (action, options = {}) => {
239
+ const definition = action === "uninstall" ? buildDaemonService({
240
+ platform: options.platform ?? process.platform,
241
+ homeDir: options.homeDir ?? homedir(),
242
+ stateDir: options.stateDir ?? pusharyDir(),
243
+ execPath: options.execPath ?? process.execPath,
244
+ daemonEntry: options.daemonEntry ?? process.execPath,
245
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
246
+ uid: options.uid ?? process.getuid?.()
247
+ }) : resolveDaemonServiceDefinition(options);
248
+ if (!definition) {
249
+ return {
250
+ kind: "unavailable",
251
+ detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
252
+ };
253
+ }
254
+ return applyDaemonService(action, definition, { run: options.run });
255
+ };
40
256
  var spawnDetachedDaemon = (plan, options = {}) => {
41
257
  const spawnImpl = options.spawnImpl ?? spawnClaude;
42
258
  const child = spawnImpl(plan.command, plan.args, {
@@ -278,6 +494,31 @@ var requestDaemonHandoff = async (initial, options = {}) => {
278
494
  }
279
495
  return status;
280
496
  };
497
+ var stopDaemonRunning = async (options = {}) => {
498
+ const dir = options.dir ?? pusharyDir();
499
+ const initial = readDaemonStatus(options);
500
+ if (initial.pid === null || initial.processAlive !== true) {
501
+ safeUnlink(statePath(dir));
502
+ safeUnlink(lockPath(dir));
503
+ return { kind: "absent" };
504
+ }
505
+ let status;
506
+ try {
507
+ status = await requestDaemonHandoff(initial, options);
508
+ } catch (err) {
509
+ return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
510
+ }
511
+ if (status.pid === initial.pid && status.processAlive === true) {
512
+ return { kind: "unavailable", detail: "daemon did not stop" };
513
+ }
514
+ safeUnlink(statePath(dir));
515
+ safeUnlink(lockPath(dir));
516
+ return { kind: "stopped" };
517
+ };
518
+ var replaceDaemonService = async (options = {}) => {
519
+ const stopped = await stopDaemonRunning(options);
520
+ return stopped.kind === "unavailable" ? stopped : manageDaemonService("install", options);
521
+ };
281
522
  var launchInstalledDaemon = (options) => {
282
523
  const entryExists = options.entryExists ?? existsSync;
283
524
  const argvEntry = process.argv[1];
@@ -336,8 +577,12 @@ var ensureDaemonRunning = async (options = {}) => {
336
577
 
337
578
  export {
338
579
  DAEMON_LEASE_RENEW_INTERVAL_MS,
580
+ parseDaemonAction,
581
+ manageDaemonService,
339
582
  readDaemonStatus,
340
583
  acquireDaemonLease,
341
584
  requestDaemonHandoff,
585
+ stopDaemonRunning,
586
+ replaceDaemonService,
342
587
  ensureDaemonRunning
343
588
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-7TS2G4QG.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-VKVHY5HD.js";
6
+ } from "./chunk-V37ZXMH3.js";
7
7
  import {
8
8
  HOOK_LOCATOR_BINARY
9
9
  } from "./chunk-L4C2JXJS.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-O5B7EK5U.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-VKVHY5HD.js";
6
+ } from "./chunk-V37ZXMH3.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -1,6 +1,3 @@
1
- import {
2
- canonicalJson
3
- } from "./chunk-GIRT7677.js";
4
1
  import {
5
2
  HOOK_BUDGETS,
6
3
  HOOK_LOCATOR_BINARY
@@ -10,6 +7,16 @@ import {
10
7
  import { createHash } from "crypto";
11
8
  import { homedir } from "os";
12
9
  import { join } from "path";
10
+
11
+ // src/canonical-json.ts
12
+ var canonicalJson = (value) => {
13
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
14
+ if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
15
+ const record = value;
16
+ return "{" + Object.keys(record).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(record[key])).join(",") + "}";
17
+ };
18
+
19
+ // src/codex-config.ts
13
20
  var CODEX_HOOK_BINARY = "pushary-codex-hook";
14
21
  var codexHome = () => codexHomeFrom(homedir());
15
22
  var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join(home, ".codex");
@@ -263,6 +270,7 @@ var geminiMd = () => join2(homedir2(), ".gemini", "GEMINI.md");
263
270
  var pusharyConfigFile = () => join2(homedir2(), ".pushary", "config.json");
264
271
 
265
272
  export {
273
+ canonicalJson,
266
274
  CODEX_HOOK_BINARY,
267
275
  codexHome,
268
276
  codexHomeFrom,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getMachineId
3
- } from "./chunk-RN3NOEJF.js";
3
+ } from "./chunk-2HYPZLYY.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-JT6TIBYH.js";
3
+ } from "../chunk-MZBT2LXW.js";
4
4
  import "../chunk-ATBKJNWS.js";
5
5
  import "../chunk-VZKXBFUA.js";
6
6
  import "../chunk-YHG74UFF.js";
@@ -12,19 +12,21 @@ import {
12
12
  handlePostToolUse,
13
13
  handleStop,
14
14
  reportEvent
15
- } from "../chunk-3UBYTSCY.js";
15
+ } from "../chunk-NFI2K2ZQ.js";
16
16
  import {
17
17
  askUser,
18
18
  cancelQuestion,
19
19
  waitForAnswer
20
- } from "../chunk-HUJAAWNL.js";
21
- import "../chunk-LGXTBUT4.js";
22
- import "../chunk-RN3NOEJF.js";
20
+ } from "../chunk-GKAKAOKC.js";
21
+ import "../chunk-KOPUTJQC.js";
22
+ import "../chunk-2HYPZLYY.js";
23
+ import "../chunk-V37ZXMH3.js";
23
24
  import "../chunk-BSZYIAZL.js";
24
25
  import "../chunk-SAF6HGAA.js";
25
26
  import "../chunk-OFRNZWC4.js";
26
27
  import "../chunk-7QLSKOSU.js";
27
28
  import "../chunk-KZERVKTD.js";
29
+ import "../chunk-6MTNS63X.js";
28
30
  import {
29
31
  getApiKey,
30
32
  getBaseUrl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.3",
3
+ "version": "0.89.4",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,11 +0,0 @@
1
- // src/canonical-json.ts
2
- var canonicalJson = (value) => {
3
- if (value === null || typeof value !== "object") return JSON.stringify(value);
4
- if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
5
- const record = value;
6
- return "{" + Object.keys(record).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(record[key])).join(",") + "}";
7
- };
8
-
9
- export {
10
- canonicalJson
11
- };
@@ -1,9 +0,0 @@
1
- // src/identity.ts
2
- import { createHash } from "crypto";
3
- import { hostname } from "os";
4
- var deriveMachineId = (host) => createHash("sha256").update(host).digest("hex").slice(0, 8);
5
- var getMachineId = () => deriveMachineId(hostname());
6
-
7
- export {
8
- getMachineId
9
- };
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  resolveShellRc
3
3
  } from "./chunk-BC3VCZ3E.js";
4
+ import {
5
+ resolveInstallSourceFor
6
+ } from "./chunk-OFRNZWC4.js";
4
7
  import {
5
8
  backupFile,
6
9
  readJsonSafe,
7
10
  writeFileAtomic,
8
11
  writeJsonAtomic
9
12
  } from "./chunk-6MTNS63X.js";
10
- import {
11
- resolveInstallSourceFor
12
- } from "./chunk-OFRNZWC4.js";
13
13
  import {
14
14
  configFilePath
15
15
  } from "./chunk-2UMNXADU.js";