@pushary/agent-hooks 1.5.0 → 1.5.1

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 (59) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -1
  3. package/dist/bin/pushary-bell-hook.js +9 -9
  4. package/dist/bin/pushary-bell.js +2 -2
  5. package/dist/bin/pushary-claude.js +10 -10
  6. package/dist/bin/pushary-clean.js +12 -12
  7. package/dist/bin/pushary-codex-bridge.js +4 -4
  8. package/dist/bin/pushary-codex-hook.js +13 -13
  9. package/dist/bin/pushary-codex.js +3 -3
  10. package/dist/bin/pushary-daemon.js +15 -14
  11. package/dist/bin/pushary-disconnect.js +6 -6
  12. package/dist/bin/pushary-doctor.js +43 -15
  13. package/dist/bin/pushary-elicitation-hook.js +9 -9
  14. package/dist/bin/pushary-gemini-bridge.js +3 -3
  15. package/dist/bin/pushary-gemini-hook.js +11 -11
  16. package/dist/bin/pushary-hook.js +14 -14
  17. package/dist/bin/pushary-login.js +3 -3
  18. package/dist/bin/pushary-logout.js +1 -1
  19. package/dist/bin/pushary-notification-hook.js +12 -12
  20. package/dist/bin/pushary-opencode-hook.js +10 -10
  21. package/dist/bin/pushary-permission-denied-hook.js +14 -14
  22. package/dist/bin/pushary-permission-hook.js +14 -14
  23. package/dist/bin/pushary-post-hook.js +12 -12
  24. package/dist/bin/pushary-prompt-hook.js +12 -12
  25. package/dist/bin/pushary-session-end-hook.js +12 -12
  26. package/dist/bin/pushary-session-start-hook.js +16 -15
  27. package/dist/bin/pushary-setup.js +52 -51
  28. package/dist/bin/pushary-status.js +5 -5
  29. package/dist/bin/pushary-stop-hook.js +12 -12
  30. package/dist/bin/pushary-stopfailure-hook.js +12 -12
  31. package/dist/bin/pushary-transcript-register.js +8 -8
  32. package/dist/bin/pushary-upgrade.js +57 -27
  33. package/dist/{chunk-EADZPVYW.js → chunk-2AUIEZYS.js} +1 -1
  34. package/dist/{chunk-J4J45VPZ.js → chunk-4ERLRNOK.js} +2 -2
  35. package/dist/{chunk-SOCEY746.js → chunk-5UQ5ATIE.js} +1 -1
  36. package/dist/{chunk-KEOLNHR3.js → chunk-5ZZG76S2.js} +1 -1
  37. package/dist/chunk-6EEANS74.js +300 -0
  38. package/dist/{chunk-DYHXOXUF.js → chunk-6IU45EHE.js} +6 -20
  39. package/dist/{chunk-ORLJNJEO.js → chunk-6QFEQIPM.js} +1 -1
  40. package/dist/chunk-BY2CRWBO.js +38 -0
  41. package/dist/{chunk-FDHMNT24.js → chunk-D573TLP6.js} +1 -1
  42. package/dist/{chunk-3NBODGQS.js → chunk-HUAOMX4U.js} +1 -1
  43. package/dist/{chunk-F57RGL7K.js → chunk-IV3RHICU.js} +3 -3
  44. package/dist/{chunk-PNNBMH4E.js → chunk-JKBDGZFL.js} +12 -6
  45. package/dist/{chunk-DLJN5XEU.js → chunk-KHXVTWYS.js} +1 -1
  46. package/dist/{chunk-MYP4UUFH.js → chunk-M2LMXPJZ.js} +1 -1
  47. package/dist/{chunk-CQYHOHOK.js → chunk-MDJJW3LP.js} +2 -2
  48. package/dist/{chunk-OSSDK5RS.js → chunk-MU3WU2FO.js} +6 -2
  49. package/dist/{chunk-IYTQ4B5I.js → chunk-PJ27EHCD.js} +4 -4
  50. package/dist/{chunk-JF736N4C.js → chunk-R5KY3PBN.js} +4 -4
  51. package/dist/{chunk-NFKVRMTM.js → chunk-TZGCUNOX.js} +3 -3
  52. package/dist/{chunk-PCGZXWGQ.js → chunk-WNJSGZY7.js} +2 -2
  53. package/dist/{chunk-UQHUUZ5H.js → chunk-WOLPADR6.js} +1 -1
  54. package/dist/{chunk-7XT76LWT.js → chunk-X7FESZTD.js} +2 -2
  55. package/dist/{chunk-H5LHTC5X.js → chunk-YAWXKL2X.js} +1 -1
  56. package/dist/{chunk-NTQSICPJ.js → chunk-Z4VGIBJ7.js} +2 -2
  57. package/dist/{reapply-D2TFN6XK.js → reapply-5CZSTJZI.js} +10 -6
  58. package/dist/src/index.js +8 -8
  59. package/package.json +1 -1
