@pushary/agent-hooks 0.89.13 → 0.90.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 (78) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/data/SKILL.md +1 -1
  3. package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
  4. package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
  5. package/dist/bin/pushary-bell-hook.js +4 -4
  6. package/dist/bin/pushary-bell.js +8 -8
  7. package/dist/bin/pushary-claude.js +72 -21
  8. package/dist/bin/pushary-clean.js +16 -42
  9. package/dist/bin/pushary-codex-bridge.js +37 -12
  10. package/dist/bin/pushary-codex-hook.js +45 -12
  11. package/dist/bin/pushary-codex.js +7 -7
  12. package/dist/bin/pushary-connect.js +11 -10
  13. package/dist/bin/pushary-daemon.js +186 -62
  14. package/dist/bin/pushary-disconnect.js +8 -8
  15. package/dist/bin/pushary-doctor.js +108 -25
  16. package/dist/bin/pushary-elicitation-hook.js +4 -4
  17. package/dist/bin/pushary-gemini-bridge.js +33 -11
  18. package/dist/bin/pushary-gemini-hook.js +21 -10
  19. package/dist/bin/pushary-hook.js +13 -13
  20. package/dist/bin/pushary-login.js +18 -7
  21. package/dist/bin/pushary-logout.js +6 -6
  22. package/dist/bin/pushary-mode.js +28 -21
  23. package/dist/bin/pushary-notification-hook.js +7 -7
  24. package/dist/bin/pushary-opencode-hook.js +8 -8
  25. package/dist/bin/pushary-permission-denied-hook.js +11 -11
  26. package/dist/bin/pushary-permission-hook.js +11 -11
  27. package/dist/bin/pushary-post-hook.js +14 -8
  28. package/dist/bin/pushary-prompt-hook.js +7 -7
  29. package/dist/bin/pushary-session-end-hook.js +7 -7
  30. package/dist/bin/pushary-session-start-hook.js +7 -7
  31. package/dist/bin/pushary-setup.js +168 -45
  32. package/dist/bin/pushary-stats.js +4 -4
  33. package/dist/bin/pushary-status.js +11 -10
  34. package/dist/bin/pushary-stop-hook.js +14 -8
  35. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  36. package/dist/bin/pushary-suggestions.js +2 -2
  37. package/dist/bin/pushary-transcripts.js +6 -6
  38. package/dist/bin/pushary-upgrade.js +15 -15
  39. package/dist/bin/pushary-wait.js +5 -5
  40. package/dist/bin/pushary.js +1 -1
  41. package/dist/{chunk-B2HND22S.js → chunk-2LAHXHBR.js} +1 -1
  42. package/dist/{chunk-GMJDQDXU.js → chunk-43GMYSS4.js} +1 -0
  43. package/dist/{chunk-JTBDBKHU.js → chunk-54ELC7D7.js} +1 -1
  44. package/dist/{chunk-C56VHSXU.js → chunk-5NAZ7SFS.js} +1 -1
  45. package/dist/{chunk-4IPSMEGT.js → chunk-7FE2KABY.js} +12 -2
  46. package/dist/{chunk-RAEPAUNJ.js → chunk-7TPQEWUZ.js} +1 -1
  47. package/dist/{chunk-ZO43JTT3.js → chunk-AJYV3VGU.js} +1 -1
  48. package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
  49. package/dist/{chunk-5MNTWAAJ.js → chunk-B7UZHEBL.js} +29 -22
  50. package/dist/{chunk-MNTZKFZI.js → chunk-BQBEH6TV.js} +5 -5
  51. package/dist/{chunk-WHDSJ6PD.js → chunk-BVBEURWJ.js} +1 -1
  52. package/dist/{chunk-ZWEKONU6.js → chunk-CELX75MW.js} +1 -1
  53. package/dist/{chunk-ZP3SNUP7.js → chunk-CNPAT3HL.js} +3 -2
  54. package/dist/{chunk-YXT55PYF.js → chunk-DYP6SMOR.js} +1 -1
  55. package/dist/chunk-DZDI62LY.js +145 -0
  56. package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
  57. package/dist/{chunk-7JZCADBR.js → chunk-F2ICEWZL.js} +101 -19
  58. package/dist/{chunk-VASZ6TWW.js → chunk-FQL5E6NG.js} +2 -2
  59. package/dist/{chunk-JHJHCMAF.js → chunk-FXEVRLHC.js} +20 -2
  60. package/dist/{chunk-JVLB5CAJ.js → chunk-GL3FFM7A.js} +7 -4
  61. package/dist/{chunk-5YEF55AX.js → chunk-H2QZRQR2.js} +1 -1
  62. package/dist/{chunk-S4BLNILH.js → chunk-I463AEG3.js} +1 -1
  63. package/dist/{chunk-ANMHIZZY.js → chunk-JW4KO5AF.js} +1 -1
  64. package/dist/{chunk-3YGGEDJK.js → chunk-KUZWXPUS.js} +3 -2
  65. package/dist/{chunk-N4QJ3CLX.js → chunk-L4E2BIJ2.js} +80 -7
  66. package/dist/{chunk-NBDU3R3G.js → chunk-OSALSIUF.js} +1 -1
  67. package/dist/{chunk-KNLKHJZR.js → chunk-PYA4FLKI.js} +32 -21
  68. package/dist/chunk-QCGNGEGE.js +15 -0
  69. package/dist/chunk-S6LYP7QA.js +127 -0
  70. package/dist/{chunk-OX35BSPV.js → chunk-TDZFPMSZ.js} +70 -11
  71. package/dist/{chunk-M3UABYRT.js → chunk-UZNKVRLE.js} +1 -1
  72. package/dist/{chunk-FOGW6O2Y.js → chunk-VHWCVD42.js} +1 -1
  73. package/dist/{chunk-QINYXLA5.js → chunk-VNMKIJZB.js} +15 -2
  74. package/dist/{chunk-CBGGUEKE.js → chunk-Y6BWPERV.js} +90 -7
  75. package/dist/src/index.d.ts +3 -0
  76. package/dist/src/index.js +11 -11
  77. package/package.json +1 -1
  78. package/dist/chunk-MSTHT2M7.js +0 -72
