@pushary/agent-hooks 0.89.3 → 0.89.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) 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 +25 -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 +155 -40
  11. package/dist/bin/pushary-disconnect.js +3 -4
  12. package/dist/bin/pushary-doctor.js +77 -39
  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 +125 -39
  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 +110 -46
  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-GOEXZ5QJ.js → chunk-3XTFX4CL.js} +10 -2
  39. package/dist/{chunk-ZYPS2XMT.js → chunk-4ZHQNBDP.js} +1 -1
  40. package/dist/{chunk-HUJAAWNL.js → chunk-GKAKAOKC.js} +10 -3
  41. package/dist/{chunk-LGXTBUT4.js → chunk-KOPUTJQC.js} +1 -0
  42. package/dist/{chunk-LALHWDWL.js → chunk-L3YUHEB6.js} +16 -4
  43. package/dist/{chunk-SQOFGPNX.js → chunk-LURY4WNF.js} +1 -1
  44. package/dist/{chunk-JT6TIBYH.js → chunk-MZBT2LXW.js} +4 -4
  45. package/dist/{chunk-3UBYTSCY.js → chunk-NFI2K2ZQ.js} +15 -7
  46. package/dist/chunk-NKHEHEUM.js +56 -0
  47. package/dist/{chunk-3O2XPYLF.js → chunk-OBI4YMCT.js} +1 -1
  48. package/dist/{chunk-LUCFN47K.js → chunk-QH4AA7QV.js} +1 -1
  49. package/dist/chunk-TRQ46MXR.js +648 -0
  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/{chunk-B7JE4XHD.js → chunk-WQGMUIDW.js} +1 -1
  53. package/dist/src/index.js +7 -5
  54. package/package.json +1 -1
  55. package/dist/chunk-GIRT7677.js +0 -11
  56. package/dist/chunk-K42SZKYF.js +0 -343
  57. package/dist/chunk-NRGRUOAR.js +0 -25
  58. package/dist/chunk-RN3NOEJF.js +0 -9
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.5",
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,343 +0,0 @@
1
- import {
2
- needsShell,
3
- spawnClaude
4
- } from "./chunk-XHKBHWLX.js";
5
- import {
6
- resolveGlobalBinary
7
- } from "./chunk-PZA7G4CN.js";
8
- import {
9
- pusharyDir
10
- } from "./chunk-VKVHY5HD.js";
11
- import {
12
- getMachineId
13
- } from "./chunk-RN3NOEJF.js";
14
- import {
15
- getPackageVersion
16
- } from "./chunk-DINDL2CF.js";
17
- import {
18
- readJsonSafe,
19
- writeJsonAtomic
20
- } from "./chunk-6MTNS63X.js";
21
- import {
22
- getApiKey
23
- } from "./chunk-2UMNXADU.js";
24
-
25
- // src/daemon-lifecycle.ts
26
- import { dirname, join } from "path";
27
- import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
28
- import { createHash, randomUUID } from "crypto";
29
- var daemonNeedsHandoff = (status) => status.kind === "version-mismatch" || status.kind === "configuration-mismatch" || status.kind === "unresponsive" && status.processAlive === true;
30
- var statePath = (dir) => join(dir, "daemon-state.json");
31
- var lockPath = (dir) => join(dir, "daemon.lock");
32
- var targetPath = (dir) => join(dir, "daemon-target.json");
33
- var DAEMON_LEASE_RENEW_INTERVAL_MS = 3e4;
34
- var LOCAL_LEASE_TIMEOUT_MS = DAEMON_LEASE_RENEW_INTERVAL_MS * 3;
35
- var buildDaemonLaunch = (input) => input.siblingEntry ? { command: input.execPath, args: [input.siblingEntry], shell: false } : {
36
- command: input.daemonBinary,
37
- args: [],
38
- shell: needsShell(input.daemonBinary, input.platform ?? process.platform)
39
- };
40
- var spawnDetachedDaemon = (plan, options = {}) => {
41
- const spawnImpl = options.spawnImpl ?? spawnClaude;
42
- const child = spawnImpl(plan.command, plan.args, {
43
- detached: true,
44
- stdio: "ignore",
45
- windowsHide: true,
46
- shell: plan.shell
47
- });
48
- child.once?.("error", () => {
49
- });
50
- child.unref();
51
- return typeof child.pid === "number" ? child.pid : null;
52
- };
53
- var processIsAlive = (pid) => {
54
- try {
55
- process.kill(pid, 0);
56
- return true;
57
- } catch (err) {
58
- return err.code === "EPERM";
59
- }
60
- };
61
- var isString = (value) => typeof value === "string" && value.length > 0;
62
- var isTimestamp = (value) => isString(value) && Number.isFinite(Date.parse(value));
63
- var currentConfigurationId = () => {
64
- try {
65
- return createHash("sha256").update(getApiKey()).digest("hex").slice(0, 16);
66
- } catch {
67
- return null;
68
- }
69
- };
70
- var parseDaemonState = (value) => {
71
- if (typeof value !== "object" || value === null) return null;
72
- const state = value;
73
- 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))) return null;
74
- return {
75
- instanceId: state.instanceId,
76
- pid: state.pid,
77
- version: state.version,
78
- configurationId: state.configurationId ?? null,
79
- machineId: state.machineId,
80
- startedAt: state.startedAt,
81
- updatedAt: state.updatedAt,
82
- lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt
83
- };
84
- };
85
- var parseLockOwner = (value) => {
86
- if (typeof value !== "object" || value === null) return null;
87
- const owner = value;
88
- return isString(owner.instanceId) && Number.isInteger(owner.pid) && owner.pid > 0 ? { instanceId: owner.instanceId, pid: owner.pid } : null;
89
- };
90
- var readDaemonStatus = (options = {}) => {
91
- const dir = options.dir ?? pusharyDir();
92
- const installedVersion = options.installedVersion ?? getPackageVersion();
93
- const state = readJsonSafe(statePath(dir));
94
- if (state.kind === "missing") {
95
- return {
96
- kind: "absent",
97
- installedVersion,
98
- machineId: null,
99
- pid: null,
100
- processAlive: null,
101
- daemonVersion: null,
102
- startedAt: null,
103
- updatedAt: null,
104
- lastSuccessfulHeartbeatAt: null
105
- };
106
- }
107
- const value = state.kind === "ok" ? parseDaemonState(state.value) : null;
108
- if (value) {
109
- const alive = (options.isProcessAlive ?? processIsAlive)(value.pid);
110
- const fresh = (options.now ?? Date.now)() - Date.parse(value.updatedAt) <= LOCAL_LEASE_TIMEOUT_MS;
111
- const expectedConfigurationId = options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
112
- return {
113
- kind: !alive || !fresh ? "unresponsive" : value.version !== installedVersion ? "version-mismatch" : expectedConfigurationId !== null && value.configurationId !== null && value.configurationId !== expectedConfigurationId ? "configuration-mismatch" : "running",
114
- installedVersion,
115
- machineId: value.machineId,
116
- pid: value.pid,
117
- processAlive: alive,
118
- daemonVersion: value.version,
119
- startedAt: value.startedAt,
120
- updatedAt: value.updatedAt,
121
- lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt
122
- };
123
- }
124
- return {
125
- kind: "invalid",
126
- installedVersion,
127
- machineId: null,
128
- pid: null,
129
- processAlive: null,
130
- daemonVersion: null,
131
- startedAt: null,
132
- updatedAt: null,
133
- lastSuccessfulHeartbeatAt: null
134
- };
135
- };
136
- var safeUnlink = (path) => {
137
- try {
138
- unlinkSync(path);
139
- } catch (err) {
140
- if (err.code !== "ENOENT") throw err;
141
- }
142
- };
143
- var requestDaemonVersion = (version, options = {}) => {
144
- const dir = options.dir ?? pusharyDir();
145
- const now = options.now ?? Date.now;
146
- writeJsonAtomic(targetPath(dir), { version, requestedAt: new Date(now()).toISOString() }, 384);
147
- };
148
- var acquireDaemonLease = (options = {}) => {
149
- const dir = options.dir ?? pusharyDir();
150
- const installedVersion = options.installedVersion ?? getPackageVersion();
151
- const machineId = options.machineId ?? getMachineId();
152
- const pid = options.pid ?? process.pid;
153
- const now = options.now ?? Date.now;
154
- const instanceId = randomUUID();
155
- const startedAt = new Date(now()).toISOString();
156
- const writeLock = () => {
157
- mkdirSync(dir, { recursive: true });
158
- writeFileSync(lockPath(dir), JSON.stringify({ instanceId, pid }), { flag: "wx", mode: 384 });
159
- };
160
- try {
161
- writeLock();
162
- } catch (err) {
163
- if (err.code !== "EEXIST") {
164
- return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
165
- }
166
- const status = readDaemonStatus(options);
167
- if (status.kind === "running" || status.kind === "version-mismatch" || status.kind === "configuration-mismatch") {
168
- return { kind: "already-running", status };
169
- }
170
- const lock = readJsonSafe(lockPath(dir));
171
- const owner = lock.kind === "ok" ? parseLockOwner(lock.value) : null;
172
- if (!owner) return { kind: "unavailable", detail: "daemon lock is invalid" };
173
- if ((options.isProcessAlive ?? processIsAlive)(owner.pid)) {
174
- return { kind: "unavailable", detail: "daemon owner is still running or starting" };
175
- }
176
- const stale = join(dir, `daemon.lock.${instanceId}.stale`);
177
- let ownsReplacement = false;
178
- try {
179
- renameSync(lockPath(dir), stale);
180
- writeLock();
181
- ownsReplacement = true;
182
- safeUnlink(statePath(dir));
183
- safeUnlink(stale);
184
- } catch (retryError) {
185
- if (ownsReplacement) safeUnlink(lockPath(dir));
186
- try {
187
- safeUnlink(stale);
188
- } catch {
189
- }
190
- return { kind: "unavailable", detail: retryError instanceof Error ? retryError.message : String(retryError) };
191
- }
192
- }
193
- const state = {
194
- instanceId,
195
- pid,
196
- version: installedVersion,
197
- configurationId: options.configurationId === void 0 ? currentConfigurationId() : options.configurationId,
198
- machineId,
199
- startedAt,
200
- updatedAt: startedAt,
201
- lastSuccessfulHeartbeatAt: null
202
- };
203
- try {
204
- writeJsonAtomic(statePath(dir), state, 384);
205
- } catch (err) {
206
- safeUnlink(lockPath(dir));
207
- return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
208
- }
209
- let currentState = state;
210
- const isOwner = () => {
211
- const owner = readJsonSafe(lockPath(dir));
212
- return owner.kind === "ok" && owner.value.instanceId === instanceId;
213
- };
214
- const updateState = (next) => {
215
- if (!isOwner()) return;
216
- try {
217
- writeJsonAtomic(statePath(dir), next, 384);
218
- currentState = next;
219
- } catch {
220
- }
221
- };
222
- return {
223
- kind: "acquired",
224
- lease: {
225
- isOwner,
226
- renew: (at = now()) => {
227
- updateState({ ...currentState, updatedAt: new Date(at).toISOString() });
228
- },
229
- recordSuccessfulHeartbeat: (at = now()) => {
230
- const timestamp = new Date(at).toISOString();
231
- updateState({
232
- ...currentState,
233
- updatedAt: timestamp,
234
- lastSuccessfulHeartbeatAt: timestamp
235
- });
236
- },
237
- shouldYield: () => {
238
- if (!isOwner()) return true;
239
- const target = readJsonSafe(targetPath(dir));
240
- 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));
241
- },
242
- release: () => {
243
- if (!isOwner()) return;
244
- safeUnlink(statePath(dir));
245
- safeUnlink(lockPath(dir));
246
- }
247
- }
248
- };
249
- };
250
- var hasConfiguration = () => {
251
- try {
252
- getApiKey();
253
- return true;
254
- } catch {
255
- return false;
256
- }
257
- };
258
- var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
259
- var requestDaemonHandoff = async (initial, options = {}) => {
260
- const now = options.now ?? Date.now;
261
- const requestedAt = Math.max(
262
- now(),
263
- initial.startedAt === null ? 0 : Date.parse(initial.startedAt) + 1
264
- );
265
- requestDaemonVersion(options.installedVersion ?? initial.installedVersion, {
266
- dir: options.dir,
267
- now: () => requestedAt
268
- });
269
- if (initial.pid === null || initial.processAlive !== true) return initial;
270
- const sleep = options.sleep ?? wait;
271
- const pollIntervalMs = options.pollIntervalMs ?? 50;
272
- const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
273
- let status = initial;
274
- for (let attempt = 0; attempt < attempts; attempt++) {
275
- await sleep(pollIntervalMs);
276
- status = readDaemonStatus(options);
277
- if (status.pid !== initial.pid || status.processAlive !== true) break;
278
- }
279
- return status;
280
- };
281
- var launchInstalledDaemon = (options) => {
282
- const entryExists = options.entryExists ?? existsSync;
283
- const argvEntry = process.argv[1];
284
- const candidate = options.daemonEntry !== void 0 ? options.daemonEntry : argvEntry ? join(dirname(argvEntry), "pushary-daemon.js") : null;
285
- const siblingEntry = candidate && entryExists(candidate) ? candidate : null;
286
- const platform = options.platform ?? process.platform;
287
- const daemonBinary = options.daemonBinary ?? resolveGlobalBinary("pushary-daemon") ?? (platform === "win32" ? "pushary-daemon.cmd" : "pushary-daemon");
288
- const plan = buildDaemonLaunch({
289
- siblingEntry,
290
- execPath: options.execPath ?? process.execPath,
291
- daemonBinary,
292
- platform
293
- });
294
- spawnDetachedDaemon(plan, { spawnImpl: options.spawnImpl });
295
- };
296
- var ensureDaemonRunning = async (options = {}) => {
297
- const initial = readDaemonStatus(options);
298
- if (!(options.configured ?? hasConfiguration)()) {
299
- return { kind: "not-configured", status: initial, detail: "Pushary is not configured on this machine" };
300
- }
301
- if (initial.kind === "running") return { kind: "running", status: initial };
302
- let handoffStatus;
303
- try {
304
- handoffStatus = await requestDaemonHandoff(initial, options);
305
- } catch (err) {
306
- return {
307
- kind: "unavailable",
308
- status: initial,
309
- detail: err instanceof Error ? err.message : String(err)
310
- };
311
- }
312
- const sleep = options.sleep ?? wait;
313
- const pollIntervalMs = options.pollIntervalMs ?? 50;
314
- const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
315
- if (handoffStatus.kind === "running") return { kind: "running", status: handoffStatus };
316
- if (daemonNeedsHandoff(handoffStatus)) {
317
- return { kind: "unavailable", status: handoffStatus, detail: "unhealthy daemon did not yield" };
318
- }
319
- try {
320
- (options.spawnDaemon ?? (() => launchInstalledDaemon(options)))();
321
- } catch (err) {
322
- return {
323
- kind: "unavailable",
324
- status: initial,
325
- detail: err instanceof Error ? err.message : String(err)
326
- };
327
- }
328
- let status = initial;
329
- for (let attempt = 0; attempt < attempts; attempt++) {
330
- status = readDaemonStatus(options);
331
- if (status.kind === "running") return { kind: "started", status };
332
- await sleep(pollIntervalMs);
333
- }
334
- return { kind: "unavailable", status, detail: `daemon did not become healthy (${status.kind})` };
335
- };
336
-
337
- export {
338
- DAEMON_LEASE_RENEW_INTERVAL_MS,
339
- readDaemonStatus,
340
- acquireDaemonLease,
341
- requestDaemonHandoff,
342
- ensureDaemonRunning
343
- };
@@ -1,25 +0,0 @@
1
- // src/live-capabilities.ts
2
- var CODEX_BRIDGE_CAPABILITY = "codex-bridge";
3
- var CODEX_RESUME_CAPABILITY = "codex-resume";
4
- var GEMINI_BRIDGE_CAPABILITY = "gemini-bridge";
5
- var GEMINI_RESUME_CAPABILITY = "gemini-resume";
6
- var GEMINI_BRIDGE_MIN_VERSION = [0, 40, 0];
7
- var version = (raw) => {
8
- const match = raw?.match(/^(\d+)\.(\d+)\.(\d+)/);
9
- return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
10
- };
11
- var geminiLiveCapabilities = (rawVersion) => {
12
- const installed = version(rawVersion);
13
- if (!installed) return [];
14
- for (let index = 0; index < GEMINI_BRIDGE_MIN_VERSION.length; index++) {
15
- if (installed[index] > GEMINI_BRIDGE_MIN_VERSION[index]) break;
16
- if (installed[index] < GEMINI_BRIDGE_MIN_VERSION[index]) return [];
17
- }
18
- return [GEMINI_BRIDGE_CAPABILITY, GEMINI_RESUME_CAPABILITY];
19
- };
20
-
21
- export {
22
- CODEX_BRIDGE_CAPABILITY,
23
- CODEX_RESUME_CAPABILITY,
24
- geminiLiveCapabilities
25
- };
@@ -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
- };