@@ -0,0 +1,300 @@
1
+ import {
2
+ spawnClaude
3
+ } from "./chunk-XHKBHWLX.js";
4
+ import {
5
+ codexHome,
6
+ codexHooksJsonIn,
7
+ isPusharyCodexCommand,
8
+ resolvePathBinary
9
+ } from "./chunk-6IU45EHE.js";
10
+ import {
11
+ getPackageVersion
12
+ } from "./chunk-HWJW3CPO.js";
13
+
14
+ // src/codex-app-server.ts
15
+ import { existsSync, realpathSync } from "fs";
16
+ import { join } from "path";
17
+ var CODEX_HOOK_STATUS = {
18
+ trusted: "trusted",
19
+ untrusted: "untrusted",
20
+ modified: "modified",
21
+ managed: "managed",
22
+ disabled: "disabled",
23
+ notListed: "not listed"
24
+ };
25
+ var CODEX_APP_SERVER_ARGS = ["app-server"];
26
+ var CODEX_APP_SERVER_TIMEOUT_MS = 1e4;
27
+ var TERMINATE_GRACE_MS = 1e3;
28
+ var MAX_RPC_LINE = 4 * 1024 * 1024;
29
+ var STDERR_TAIL_LIMIT = 400;
30
+ var PUSHARY_CLIENT_NAME = "pushary";
31
+ var METHOD_INITIALIZE = "initialize";
32
+ var METHOD_INITIALIZED = "initialized";
33
+ var METHOD_HOOKS_LIST = "hooks/list";
34
+ var METHOD_CONFIG_BATCH_WRITE = "config/batchWrite";
35
+ var HOOKS_STATE_KEY_PATH = "hooks.state";
36
+ var CODEX_INSTALLATION_ID_FILE = "installation_id";
37
+ var CODEX_NOT_ASKED = {
38
+ notInteractive: "Pushary only starts Codex when you run setup, upgrade or doctor yourself at a terminal",
39
+ noCodexOnPath: "there is no codex command on PATH",
40
+ neverOpened: "Codex has not been opened on this machine yet"
41
+ };
42
+ var UNRECOGNISED_LISTING = "Codex answered hooks/list in a shape Pushary does not recognise";
43
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
44
+ var isCodexListedHook = (value) => isRecord(value) && typeof value.key === "string" && typeof value.eventName === "string" && typeof value.command === "string" && typeof value.sourcePath === "string" && typeof value.enabled === "boolean" && typeof value.isManaged === "boolean" && typeof value.currentHash === "string" && typeof value.trustStatus === "string";
45
+ var listedHookFields = (hook) => ({
46
+ key: hook.key,
47
+ eventName: hook.eventName,
48
+ command: hook.command,
49
+ sourcePath: hook.sourcePath,
50
+ enabled: hook.enabled,
51
+ isManaged: hook.isManaged,
52
+ currentHash: hook.currentHash,
53
+ trustStatus: hook.trustStatus
54
+ });
55
+ var diagnosticMessages = (value) => {
56
+ if (!Array.isArray(value)) return [];
57
+ return value.flatMap((entry) => {
58
+ if (typeof entry === "string") return [entry];
59
+ if (isRecord(entry) && typeof entry.message === "string") return [entry.message];
60
+ return [];
61
+ });
62
+ };
63
+ var hooksListingOf = (result) => {
64
+ if (!isRecord(result) || !Array.isArray(result.data)) return null;
65
+ const hooks = [];
66
+ const warnings = [];
67
+ for (const layer of result.data) {
68
+ if (!isRecord(layer) || !Array.isArray(layer.hooks)) return null;
69
+ hooks.push(...layer.hooks.filter(isCodexListedHook).map(listedHookFields));
70
+ warnings.push(...diagnosticMessages(layer.warnings), ...diagnosticMessages(layer.errors));
71
+ }
72
+ return { hooks, warnings };
73
+ };
74
+ var isPusharyHookIn = (hooksJsonPath, hook) => hook.sourcePath === hooksJsonPath && isPusharyCodexCommand(hook.command);
75
+ var isTrustableByPushary = (hook) => hook.enabled && !hook.isManaged && hook.trustStatus !== CODEX_HOOK_STATUS.managed;
76
+ var pusharyHooksIn = (hooks, hooksJsonPath) => hooks.filter((hook) => isPusharyHookIn(hooksJsonPath, hook));
77
+ var selectPusharyCodexHooks = (hooks, hooksJsonPath) => pusharyHooksIn(hooks, hooksJsonPath).filter(isTrustableByPushary);
78
+ var codexTrustVerdict = (hooks, hooksJsonPath) => {
79
+ if (pusharyHooksIn(hooks, hooksJsonPath).length === 0) return { kind: "unlisted" };
80
+ const untrusted = selectPusharyCodexHooks(hooks, hooksJsonPath).filter((hook) => hook.trustStatus !== CODEX_HOOK_STATUS.trusted);
81
+ return untrusted.length === 0 ? { kind: "trusted" } : { kind: "untrusted", hooks: untrusted };
82
+ };
83
+ var codexTrustEdit = (hooks) => ({
84
+ edits: [{
85
+ keyPath: HOOKS_STATE_KEY_PATH,
86
+ mergeStrategy: "upsert",
87
+ value: Object.fromEntries(hooks.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]))
88
+ }],
89
+ reloadUserConfig: true
90
+ });
91
+ var initializeParams = (clientVersion) => ({
92
+ clientInfo: { name: PUSHARY_CLIENT_NAME, title: null, version: clientVersion }
93
+ });
94
+ var hooksListParams = (cwd) => ({ cwds: [cwd] });
95
+ var hookEventOf = (eventName) => eventName.charAt(0).toUpperCase() + eventName.slice(1);
96
+ var readingOf = (hook) => ({
97
+ event: hookEventOf(hook.eventName),
98
+ status: hook.enabled ? hook.trustStatus : CODEX_HOOK_STATUS.disabled
99
+ });
100
+ var codexHookTrustByEvent = (events, hooks) => events.map((event) => hooks.map(readingOf).find((reading) => reading.event === event) ?? { event, status: CODEX_HOOK_STATUS.notListed });
101
+ var CODEX_TRUST_FAILURES = /* @__PURE__ */ new Set([CODEX_HOOK_STATUS.untrusted, CODEX_HOOK_STATUS.modified]);
102
+ var isCodexTrustFailure = (reading) => CODEX_TRUST_FAILURES.has(reading.status);
103
+ var eventsWithStatus = (readings, status) => readings.filter((reading) => reading.status === status).map((reading) => reading.event);
104
+ var assessCodexHookTrust = (events, hooks) => {
105
+ const readings = codexHookTrustByEvent(events, hooks);
106
+ const notListed = eventsWithStatus(readings, CODEX_HOOK_STATUS.notListed);
107
+ if (readings.length > 0 && notListed.length === readings.length) return { kind: "unlisted" };
108
+ const disabled = eventsWithStatus(readings, CODEX_HOOK_STATUS.disabled);
109
+ const failures = readings.filter(isCodexTrustFailure);
110
+ if (failures.length > 0) return { kind: "untrusted", failures, notListed, disabled };
111
+ const trusted = eventsWithStatus(readings, CODEX_HOOK_STATUS.trusted).length;
112
+ return { kind: "trusted", trusted, total: readings.length, notListed, disabled };
113
+ };
114
+ var mayAskCodex = (conditions) => {
115
+ if (!conditions.interactive) return { kind: "skipped", reason: CODEX_NOT_ASKED.notInteractive };
116
+ if (!conditions.codexOnPath) return { kind: "skipped", reason: CODEX_NOT_ASKED.noCodexOnPath };
117
+ if (!conditions.codexOpenedBefore) return { kind: "skipped", reason: CODEX_NOT_ASKED.neverOpened };
118
+ return { kind: "ask", binary: conditions.codexOnPath };
119
+ };
120
+ var canonicalPath = (path) => {
121
+ try {
122
+ return realpathSync(path);
123
+ } catch {
124
+ return path;
125
+ }
126
+ };
127
+ var withCanonicalSources = (hooks) => hooks.map((hook) => ({ ...hook, sourcePath: canonicalPath(hook.sourcePath) }));
128
+ var hasExited = (child) => child.exitCode !== null || child.signalCode !== null;
129
+ var waitForExit = (child, ms) => new Promise((resolve) => {
130
+ if (hasExited(child)) return resolve(true);
131
+ const timer = setTimeout(() => resolve(false), ms);
132
+ child.once("exit", () => {
133
+ clearTimeout(timer);
134
+ resolve(true);
135
+ });
136
+ });
137
+ var terminate = async (child) => {
138
+ if (child.pid === void 0 || hasExited(child)) return;
139
+ child.stdin?.end();
140
+ child.kill("SIGTERM");
141
+ if (await waitForExit(child, TERMINATE_GRACE_MS)) return;
142
+ child.kill("SIGKILL");
143
+ await waitForExit(child, TERMINATE_GRACE_MS);
144
+ };
145
+ var rpcErrorMessage = (error) => isRecord(error) && typeof error.message === "string" ? error.message : "Codex returned an error";
146
+ var describeExit = (code, signal, stderr) => {
147
+ const how = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
148
+ const tail = stderr.trim();
149
+ return tail ? `Codex app-server exited (${how}): ${tail}` : `Codex app-server exited (${how})`;
150
+ };
151
+ var runCodexAppServer = (launch, conversation) => {
152
+ const child = spawnClaude(launch.binary, CODEX_APP_SERVER_ARGS, {
153
+ cwd: launch.codexHome,
154
+ env: { ...process.env, CODEX_HOME: launch.codexHome },
155
+ stdio: ["pipe", "pipe", "pipe"],
156
+ windowsHide: true
157
+ });
158
+ const pending = /* @__PURE__ */ new Map();
159
+ let nextId = 1;
160
+ let buffer = "";
161
+ let stderrTail = "";
162
+ let stoppedReason = null;
163
+ let stop = () => {
164
+ };
165
+ const stopped = new Promise((resolve) => {
166
+ stop = (reason) => {
167
+ if (stoppedReason !== null) return;
168
+ stoppedReason = reason;
169
+ for (const settle of pending.values()) settle({ kind: "error", message: reason });
170
+ pending.clear();
171
+ resolve({ kind: "unavailable", reason });
172
+ };
173
+ });
174
+ const write = (message) => {
175
+ if (stoppedReason === null) child.stdin?.write(`${JSON.stringify(message)}
176
+ `);
177
+ };
178
+ const handleLine = (line) => {
179
+ let message;
180
+ try {
181
+ message = JSON.parse(line);
182
+ } catch {
183
+ return;
184
+ }
185
+ if (!isRecord(message) || typeof message.id !== "number" || "method" in message) return;
186
+ const settle = pending.get(message.id);
187
+ if (!settle) return;
188
+ pending.delete(message.id);
189
+ settle("error" in message && message.error !== void 0 && message.error !== null ? { kind: "error", message: rpcErrorMessage(message.error) } : { kind: "result", value: message.result });
190
+ };
191
+ child.stdin?.on("error", () => {
192
+ });
193
+ child.stdout?.setEncoding("utf8");
194
+ child.stdout?.on("data", (chunk) => {
195
+ buffer += chunk;
196
+ let newline = buffer.indexOf("\n");
197
+ while (newline >= 0) {
198
+ handleLine(buffer.slice(0, newline));
199
+ buffer = buffer.slice(newline + 1);
200
+ newline = buffer.indexOf("\n");
201
+ }
202
+ if (buffer.length > MAX_RPC_LINE) stop("Codex app-server sent an oversized message");
203
+ });
204
+ child.stderr?.setEncoding("utf8");
205
+ child.stderr?.on("data", (chunk) => {
206
+ stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_LIMIT);
207
+ });
208
+ child.once("error", (error) => stop(`could not start ${launch.binary}: ${error.message}`));
209
+ child.once("close", (code, signal) => stop(describeExit(code, signal, stderrTail)));
210
+ const timer = setTimeout(
211
+ () => stop(`Codex app-server did not answer within ${launch.timeoutMs / 1e3}s`),
212
+ launch.timeoutMs
213
+ );
214
+ const session = {
215
+ request: (method, params) => {
216
+ if (stoppedReason !== null) return Promise.resolve({ kind: "error", message: stoppedReason });
217
+ const id = nextId++;
218
+ return new Promise((resolve) => {
219
+ pending.set(id, resolve);
220
+ write({ id, method, params });
221
+ });
222
+ },
223
+ notify: (method) => write({ method })
224
+ };
225
+ const conversed = conversation(session).catch((error) => ({
226
+ kind: "unavailable",
227
+ reason: error instanceof Error ? error.message : String(error)
228
+ }));
229
+ return Promise.race([conversed, stopped]).finally(async () => {
230
+ clearTimeout(timer);
231
+ await terminate(child);
232
+ });
233
+ };
234
+ var launchOf = (options) => {
235
+ const home = options.codexHome ?? codexHome();
236
+ const decision = mayAskCodex({
237
+ interactive: options.interactive ?? process.stdout.isTTY === true,
238
+ codexOnPath: options.codexOnPath === void 0 ? resolvePathBinary("codex") ?? null : options.codexOnPath,
239
+ codexOpenedBefore: existsSync(join(home, CODEX_INSTALLATION_ID_FILE))
240
+ });
241
+ if (decision.kind === "skipped") return decision;
242
+ return { binary: decision.binary, codexHome: home, timeoutMs: options.timeoutMs ?? CODEX_APP_SERVER_TIMEOUT_MS };
243
+ };
244
+ var isSkipped = (value) => "kind" in value && value.kind === "skipped";
245
+ var openSession = async (session) => {
246
+ const initialized = await session.request(METHOD_INITIALIZE, initializeParams(getPackageVersion()));
247
+ if (initialized.kind === "error") return { kind: "unavailable", reason: `Codex refused initialize: ${initialized.message}` };
248
+ session.notify(METHOD_INITIALIZED);
249
+ return null;
250
+ };
251
+ var listHooks = async (session, launch) => {
252
+ const listed = await session.request(METHOD_HOOKS_LIST, hooksListParams(launch.codexHome));
253
+ if (listed.kind === "error") return { kind: "unavailable", reason: `Codex could not list hooks: ${listed.message}` };
254
+ const listing = hooksListingOf(listed.value);
255
+ if (!listing) return { kind: "unavailable", reason: UNRECOGNISED_LISTING };
256
+ return { hooks: withCanonicalSources(listing.hooks), warnings: listing.warnings };
257
+ };
258
+ var isListing = (value) => !("kind" in value);
259
+ var confirmationAfterWrite = (listing, hooksJsonPath, writeError) => {
260
+ const verdict = codexTrustVerdict(listing.hooks, hooksJsonPath);
261
+ if (verdict.kind === "trusted") return { kind: "verified", wrote: true };
262
+ if (verdict.kind === "unlisted") return { kind: "unlisted", warnings: listing.warnings };
263
+ return { kind: "refused", hooks: verdict.hooks.map(readingOf), writeError };
264
+ };
265
+ var trustConversation = (launch, hooksJsonPath) => async (session) => {
266
+ const unavailable = await openSession(session);
267
+ if (unavailable) return unavailable;
268
+ const before = await listHooks(session, launch);
269
+ if (!isListing(before)) return before;
270
+ const verdict = codexTrustVerdict(before.hooks, hooksJsonPath);
271
+ if (verdict.kind === "trusted") return { kind: "verified", wrote: false };
272
+ if (verdict.kind === "unlisted") return { kind: "unlisted", warnings: before.warnings };
273
+ const written = await session.request(METHOD_CONFIG_BATCH_WRITE, codexTrustEdit(verdict.hooks));
274
+ const after = await listHooks(session, launch);
275
+ if (!isListing(after)) return after;
276
+ return confirmationAfterWrite(after, hooksJsonPath, written.kind === "error" ? written.message : null);
277
+ };
278
+ var listingConversation = (launch, hooksJsonPath) => async (session) => {
279
+ const unavailable = await openSession(session);
280
+ if (unavailable) return unavailable;
281
+ const listing = await listHooks(session, launch);
282
+ if (!isListing(listing)) return listing;
283
+ return { kind: "listed", hooks: pusharyHooksIn(listing.hooks, hooksJsonPath) };
284
+ };
285
+ var ensureCodexTrustsPusharyHooks = async (options = {}) => {
286
+ const launch = launchOf(options);
287
+ if (isSkipped(launch)) return launch;
288
+ return runCodexAppServer(launch, trustConversation(launch, canonicalPath(codexHooksJsonIn(launch.codexHome))));
289
+ };
290
+ var readCodexTrustForPusharyHooks = async (options = {}) => {
291
+ const launch = launchOf(options);
292
+ if (isSkipped(launch)) return launch;
293
+ return runCodexAppServer(launch, listingConversation(launch, canonicalPath(codexHooksJsonIn(launch.codexHome))));
294
+ };
295
+
296
+ export {
297
+ assessCodexHookTrust,
298
+ ensureCodexTrustsPusharyHooks,
299
+ readCodexTrustForPusharyHooks
300
+ };
@@ -314,7 +314,8 @@ var CODEX_HOOK_BINARY = "pushary-codex-hook";
314
314
  var codexHome = () => codexHomeFrom(homedir());
