@pushary/agent-hooks 0.97.0 → 0.99.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 (80) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/bin/pushary-bell-hook.js +20 -19
  3. package/dist/bin/pushary-bell.js +7 -6
  4. package/dist/bin/pushary-claude.js +21 -20
  5. package/dist/bin/pushary-clean.js +19 -18
  6. package/dist/bin/pushary-codex-bridge.js +11 -10
  7. package/dist/bin/pushary-codex-hook.js +33 -23
  8. package/dist/bin/pushary-codex.js +8 -7
  9. package/dist/bin/pushary-connect.js +7 -6
  10. package/dist/bin/pushary-cowork.js +3 -3
  11. package/dist/bin/pushary-daemon.js +24 -23
  12. package/dist/bin/pushary-disconnect.js +6 -6
  13. package/dist/bin/pushary-doctor.js +22 -21
  14. package/dist/bin/pushary-elicitation-hook.js +19 -18
  15. package/dist/bin/pushary-gemini-bridge.js +10 -9
  16. package/dist/bin/pushary-gemini-hook.js +22 -21
  17. package/dist/bin/pushary-hook.js +24 -23
  18. package/dist/bin/pushary-login.js +8 -7
  19. package/dist/bin/pushary-logout.js +5 -4
  20. package/dist/bin/pushary-mode.js +3 -3
  21. package/dist/bin/pushary-notification-hook.js +21 -20
  22. package/dist/bin/pushary-opencode-hook.js +22 -21
  23. package/dist/bin/pushary-permission-denied-hook.js +25 -24
  24. package/dist/bin/pushary-permission-hook.js +24 -23
  25. package/dist/bin/pushary-post-hook.js +21 -20
  26. package/dist/bin/pushary-prompt-hook.js +21 -20
  27. package/dist/bin/pushary-session-end-hook.js +21 -20
  28. package/dist/bin/pushary-session-start-hook.js +24 -23
  29. package/dist/bin/pushary-setup.js +38 -28
  30. package/dist/bin/pushary-stats.js +2 -2
  31. package/dist/bin/pushary-status.js +11 -10
  32. package/dist/bin/pushary-stop-hook.js +21 -20
  33. package/dist/bin/pushary-stopfailure-hook.js +21 -20
  34. package/dist/bin/pushary-transcript-register.js +18 -17
  35. package/dist/bin/pushary-transcripts.js +6 -5
  36. package/dist/bin/pushary-upgrade.js +15 -14
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-SW37HB27.js → chunk-3RVCQDC4.js} +1 -1
  39. package/dist/{chunk-AXSDMDJI.js → chunk-3ZAEUPM4.js} +1 -1
  40. package/dist/{chunk-B3557BDL.js → chunk-4C4XFAGJ.js} +2 -2
  41. package/dist/{chunk-VTUN7WSG.js → chunk-4LL6LG5X.js} +1 -43
  42. package/dist/{chunk-VF7YOT2W.js → chunk-4PBCNVI3.js} +4 -2
  43. package/dist/{chunk-IZKCA35M.js → chunk-562UJ2DA.js} +2 -2
  44. package/dist/{chunk-ROTIPRM4.js → chunk-5KLAETSU.js} +1 -1
  45. package/dist/{chunk-DGUMR5LZ.js → chunk-64WARXGV.js} +5 -5
  46. package/dist/{chunk-VDTC5DEM.js → chunk-6C6MS57P.js} +1 -1
  47. package/dist/{chunk-KDPYXMUI.js → chunk-6IFUXNFX.js} +2 -2
  48. package/dist/{chunk-XDMHLQMH.js → chunk-6KLCP7VF.js} +4 -4
  49. package/dist/{chunk-VCKQ45QA.js → chunk-A76GNRIK.js} +1 -1
  50. package/dist/{chunk-ICUBJNFL.js → chunk-CP34FI4B.js} +2 -2
  51. package/dist/{chunk-LYWTLNMG.js → chunk-CRFLU455.js} +4 -4
  52. package/dist/{chunk-WIKEADOS.js → chunk-EXHXR2X5.js} +1 -1
  53. package/dist/chunk-GCD3SQSV.js +44 -0
  54. package/dist/{chunk-EHQC3GWL.js → chunk-HB2KB7QL.js} +1 -1
  55. package/dist/{chunk-C2FYSFTQ.js → chunk-ID5FDKDZ.js} +1 -1
  56. package/dist/{chunk-BYH75I7Y.js → chunk-IRLTW6KF.js} +1 -1
  57. package/dist/{chunk-67RNDRKL.js → chunk-JBJBVERL.js} +111 -31
  58. package/dist/{chunk-3SNTEHOH.js → chunk-JL33EDN7.js} +4 -4
  59. package/dist/{chunk-FU5SQKAY.js → chunk-JXELRCU5.js} +16 -23
  60. package/dist/{chunk-YRXVC4HL.js → chunk-K4XFCEVI.js} +1 -1
  61. package/dist/{chunk-GGYH6LIE.js → chunk-K6BASBQJ.js} +1 -1
  62. package/dist/{chunk-H4VNAOCG.js → chunk-LBNAGGLV.js} +1 -1
  63. package/dist/{chunk-DOKFLZWL.js → chunk-LWKPMSBB.js} +69 -46
  64. package/dist/{chunk-6VXZKOBB.js → chunk-NSFNEDCT.js} +8 -8
  65. package/dist/{chunk-5ODZEFP3.js → chunk-O3LZQEQP.js} +1 -1
  66. package/dist/{chunk-QFAZ6QCT.js → chunk-OPR2BPD4.js} +1 -1
  67. package/dist/{chunk-HUZW6SEO.js → chunk-OUAE3GKO.js} +2 -2
  68. package/dist/{chunk-IBRX7LRE.js → chunk-PD6JY33P.js} +9 -8
  69. package/dist/{chunk-6WJKYD3Q.js → chunk-QS55LDX4.js} +11 -8
  70. package/dist/{chunk-4AXWBCCO.js → chunk-RALVI7OF.js} +4 -4
  71. package/dist/{chunk-OYTOLQ3N.js → chunk-SI7GYBQP.js} +1 -1
  72. package/dist/{chunk-YX3U2CLP.js → chunk-T3JZZUAR.js} +6 -6
  73. package/dist/{chunk-K7MLXXPY.js → chunk-V262ATS5.js} +3 -3
  74. package/dist/{chunk-DNLFSRCF.js → chunk-WGV3VY7O.js} +2 -2
  75. package/dist/{chunk-VOC55PQV.js → chunk-XOOWC62T.js} +1 -1
  76. package/dist/{chunk-CDSA3NTB.js → chunk-XPPXOKIG.js} +1 -1
  77. package/dist/{chunk-A4AIISZZ.js → chunk-YKLCWWEI.js} +1 -1
  78. package/dist/reapply-HQ264ZTR.js +30 -0
  79. package/dist/src/index.js +11 -10
  80. package/package.json +1 -1
