@pushary/agent-hooks 0.89.9 → 0.89.11

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 (68) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/bin/pushary-bell-hook.js +4 -4
  3. package/dist/bin/pushary-bell.js +7 -7
  4. package/dist/bin/pushary-claude.js +84 -36
  5. package/dist/bin/pushary-clean.js +52 -45
  6. package/dist/bin/pushary-codex-bridge.js +15 -9
  7. package/dist/bin/pushary-codex-hook.js +12 -8
  8. package/dist/bin/pushary-codex.js +7 -7
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-daemon.js +16 -16
  11. package/dist/bin/pushary-disconnect.js +34 -9
  12. package/dist/bin/pushary-doctor.js +19 -19
  13. package/dist/bin/pushary-elicitation-hook.js +4 -4
  14. package/dist/bin/pushary-gemini-bridge.js +15 -8
  15. package/dist/bin/pushary-gemini-hook.js +8 -8
  16. package/dist/bin/pushary-hook.js +11 -10
  17. package/dist/bin/pushary-login.js +4 -4
  18. package/dist/bin/pushary-logout.js +4 -4
  19. package/dist/bin/pushary-mode.js +3 -3
  20. package/dist/bin/pushary-notification-hook.js +7 -7
  21. package/dist/bin/pushary-opencode-hook.js +8 -8
  22. package/dist/bin/pushary-permission-denied-hook.js +11 -10
  23. package/dist/bin/pushary-permission-hook.js +11 -10
  24. package/dist/bin/pushary-post-hook.js +7 -7
  25. package/dist/bin/pushary-prompt-hook.js +7 -7
  26. package/dist/bin/pushary-session-end-hook.js +7 -7
  27. package/dist/bin/pushary-session-start-hook.js +7 -7
  28. package/dist/bin/pushary-setup.js +52 -54
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +8 -8
  31. package/dist/bin/pushary-stop-hook.js +7 -7
  32. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  33. package/dist/bin/pushary-upgrade.js +15 -15
  34. package/dist/bin/pushary-wait.js +3 -3
  35. package/dist/{chunk-SS72Z7FV.js → chunk-253WC6O5.js} +1 -1
  36. package/dist/{chunk-T3LRWZKI.js → chunk-2LPEZYMC.js} +1 -1
  37. package/dist/{chunk-O5B7EK5U.js → chunk-34GN3IPX.js} +1 -1
  38. package/dist/{chunk-V6N4Q223.js → chunk-3CUDMTL4.js} +1 -1
  39. package/dist/{chunk-VZKXBFUA.js → chunk-3HFK3UXM.js} +1 -1
  40. package/dist/{chunk-543KNJKC.js → chunk-3SQYKLBW.js} +3 -3
  41. package/dist/{chunk-ATBKJNWS.js → chunk-6EKQZA6R.js} +5 -1
  42. package/dist/{chunk-NKHEHEUM.js → chunk-6XUABEX2.js} +1 -1
  43. package/dist/{chunk-I5L6SK5V.js → chunk-BLODMQKJ.js} +1 -1
  44. package/dist/{chunk-DKDDLF5P.js → chunk-DKWM5T3P.js} +1 -1
  45. package/dist/{chunk-4XZCDSNG.js → chunk-FXIL7RCL.js} +5 -5
  46. package/dist/{chunk-BAE45YLP.js → chunk-FZ73G2RO.js} +3 -3
  47. package/dist/{chunk-PMHTCDEO.js → chunk-IUN6VOKV.js} +1 -1
  48. package/dist/{chunk-OFRNZWC4.js → chunk-IUWRVTQK.js} +1 -1
  49. package/dist/{chunk-EJT2ER6G.js → chunk-J4K75V4D.js} +46 -35
  50. package/dist/{chunk-GKAKAOKC.js → chunk-JHJHCMAF.js} +65 -12
  51. package/dist/{chunk-J2JZJZKB.js → chunk-KVE5RCQL.js} +1 -1
  52. package/dist/{chunk-A4O5ROZK.js → chunk-MZGWKIUZ.js} +2 -2
  53. package/dist/chunk-NCVIWGVA.js +18 -0
  54. package/dist/{chunk-C623LWJR.js → chunk-OLAGKKSR.js} +1 -1
  55. package/dist/{chunk-EA3E74P2.js → chunk-PZRWFWSR.js} +6 -6
  56. package/dist/{chunk-VA7VUNRD.js → chunk-Q223OM5J.js} +58 -8
  57. package/dist/{chunk-GMI5QZLG.js → chunk-QA72MKIT.js} +5 -1
  58. package/dist/{chunk-AIGDBRIJ.js → chunk-SYUIXXQW.js} +1 -1
  59. package/dist/{chunk-TF247BET.js → chunk-TEZ2FZD6.js} +1 -1
  60. package/dist/{chunk-KOPUTJQC.js → chunk-TG3T3M4K.js} +1 -1
  61. package/dist/{chunk-FX5QSMPY.js → chunk-TRVIRETT.js} +1 -1
  62. package/dist/{chunk-U5TQCQ25.js → chunk-TY5HVMXT.js} +1 -1
  63. package/dist/chunk-UDBJHQMG.js +240 -0
  64. package/dist/{chunk-PZA7G4CN.js → chunk-X4ZUUFKV.js} +1 -0
  65. package/dist/{chunk-L4C2JXJS.js → chunk-X6RXX2NF.js} +5 -0
  66. package/dist/src/index.d.ts +5 -0
  67. package/dist/src/index.js +11 -10
  68. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-VA7VUNRD.js";
