@pushary/agent-hooks 0.87.1 → 0.87.3

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 (63) hide show
  1. package/data/cursor-plugin/hooks/hooks.json +7 -0
  2. package/data/cursor-plugin/scripts/pushary-gate.mjs +85 -28
  3. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +42 -1
  4. package/dist/bin/pushary-bell-hook.js +4 -4
  5. package/dist/bin/pushary-bell.js +7 -6
  6. package/dist/bin/pushary-claude.js +14 -11
  7. package/dist/bin/pushary-clean.js +36 -20
  8. package/dist/bin/pushary-codex-bridge.js +29 -11
  9. package/dist/bin/pushary-codex-hook.js +129 -52
  10. package/dist/bin/pushary-codex.js +4 -3
  11. package/dist/bin/pushary-connect.js +6 -6
  12. package/dist/bin/pushary-daemon.js +5 -4
  13. package/dist/bin/pushary-disconnect.js +65 -8
  14. package/dist/bin/pushary-doctor.js +79 -18
  15. package/dist/bin/pushary-elicitation-hook.js +1 -1
  16. package/dist/bin/pushary-gemini-bridge.js +6 -5
  17. package/dist/bin/pushary-gemini-hook.js +12 -10
  18. package/dist/bin/pushary-hook.js +6 -5
  19. package/dist/bin/pushary-login.js +4 -4
  20. package/dist/bin/pushary-logout.js +5 -4
  21. package/dist/bin/pushary-mode.js +3 -3
  22. package/dist/bin/pushary-notification-hook.js +4 -3
  23. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  24. package/dist/bin/pushary-opencode-hook.js +329 -0
  25. package/dist/bin/pushary-permission-denied-hook.js +6 -5
  26. package/dist/bin/pushary-permission-hook.js +6 -5
  27. package/dist/bin/pushary-post-hook.js +4 -3
  28. package/dist/bin/pushary-prompt-hook.js +4 -3
  29. package/dist/bin/pushary-session-end-hook.js +4 -3
  30. package/dist/bin/pushary-session-start-hook.js +4 -3
  31. package/dist/bin/pushary-setup.js +240 -85
  32. package/dist/bin/pushary-stats.js +2 -2
  33. package/dist/bin/pushary-status.js +5 -5
  34. package/dist/bin/pushary-stop-hook.js +4 -3
  35. package/dist/bin/pushary-stopfailure-hook.js +4 -3
  36. package/dist/bin/pushary-upgrade.js +7 -6
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-5646VKR4.js → chunk-2JYQAW4B.js} +5 -53
  39. package/dist/chunk-7J67NYK4.js +287 -0
  40. package/dist/{chunk-M7RYIZX6.js → chunk-B7ZBHWOW.js} +10 -1
  41. package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
  42. package/dist/{chunk-LT2VLPUK.js → chunk-DPUOW6NS.js} +18 -2
  43. package/dist/{chunk-A3PFIWWN.js → chunk-EJL3PJU7.js} +56 -6
  44. package/dist/chunk-GIRT7677.js +11 -0
  45. package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
  46. package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
  47. package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
  48. package/dist/{chunk-SJVLFIYP.js → chunk-KCAFCGGY.js} +1 -1
  49. package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
  50. package/dist/{chunk-QBWYNVKY.js → chunk-LGVHYFIB.js} +1 -1
  51. package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
  52. package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
  53. package/dist/{chunk-XW5CHAQX.js → chunk-TAYHEJFF.js} +2 -2
  54. package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
  55. package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
  56. package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
  57. package/dist/{chunk-B3V2H6SA.js → chunk-VL5ZNK6V.js} +43 -12
  58. package/dist/{chunk-V2XPF77V.js → chunk-VOE4SYPR.js} +42 -279
  59. package/dist/{chunk-OT4YD54Q.js → chunk-YX6ZKCC5.js} +1 -1
  60. package/dist/{chunk-2T3GX7WG.js → chunk-YYTLTEGU.js} +1 -1
  61. package/dist/chunk-ZGC3W23N.js +371 -0
  62. package/dist/src/index.js +6 -5
  63. package/package.json +2 -1
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-BSZYIAZL.js";
12
12
  import {
13
13
  reportedInstallSource
14
- } from "./chunk-Y3UTTFTZ.js";
14
+ } from "./chunk-KBS53WOE.js";
15
15
 