@@ -2,16 +2,17 @@ import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  GEMINI_HOOK_EVENTS,
4
4
  hasGeminiHooks
5
- } from "./chunk-IBRX7LRE.js";
5
+ } from "./chunk-PD6JY33P.js";
6
6
  import {
7
7
  CODEX_HOOK_EVENTS,
8
- hasCodexHooks
9
- } from "./chunk-67RNDRKL.js";
8
+ hasCodexHooks,
9
+ holdBudgetOf
10
+ } from "./chunk-JBJBVERL.js";
10
11
  import {
11
12
  CURSOR_GATE_EVENTS,
12
13
  HOOK_LOCATOR_BINARY,
13
14
  hookCommandHoldBudget
14
- } from "./chunk-HUZW6SEO.js";
15
+ } from "./chunk-OUAE3GKO.js";
15
16
 
16
17
  // src/diagnostics/wiring.ts
17
18
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -73,31 +74,20 @@ var toSeconds = (timeout, unit) => {
73
74
  };
74
75
  var registeredHooks = (entries, shape, unit) => {
75
76
  if (!Array.isArray(entries)) return [];
76
- return entries.flatMap((entry, entryIndex) => {
77
+ return entries.flatMap((entry) => {
77
78
  const container = record(entry);
78
79
  if (!container) return [];
79
80
  const hooks = shape === "nested" ? Array.isArray(container.hooks) ? container.hooks : [] : [container];
80
- return hooks.flatMap((hook, hookIndex) => {
81
+ return hooks.flatMap((hook) => {
81
82
  const command = record(hook)?.command;
82
83
  if (typeof command !== "string" || command.trim() === "") return [];
83
- return [{
84
- command,
85
- timeoutSeconds: toSeconds(record(hook)?.timeout, unit),
86
- order: [entryIndex, hookIndex]
87
- }];
84
+ return [{ command, timeoutSeconds: toSeconds(record(hook)?.timeout, unit) }];
88
85
  });
89
86
  });
90
87
  };
91
- var runsBefore = (a, b) => a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
92
88
  var holdersOnEvent = (event, hooks) => {
93
- const ours = hooks.find((hook) => pusharyHookExecutable(hook.command) !== null);
94
- if (!ours) return [];
95
- return hooks.filter((hook) => pusharyHookExecutable(hook.command) === null).map((hook) => ({
96
- event,
97
- command: hook.command,
98
- timeoutSeconds: hook.timeoutSeconds,
99
- position: runsBefore(hook.order, ours.order) ? "before" : "after"
100
- }));
89
+ if (!hooks.some((hook) => pusharyHookExecutable(hook.command) !== null)) return [];
90
+ return hooks.filter((hook) => pusharyHookExecutable(hook.command) === null).map((hook) => ({ event, command: hook.command, timeoutSeconds: hook.timeoutSeconds }));
101
91
  };
102
92
  var otherHolders = (hooks, events, shape, unit) => {
103
93
  const table = record(hooks);
@@ -112,11 +102,14 @@ var claudeOtherHolders = (settings) => otherHolders(record(settings)?.hooks, CLA
112
102
  var codexOtherHolders = (hooks) => otherHolders(record(hooks)?.hooks, CODEX_GATE_EVENTS, "nested", "seconds");
113
103
  var geminiOtherHolders = (settings) => otherHolders(record(settings)?.hooks, GEMINI_GATE_EVENTS, "nested", "milliseconds");
114
104
  var cursorOtherHolders = (userHooks) => otherHolders(record(userHooks)?.hooks, CURSOR_GATE_EVENTS, "flat", "seconds");
115
- var holdEvents = (table) => table.filter((hook) => hook.holds).map((hook) => hook.event);
105
+ var holdEvents = (table) => table.flatMap((hook) => {
106
+ const seconds = holdBudgetOf(hook);
107
+ return seconds === void 0 ? [] : [{ event: hook.event, seconds }];
108
+ });
116
109
  var unprovenHolds = (hooks, events) => {
117
110
  const table = record(hooks);
118
111
  if (!table) return [];
119
- return events.filter((event) => registeredHooks(table[event], "nested", "seconds").some((hook) => pusharyHookExecutable(hook.command) !== null && hookCommandHoldBudget(hook.command) === void 0));
112
+ return events.filter(({ event, seconds }) => registeredHooks(table[event], "nested", "seconds").some((hook) => pusharyHookExecutable(hook.command) !== null && hookCommandHoldBudget(hook.command) !== seconds)).map(({ event }) => event);
120
113
  };
121
114
  var claudeUnprovenHolds = (settings) => unprovenHolds(record(settings)?.hooks, holdEvents(CLAUDE_HOOK_EVENTS));
122
115
  var codexUnprovenHolds = (hooks) => unprovenHolds(record(hooks)?.hooks, holdEvents(CODEX_HOOK_EVENTS));
@@ -134,7 +127,7 @@ var describeHoldTimeout = (holder) => {
134
127
  if (seconds >= 60 && seconds % 60 === 0) return `${seconds / 60}m timeout`;
135
128
  return `${seconds}s timeout`;
136
129
  };
137
- var describeOtherHolder = (holder) => `another hook on ${holder.event}: ${holderExecutable(holder.command)} (${describeHoldTimeout(holder)}, runs ${holder.position} ours)`;
130
+ var describeOtherHolder = (holder) => `another hook also registered on ${holder.event}: ${holderExecutable(holder.command)} (${describeHoldTimeout(holder)})`;
138
131
  var describeHoldSentence = (agentLabel) => `${agentLabel} waits for every hook on an event, so the terminal cannot move until that one returns, however fast your phone answered.`;
139
132
 
140
133
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-IBRX7LRE.js";
3
+ } from "./chunk-PD6JY33P.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-VF7YOT2W.js";
3
+ } from "./chunk-4PBCNVI3.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-7QLSKOSU.js";
7
7
  import {
8
8
  isValidApiKey
9
- } from "./chunk-HUZW6SEO.js";
9
+ } from "./chunk-OUAE3GKO.js";
10
10
 
11
11
  // src/setup/options.ts
12
12
  var AGENT_CHOICES = ["claude_code", "codex", "gemini_cli", "hermes", "cursor", "vscode", "opencode", "custom"];
@@ -1,17 +1,20 @@
1
1
  import {
2
2
  detectClaudeVersion
3
- } from "./chunk-YRXVC4HL.js";
3
+ } from "./chunk-K4XFCEVI.js";
4
4
  import {
5
5
  addClaudeMcpServer,
6
6
  addPusharyHooks,
7
7
  addPusharyToolPermissions
8
- } from "./chunk-6WJKYD3Q.js";
8
+ } from "./chunk-QS55LDX4.js";
9
9
  import {
10
+ claudeUnprovenHolds,
10
11
  claudeWired,
12
+ codexUnprovenHolds,
11
13
  codexWired,
12
14
  geminiWired
13
- } from "./chunk-FU5SQKAY.js";
15
+ } from "./chunk-JXELRCU5.js";
14
16
  import {
17
+ CLAUDE_HOOK_BINARY,
15
18
  CLAUDE_HOOK_EVENTS,
16
19
  GEMINI_HOOK_BINARY,
17
20
  addGeminiHooks,
@@ -21,7 +24,7 @@ import {
21
24
  needsReregistration,
22
25
  registrationSignatures,
23
26
  supportedClaudeEvents
24
- } from "./chunk-IBRX7LRE.js";
27
+ } from "./chunk-PD6JY33P.js";
25
28
  import {
26
29
  CODEX_HOOK_BINARY,
27
30
  addCodexHookTrust,
@@ -31,67 +34,48 @@ import {
31
34
  codexConfigToml,
32
35
  codexHookPositions,
33
36
  codexHooksJson,
37
+ codexRegistrationHolder,
38
+ codexVacatedIndexes,
39
+ dropVacatedCodexHookTrust,
34
40
  guardBinary,
35
41
  hasCodexHooks,
36
- resolveGlobalBinDir,
42
+ namesOurBinary,
43
+ registeredCommands,
44
+ registrationHolderOf,
37
45
  resolveGlobalBinary,
38
46
  vscodePluginMcp
39
- } from "./chunk-67RNDRKL.js";
47
+ } from "./chunk-JBJBVERL.js";
40
48
  import {
41
49
  readJsonSafe,
50
+ writeFileAtomic,
42
51
  writeJsonAtomic
43
- } from "./chunk-VTUN7WSG.js";
44
- import {
45
- HOOK_LOCATOR_BINARY
46
- } from "./chunk-HUZW6SEO.js";
52
+ } from "./chunk-4LL6LG5X.js";
47
53
 