3
+ } from "./chunk-Q223OM5J.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-O5B7EK5U.js";
3
+ } from "./chunk-34GN3IPX.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-GMI5QZLG.js";
6
+ } from "./chunk-QA72MKIT.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -0,0 +1,18 @@
1
+ import {
2
+ shouldDeferToNativeMode
3
+ } from "./chunk-X6RXX2NF.js";
4
+
5
+ // src/native-defer.ts
6
+ var nativeDeferOptions = () => {
7
+ const respect = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
8
+ const bypass = process.env.PUSHARY_DEFER_BYPASS;
9
+ return {
10
+ respectPermissionMode: !(respect === "0" || respect === "false"),
11
+ deferBypass: bypass === "1" || bypass === "true"
12
+ };
13
+ };
14
+ var defersToNativeMode = (permissionMode, toolName) => shouldDeferToNativeMode(permissionMode, toolName, nativeDeferOptions());
15
+
16
+ export {
17
+ defersToNativeMode
18
+ };
@@ -5,7 +5,7 @@ import {
5
5
  generateSessionKey,
6
6
  uploadTranscriptRecords,
7
7
  wrapSessionKey
8
- } from "./chunk-PMHTCDEO.js";
8
+ } from "./chunk-IUN6VOKV.js";
9
9
 
10
10
  // src/live-session.ts
11
11
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  detectClaudeVersion
3
- } from "./chunk-J2JZJZKB.js";
3
+ } from "./chunk-KVE5RCQL.js";
4
4
  import {
5
5
  claudeWired,
6
6
  codexWired,
7
7
  geminiWired
8
- } from "./chunk-BAE45YLP.js";
8
+ } from "./chunk-FZ73G2RO.js";
9
9
  import {
10
10
  GEMINI_HOOK_BINARY,
11
11
  addClaudeMcpServer,
@@ -14,14 +14,14 @@ import {
14
14
  addPusharyHooks,
15
15
  addPusharyToolPermissions,
16
16
  hasGeminiHooks
17
- } from "./chunk-VA7VUNRD.js";
17
+ } from "./chunk-Q223OM5J.js";
18
18
  import {
19
19
  guardBinary
20
- } from "./chunk-AIGDBRIJ.js";
20
+ } from "./chunk-SYUIXXQW.js";
21
21
  import {
22
22
  resolveGlobalBinDir,
23
23
  resolveGlobalBinary
24
- } from "./chunk-PZA7G4CN.js";
24
+ } from "./chunk-X4ZUUFKV.js";
25
25
  import {
26
26
  CODEX_HOOK_BINARY,
27
27
  addCodexHookTrust,
@@ -32,7 +32,7 @@ import {
32
32
  codexHooksJson,
33
33
  hasCodexHooks,
34
34
  vscodePluginMcp
35
- } from "./chunk-GMI5QZLG.js";
35
+ } from "./chunk-QA72MKIT.js";
36
36
  import {
37
37
  readJsonSafe
38
38
  } from "./chunk-XX2V4QS5.js";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  PRETOOLUSE_GATED_TOOLS,