@@ -1,22 +1,23 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-3YGGEDJK.js";
3
+ } from "./chunk-KUZWXPUS.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-4LL6LG5X.js";
7
7
 
8
8
  // src/process-ownership.ts
9
9
  import { createHash, randomUUID } from "crypto";
10
- import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
10
+ import { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
11
11
  import { createConnection, createServer } from "net";
12
12
  import { join } from "path";
13
13
  var MAX_PROOF_BYTES = 1024;
14
14
  var ownershipDir = (options) => options.dir ?? join(pusharyDir(), "processes");
15
15
  var proofPath = (dir, sessionId) => join(dir, `${createHash("sha256").update(sessionId).digest("hex")}.json`);
16
+ var isOptionalTimestamp = (value) => value === void 0 || typeof value === "string" && Number.isFinite(Date.parse(value));
16
17
  var isProof = (value) => {
17
18
  if (!value || typeof value !== "object") return false;
18
19
  const proof = value;
19
- return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string");
20
+ return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string") && isOptionalTimestamp(proof.startedAt) && isOptionalTimestamp(proof.heartbeatAt);
20
21
  };
21
22
  var removeProof = (path, token) => {
22
23
  const current = readJsonSafe(path);
@@ -34,16 +35,74 @@ var processIsAlive = (pid) => {
34
35
  return error.code === "EPERM";
35
36
  }
36
37
  };
37
- var pruneProofs = (dir) => {
38
+ var fileTimes = (path) => {
39
+ try {
40
+ const stat = statSync(path);
41
+ return { createdAt: stat.birthtimeMs || stat.mtimeMs, modifiedAt: stat.mtimeMs };
42
+ } catch {
43
+ return { createdAt: 0, modifiedAt: 0 };
44
+ }
45
+ };
46
+ var parseTimestamp = (value) => {
47
+ if (value === void 0) return null;
48
+ const parsed = Date.parse(value);
49
+ return Number.isFinite(parsed) ? parsed : null;
50
+ };
51
+ var proofEntry = (proof, path) => {
52
+ const times = fileTimes(path);
53
+ const recordedStart = parseTimestamp(proof.startedAt);
54
+ const startedAt = recordedStart ?? times.createdAt;
55
+ return {
56
+ sessionId: proof.sessionId,
57
+ pid: proof.pid,
58
+ spawnId: proof.spawnId ?? null,
59
+ startedAt,
60
+ lastSeenAt: parseTimestamp(proof.heartbeatAt) ?? recordedStart ?? times.modifiedAt,
61
+ alive: processIsAlive(proof.pid)
62
+ };
63
+ };
64
+ var safeUnlink = (path) => {
65
+ try {
66
+ unlinkSync(path);
67
+ } catch {
68
+ }
69
+ };
70
+ var listProcessProofs = (options = {}) => {
71
+ const dir = ownershipDir(options);
72
+ const entries = [];
38
73
  try {
39
74
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
40
75
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
41
76
  const path = join(dir, entry.name);
42
77
  const stored = readJsonSafe(path);
43
- if (stored.kind !== "ok" || !isProof(stored.value) || !processIsAlive(stored.value.pid)) unlinkSync(path);
78
+ if (stored.kind !== "ok" || !isProof(stored.value)) {
79
+ if (options.reapDead) safeUnlink(path);
80
+ continue;
81
+ }
82
+ const listed = proofEntry(stored.value, path);
83
+ if (!listed.alive && options.reapDead) safeUnlink(path);
84
+ entries.push(listed);
44
85
  }
45
86
  } catch {
46
87
  }
88
+ return entries;
89
+ };
90
+ var pruneProofs = (dir) => {
91
+ listProcessProofs({ dir, reapDead: true });
92
+ };
93
+ var wasSpawned = (entry) => entry.spawnId !== null;
94
+ var silentSpawnedProofs = (entries, now, silenceMs) => {
95
+ const freshestByPid = /* @__PURE__ */ new Map();
96
+ for (const entry of entries) {
97
+ freshestByPid.set(entry.pid, Math.max(freshestByPid.get(entry.pid) ?? 0, entry.lastSeenAt));
98
+ }
99
+ const reported = /* @__PURE__ */ new Set();
100
+ return entries.filter((entry) => {
101
+ if (!entry.alive || !wasSpawned(entry) || reported.has(entry.pid)) return false;
102
+ if (now - (freshestByPid.get(entry.pid) ?? 0) < silenceMs) return false;
103
+ reported.add(entry.pid);
104
+ return true;
105
+ });
47
106
  };
48
107
  var startProcessOwnership = (identity, options = {}) => {
49
108
  const dir = ownershipDir(options);
@@ -88,7 +147,7 @@ var startProcessOwnership = (identity, options = {}) => {
88
147
  return;
89
148
  }
90
149
  try {
91
- proof = { ...identity, port: address.port, token };
150
+ proof = { ...identity, port: address.port, token, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
92
151
  mkdirSync(dir, { recursive: true, mode: 448 });
93
152
  pruneProofs(dir);
94
153
  writeFileSync(path, JSON.stringify(proof), {
@@ -107,6 +166,14 @@ var startProcessOwnership = (identity, options = {}) => {
107
166
  process.once("exit", cleanup);
108
167
  return {
109
168
  ready,
169
+ touch(at = Date.now()) {
170
+ if (stopped || !proof) return;
171
+ proof = { ...proof, heartbeatAt: new Date(at).toISOString() };
172
+ try {
173
+ writeFileSync(path, JSON.stringify(proof), { encoding: "utf8", mode: 384 });
174
+ } catch {
175
+ }
176
+ },
110
177
  stop() {
111
178
  if (stopped) return;
112
179
  stopped = true;
@@ -184,9 +251,25 @@ var verifyProof = (proof, options) => new Promise((resolve) => {
184
251
  });
185
252
  });
186
253
 
254
+ // src/session-idle.ts
255
+ var SESSION_IDLE_TIMEOUT_MS = 10 * 6e4;
256
+ var SESSION_REAP_GRACE_MS = 5 * 6e4;
257
+ var SESSION_REAP_SILENCE_MS = SESSION_IDLE_TIMEOUT_MS + SESSION_REAP_GRACE_MS;
258
+ var idleDeadlinePassed = (idleSince, now, timeoutMs = SESSION_IDLE_TIMEOUT_MS) => idleSince !== null && now - idleSince >= timeoutMs;
259
+ var describeIdleWindow = (timeoutMs) => {
260
+ const minutes = Math.round(timeoutMs / 6e4);
261
+ return minutes >= 1 ? `${minutes} minute${minutes === 1 ? "" : "s"}` : `${Math.round(timeoutMs / 1e3)}s`;
262
+ };
263
+
187
264
  export {
265
+ listProcessProofs,
266
+ silentSpawnedProofs,
188
267
  startProcessOwnership,
189
268
  withSpawnProcessOwnership,
190
269
  ownsLiveProcess,
191
- findLiveProcessBySpawnId
270
+ findLiveProcessBySpawnId,
271
+ SESSION_IDLE_TIMEOUT_MS,
272
+ SESSION_REAP_SILENCE_MS,
273
+ idleDeadlinePassed,
274
+ describeIdleWindow
192
275
  };
@@ -39,6 +39,7 @@ interface AgentEvent {
39
39
  machineId?: string;
40
40
  repoKey?: string;
41
41
  sessionId?: string;
42
+ parentSessionId?: string;
42
43
  error?: string;
43
44
  taskTitle?: string;
44
45
  decisionSource?: DecisionSource;
@@ -80,6 +81,7 @@ declare const handlePostToolUse: (input: {
80
81
  cwd?: string;
81
82
  session_id?: string;
82
83
  transcript_path?: string;
84
+ tool_use_id?: string;
83
85
  }, agent?: AgentIdentity) => Promise<string | undefined>;
84
86
  interface StopHookOutput {
85
87
  readonly decision: 'block';
@@ -88,6 +90,7 @@ interface StopHookOutput {
88
90
  declare const handleStop: (input: {
89
91
  cwd?: string;
90
92
  session_id?: string;
93
+ agent_id?: string;
91
94
  stop_hook_active?: boolean;
92
95
  transcript_path?: string;
93
96
  last_assistant_message?: string;
package/dist/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-5MNTWAAJ.js";
4
- import "../chunk-B2HND22S.js";
3
+ } from "../chunk-B7UZHEBL.js";
4
+ import "../chunk-2LAHXHBR.js";
5
5
  import {
6
6
  fetchAccountPublicKey
7
7
  } from "../chunk-QZCFVWA7.js";
8
- import "../chunk-S4BLNILH.js";
9
- import "../chunk-FOGW6O2Y.js";
8
+ import "../chunk-I463AEG3.js";
9
+ import "../chunk-VHWCVD42.js";
10
10
  import "../chunk-YHG74UFF.js";
11
11
  import {
12
12
  fetchModeOverride,
@@ -16,20 +16,20 @@ import {
16
16
  handlePostToolUse,
17
17
  handleStop,
18
18
  reportEvent
19
- } from "../chunk-OX35BSPV.js";
19
+ } from "../chunk-TDZFPMSZ.js";
20
20
  import {
21
21
  askUser,
22
22
  cancelQuestion,
23
23
  waitForAnswer
24
- } from "../chunk-JHJHCMAF.js";
25
- import "../chunk-N4QJ3CLX.js";
26
- import "../chunk-JTBDBKHU.js";
27
- import "../chunk-3YGGEDJK.js";
24
+ } from "../chunk-FXEVRLHC.js";
25
+ import "../chunk-L4E2BIJ2.js";
26
+ import "../chunk-54ELC7D7.js";
27
+ import "../chunk-KUZWXPUS.js";
28
28
  import "../chunk-BSZYIAZL.js";
29
29
  import "../chunk-SAF6HGAA.js";
30
30
  import "../chunk-7QLSKOSU.js";
31
31
  import "../chunk-KZERVKTD.js";
32
- import "../chunk-ANMHIZZY.js";
32
+ import "../chunk-JW4KO5AF.js";
33
33
  import "../chunk-4LL6LG5X.js";
34
34
  import {
35
35
  getApiKey,
@@ -37,7 +37,7 @@ import {
37
37
  } from "../chunk-2UMNXADU.js";
38
38
  import {
39
39
  resolvePolicy
40
- } from "../chunk-QINYXLA5.js";
40
+ } from "../chunk-VNMKIJZB.js";
41
41
  export {
42
42
  askUser,
43
43
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.13",
3
+ "version": "0.90.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,72 +0,0 @@
1
- import {
2
- hasGeminiHooks
3
- } from "./chunk-JVLB5CAJ.js";
4
- import {
5
- hasCodexHooks
6
- } from "./chunk-3YGGEDJK.js";
7
- import {
8
- HOOK_LOCATOR_BINARY
9
- } from "./chunk-QINYXLA5.js";
10
-
11
- // src/diagnostics/wiring.ts
12
- var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
13
- var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
14
- var EXACT_HOOK_BINARY = /^pushary-[a-z-]*(hook|gate)$/;
15
- var pusharyHookExecutable = (command) => {
16
- const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
17
- for (const raw of tokens.reverse()) {
18
- const token = raw[0] === '"' && raw.at(-1) === '"' || raw[0] === "'" && raw.at(-1) === "'" ? raw.slice(1, -1) : raw;
19
- const name = token.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "") ?? "";
20
- if (name === HOOK_LOCATOR_BINARY || EXACT_HOOK_BINARY.test(name)) return token;
21
- }
22
- return null;
23
- };
24
- var claudeWired = ({ claudeJson, settings, skillExists }) => {
25
- if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
26
- const s = record(settings);
27
- if (s) {
28
- const hooks = JSON.stringify(s.hooks ?? {});
29
- if (CLAUDE_HOOK_BINARY.test(hooks) || hooks.includes(HOOK_LOCATOR_BINARY)) return true;
30
- const allow = record(s.permissions)?.allow;
31
- if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
32
- }
33
- return skillExists;
34
- };
35
- var codexWired = ({ config, hooks, skillExists }) => {
36
- if (record(record(config)?.mcp_servers)?.pushary) return true;
37
- const h = record(hooks);
38
- if (h && hasCodexHooks(h)) return true;
39
- return skillExists;
40
- };
41
- var geminiWired = ({ settings }) => {
42
- const s = record(settings);
43
- if (!s) return false;
44
- if (record(s.mcpServers)?.pushary) return true;
45
- return hasGeminiHooks(s);
46
- };
47
- var cursorWired = ({ userHooks, mcp, pluginDirExists }) => {
48
- if (pluginDirExists) return true;
49
- if (record(record(mcp)?.mcpServers)?.pushary) return true;
50
- const hooks = record(record(userHooks)?.hooks);
51
- if (!hooks) return false;
52
- return Object.values(hooks).some((entries) => Array.isArray(entries) && entries.some((entry) => pusharyHookExecutable(String(record(entry)?.command ?? "")) !== null));
53
- };
54
- var cursorMcpAuthorization = (pluginMcp, userMcp) => {
55
- const pluginServers = record(record(pluginMcp)?.mcpServers);
56
- const usePlugin = pluginServers !== null && Object.hasOwn(pluginServers, "pushary");
57
- const servers = usePlugin ? pluginServers : record(record(userMcp)?.mcpServers);
58
- const headers = record(record(servers?.pushary)?.headers);
59
- return {
60
- authorization: typeof headers?.Authorization === "string" ? headers.Authorization : null,
61
- source: usePlugin ? "plugin mcp.json" : "~/.cursor/mcp.json"
62
- };
63
- };
64
-
65
- export {
66
- pusharyHookExecutable,
67
- claudeWired,
68
- codexWired,
69
- geminiWired,
70
- cursorWired,
71
- cursorMcpAuthorization
72
- };