48
54
  // src/reapply.ts
49
- import { join } from "path";
55
+ import { dirname, join } from "path";
50
56
  import { homedir } from "os";
51
- import { readFileSync, writeFileSync } from "fs";
57
+ import { readFileSync, statSync } from "fs";
52
58
  import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
53
59
 
54
60
  // src/reregister.ts
55
61
  var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
56
- var commandsUnder = (settings) => {
57
- const hooks = asRecord(settings?.hooks);
58
- if (!hooks) return [];
59
- const found = [];
60
- for (const [event, value] of Object.entries(hooks)) {
61
- if (!Array.isArray(value)) continue;
62
- for (const item of value) {
63
- const inner = asRecord(item)?.hooks;
64
- if (!Array.isArray(inner)) continue;
65
- for (const hook of inner) {
66
- const command = asRecord(hook)?.command;
67
- if (typeof command === "string") found.push({ event, command });
68
- }
69
- }
70
- }
71
- return found;
72
- };
73
- var segments = (command) => command.split(/[\s/\\"']+/).map((segment) => segment.replace(/\.(cmd|exe)$/i, ""));
74
- var CLI_BINARIES = new Set(CLAUDE_HOOK_EVENTS.map((hook) => hook.binary));
75
- var namesLocator = (command) => segments(command).includes(HOOK_LOCATOR_BINARY);
76
- var namesCliBinary = (command) => segments(command).some((segment) => CLI_BINARIES.has(segment));
77
- var registrationHolder = (settings) => {
78
- const commands = commandsUnder(settings);
79
- if (commands.some(({ command }) => namesLocator(command))) return "app";
80
- return commands.some(({ command }) => namesCliBinary(command)) ? "cli" : "none";
81
- };
62
+ var CLAUDE_CLI_BINARIES = new Set(CLAUDE_HOOK_EVENTS.map((hook) => hook.binary));
63
+ var registrationHolder = (settings) => registrationHolderOf(settings, CLAUDE_CLI_BINARIES);
82
64
  var registeredPusharyEvents = (settings) => new Set(
83
- commandsUnder(settings).filter(({ command }) => namesLocator(command) || namesCliBinary(command)).map(({ event }) => event)
65
+ registeredCommands(settings).filter(({ command }) => namesOurBinary(command, CLAUDE_CLI_BINARIES)).map(({ event }) => event)
84
66
  );
85
67
  var reregisterVerdict = ({ settings, stored, version }) => {
86
68
  const holder = registrationHolder(settings);
87
69
  if (holder === "none") return { kind: "not_wired" };
88
70
  if (holder === "app") return { kind: "app_owned" };
89
- if (!needsReregistration(stored, version)) return { kind: "current" };
71
+ const unproven = claudeUnprovenHolds(settings);
72
+ if (unproven.length === 0 && !needsReregistration(stored, version)) return { kind: "current" };
90
73
  const supported = new Set(supportedClaudeEvents(version).map((hook) => hook.event));
91
74
  const present = registeredPusharyEvents(settings);
92
75
  const missing = [...supported].filter((event) => !present.has(event));
93
76
  const extra = [...present].filter((event) => !supported.has(event));
94
- return missing.length > 0 || extra.length > 0 ? { kind: "stale", version, missing, extra } : { kind: "record_only", version };
77
+ const repairable = unproven.filter((event) => supported.has(event));
78
+ return missing.length > 0 || extra.length > 0 || repairable.length > 0 ? { kind: "stale", version, missing, extra, unproven: repairable } : { kind: "record_only", version };
95
79
  };
96
80
  var receiptFor = (version, now = /* @__PURE__ */ new Date()) => ({
97
81
  signatures: registrationSignatures(supportedClaudeEvents(version), version),
@@ -117,8 +101,17 @@ var readJson = (filePath) => {
117
101
  return null;
118
102
  };
119
103
  var writeJson = (filePath, data) => writeJsonAtomic(filePath, data);
120
- var hasPusharyClaudeHooks = (settings) => JSON.stringify(settings.hooks ?? {}).includes("pushary-hook");
104
+ var hasPusharyClaudeHooks = (settings) => JSON.stringify(settings.hooks ?? {}).includes(CLAUDE_HOOK_BINARY);
121
105
  var hasPusharyMcp = (claudeJson) => !!claudeJson.mcpServers?.pushary;
106
+ var PRIVATE_TO_OWNER = 384;
107
+ var modeToPreserve = (filePath) => {
108
+ try {
109
+ return statSync(filePath).mode & 511;
110
+ } catch {
111
+ return PRIVATE_TO_OWNER;
112
+ }
113
+ };
114
+ var wouldWriteBareNames = (resolve, probe) => resolve(probe) === void 0;
122
115
  var reapplyClaudeSettings = (apiKey, paths = {}) => {
123
116
  const settingsPath = paths.settingsPath ?? CLAUDE_SETTINGS;
124
117
  const claudeJsonPath = paths.claudeJsonPath ?? CLAUDE_JSON;
@@ -134,7 +127,8 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
134
127
  if (!settingsWritable) throw new Error("settings.json could not be parsed");
135
128
  const s = settings;
136
129
  addPusharyToolPermissions(s);
137
- addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"), (paths.detectVersion ?? detectClaudeVersion)());
130
+ const binary = (paths.resolveBinary ?? resolveGlobalBinary)(CLAUDE_HOOK_BINARY);
131
+ addPusharyHooks(s, binary ? dirname(binary) : void 0, (paths.detectVersion ?? detectClaudeVersion)());
138
132
  writeJson(settingsPath, s);
139
133
  hooks = true;
140
134
  } catch {
@@ -166,6 +160,10 @@ var reapplyClaudeForVersion = (apiKey, paths = {}) => {
166
160
  if (verdict.kind === "not_wired" || verdict.kind === "app_owned" || verdict.kind === "current") {
167
161
  return { verdict: verdict.kind, rewrote: false, missing: [] };
168
162
  }
163
+ const resolveBinary = paths.resolveBinary ?? resolveGlobalBinary;
164
+ if (verdict.kind === "stale" && wouldWriteBareNames(resolveBinary, CLAUDE_HOOK_BINARY)) {
165
+ return { verdict: "stale", rewrote: false, missing: verdict.missing };
166
+ }
169
167
  const rewrote = verdict.kind === "stale" ? reapplyClaudeSettings(apiKey, { ...paths, settingsPath }).hooks : false;
170
168
  if (verdict.kind === "stale" && !rewrote) return { verdict: "stale", rewrote: false, missing: verdict.missing };
171
169
  writeJsonAtomic(receiptPath, receiptFor(version));
@@ -211,9 +209,10 @@ var reapplyCodexSettings = (apiKey, paths = {}) => {
211
209
  const hooksConfig = readJson(hooksPath);
212
210
  const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
213
211
  if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
214
- const command = guardBinary(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
212
+ const command = guardBinary((paths.resolveBinary ?? resolveGlobalBinary)(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
215
213
  let hooks = false;
216
214
  let mcp = false;
215
+ const vacated = codexVacatedIndexes(hooksConfig ?? void 0);
217
216
  if (hooksConfig != null) {
218
217
  try {
219
218
  addCodexHooks(hooksConfig, command);
@@ -224,17 +223,35 @@ var reapplyCodexSettings = (apiKey, paths = {}) => {
224
223
  }
225
224
  if (config != null) {
226
225
  try {
227
- if (hooks) addCodexHookTrust(config, hooksPath, command, codexHookPositions(hooksConfig ?? void 0));
226
+ if (hooks) {
227
+ dropVacatedCodexHookTrust(config, hooksPath, vacated);
228
+ addCodexHookTrust(config, hooksPath, command, codexHookPositions(hooksConfig ?? void 0));
229
+ }
228
230
  if (apiKey) {
229
231
  addCodexMcpServer(config, apiKey);
230
232
  mcp = true;
231
233
  }
232
- if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
234
+ if (hooks || apiKey) {
235
+ writeFileAtomic(configPath, stringifyTOML(config), modeToPreserve(configPath), "utf-8");
236
+ }
233
237
  } catch {
234
238
  }
235
239
  }
236
240
  return { configured: true, reapplied: hooks || mcp, hooks, mcp };
237
241
  };
242
+ var repairCodexHolds = (paths = {}) => {
243
+ const idle = { configured: false, reapplied: false, hooks: false, mcp: false };
244
+ try {
245
+ const hooksPath = paths.hooksPath ?? codexHooksJson();
246
+ const hooksConfig = readJson(hooksPath);
247
+ if (!hooksConfig || codexRegistrationHolder(hooksConfig) !== "cli") return idle;
248
+ if (codexUnprovenHolds(hooksConfig).length === 0) return idle;
249
+ if (wouldWriteBareNames(paths.resolveBinary ?? resolveGlobalBinary, CODEX_HOOK_BINARY)) return idle;
250
+ return reapplyCodexSettings(void 0, paths);
251
+ } catch {
252
+ return idle;
253
+ }
254
+ };
238
255
  var reapplyGeminiSettings = (apiKey, paths = {}) => {
239
256
  const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
240
257
  const settings = readJson(settingsPath);
@@ -294,7 +311,13 @@ var reapplyAllAgents = (apiKey) => {
294
311
  };
295
312
 
296
313
  export {
314
+ reapplyClaudeSettings,
297
315
  reapplyClaudeForVersion,
298
316
  inspectConfiguredManagedProviders,
317
+ reapplyCodexSettings,
318
+ repairCodexHolds,
319
+ reapplyGeminiSettings,
320
+ reapplyCursorPlugin,
321
+ reapplyVsCodePlugin,
299
322
  reapplyAllAgents
300
323
  };
@@ -3,19 +3,19 @@ import {
3
3
  loadPin,
4
4
  savePin,
5
5
  verifyRecipients
6
- } from "./chunk-A4AIISZZ.js";
6
+ } from "./chunk-YKLCWWEI.js";
7
7
  import {
8
8
  hookOwnerOf
9
- } from "./chunk-4AXWBCCO.js";
9
+ } from "./chunk-RALVI7OF.js";
10
10
  import {
11
11
  ensureDaemonRunning
12
- } from "./chunk-LYWTLNMG.js";
12
+ } from "./chunk-CRFLU455.js";
13
13
  import {
14
14
  buildTranscriptRecords,
15
15
  decodePublicKey,
16
16
  generateSessionKey,
17
17
  wrapSessionKey
18
- } from "./chunk-CDSA3NTB.js";
18
+ } from "./chunk-XPPXOKIG.js";
19
19
  import {
20
20
  fetchTranscriptRecipients,
21
21
  uploadTranscriptRecords
@@ -23,17 +23,17 @@ import {
23
23
  import {
24
24
  transcriptEnvironmentPreference,
25
25
  transcriptsEnabled
26
- } from "./chunk-C2FYSFTQ.js";
26
+ } from "./chunk-ID5FDKDZ.js";
27
27
  import {
28
28
  codexHome,
29
29
  pusharyDir
30
- } from "./chunk-67RNDRKL.js";
30
+ } from "./chunk-JBJBVERL.js";
31
31
  import {
32
32
  readTranscriptPreference
33
- } from "./chunk-VF7YOT2W.js";
33
+ } from "./chunk-4PBCNVI3.js";
34
34
  import {
35
35
  writeJsonAtomic
36
- } from "./chunk-VTUN7WSG.js";
36
+ } from "./chunk-4LL6LG5X.js";
37
37
  import {
38
38
  getApiKey,
39
39
  getBaseUrl
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  registerTranscript
3
- } from "./chunk-6VXZKOBB.js";
3
+ } from "./chunk-NSFNEDCT.js";
4
4
 
5
5
  // src/admission.ts
6
6
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PRETOOLUSE_GATED_TOOLS
3
- } from "./chunk-HUZW6SEO.js";
3
+ } from "./chunk-OUAE3GKO.js";
4
4
 
5
5
  // src/claude-tools.ts
6
6
  var POSTTOOLUSE_MIRROR_ONLY_TOOLS = ["TodoWrite", "Task", "WebFetch", "WebSearch"];
@@ -1397,8 +1397,8 @@ var claudePermissionDeniedOutput = (decision) => ({
1397
1397
  }
1398
1398
  });
1399
1399
  var CODEX_RESCUE_COMMAND_MAX_LENGTH = 2e3;
1400
- var CODEX_RESCUE_APPROVED = "The user approved this command from Pushary after the auto-reviewer denied it.";
1401
- var codexRescueRetryInstruction = (command) => command.length <= CODEX_RESCUE_COMMAND_MAX_LENGTH ? `Run it again exactly as before, unchanged: ${command}` : `Run it again exactly as before, unchanged. It is too long to repeat in full here and begins: ${command.slice(0, CODEX_RESCUE_COMMAND_MAX_LENGTH)}`;
1400
+ var CODEX_RESCUE_APPROVED = "I approved this command in Pushary after the auto-reviewer denied it. If it is denied again, stop and tell me.";
1401
+ var codexRescueRetryInstruction = (command) => command.length <= CODEX_RESCUE_COMMAND_MAX_LENGTH ? `Run it again now, exactly as before: ${command}` : `Run it again now, exactly as before. It is too long to repeat in full here and begins: ${command.slice(0, CODEX_RESCUE_COMMAND_MAX_LENGTH)}`;
1402
1402
  var codexStopRescueOutput = (decision, command) => decision === "allow" ? { decision: "block", reason: `${CODEX_RESCUE_APPROVED} ${codexRescueRetryInstruction(command)}` } : { continue: true };
1403
1403
  var stoppedOutput = (output, stopReason) => ({ ...output, continue: false, stopReason });
1404
1404
  var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  NOTIFICATION_MATCHED_TYPES,
3
3
  POSTTOOLUSE_MIRRORED_TOOLS
4
- } from "./chunk-QFAZ6QCT.js";
4
+ } from "./chunk-OPR2BPD4.js";
5
5
  import {
6
- isProtectedByScope
7
- } from "./chunk-67RNDRKL.js";
6
+ isProtectedByScope,
7
+ registerHookEntry
8
+ } from "./chunk-JBJBVERL.js";
8
9
  import {
9
10
  CLAUDE_PRETOOLUSE_MATCHER,
10
11
  HOOK_BUDGETS,
11
12
  HOOK_LOCATOR_BINARY
12
- } from "./chunk-HUZW6SEO.js";
13
+ } from "./chunk-OUAE3GKO.js";
13
14
 