3
3
  PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
- } from "./chunk-ATBKJNWS.js";
4
+ } from "./chunk-6EKQZA6R.js";
5
5
  import {
6
6
  guardBinary
7
- } from "./chunk-AIGDBRIJ.js";
7
+ } from "./chunk-SYUIXXQW.js";
8
8
  import {
9
9
  isProtectedByScope
10
- } from "./chunk-GMI5QZLG.js";
10
+ } from "./chunk-QA72MKIT.js";
11
11
  import {
12
12
  HOOK_BUDGETS,
13
13
  HOOK_LOCATOR_BINARY
14
- } from "./chunk-L4C2JXJS.js";
14
+ } from "./chunk-X6RXX2NF.js";
15
15
 
16
16
  // src/claude-config.ts
17
17
  import { join } from "path";
@@ -60,6 +60,7 @@ var CLAUDE_HOOK_EVENTS = [
60
60
  var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
61
61
 
62
62
  // src/claude-config.ts
63
+ import { isDeepStrictEqual } from "util";
63
64
  var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
64
65
  var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
65
66
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -85,14 +86,59 @@ var isPusharyHook = (entry2, scope) => {
85
86
  return namesPusharyBinary(command);
86
87
  });
87
88
  };
89
+ var pusharyMcpServer = (apiKey) => ({
90
+ type: "http",
91
+ url: PUSHARY_MCP_URL,
92
+ headers: { Authorization: `Bearer ${apiKey}` }
93
+ });
88
94
  var addClaudeMcpServer = (config, apiKey) => {
89
95
  const mcpServers = ensureRecord(config, "mcpServers");
90
- mcpServers.pushary = {
91
- type: "http",
92
- url: PUSHARY_MCP_URL,
93
- headers: { Authorization: `Bearer ${apiKey}` }
96
+ mcpServers.pushary = pusharyMcpServer(apiKey);
97
+ };
98
+ var isClaudeMcpSnapshot = (value) => {
99
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
100
+ const snapshot = value;
101
+ return typeof snapshot.hadMcpServers === "boolean" && typeof snapshot.mcpServersWasRecord === "boolean" && (!snapshot.hadMcpServers || snapshot.mcpServersWasRecord || Object.hasOwn(snapshot, "mcpServers")) && typeof snapshot.hadPushary === "boolean" && (!snapshot.hadPushary || Object.hasOwn(snapshot, "pushary")) && (!Object.hasOwn(snapshot, "installedPushary") || asRecord(snapshot.installedPushary)?.type === "http" && asRecord(snapshot.installedPushary)?.url === PUSHARY_MCP_URL);
102
+ };
103
+ var captureClaudeMcpServer = (config, apiKey) => {
104
+ const mcpServers = asRecord(config.mcpServers);
105
+ const hadMcpServers = Object.hasOwn(config, "mcpServers");
106
+ return {
107
+ hadMcpServers,
108
+ mcpServersWasRecord: Boolean(mcpServers),
109
+ ...hadMcpServers && !mcpServers ? { mcpServers: structuredClone(config.mcpServers) } : {},
110
+ hadPushary: Boolean(mcpServers && Object.hasOwn(mcpServers, "pushary")),
111
+ ...mcpServers && Object.hasOwn(mcpServers, "pushary") ? { pushary: structuredClone(mcpServers.pushary) } : {},
112
+ ...apiKey ? { installedPushary: pusharyMcpServer(apiKey) } : {}
94
113
  };
95
114
  };
115
+ var updateClaudeMcpSnapshot = (snapshot, apiKey) => ({
116
+ ...snapshot,
117
+ installedPushary: pusharyMcpServer(apiKey)
118
+ });
119
+ var restoreClaudeMcpServer = (config, snapshot) => {
120
+ const mcpServers = asRecord(config.mcpServers);
121
+ const current = asRecord(mcpServers?.pushary);
122
+ if (Object.hasOwn(snapshot, "installedPushary")) {
123
+ if (!isDeepStrictEqual(mcpServers?.pushary, snapshot.installedPushary)) return false;
124
+ } else if (current?.type !== "http" || current.url !== PUSHARY_MCP_URL) {
125
+ return false;
126
+ }
127
+ if (!snapshot.mcpServersWasRecord) {
128
+ if (Object.keys(mcpServers).length === 1) {
129
+ if (snapshot.hadMcpServers) config.mcpServers = structuredClone(snapshot.mcpServers);
130
+ else delete config.mcpServers;
131
+ } else {
132
+ delete mcpServers.pushary;
133
+ }
134
+ } else if (snapshot.hadPushary) {
135
+ mcpServers.pushary = structuredClone(snapshot.pushary);
136
+ } else {
137
+ delete mcpServers.pushary;
138
+ if (!snapshot.hadMcpServers && Object.keys(mcpServers).length === 0) delete config.mcpServers;
139
+ }
140
+ return true;
141
+ };
96
142
  var removeClaudeMcpServers = (config) => {
97
143
  let changed = false;
98
144
  const removeFrom = (target) => {
@@ -285,6 +331,10 @@ export {
285
331
  parseVersion,
286
332
  compareVersion,
287
333
  addClaudeMcpServer,
334
+ isClaudeMcpSnapshot,
335
+ captureClaudeMcpServer,
336
+ updateClaudeMcpSnapshot,
337
+ restoreClaudeMcpServer,
288
338
  removeClaudeMcpServers,
289
339
  addPusharyToolPermissions,
290
340
  addPusharyHooks,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-L4C2JXJS.js";
4
+ } from "./chunk-X6RXX2NF.js";
5
5
 
6
6
  // src/codex-config.ts
7
7
  import { createHash } from "crypto";
@@ -268,6 +268,8 @@ var vscodePluginDir = () => join2(homedir2(), ".pushary", "plugins", "vscode");
268
268
  var vscodePluginMcp = () => join2(vscodePluginDir(), ".mcp.json");
269
269
  var claudeSettingsLocal = () => join2(homedir2(), ".claude", "settings.local.json");
270
270
  var pusharyDir = () => join2(homedir2(), ".pushary");
271
+ var claudeMcpReceipt = () => join2(pusharyDir(), "setup", "claude-mcp.json");
272
+ var hermesConfigReceipt = () => join2(pusharyDir(), "setup", "hermes-config.json");
271
273
  var codexSkillDir2 = () => join2(codexHome(), "skills", "pushary");
272
274
  var codexAgentsMd2 = () => join2(codexHome(), "AGENTS.md");
273
275
  var geminiSettings = () => join2(homedir2(), ".gemini", "settings.json");
@@ -306,6 +308,8 @@ export {
306
308
  vscodePluginMcp,
307
309
  claudeSettingsLocal,
308
310
  pusharyDir,
311
+ claudeMcpReceipt,
312
+ hermesConfigReceipt,
309
313
  codexSkillDir2,
310
314
  codexAgentsMd2,
311
315
  geminiSettings,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  quoteIfNeeded
3
- } from "./chunk-PZA7G4CN.js";
3
+ } from "./chunk-X4ZUUFKV.js";
4
4
 
5
5
  // src/hook-command.ts
6
6
  var HOOK_SHELL = "/bin/sh -c";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-GMI5QZLG.js";
3
+ } from "./chunk-QA72MKIT.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-XX2V4QS5.js";
@@ -5,7 +5,7 @@ import {
5
5
  FILE_TARGET_TOOLS,
6
6
  redactSecrets,
7
7
  redactSecretsDeep
8
- } from "./chunk-L4C2JXJS.js";
8
+ } from "./chunk-X6RXX2NF.js";
9
9
 