16
16
  // src/reach.ts
17
17
  var reachVerdict = (channels) => {
@@ -72,6 +72,15 @@ var describeAppRepair = (verdict) => {
72
72
  ];
73
73
  }
74
74
  };
75
+ var describeJustPairedRepair = (verdict) => {
76
+ if (verdict.kind === "nobody") {
77
+ return [
78
+ "The app asks for the notification permission after it grants the pairing.",
79
+ "Open the Pushary app and allow notifications. That is the whole remaining step."
80
+ ];
81
+ }
82
+ return describeAppRepair(verdict) ?? [];
83
+ };
75
84
 
76
85
  // src/setup/verify.ts
77
86
  var plural2 = (count, one) => `${count} ${one}${count === 1 ? "" : "s"}`;
@@ -586,14 +595,55 @@ var describeExistingConnection = (channels) => {
586
595
  }
587
596
  return "Phone already connected";
588
597
  };
598
+ var APP_REGISTER_TIMEOUT_MS = 45e3;
599
+ var appRegisterTimeoutMs = () => {
600
+ const raw = Number(process.env.PUSHARY_APP_REGISTER_TIMEOUT_MS);
601
+ return Number.isFinite(raw) && raw >= 0 ? raw : APP_REGISTER_TIMEOUT_MS;
602
+ };
603
+ var ownedApp = (status) => status.yours?.appDevices ?? status.appDevices;
604
+ var waitForAppRegistration = async (apiKey) => {
605
+ const deadline = Date.now() + appRegisterTimeoutMs();
606
+ if (Date.now() >= deadline) return { kind: "timeout" };
607
+ const stop = startSpinner(() => {
608
+ const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
609
+ return `Waiting for your phone to register for notifications ${dim(`(${left}s)`)}`;
610
+ });
611
+ try {
612
+ while (Date.now() < deadline) {
613
+ await sleep(CONNECT_POLL_INTERVAL_MS);
614
+ const probe = await fetchChannels(apiKey);
615
+ if (probe.kind === "unauthorized") {
616
+ stop(yellow("!"), "The server stopped accepting this key");
617
+ return { kind: "unauthorized" };
618
+ }
619
+ if (probe.kind === "locked") {
620
+ stop(yellow("!"), "This workspace has no active plan");
621
+ return { kind: "locked" };
622
+ }
623
+ if (probe.kind !== "ok") continue;
624
+ if (!probe.status.precise || ownedApp(probe.status) > 0) {
625
+ stop(check, "Phone registered for notifications");
626
+ return { kind: "connected", channels: probe.status };
627
+ }
628
+ }
629
+ } catch {
630
+ }
631
+ stop(yellow("!"), "This phone has not registered for notifications yet");
632
+ return { kind: "timeout" };
633
+ };
589
634
  var confirmAppConnection = async (apiKey) => {
590
635
  const probe = await fetchChannels(apiKey);
591
636
  if (probe.kind !== "ok") return describeProbeFailure(probe);
592
- const channels = probe.status;
593
- const ownedApp = channels.yours?.appDevices ?? channels.appDevices;
594
- if (channels.precise && ownedApp === 0) {
595
- const repair = describeAppRepair(reachVerdict(channels)) ?? [];
596
- console.log(` ${yellow("!")} No device of yours is registered for notifications yet.`);
637
+ let channels = probe.status;
638
+ if (channels.precise && ownedApp(channels) === 0) {
639
+ const settled = await waitForAppRegistration(apiKey);
640
+ if (settled.kind === "unauthorized") return describeProbeFailure({ kind: "unauthorized" });
641
+ if (settled.kind === "locked") return describeProbeFailure({ kind: "locked" });
642
+ if (settled.kind === "connected") channels = settled.channels;
643
+ }
644
+ if (channels.precise && ownedApp(channels) === 0) {
645
+ const repair = describeJustPairedRepair(reachVerdict(channels));
646
+ console.log(` ${yellow("!")} Paired, but this phone has not registered for notifications yet.`);
597
647
  for (const line of repair) console.log(` ${dim(line)}`);
598
648
  console.log(` ${dim("Then run")} ${cyan("pushary doctor")}${dim(".")}`);
599
649
  return phoneFailure("app_not_registered", repair);
@@ -0,0 +1,11 @@
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,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-5646VKR4.js";
3
+ } from "./chunk-K42RXJPG.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -1,7 +1,11 @@
1
+ import {
2
+ PRETOOLUSE_GATED_TOOLS,
3
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
+ } from "./chunk-ATBKJNWS.js";
1
5
  import {
2
6
  HOOK_BUDGETS,
3
7
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-7YJS2VTG.js";
8
+ } from "./chunk-DMW54VWK.js";
5
9
 
6
10
  // src/gemini-config.ts
7
11
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
@@ -102,7 +106,54 @@ var missingGeminiHookEvents = (settings) => {
102
106
  }).map((definition) => definition.event);
103
107
  };