14
15
  // src/gemini-config.ts
15
16
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
@@ -51,8 +52,7 @@ var addGeminiHooks = (settings, command) => {
51
52
  const hooks = ensureRecord(settings, "hooks");
52
53
  for (const definition of GEMINI_HOOK_EVENTS) {
53
54
  const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
54
- const entries = existing.filter((entry) => !isPusharyGeminiHook(entry));
55
- entries.push({
55
+ hooks[definition.event] = registerHookEntry(existing, isPusharyGeminiHook, {
56
56
  ...definition.matcher ? { matcher: definition.matcher } : {},
57
57
  hooks: [{
58
58
  name: "pushary",
@@ -61,7 +61,6 @@ var addGeminiHooks = (settings, command) => {
61
61
  timeout: definition.timeoutMs
62
62
  }]
63
63
  });
64
- hooks[definition.event] = entries;
65
64
  }
66
65
  };
67
66
  var removeGeminiHooks = (settings, scope) => {
@@ -130,8 +129,9 @@ var formatVersion = (version) => version.join(".");
130
129
  var MIRRORED_TOOLS = POSTTOOLUSE_MIRRORED_TOOLS.join("|");
131
130
  var ESTABLISHED = [1, 0, 0];
132
131
  var OBSERVED = [2, 0, 0];
132
+ var CLAUDE_HOOK_BINARY = "pushary-hook";
133
133
  var CLAUDE_HOOK_EVENTS = [
134
- { event: "PreToolUse", binary: "pushary-hook", matcher: CLAUDE_PRETOOLUSE_MATCHER, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
134
+ { event: "PreToolUse", binary: CLAUDE_HOOK_BINARY, matcher: CLAUDE_PRETOOLUSE_MATCHER, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
135
135
  { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
136
136
  { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
137
137
  { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
@@ -176,6 +176,7 @@ export {
176
176
  parseVersion,
177
177
  compareVersion,
178
178
  formatVersion,
179
+ CLAUDE_HOOK_BINARY,
179
180
  CLAUDE_HOOK_EVENTS,
180
181
  supportedClaudeEvents,
181
182
  registrationSignatures,
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  supportedClaudeEvents
4
- } from "./chunk-IBRX7LRE.js";
4
+ } from "./chunk-PD6JY33P.js";
5
5
  import {
6
6
  guardBinary,
7
7
  holdBudgetOf,
8
- isProtectedByScope
9
- } from "./chunk-67RNDRKL.js";
8
+ isProtectedByScope,
9
+ registerHookEntry
10
+ } from "./chunk-JBJBVERL.js";
10
11
  import {
11
12
  HOOK_LOCATOR_BINARY,
12
13
  withHookHoldBudget
13
- } from "./chunk-HUZW6SEO.js";
14
+ } from "./chunk-OUAE3GKO.js";
14
15
 
15
16
  // src/claude-config.ts
16
17
  import { join } from "path";
@@ -126,12 +127,14 @@ var addPusharyHooks = (settings, binDir, version = null) => {
126
127
  const hooks = ensureRecord(settings, "hooks");
127
128
  const supported = new Map(supportedClaudeEvents(version).map((hook) => [hook.event, hook]));
128
129
  for (const known of CLAUDE_HOOK_EVENTS) {
129
- const current = hooks[known.event];
130
- const others = (Array.isArray(current) ? current : []).filter((item) => !isPusharyHook(item));
130
+ const current = Array.isArray(hooks[known.event]) ? hooks[known.event] : [];
131
131
  const definition = supported.get(known.event);
132
132
  if (definition) {
133
- hooks[known.event] = [...others, entry(definition, resolve)];
134
- } else if (others.length > 0) {
133
+ hooks[known.event] = registerHookEntry(current, isPusharyHook, entry(definition, resolve));
134
+ continue;
135
+ }
136
+ const others = current.filter((item) => !isPusharyHook(item));
137
+ if (others.length > 0) {
135
138
  hooks[known.event] = others;
136
139
  } else {
137
140
  delete hooks[known.event];
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AGENT_CHOICES
3
- } from "./chunk-H4VNAOCG.js";
3
+ } from "./chunk-LBNAGGLV.js";
4
4
  import {
5
5
  isBridgeLocator
6
- } from "./chunk-BYH75I7Y.js";
6
+ } from "./chunk-IRLTW6KF.js";
7
7
  import {
8
8
  pusharyHookExecutable
9
- } from "./chunk-FU5SQKAY.js";
9
+ } from "./chunk-JXELRCU5.js";
10
10
  import {
11
11
  BELL_HOOK_BINARY
12
- } from "./chunk-WIKEADOS.js";
12
+ } from "./chunk-EXHXR2X5.js";
13
13
 
14
14
  // src/setup/hook-ownership.ts
15
15
  var commandsIn = (value) => {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_HOLD_BUDGET_ENV,
3
3
  hookHoldBudgetProof
4
- } from "./chunk-HUZW6SEO.js";
4
+ } from "./chunk-OUAE3GKO.js";
5
5
 
6
6
  // src/hold-budget.ts
7
7
  var takeHookHoldBudget = (args = process.argv.slice(2), env = process.env) => {
@@ -4,11 +4,11 @@ import {
4
4
  forgetBlockedActionHint,
5
5
  hasBlockedActionHint,
6
6
  recordBlockedActionHint
7
- } from "./chunk-XDMHLQMH.js";
7
+ } from "./chunk-6KLCP7VF.js";
8
8
  import {
9
9
  isGatingMoment,
10
10
  recordKeylessMoment
11
- } from "./chunk-VOC55PQV.js";
11
+ } from "./chunk-XOOWC62T.js";
12
12
  import {
13
13
  denyReasonFrom,
14
14
  isDeferAnswer
@@ -24,7 +24,7 @@ import {
24
24
  reportEvent,
25
25
  settleQuestionsForToolUse,
26
26
  throttlePass
27
- } from "./chunk-DGUMR5LZ.js";
27
+ } from "./chunk-64WARXGV.js";
28
28
  import {
29
29
  UNNAMED_ACTION,
30
30
  deriveAction,
@@ -34,7 +34,7 @@ import {
34
34
  describeToolCall,
35
35
  permissionDeniedAction,
36
36
  scopePathFor
37
- } from "./chunk-VCKQ45QA.js";
37
+ } from "./chunk-A76GNRIK.js";
38
38
  import {
39
39
  DECISION_STOP_REASON,
40
40
  askUser,
@@ -49,7 +49,7 @@ import {
49
49
  } from "./chunk-LGXUGYUH.js";
50
50
  import {
51
51
  getMachineId
52
- } from "./chunk-IZKCA35M.js";
52
+ } from "./chunk-562UJ2DA.js";
53
53
  import {
54
54
  getApiKey,
55
55
  getBaseUrl
@@ -73,7 +73,7 @@ import {
73
73
  parseAgentQuestions,
74
74
  resolveGate,
75
75
  stoppedOutput
76
- } from "./chunk-HUZW6SEO.js";
76
+ } from "./chunk-OUAE3GKO.js";
77
77
 
78
78
  // src/decision-episode.ts
79
79
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-EHQC3GWL.js";
3
+ } from "./chunk-HB2KB7QL.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-67RNDRKL.js";
6
+ } from "./chunk-JBJBVERL.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -264,7 +264,7 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
264
264
 
265
265
  - When you need the user to make a decision or answer a clarifying question, call \`ask_user\` instead of guessing or stalling. Use type "confirm" for yes/no, "select" for a fixed set of options, and "input" for free text. Read \`answered\`, \`status\` and the returned handoff instead of assuming the tool blocked. Silence is not consent; choice/text answers do not authorize a separate action.
266
266
  - Ask once, on one channel. If you also have a built-in way to prompt the user, use that or \`ask_user\` for a given question, never both. \`ask_user\` is the one that reaches the user when they are away from this machine, so prefer it. Raising both strands the user in whichever prompt you are not waiting on.
267
- - Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate.
267
+ - Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate. When a Pushary hook result says the user approved a specific command after a denial, rerunning that exact command is the approved path and not a bypass of that gate.
268
268
  - Call \`propose_scope\` only for an unresolved or requested file boundary. A ratified scope narrows what may proceed; it does not bypass other approval rules. Do not add a plan approval merely because work spans several files, and do not propose a new scope mid-run just to widen it. Let the installed gate request any scope expansion before the edit executes.
269
269
  - When you are blocked, stuck, or hit an error you cannot resolve on your own, call \`send_notification\` so the user knows, and call \`ask_user\` if you need a decision to continue.
270
270
  - When meaningful work finishes while the user is away, or they requested an alert, call \`send_notification\` so the user knows it is done. Skip trivial completions and duplicate reports to someone already reading. Always pass \`context\` with a \`type\` of "task_complete", "error" or "info": it is what tells Pushary this is a task update, so the user's own setting for where updates land can route it.
@@ -2,10 +2,10 @@ import {
2
2
  resolveBinary,
3
3
  resolveGlobalPackageVersion,
4
4
  resolvePackageVersionFromBinary
5
- } from "./chunk-67RNDRKL.js";
5
+ } from "./chunk-JBJBVERL.js";
6
6
  import {
7
7
  MANAGED_PROVIDERS
8
- } from "./chunk-HUZW6SEO.js";
8
+ } from "./chunk-OUAE3GKO.js";
9
9
 
10
10
  // src/live-capabilities.ts
11
11
  var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-HUZW6SEO.js";
3
+ } from "./chunk-OUAE3GKO.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  redactSecrets
3
- } from "./chunk-HUZW6SEO.js";
3
+ } from "./chunk-OUAE3GKO.js";
4
4
 
5
5
  // src/crypto.ts
6
6
  import nacl from "tweetnacl";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  writeJsonAtomicDurable
3
- } from "./chunk-VTUN7WSG.js";
3
+ } from "./chunk-4LL6LG5X.js";
4
4
 
5
5
  // src/transcript-recipient-pin.ts
6
6
  import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";