10
10
  // ../hook-mapping/src/describe.ts
11
11
  import { isAbsolute, relative, resolve } from "path";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-V6N4Q223.js";
3
+ } from "./chunk-3CUDMTL4.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getMachineId
3
- } from "./chunk-DKDDLF5P.js";
3
+ } from "./chunk-DKWM5T3P.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -0,0 +1,240 @@
1
+ import {
2
+ resolvePathBinary
3
+ } from "./chunk-X4ZUUFKV.js";
4
+
5
+ // src/hermes-config.ts
6
+ import { execFileSync } from "child_process";
7
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
8
+ import { homedir, tmpdir } from "os";
9
+ import { join } from "path";
10
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
11
+ var isSnapshot = (value) => isRecord(value) && typeof value.present === "boolean" && Object.hasOwn(value, "value");
12
+ var isHermesConfigReceipt = (value) => {
13
+ if (!isRecord(value)) return false;
14
+ return ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"].every((key) => isSnapshot(value[key])) && typeof value.pushary_enabled === "boolean" && typeof value.clarify_disabled === "boolean";
15
+ };
16
+ var HERMES_CONFIG_SCRIPT = String.raw`
17
+ import json, os, sys
18
+ from hermes_cli.config import load_config, save_config
19
+
20
+ mode, receipt_path = sys.argv[1], sys.argv[2]
21
+ c = load_config()
22
+
23
+ def snap(obj, key):
24
+ return {"present": key in obj, "value": obj.get(key)}
25
+
26
+ restore_conflicts = []
27
+
28
+ def restore(obj, key, before, ours):
29
+ if before.get("present") and before.get("value") == ours:
30
+ return
31
+ if before.get("present"):
32
+ if key in obj and obj.get(key) == before.get("value"):
33
+ return
34
+ elif key not in obj:
35
+ return
36
+ if obj.get(key) != ours:
37
+ restore_conflicts.append(key)
38
+ return
39
+ if before.get("present"):
40
+ obj[key] = before.get("value")
41
+ else:
42
+ obj.pop(key, None)
43
+
44
+ def restore_container(obj, key, before, current, changed):
45
+ raw = obj.get(key)
46
+ if isinstance(before.get("value"), dict):
47
+ if not isinstance(raw, dict):
48
+ if changed:
49
+ restore_conflicts.append(key)
50
+ return
51
+ if current or before.get("present"):
52
+ obj[key] = current
53
+ else:
54
+ obj.pop(key, None)
55
+ elif isinstance(raw, dict) and current:
56
+ restore_conflicts.append(key)
57
+ else:
58
+ restore(obj, key, before, {})
59
+
60
+ def restore_list_member(obj, key, before, ours, ours_before):
61
+ raw = obj.get(key)
62
+ if isinstance(before.get("value"), list):
63
+ if ours_before:
64
+ return
65
+ if not isinstance(raw, list):
66
+ restore_conflicts.append(key)
67
+ return
68
+ remaining = [item for item in raw if item != ours]
69
+ if remaining or before.get("present"):
70
+ obj[key] = remaining
71
+ else:
72
+ obj.pop(key, None)
73
+ return
74
+ if before.get("present") and raw == before.get("value"):
75
+ return
76
+ if not before.get("present") and key not in obj:
77
+ return
78
+ if not isinstance(raw, list):
79
+ restore_conflicts.append(key)
80
+ return
81
+ remaining = [item for item in raw if item != ours]
82
+ if remaining:
83
+ restore_conflicts.append(key)
84
+ else:
85
+ restore(obj, key, before, raw)
86
+
87
+ def valid_snapshot(value):
88
+ return isinstance(value, dict) and isinstance(value.get("present"), bool) and "value" in value
89
+
90
+ def valid_receipt(value):
91
+ snapshots = ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"]
92
+ return (isinstance(value, dict)
93
+ and all(valid_snapshot(value.get(key)) for key in snapshots)
94
+ and isinstance(value.get("pushary_enabled"), bool)
95
+ and isinstance(value.get("clarify_disabled"), bool))
96
+
97
+ if mode == "setup":
98
+ security_before = snap(c, "security")
99
+ s = c.get("security") if isinstance(c.get("security"), dict) else {}
100
+ approval_before = snap(s, "approval")
101
+ ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
102
+ plugins_before = snap(c, "plugins")
103
+ p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
104
+ agent_before = snap(c, "agent")
105
+ a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
106
+ enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
107
+ disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
108
+ receipt = {
109
+ "security": security_before,
110
+ "approval": approval_before,
111
+ "transport": snap(ap, "transport"),
112
+ "fallback": snap(ap, "transport_fallback"),
113
+ "plugins": plugins_before,
114
+ "enabled": snap(p, "enabled"),
115
+ "pushary_enabled": "pushary" in enabled,
116
+ "agent": agent_before,
117
+ "disabled": snap(a, "disabled_toolsets"),
118
+ "clarify_disabled": "clarify" in disabled,
119
+ }
120
+ if os.path.exists(receipt_path):
121
+ with open(receipt_path, encoding="utf-8") as handle:
122
+ existing_receipt = json.load(handle)
123
+ if not valid_receipt(existing_receipt):
124
+ raise ValueError("invalid Pushary Hermes setup receipt")
125
+ else:
126
+ os.makedirs(os.path.dirname(receipt_path), exist_ok=True)
127
+ temp = receipt_path + ".tmp"
128
+ with open(temp, "x", encoding="utf-8") as handle:
129
+ json.dump(receipt, handle)
130
+ os.chmod(temp, 0o600)
131
+ os.replace(temp, receipt_path)
132
+ ap["transport"] = "pushary"
133
+ ap["transport_fallback"] = "builtin"
134
+ s["approval"] = ap
135
+ c["security"] = s
136
+ p["enabled"] = enabled if "pushary" in enabled else enabled + ["pushary"]
137
+ c["plugins"] = p
138
+ a["disabled_toolsets"] = disabled if "clarify" in disabled else disabled + ["clarify"]
139
+ c["agent"] = a
140
+ else:
141
+ receipt_exists = os.path.exists(receipt_path)
142
+ try:
143
+ with open(receipt_path, encoding="utf-8") as handle:
144
+ receipt = json.load(handle)
145
+ except (OSError, ValueError):
146
+ if receipt_exists:
147
+ raise
148
+ receipt = None
149
+ s = c.get("security") if isinstance(c.get("security"), dict) else {}
150
+ ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
151
+ if receipt:
152
+ if not valid_receipt(receipt):
153
+ raise ValueError("invalid Pushary Hermes setup receipt")
154
+ approval_changed = (not receipt["transport"].get("present")
155
+ or receipt["transport"].get("value") != "pushary"
156
+ or not receipt["fallback"].get("present")
157
+ or receipt["fallback"].get("value") != "builtin")
158
+ plugins_changed = not (isinstance(receipt["enabled"].get("value"), list)
159
+ and receipt["pushary_enabled"])
160
+ agent_changed = not (isinstance(receipt["disabled"].get("value"), list)
161
+ and receipt["clarify_disabled"])
162
+ restore(ap, "transport", receipt["transport"], "pushary")
163
+ restore(ap, "transport_fallback", receipt["fallback"], "builtin")
164
+ else:
165
+ if ap.get("transport") == "pushary": ap.pop("transport", None)
166
+ if ap.get("transport_fallback") == "builtin": ap.pop("transport_fallback", None)
167
+ if receipt:
168
+ restore_container(s, "approval", receipt["approval"], ap, approval_changed)
169
+ restore_container(c, "security", receipt["security"], s, approval_changed)
170
+ else:
171
+ if ap: s["approval"] = ap
172
+ else: s.pop("approval", None)
173
+ if s: c["security"] = s
174
+ else: c.pop("security", None)
175
+
176
+ p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
177
+ if receipt:
178
+ restore_list_member(p, "enabled", receipt["enabled"], "pushary", receipt["pushary_enabled"])
179
+ restore_container(c, "plugins", receipt["plugins"], p, plugins_changed)
180
+ else:
181
+ enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
182
+ enabled = [item for item in enabled if item != "pushary"]
183
+ if enabled: p["enabled"] = enabled
184
+ else: p.pop("enabled", None)
185
+ if p: c["plugins"] = p
186
+ else: c.pop("plugins", None)
187
+
188
+ a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
189
+ if receipt:
190
+ restore_list_member(a, "disabled_toolsets", receipt["disabled"], "clarify", receipt["clarify_disabled"])
191
+ restore_container(c, "agent", receipt["agent"], a, agent_changed)
192
+ else:
193
+ disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
194
+ disabled = [item for item in disabled if item != "clarify"]
195
+ if disabled: a["disabled_toolsets"] = disabled
196
+ else: a.pop("disabled_toolsets", None)
197
+ if a: c["agent"] = a
198
+ else: c.pop("agent", None)
199
+
200
+ if receipt and restore_conflicts:
201
+ raise ValueError("Hermes config changed after Pushary setup")
202
+
203
+ save_config(c)
204
+ `;
205
+ var resolveHermesPython = () => {
206
+ const launcher = resolvePathBinary("hermes");
207
+ if (launcher && process.platform !== "win32") {
208
+ try {
209
+ const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
210
+ const interpreter = shebang?.startsWith("#!") ? shebang.slice(2).trim().split(/\s+/)[0] : void 0;
211
+ if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
212
+ } catch {
213
+ }
214
+ }
215
+ const root = join(homedir(), ".hermes", "hermes-agent", "venv");
216
+ const candidates = process.platform === "win32" ? [join(root, "Scripts", "python.exe"), join(root, "Scripts", "python3.exe")] : [join(root, "bin", "python3"), join(root, "bin", "python")];
217
+ return candidates.find(existsSync) ?? null;
218
+ };
219
+ var runHermesPythonScript = (python, script, ...args) => {
220
+ const path = join(tmpdir(), `pushary-hermes-${process.pid}-${Date.now()}.py`);
221
+ try {
222
+ writeFileSync(path, script, { encoding: "utf-8", mode: 384 });
223
+ execFileSync(python, [path, ...args], { env: { ...process.env }, stdio: "pipe", timeout: 15e3 });
224
+ return true;
225
+ } catch {
226
+ return false;
227
+ } finally {
228
+ try {
229
+ rmSync(path, { force: true });
230
+ } catch {
231
+ }
232
+ }
233
+ };
234
+
235
+ export {
236
+ isHermesConfigReceipt,
237
+ HERMES_CONFIG_SCRIPT,
238
+ resolveHermesPython,
239
+ runHermesPythonScript
240
+ };
@@ -105,6 +105,7 @@ export {
105
105
  npmErrorMessage,
106
106
  execNpm,
107
107
  resolveGlobalBinary,
108
+ resolvePathBinary,
108
109
  resolveBinary,
109
110
  resolvePackageVersionFromBinary,
110
111
  resolveGlobalPackageVersion,
@@ -1080,6 +1080,9 @@ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
1080
1080
  if (permissionMode === "bypassPermissions") return options.deferBypass === true;
1081
1081
  return false;
1082
1082
  };