315
315
  var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join2(home, ".codex");
316
316
  var codexConfigToml = () => join2(codexHome(), "config.toml");
317
- var codexHooksJson = () => join2(codexHome(), "hooks.json");
317
+ var codexHooksJsonIn = (home) => join2(home, "hooks.json");
318
+ var codexHooksJson = () => codexHooksJsonIn(codexHome());
318
319
  var codexAgentsMd = () => join2(codexHome(), "AGENTS.md");
319
320
  var codexSkillDir = () => join2(codexHome(), "skills", "pushary");
320
321
  var CODEX_HOOK_EVENTS = [
@@ -386,10 +387,11 @@ var readCodexMcpAuth = (config) => {
386
387
  if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
387
388
  return void 0;
388
389
  };
390
+ var isPusharyCodexCommand = (command) => command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
389
391
  var isPusharyCommand = (hook, scope) => {
390
392
  const command = String(asRecord2(hook)?.command ?? "");
391
393
  if (isProtectedByScope(command, scope)) return false;
392
- return command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
394
+ return isPusharyCodexCommand(command);
393
395
  };
394
396
  var isPusharyCodexHook = (entry, scope) => {
395
397
  const hooks = asRecord2(entry)?.hooks;
@@ -463,23 +465,6 @@ var codexEntryTrustHash = (eventKey, entry) => {
463
465
  if (typeof entry.matcher === "string") identity.matcher = entry.matcher;
464
466
  return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
465
467
  };
466
- var codexTrustSchemeAgrees = (hooksConfig, config, hooksJsonPath) => {
467
- const hooks = asRecord2(hooksConfig?.hooks);
468
- const state = asRecord2(asRecord2(config.hooks)?.state);
469
- if (!hooks || !state) return false;
470
- for (const [event, entries] of Object.entries(hooks)) {
471
- if (!Array.isArray(entries)) continue;
472
- const eventKey = CODEX_EVENT_KEY[event] ?? codexEventKeyOf(event);
473
- for (const [index, entry] of entries.entries()) {
474
- const record = asRecord2(entry);
475
- if (!record) continue;
476
- const stored = asRecord2(state[`${hooksJsonPath}:${eventKey}:${index}:0`])?.trusted_hash;
477
- if (typeof stored !== "string") continue;
478
- if (stored === codexEntryTrustHash(eventKey, record)) return true;
479
- }
480
- }
481
- return false;
482
- };
483
468
  var codexHookPositions = (hooksConfig) => {
484
469
  const hooks = asRecord2(hooksConfig?.hooks);
485
470
  const positions = {};
@@ -676,18 +661,19 @@ export {
676
661
  codexHome,
677
662
  codexHomeFrom,
678
663
  codexConfigToml,
664
+ codexHooksJsonIn,
679
665
  codexHooksJson,
680
666
  codexAgentsMd,
681
667
  codexSkillDir,
682
668
  CODEX_HOOK_EVENTS,
683
669
  addCodexMcpServer,
684
670
  readCodexMcpAuth,
671
+ isPusharyCodexCommand,
685
672
  codexRegistrationHolder,
686
673
  addCodexHooks,
687
674
  removeCodexHooks,
688
675
  hasCodexHooks,
689
676
  missingCodexHookEvents,
690
- codexTrustSchemeAgrees,
691
677
  codexHookPositions,
692
678
  codexVacatedIndexes,
693
679
  dropVacatedCodexHookTrust,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  registerTranscript
3
- } from "./chunk-NFKVRMTM.js";
3
+ } from "./chunk-TZGCUNOX.js";
4
4
  import {
5
5
  degradedLine
6
6
  } from "./chunk-T4QY64OR.js";
@@ -0,0 +1,38 @@
1
+ import {
2
+ NPX_LATEST_COMMAND
3
+ } from "./chunk-EA3IELHP.js";
4
+
5
+ // src/setup/codex-trust.ts
6
+ var CODEX_TRUST_TELEMETRY = {
7
+ verified: "verified",
8
+ refused: "manual",
9
+ unlisted: "manual",
10
+ skipped: "unverified",
11
+ unavailable: "unverified"
12
+ };
13
+ var isUnanswered = (confirmation) => confirmation.kind === "skipped" || confirmation.kind === "unavailable";
14
+ var codexTrustOutcome = (confirmation, portedTrustWritten) => isUnanswered(confirmation) && !portedTrustWritten ? "manual" : CODEX_TRUST_TELEMETRY[confirmation.kind];
15
+ var CODEX_TRUST_REFUSED = "Codex did not accept the trust Pushary asked for, so its hooks are not trusted yet. Open Codex, run /hooks, and choose Trust for the Pushary hooks.";
16
+ var describeUnlistedCodexHooks = (warnings) => {
17
+ const said = warnings[0] ? ` Codex said: ${warnings[0]}` : "";
18
+ return `Codex does not list the Pushary hooks, so it will not run them.${said} Run ${NPX_LATEST_COMMAND} doctor for details.`;
19
+ };
20
+ var describeCodexTrustConfirmation = (confirmation) => {
21
+ switch (confirmation.kind) {
22
+ case "verified":
23
+ return confirmation.wrote ? "Codex trusted the Pushary hooks when asked, and confirmed it." : "Codex confirms the Pushary hooks are trusted.";
24
+ case "refused":
25
+ return confirmation.writeError ? `${CODEX_TRUST_REFUSED} Codex said: ${confirmation.writeError}` : CODEX_TRUST_REFUSED;
26
+ case "unlisted":
27
+ return describeUnlistedCodexHooks(confirmation.warnings);
28
+ case "skipped":
29
+ return `Pushary did not ask Codex to confirm the trust: ${confirmation.reason}.`;
30
+ case "unavailable":
31
+ return `Codex could not be asked to confirm the Pushary hooks are trusted (${confirmation.reason}).`;
32
+ }
33
+ };
34
+
35
+ export {
36
+ codexTrustOutcome,
37
+ describeCodexTrustConfirmation
38
+ };
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-PYCQXCI5.js";
11
11
  import {
12
12
  getMachineId
13
- } from "./chunk-UQHUUZ5H.js";
13
+ } from "./chunk-WOLPADR6.js";
14
14
  import {
15
15
  withRetry
16
16
  } from "./chunk-HETOKNIL.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-EADZPVYW.js";
3
+ } from "./chunk-2AUIEZYS.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -2,17 +2,17 @@ import {
2
2
  detectProviderLiveCapabilities,