104
108
 
109
+ // src/agent-version.ts
110
+ var parseVersion = (raw) => {
111
+ const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
112
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
113
+ };
114
+ var compareVersion = (a, b) => {
115
+ for (let index = 0; index < 3; index++) {
116
+ if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
117
+ }
118
+ return 0;
119
+ };
120
+ var atLeast = (version, floor) => compareVersion(version, floor) >= 0;
121
+
122
+ // src/claude-events.ts
123
+ var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS].join("|");
124
+ var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
125
+ var ESTABLISHED = [1, 0, 0];
126
+ var OBSERVED = [2, 0, 0];
127
+ var CLAUDE_HOOK_EVENTS = [
128
+ { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
129
+ { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
130
+ { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
131
+ { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
132
+ { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
133
+ { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
134
+ { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
135
+ { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
136
+ { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
137
+ { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, delivery: "telemetry", since: ESTABLISHED },
138
+ // Observability only. Each carries session shape the island cannot get any other
139
+ // way, and none of them decides anything.
140
+ { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
141
+ { event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
142
+ { event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
143
+ { event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
144
+ { event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
145
+ // An MCP server asking the person for a value, answered from the phone rather
146
+ // than the terminal. No matcher: Claude Code matches this event on the MCP
147
+ // server's name, and we have no business preferring one server over another.
148
+ { event: "Elicitation", binary: "pushary-elicitation-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: OBSERVED, cliOnly: true }
149
+ ];
150
+ var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
151
+
105
152
  export {
153
+ parseVersion,
154
+ compareVersion,
155
+ CLAUDE_HOOK_EVENTS,
156
+ supportedClaudeEvents,
106
157
  GEMINI_HOOK_BINARY,
107
158
  addGeminiMcpServer,
108
159
  addGeminiHooks,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-7YJS2VTG.js";
6
+ } from "./chunk-DMW54VWK.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-B3V2H6SA.js";
10
+ } from "./chunk-VL5ZNK6V.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-2UMNXADU.js";
13
13
  import {
14
14
  isValidApiKey
15
- } from "./chunk-7YJS2VTG.js";
15
+ } from "./chunk-DMW54VWK.js";
16
16
 
17
17
  // src/session.ts
18
18
  var IDENTITY_PATH = "/api/v1/server/identity";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-B3V2H6SA.js";
3
+ } from "./chunk-VL5ZNK6V.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -5,25 +5,19 @@ import {
5
5
  import {
6
6
  isGatingMoment,
7
7
  recordKeylessMoment
8
- } from "./chunk-7MBDFNUC.js";
8
+ } from "./chunk-VIHRP25S.js";
9
9
  import {
10
10
  denyReasonFrom,
11
11
  isDeferAnswer
12
12
  } from "./chunk-YHG74UFF.js";
13
13
  import {
14
- deriveAction,
15
- deriveActionBody,
16
- deriveBlocker,
17
- deriveToolTarget,
18
- describeToolCall,
19
14
  fetchModeState,
20
15
  getPolicy,
21
16
  readLastPrompt,
22
17
  readLastUserPrompt,
23
18
  repoKeyFor,
24
- scopePathFor,
25
19
  throttlePass
26
- } from "./chunk-V2XPF77V.js";
20
+ } from "./chunk-VOE4SYPR.js";
27
21
  import {
28
22
  DEFAULT_SESSION,
29
23
  askUser,
@@ -32,6 +26,14 @@ import {
32
26
  savePendingQuestion,
33
27
  sendNotification
34
28
  } from "./chunk-J3YDT4HM.js";
29
+ import {
30
+ deriveAction,
31
+ deriveActionBody,
32
+ deriveBlocker,
33
+ deriveToolTarget,
34
+ describeToolCall,
35
+ scopePathFor
36
+ } from "./chunk-7J67NYK4.js";
35
37
  import {
36
38
  getMachineId
37
39
  } from "./chunk-RN3NOEJF.js";
@@ -48,7 +50,7 @@ import {
48
50
  hookWaitDeadline,
49
51
  parseAgentQuestionAnswers,
50
52
  resolveGate
51
- } from "./chunk-7YJS2VTG.js";
53
+ } from "./chunk-DMW54VWK.js";
52
54
 
53
55
  // src/decision-episode.ts
54
56
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  redactSecrets
6
- } from "./chunk-7YJS2VTG.js";
6
+ } from "./chunk-DMW54VWK.js";
7
7
 
8
8
  // src/crypto.ts
9
9
  import nacl from "tweetnacl";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hasGeminiHooks
3
- } from "./chunk-ZZE6FN7T.js";
3
+ } from "./chunk-K42RXJPG.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-B3V2H6SA.js";
6
+ } from "./chunk-VL5ZNK6V.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-A4I5JGCW.js";
4
+ } from "./chunk-KFAH6KRE.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -5,7 +5,7 @@ import {
5
5
  generateSessionKey,
6
6
  uploadTranscriptRecords,
7
7
  wrapSessionKey
8
- } from "./chunk-ECEUMWBH.js";
8
+ } from "./chunk-QLMX5LLV.js";
9
9
 
10
10
  // src/live-session.ts
11
11
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-7YJS2VTG.js";
3
+ } from "./chunk-DMW54VWK.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,7 +1,10 @@
1
+ import {
2
+ canonicalJson
3
+ } from "./chunk-GIRT7677.js";
1
4
  import {
2
5
  HOOK_BUDGETS,
3
6
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-7YJS2VTG.js";
7
+ } from "./chunk-DMW54VWK.js";
5
8
 
6
9
  // src/codex-config.ts
7
10
  import { createHash } from "crypto";
@@ -20,7 +23,11 @@ var CODEX_HOOK_EVENTS = [
20
23
  { event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
21
24
  { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
22
25
  { event: "Stop", delivery: "telemetry", timeout: 10 },
23
- { event: "SessionStart", matcher: "startup|resume", delivery: "telemetry", timeout: 10 }
26
+ { event: "SessionStart", matcher: "startup|resume", delivery: "telemetry", timeout: 10 },
27
+ // Codex caps SessionEnd at 3s and defaults it to 1s, unlike every other event
28
+ // here. It also runs synchronously whatever `async` says, so the handler must
29
+ // fit the window rather than assume the 10s Claude Code allows.
30
+ { event: "SessionEnd", delivery: "telemetry", timeout: 3 }
24
31
  ];
25
32
  var CODEX_EVENT_KEY = {
26
33
  PermissionRequest: "permission_request",
@@ -28,8 +35,10 @@ var CODEX_EVENT_KEY = {
28
35
  PostToolUse: "post_tool_use",
29
36
  UserPromptSubmit: "user_prompt_submit",
30
37
  Stop: "stop",
31
- SessionStart: "session_start"
38
+ SessionStart: "session_start",
39
+ SessionEnd: "session_end"
32
40
  };
41
+ var codexEventKeyOf = (event) => event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
33
42
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
34
43
  var ensureRecord = (target, key) => {
35
44
  const existing = asRecord(target[key]);
@@ -121,19 +130,39 @@ var missingCodexHookEvents = (config) => {
121
130
  return !Array.isArray(entries) || !entries.some(isPusharyCodexHook);
122
131
  }).map((definition) => definition.event);
123
132
  };
124
- var canonicalJson = (value) => {
125
- if (value === null || typeof value !== "object") return JSON.stringify(value);
126
- if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
127
- const obj = value;
128
- return "{" + Object.keys(obj).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(obj[key])).join(",") + "}";
129
- };
130
133
  var codexHookTrustHash = (definition, command) => {
131
- const hook = { async: false, command, timeout: definition.timeout, type: "command" };
134
+ const hook = { command, timeout: definition.timeout, type: "command" };
132
135
  if (definition.statusMessage) hook.statusMessage = definition.statusMessage;
133
- const identity = { event_name: CODEX_EVENT_KEY[definition.event], hooks: [hook] };
134
- if (definition.matcher) identity.matcher = definition.matcher;
136
+ const entry = { hooks: [hook] };
137
+ if (definition.matcher) entry.matcher = definition.matcher;
138
+ return codexEntryTrustHash(CODEX_EVENT_KEY[definition.event], entry);
139
+ };
140
+ var codexEntryTrustHash = (eventKey, entry) => {
141
+ const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
142
+ const identity = {
143
+ event_name: eventKey,
144
+ hooks: hooks.map((hook) => ({ async: false, ...asRecord(hook) }))
145
+ };
146
+ if (typeof entry.matcher === "string") identity.matcher = entry.matcher;
135
147
  return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
136
148
  };
149
+ var codexTrustSchemeAgrees = (hooksConfig, config, hooksJsonPath) => {
150
+ const hooks = asRecord(hooksConfig?.hooks);
151
+ const state = asRecord(asRecord(config.hooks)?.state);
152
+ if (!hooks || !state) return false;
153
+ for (const [event, entries] of Object.entries(hooks)) {
154
+ if (!Array.isArray(entries)) continue;
155
+ const eventKey = CODEX_EVENT_KEY[event] ?? codexEventKeyOf(event);
156
+ for (const [index, entry] of entries.entries()) {
157
+ const record = asRecord(entry);
158
+ if (!record) continue;
159
+ const stored = asRecord(state[`${hooksJsonPath}:${eventKey}:${index}:0`])?.trusted_hash;
160
+ if (typeof stored !== "string") continue;
161
+ if (stored === codexEntryTrustHash(eventKey, record)) return true;
162
+ }
163
+ }
164
+ return false;
165
+ };
137
166
  var codexHookPositions = (hooksConfig) => {
138
167
  const hooks = asRecord(hooksConfig?.hooks);
139
168
  const positions = {};
@@ -237,8 +266,10 @@ export {
237
266
  removeCodexHooks,
238
267
  hasCodexHooks,
239
268
  missingCodexHookEvents,
269
+ codexTrustSchemeAgrees,
240
270
  codexHookPositions,
241
271
  untrustedCodexHookEvents,
272
+ removeCodexHookTrust,
242
273
  removeCodexSettings,
243
274
  addCodexHookTrust,
244
275
  claudeSettings,