1083
+ var ASKED_IN_EVERY_MODE_TOOLS = ["AskUserQuestion", "ExitPlanMode"];
1084
+ var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
1085
+ var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
1083
1086
  var resolveGate = (input) => {
1084
1087
  const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
1085
1088
  if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
@@ -1088,6 +1091,7 @@ var resolveGate = (input) => {
1088
1091
  const autoApproves = verdict === "allow";
1089
1092
  const autoDenies = verdict === "deny";
1090
1093
  if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
1094
+ if (isAskedInEveryMode(toolName)) return { kind: "gate", policy };
1091
1095
  if (isModeStateDegraded(modeState)) {
1092
1096
  if (autoApproves) {
1093
1097
  return survivesDegraded(config, toolName, toolInputs, cwd, repoKey, sessionId) ? { kind: "allow", policy, source: "allowlist" } : { kind: "defer", policy };
@@ -1359,6 +1363,7 @@ export {
1359
1363
  resolvePolicy,
1360
1364
  KILL_REASON,
1361
1365
  shouldDeferToNativeMode,
1366
+ ASKED_IN_EVERY_MODE_TOOLS,
1362
1367
  resolveGate,
1363
1368
  modeStateFromResponse,
1364
1369
  claudePreToolUseOutput,
@@ -121,6 +121,7 @@ interface AskUserParams {
121
121
  actionBody?: string;
122
122
  scopePath?: string;
123
123
  toolUseId?: string;
124
+ waitEndsAt?: string;
124
125
  requestId?: string;
125
126
  }
126
127
  interface AskUserResponse {
@@ -132,6 +133,10 @@ interface AskUserResponse {
132
133
  };
133
134
  suppressed?: boolean;
134
135
  noDevices?: boolean;
136
+ answered?: boolean;
137
+ value?: string;
138
+ note?: string;
139
+ waitEndsAt?: string;
135
140
  answerUrl?: string;
136
141
  }
137
142
  interface AnswerResult {
package/dist/src/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-EJT2ER6G.js";
4
- import "../chunk-ATBKJNWS.js";
5
- import "../chunk-VZKXBFUA.js";
3
+ } from "../chunk-J4K75V4D.js";
4
+ import "../chunk-6EKQZA6R.js";
5
+ import "../chunk-NCVIWGVA.js";
6
+ import "../chunk-3HFK3UXM.js";
6
7
  import "../chunk-YHG74UFF.js";
7
8
  import {
8
9
  fetchModeOverride,
@@ -12,18 +13,18 @@ import {
12
13
  handlePostToolUse,
13
14
  handleStop,
14
15
  reportEvent
15
- } from "../chunk-4XZCDSNG.js";
16
+ } from "../chunk-FXIL7RCL.js";
16
17
  import {
17
18
  askUser,
18
19
  cancelQuestion,
19
20
  waitForAnswer
20
- } from "../chunk-GKAKAOKC.js";
21
- import "../chunk-KOPUTJQC.js";
22
- import "../chunk-DKDDLF5P.js";
23
- import "../chunk-GMI5QZLG.js";
21
+ } from "../chunk-JHJHCMAF.js";
22
+ import "../chunk-TG3T3M4K.js";
23
+ import "../chunk-DKWM5T3P.js";
24
+ import "../chunk-QA72MKIT.js";
24
25
  import "../chunk-BSZYIAZL.js";
25
26
  import "../chunk-SAF6HGAA.js";
26
- import "../chunk-OFRNZWC4.js";
27
+ import "../chunk-IUWRVTQK.js";
27
28
  import "../chunk-7QLSKOSU.js";
28
29
  import "../chunk-KZERVKTD.js";
29
30
  import "../chunk-XX2V4QS5.js";
@@ -33,7 +34,7 @@ import {
33
34
  } from "../chunk-2UMNXADU.js";
34
35
  import {
35
36
  resolvePolicy
36
- } from "../chunk-L4C2JXJS.js";
37
+ } from "../chunk-X6RXX2NF.js";
37
38
  export {
38
39
  askUser,
39
40
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.9",
3
+ "version": "0.89.11",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",