3
3
  hasSpawnProvider,
4
4
  resolveControlPolicy
5
- } from "./chunk-KEOLNHR3.js";
5
+ } from "./chunk-5ZZG76S2.js";
6
6
  import {
7
7
  daemonHasRemoteProof,
8
8
  daemonServiceManagerAvailable,
9
9
  hasDaemonServiceDefinition,
10
10
  readDaemonNeedsLogin,
11
11
  readDaemonStatus
12
- } from "./chunk-7XT76LWT.js";
12
+ } from "./chunk-X7FESZTD.js";
13
13
  import {
14
14
  getMachineId
15
- } from "./chunk-UQHUUZ5H.js";
15
+ } from "./chunk-WOLPADR6.js";
16
16
  import {
17
17
  readControlMode,
18
18
  readKeySource
@@ -1,18 +1,21 @@
1
1
  import {
2
2
  detectClaudeVersion
3
- } from "./chunk-3NBODGQS.js";
3
+ } from "./chunk-HUAOMX4U.js";
4
+ import {
5
+ ensureCodexTrustsPusharyHooks
6
+ } from "./chunk-6EEANS74.js";
4
7
  import {
5
8
  addClaudeMcpServer,
6
9
  addPusharyHooks,
7
10
  addPusharyToolPermissions
8
- } from "./chunk-J4J45VPZ.js";
11
+ } from "./chunk-4ERLRNOK.js";
9
12
  import {
10
13
  claudeUnprovenHolds,
11
14
  claudeWired,
12
15
  codexUnprovenHolds,
13
16
  codexWired,
14
17
  geminiWired
15
- } from "./chunk-NTQSICPJ.js";
18
+ } from "./chunk-Z4VGIBJ7.js";
16
19
  import {
17
20
  CLAUDE_HOOK_BINARY,
18
21
  CLAUDE_HOOK_EVENTS,
@@ -24,7 +27,7 @@ import {
24
27
  needsReregistration,
25
28
  registrationSignatures,
26
29
  supportedClaudeEvents
27
- } from "./chunk-EADZPVYW.js";
30
+ } from "./chunk-2AUIEZYS.js";
28
31
  import {
29
32
  CODEX_HOOK_BINARY,
30
33
  addCodexHookTrust,
@@ -44,7 +47,7 @@ import {
44
47
  registrationHolderOf,
45
48
  resolveGlobalBinary,
46
49
  vscodePluginMcp
47
- } from "./chunk-DYHXOXUF.js";
50
+ } from "./chunk-6IU45EHE.js";
48
51
  import {
49
52
  readJsonSafe,
50
53
  writeFileAtomic,
@@ -309,6 +312,8 @@ var reapplyAllAgents = (apiKey) => {
309
312
  }
310
313
  return done;
311
314
  };
315
+ var isRewrittenCodex = (agent) => agent.id === "codex" && agent.result.hooks;
316
+ var ensureCodexTrustAfterReapply = async (agents, options = {}) => agents.some(isRewrittenCodex) ? ensureCodexTrustsPusharyHooks(options) : null;
312
317
 
313
318
  export {
314
319
  reapplyClaudeSettings,
@@ -319,5 +324,6 @@ export {
319
324
  reapplyGeminiSettings,
320
325
  reapplyCursorPlugin,
321
326
  reapplyVsCodePlugin,
322
- reapplyAllAgents
327
+ reapplyAllAgents,
328
+ ensureCodexTrustAfterReapply
323
329
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TUXFIVOH.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-DYHXOXUF.js";
6
+ } from "./chunk-6IU45EHE.js";
7
7
  import {
8
8
  HOOK_COMPATIBLE_PROFILES
9
9
  } from "./chunk-T4QY64OR.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-DYHXOXUF.js";
3
+ } from "./chunk-6IU45EHE.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-T7E75EKA.js";
@@ -7,10 +7,10 @@ import {
7
7
  permissionDeniedAction,
8
8
  releaseThrottle,
9
9
  reportEvent
10
- } from "./chunk-FDHMNT24.js";
10
+ } from "./chunk-D573TLP6.js";
11
11
  import {
12
12
  canonicalJson
13
- } from "./chunk-DYHXOXUF.js";
13
+ } from "./chunk-6IU45EHE.js";
14
14
  import {
15
15
  writeFileAtomic
16
16
  } from "./chunk-T7E75EKA.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  autoUpdateState
3
- } from "./chunk-DYHXOXUF.js";
3
+ } from "./chunk-6IU45EHE.js";
4
4
  import {
5
5
  readJsonSafe,
6
6
  writeJsonAtomic
@@ -19,7 +19,7 @@ var GLOBAL_PACKAGE_SUBPATH = "/lib/node_modules/@pushary/agent-hooks";
19
19
  var UPGRADE_ENTRY_SUBPATH = ["dist", "bin", "pushary-upgrade.js"];
20
20
  var NON_GLOBAL_INSTALL_SEGMENTS = ["/_npx/", "/.volta/"];
21
21
  var NOT_DISABLED_VALUES = /* @__PURE__ */ new Set(["", "0"]);
22
- var AUTO_UPDATE_RESULTS = ["updated", "current", "needs_manual", "failed"];
22
+ var AUTO_UPDATE_RESULTS = ["updated", "current", "needs_manual", "failed", "finish_pending"];
23
23
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24
24
  var isAutoUpdateResult = (value) => typeof value === "string" && AUTO_UPDATE_RESULTS.some((result) => result === value);
25
25
  var stringField = (record, key) => {
@@ -53,6 +53,7 @@ var autoUpdateOffReason = (input) => {
53
53
  if (input.state.disabled === true) return "disabled_by_user";
54
54
  if (isContinuousIntegration(input.env)) return "ci";
55
55
  if (input.platform === "win32") return "windows";
56
+ if (input.platform === "linux") return "linux";
56
57
  return null;
57
58
  };
58
59
  var globalInstallPrefix = (packageRoot) => {
@@ -89,6 +90,7 @@ var parseAutoUpdateFlag = (args) => {
89
90
  };
90
91
  var AUTO_UPDATE_RESULT_LABELS = {
91
92
  updated: "updated",
93
+ finish_pending: "installed; configuration refresh pending",
92
94
  current: "up to date",
93
95
  needs_manual: "needs a manual upgrade",
94
96
  failed: "failed"
@@ -98,6 +100,7 @@ var describeAutoUpdateCheck = (state) => {
98
100
  const label = state.lastResult ? AUTO_UPDATE_RESULT_LABELS[state.lastResult] : "no result recorded";
99
101
  return [label, versionsOf(state), state.detail].filter(Boolean).join(", ");
100
102
  };
103
+ var hasPendingUpgradeFinish = (state, installed) => state.to === installed && (state.lastResult === "finish_pending" || state.lastResult === "updated" && Boolean(state.detail));
101
104
  var pendingAutoUpdateAnnouncement = (state) => state.lastResult === "updated" && state.announced !== true && state.from && state.to ? { from: state.from, to: state.to } : null;
102
105
  var describeAutoUpdateAnnouncement = (announcement) => `Pushary updated itself from ${announcement.from} to ${announcement.to}.`;
103
106
  var takeAutoUpdateAnnouncement = (statePath, markAnnounced) => {
@@ -154,6 +157,7 @@ export {
154
157
  upgradeEntryFor,
155
158
  parseAutoUpdateFlag,
156
159
  describeAutoUpdateCheck,
160
+ hasPendingUpgradeFinish,
157
161
  describeAutoUpdateAnnouncement,
158
162
  takeAutoUpdateAnnouncement,
159
163
  readAutoUpdateState,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  activeHookSource,
3
3
  activeIdentity
4
- } from "./chunk-JF736N4C.js";
4
+ } from "./chunk-R5KY3PBN.js";
5
5
  import {
6
6
  blockedActionFingerprint,
7
7
  defersToNativeMode,
@@ -9,7 +9,7 @@ import {
9
9
  hasBlockedActionHint,
10
10
  recordBlockedActionHint,
11
11
  reportMachineDenial
12
- } from "./chunk-CQYHOHOK.js";
12
+ } from "./chunk-MDJJW3LP.js";
13
13
  import {
14
14
  isGatingMoment,
15
15
  recordKeylessMoment
@@ -36,7 +36,7 @@ import {
36
36
  reportEvent,
37
37
  scopePathFor,
38
38
  settleQuestionsForToolUse
39
- } from "./chunk-FDHMNT24.js";
39
+ } from "./chunk-D573TLP6.js";
40
40
  import {
41
41
  DECISION_STOP_REASON,
42
42
  askUser,
@@ -51,7 +51,7 @@ import {
51
51
  } from "./chunk-PYCQXCI5.js";
52
52
  import {
53
53
  getMachineId
54
- } from "./chunk-UQHUUZ5H.js";
54
+ } from "./chunk-WOLPADR6.js";
55
55
  import {
56
56
  getApiKey,
57
57
  getBaseUrl
@@ -3,21 +3,21 @@ import {
3
3
  addPusharyToolPermissions,
4
4
  isPusharyHook,
5
5
  removeClaudeMcpServers
6
- } from "./chunk-J4J45VPZ.js";
6
+ } from "./chunk-4ERLRNOK.js";
7
7
  import {
8
8
  CLAUDE_HOOK_EVENTS,
9
9
  addGeminiMcpServer,
10
10
  removeGeminiMcpServer
11
- } from "./chunk-EADZPVYW.js";
11
+ } from "./chunk-2AUIEZYS.js";
12
12
  import {
13
13
  CLAUDE_CODE_AGENT,
14
14
  parseApplyPatchFiles
15
- } from "./chunk-FDHMNT24.js";
15
+ } from "./chunk-D573TLP6.js";
16
16
  import {
17
17
  guardBinary,
18
18
  holdBudgetOf,
19
19
  registerHookEntry
20
- } from "./chunk-DYHXOXUF.js";
20
+ } from "./chunk-6IU45EHE.js";
21
21
  import {
22
22
  PHONE_ANSWERED_TOOLS,
23
23
  hookCompatibleProfile,