@pushary/agent-hooks 1.4.2 → 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 (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +28 -0
  3. package/data/vscode-plugin/README.md +1 -1
  4. package/data/vscode-plugin/scripts/pushary-gate.mjs +2 -2
  5. package/dist/bin/pushary-bell-hook.js +22 -21
  6. package/dist/bin/pushary-bell.js +10 -10
  7. package/dist/bin/pushary-claude.js +28 -24
  8. package/dist/bin/pushary-clean.js +25 -25
  9. package/dist/bin/pushary-codex-bridge.js +16 -14
  10. package/dist/bin/pushary-codex-hook.js +32 -27
  11. package/dist/bin/pushary-codex.js +15 -10
  12. package/dist/bin/pushary-connect.js +11 -11
  13. package/dist/bin/pushary-cowork.js +6 -6
  14. package/dist/bin/pushary-daemon.js +32 -28
  15. package/dist/bin/pushary-disconnect.js +16 -16
  16. package/dist/bin/pushary-doctor.js +111 -40
  17. package/dist/bin/pushary-elicitation-hook.js +23 -22
  18. package/dist/bin/pushary-gemini-bridge.js +13 -11
  19. package/dist/bin/pushary-gemini-hook.js +29 -24
  20. package/dist/bin/pushary-hook.js +30 -30
  21. package/dist/bin/pushary-login.js +14 -14
  22. package/dist/bin/pushary-logout.js +8 -8
  23. package/dist/bin/pushary-mode.js +7 -7
  24. package/dist/bin/pushary-notification-hook.js +26 -25
  25. package/dist/bin/pushary-opencode-hook.js +25 -24
  26. package/dist/bin/pushary-permission-denied-hook.js +30 -29
  27. package/dist/bin/pushary-permission-hook.js +29 -28
  28. package/dist/bin/pushary-post-hook.js +26 -25
  29. package/dist/bin/pushary-prompt-hook.js +26 -25
  30. package/dist/bin/pushary-session-end-hook.js +26 -25
  31. package/dist/bin/pushary-session-start-hook.js +33 -27
  32. package/dist/bin/pushary-setup.js +94 -88
  33. package/dist/bin/pushary-stats.js +6 -6
  34. package/dist/bin/pushary-status.js +15 -15
  35. package/dist/bin/pushary-stop-hook.js +26 -25
  36. package/dist/bin/pushary-stopfailure-hook.js +26 -25
  37. package/dist/bin/pushary-suggestions.js +4 -4
  38. package/dist/bin/pushary-transcript-register.js +20 -19
  39. package/dist/bin/pushary-transcripts.js +8 -8
  40. package/dist/bin/pushary-upgrade.js +269 -45
  41. package/dist/bin/pushary-wait.js +7 -7
  42. package/dist/bin/pushary.js +8 -8
  43. package/dist/{chunk-LPYCPFBZ.js → chunk-2AUIEZYS.js} +3 -3
  44. package/dist/{chunk-4BROEAKH.js → chunk-2QT3GCU4.js} +1 -1
  45. package/dist/{chunk-55KW4FLG.js → chunk-3ZSGKLSO.js} +2 -2
  46. package/dist/{chunk-NY6XQ6CA.js → chunk-43LJSM4F.js} +2 -2
  47. package/dist/{chunk-LEAAERSQ.js → chunk-43ZDACBO.js} +1 -1
  48. package/dist/{chunk-II7PUMPB.js → chunk-4ERLRNOK.js} +3 -3
  49. package/dist/{chunk-M5CTUFF3.js → chunk-5HYNICS7.js} +1 -1
  50. package/dist/{chunk-MW6ZPC6R.js → chunk-5UQ5ATIE.js} +1 -1
  51. package/dist/{chunk-TYEO6T5J.js → chunk-5ZZG76S2.js} +2 -2
  52. package/dist/{chunk-EQRS3SGM.js → chunk-66H2QQAK.js} +1 -1
  53. package/dist/chunk-6EEANS74.js +300 -0
  54. package/dist/{chunk-TRLFZKVG.js → chunk-6IU45EHE.js} +49 -26
  55. package/dist/{chunk-JVTOML54.js → chunk-6QFEQIPM.js} +2 -2
  56. package/dist/chunk-BY2CRWBO.js +38 -0
  57. package/dist/{chunk-G653QPTR.js → chunk-D573TLP6.js} +7 -7
  58. package/dist/{chunk-25RTLTQD.js → chunk-DTQNDR7C.js} +1 -1
  59. package/dist/{chunk-FAH3D74E.js → chunk-EA3IELHP.js} +6 -0
  60. package/dist/{chunk-EYRGBYZG.js → chunk-HETOKNIL.js} +1 -1
  61. package/dist/{chunk-UM5J3J4V.js → chunk-HU5LSYL2.js} +2 -2
  62. package/dist/{chunk-PC6DU5C4.js → chunk-HUAOMX4U.js} +1 -1
  63. package/dist/{chunk-2V2IKOGJ.js → chunk-HVBF35GT.js} +2 -2
  64. package/dist/{chunk-DINDL2CF.js → chunk-HWJW3CPO.js} +25 -5
  65. package/dist/{chunk-VW66JEGU.js → chunk-IV3RHICU.js} +8 -8
  66. package/dist/{chunk-ACYIZ3KH.js → chunk-JKBDGZFL.js} +12 -6
  67. package/dist/{chunk-ZVY25BMK.js → chunk-K3QVDAZW.js} +1 -1
  68. package/dist/{chunk-MUVTZNJW.js → chunk-KHXVTWYS.js} +3 -3
  69. package/dist/{chunk-T5SZ4PML.js → chunk-KJRALILH.js} +7 -4
  70. package/dist/{chunk-VOMOZ4D5.js → chunk-M2LMXPJZ.js} +1 -1
  71. package/dist/{chunk-SUWRZYQG.js → chunk-MDJJW3LP.js} +3 -3
  72. package/dist/chunk-MU3WU2FO.js +166 -0
  73. package/dist/{chunk-2UMNXADU.js → chunk-OSJEMXSR.js} +5 -1
  74. package/dist/{chunk-TWWLVOPB.js → chunk-P4BNYJ5Q.js} +5 -5
  75. package/dist/{chunk-XDP3UYH5.js → chunk-PJ27EHCD.js} +8 -8
  76. package/dist/{chunk-LGXUGYUH.js → chunk-PYCQXCI5.js} +2 -2
  77. package/dist/{chunk-VIOL2DJS.js → chunk-R5KY3PBN.js} +5 -5
  78. package/dist/{chunk-SOUZBGYJ.js → chunk-T4QY64OR.js} +15 -14
  79. package/dist/{chunk-DY4ILT6L.js → chunk-TFKBFQCE.js} +1 -1
  80. package/dist/{chunk-GZ7E6VOB.js → chunk-TIWURSLS.js} +1 -1
  81. package/dist/{chunk-F4UWL4NY.js → chunk-TUXFIVOH.js} +1 -1
  82. package/dist/{chunk-UJBZIKSY.js → chunk-TZGCUNOX.js} +8 -8
  83. package/dist/{chunk-FU2DFEAZ.js → chunk-USQDVBEV.js} +2 -2
  84. package/dist/{chunk-YXP3WPAI.js → chunk-UYDTFCHW.js} +1 -1
  85. package/dist/{chunk-4ZDKSDVW.js → chunk-WNJSGZY7.js} +4 -4
  86. package/dist/{chunk-NHTAHAIS.js → chunk-WOLPADR6.js} +1 -1
  87. package/dist/{chunk-BZ4GDOJX.js → chunk-X7FESZTD.js} +7 -7
  88. package/dist/{chunk-NOWZUGZW.js → chunk-XWEONSLV.js} +1 -1
  89. package/dist/{chunk-Y3MM6PYN.js → chunk-YAWXKL2X.js} +1 -1
  90. package/dist/{chunk-GKIUEIDX.js → chunk-YQMFGWBD.js} +1 -1
  91. package/dist/{chunk-WPVHRUDT.js → chunk-Z4VGIBJ7.js} +3 -3
  92. package/dist/{reapply-J527BCJG.js → reapply-5CZSTJZI.js} +13 -8
  93. package/dist/src/index.js +18 -16
  94. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getBaseUrl
3
- } from "./chunk-2UMNXADU.js";
3
+ } from "./chunk-OSJEMXSR.js";
4
4
 
5
5
  // src/transcript-api.ts
6
6
  var fetchTranscriptRecipients = async (apiKey, baseUrl = getBaseUrl()) => {
@@ -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
+ };
@@ -2,12 +2,19 @@ import {
2
2
  HOOK_BUDGETS,
3
3
  HOOK_LOCATOR_BINARY,
4
4
  withHookHoldBudget
5
- } from "./chunk-SOUZBGYJ.js";
5
+ } from "./chunk-T4QY64OR.js";
6
+ import {
7
+ isReleaseVersion
8
+ } from "./chunk-HWJW3CPO.js";
6
9
 
7
10
  // src/npm.ts
8
11
  import { execFileSync, execSync } from "child_process";
9
12
  import { existsSync, readFileSync, realpathSync } from "fs";
10
13
  import { dirname, isAbsolute, join, relative, resolve } from "path";
14
+ var AGENT_HOOKS_PACKAGE = "@pushary/agent-hooks";
15
+ var LATEST_RELEASE_URL = `https://registry.npmjs.org/${AGENT_HOOKS_PACKAGE}/latest`;
16
+ var REGISTRY_TIMEOUT_MS = 5e3;
17
+ var LATEST_TAG = "latest";
11
18
  var cleanNpmEnv = () => {
12
19
  const env = {};
13
20
  for (const [key, value] of Object.entries(process.env)) {
@@ -16,10 +23,11 @@ var cleanNpmEnv = () => {
16
23
  }
17
24
  return env;
18
25
  };
26
+ var NPM_ERROR_METADATA = /^(code|syscall|path|dest|errno)\s/i;
19
27
  var npmErrorMessage = (err) => {
20
28
  const e = err;
21
29
  const text = [e?.stderr, e?.stdout].map((part) => part ? part.toString() : "").join("\n");
22
- const line = text.split("\n").map((l) => l.replace(/^npm error\s*/i, "").trim()).find((l) => l && !l.startsWith("A complete log") && !/^code\s/i.test(l));
30
+ const line = text.split("\n").map((l) => l.replace(/^npm error\s*/i, "").trim()).find((l) => l && !l.startsWith("A complete log") && !NPM_ERROR_METADATA.test(l));
23
31
  return line || e?.message || String(err);
24
32
  };
25
33
  var execNpm = (args, options = {}) => {
@@ -143,6 +151,26 @@ var resolveGlobalBinDir = (probe) => {
143
151
  return resolved ? resolved.slice(0, resolved.length - probe.length - 1) : void 0;
144
152
  };
145
153
  var quoteIfNeeded = (path) => /\s/.test(path) ? `"${path}"` : path;
154
+ var fetchLatestPublishedVersion = async (request = {}) => {
155
+ try {
156
+ const response = await (request.fetchImpl ?? fetch)(LATEST_RELEASE_URL, {
157
+ signal: AbortSignal.timeout(request.timeoutMs ?? REGISTRY_TIMEOUT_MS)
158
+ });
159
+ if (!response.ok) return void 0;
160
+ const body = await response.json();
161
+ const version = body && typeof body === "object" && "version" in body ? body.version : void 0;
162
+ return typeof version === "string" && isReleaseVersion(version) ? version : void 0;
163
+ } catch {
164
+ return void 0;
165
+ }
166
+ };
167
+ var POSIX_SHELL_WORD = /^[\w@%+=:,./-]+$/;
168
+ var quoteForPosixShell = (value) => POSIX_SHELL_WORD.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
169
+ var installAgentHooksGlobally = (request, runNpm = execNpm) => {
170
+ const prefixFlag = request.prefix === void 0 ? "" : ` --prefix ${quoteForPosixShell(request.prefix)}`;
171
+ const spec = quoteForPosixShell(`${AGENT_HOOKS_PACKAGE}@${request.version}`);
172
+ runNpm(`install -g --no-workspaces${prefixFlag} ${spec}`, { timeout: request.timeoutMs });
173
+ };
146
174
  var needsGlobalInstall = (requested, installed, binaryPresent) => requested !== installed || !binaryPresent;
147
175
  var installState;
148
176
  var installGlobally = (options) => {
@@ -153,13 +181,13 @@ var installGlobally = (options) => {
153
181
  const pinned = process.env.PUSHARY_PIN_GLOBAL_VERSION !== "0";
154
182
  if (pinned && !needsGlobalInstall(
155
183
  options.version,
156
- resolveGlobalPackageVersion("@pushary/agent-hooks"),
157
- globalPackageBinsPresent("@pushary/agent-hooks")
184
+ resolveGlobalPackageVersion(AGENT_HOOKS_PACKAGE),
185
+ globalPackageBinsPresent(AGENT_HOOKS_PACKAGE)
158
186
  )) {
159
187
  return "skipped";
160
188
  }
161
- const spec = pinned && options.version && options.version !== "0.0.0" ? `@pushary/agent-hooks@${options.version}` : "@pushary/agent-hooks@latest";
162
- execNpm(`install -g --no-workspaces ${spec}`, { timeout: options.timeoutMs ?? 12e4 });
189
+ const version = pinned && options.version && options.version !== "0.0.0" ? options.version : LATEST_TAG;
190
+ installAgentHooksGlobally({ version, timeoutMs: options.timeoutMs ?? 12e4 });
163
191
  return "installed";
164
192
  })();
165
193
  installState = started;
@@ -286,7 +314,8 @@ var CODEX_HOOK_BINARY = "pushary-codex-hook";
286
314
  var codexHome = () => codexHomeFrom(homedir());
287
315
  var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join2(home, ".codex");
288
316
  var codexConfigToml = () => join2(codexHome(), "config.toml");
289
- var codexHooksJson = () => join2(codexHome(), "hooks.json");
317
+ var codexHooksJsonIn = (home) => join2(home, "hooks.json");
318
+ var codexHooksJson = () => codexHooksJsonIn(codexHome());
290
319
  var codexAgentsMd = () => join2(codexHome(), "AGENTS.md");
291
320
  var codexSkillDir = () => join2(codexHome(), "skills", "pushary");
292
321
  var CODEX_HOOK_EVENTS = [
@@ -358,10 +387,11 @@ var readCodexMcpAuth = (config) => {
358
387
  if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
359
388
  return void 0;
360
389
  };
390
+ var isPusharyCodexCommand = (command) => command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
361
391
  var isPusharyCommand = (hook, scope) => {
362
392
  const command = String(asRecord2(hook)?.command ?? "");
363
393
  if (isProtectedByScope(command, scope)) return false;
364
- return command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
394
+ return isPusharyCodexCommand(command);
365
395
  };
366
396
  var isPusharyCodexHook = (entry, scope) => {
367
397
  const hooks = asRecord2(entry)?.hooks;
@@ -435,23 +465,6 @@ var codexEntryTrustHash = (eventKey, entry) => {
435
465
  if (typeof entry.matcher === "string") identity.matcher = entry.matcher;
436
466
  return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
437
467
  };
438
- var codexTrustSchemeAgrees = (hooksConfig, config, hooksJsonPath) => {
439
- const hooks = asRecord2(hooksConfig?.hooks);
440
- const state = asRecord2(asRecord2(config.hooks)?.state);
441
- if (!hooks || !state) return false;
442
- for (const [event, entries] of Object.entries(hooks)) {
443
- if (!Array.isArray(entries)) continue;
444
- const eventKey = CODEX_EVENT_KEY[event] ?? codexEventKeyOf(event);
445
- for (const [index, entry] of entries.entries()) {
446
- const record = asRecord2(entry);
447
- if (!record) continue;
448
- const stored = asRecord2(state[`${hooksJsonPath}:${eventKey}:${index}:0`])?.trusted_hash;
449
- if (typeof stored !== "string") continue;
450
- if (stored === codexEntryTrustHash(eventKey, record)) return true;
451
- }
452
- }
453
- return false;
454
- };
455
468
  var codexHookPositions = (hooksConfig) => {
456
469
  const hooks = asRecord2(hooksConfig?.hooks);
457
470
  const positions = {};
@@ -609,6 +622,9 @@ var claudeSettingsLocal = () => join3(homedir2(), ".claude", "settings.local.jso
609
622
  var pusharyDir = () => join3(homedir2(), ".pushary");
610
623
  var claudeMcpReceipt = () => join3(pusharyDir(), "setup", "claude-mcp.json");
611
624
  var bridgeAgentKey = () => join3(pusharyDir(), "bridge-auth", "agent-key.json");
625
+ var autoUpdateState = () => join3(pusharyDir(), "auto-update.json");
626
+ var autoUpdateLock = () => join3(pusharyDir(), "auto-update.lock");
627
+ var autoUpdateLog = () => join3(pusharyDir(), "logs", "auto-update.log");
612
628
  var claudeRegistrationReceipt = () => join3(pusharyDir(), "setup", "claude-registration.json");
613
629
  var hermesConfigReceipt = () => join3(pusharyDir(), "setup", "hermes-config.json");
614
630
  var hookTakeoverReceipt = (agent) => join3(pusharyDir(), "setup", `hook-takeover-${agent}.json`);
@@ -622,6 +638,7 @@ export {
622
638
  registeredCommands,
623
639
  namesOurBinary,
624
640
  registrationHolderOf,
641
+ LATEST_TAG,
625
642
  npmErrorMessage,
626
643
  execNpm,
627
644
  resolveGlobalBinary,
@@ -630,6 +647,8 @@ export {
630
647
  resolvePackageVersionFromBinary,
631
648
  resolveGlobalPackageVersion,
632
649
  resolveGlobalBinDir,
650
+ fetchLatestPublishedVersion,
651
+ installAgentHooksGlobally,
633
652
  installGlobally,
634
653
  holdBudgetOf,
635
654
  guardBinary,
@@ -642,18 +661,19 @@ export {
642
661
  codexHome,
643
662
  codexHomeFrom,
644
663
  codexConfigToml,
664
+ codexHooksJsonIn,
645
665
  codexHooksJson,
646
666
  codexAgentsMd,
647
667
  codexSkillDir,
648
668
  CODEX_HOOK_EVENTS,
649
669
  addCodexMcpServer,
650
670
  readCodexMcpAuth,
671
+ isPusharyCodexCommand,
651
672
  codexRegistrationHolder,
652
673
  addCodexHooks,
653
674
  removeCodexHooks,
654
675
  hasCodexHooks,
655
676
  missingCodexHookEvents,
656
- codexTrustSchemeAgrees,
657
677
  codexHookPositions,
658
678
  codexVacatedIndexes,
659
679
  dropVacatedCodexHookTrust,
@@ -675,6 +695,9 @@ export {
675
695
  pusharyDir,
676
696
  claudeMcpReceipt,
677
697
  bridgeAgentKey,
698
+ autoUpdateState,
699
+ autoUpdateLock,
700
+ autoUpdateLog,
678
701
  claudeRegistrationReceipt,
679
702
  hermesConfigReceipt,
680
703
  hookTakeoverReceipt,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  registerTranscript
3
- } from "./chunk-UJBZIKSY.js";
3
+ } from "./chunk-TZGCUNOX.js";
4
4
  import {
5
5
  degradedLine
6
- } from "./chunk-SOUZBGYJ.js";
6
+ } from "./chunk-T4QY64OR.js";
7
7
 
8
8
  // src/tool-hook-input.ts
9
9
  var OPTIONAL_TEXT_FIELDS = ["hook_event_name", "session_id", "cwd", "transcript_path", "tool_use_id", "permission_mode"];
@@ -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
+ };
@@ -1,26 +1,26 @@
1
1
  import {
2
2
  NOTIFICATION_PUSH_TYPES,
3
3
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
4
- } from "./chunk-YXP3WPAI.js";
4
+ } from "./chunk-UYDTFCHW.js";
5
5
  import {
6
6
  cancelQuestion,
7
7
  isPolicyConfig,
8
8
  sendNotification,
9
9
  waitForAnswer
10
- } from "./chunk-LGXUGYUH.js";
10
+ } from "./chunk-PYCQXCI5.js";
11
11
  import {
12
12
  getMachineId
13
- } from "./chunk-NHTAHAIS.js";
13
+ } from "./chunk-WOLPADR6.js";
14
14
  import {
15
15
  withRetry
16
- } from "./chunk-EYRGBYZG.js";
16
+ } from "./chunk-HETOKNIL.js";
17
17
  import {
18
18
  reportedInstallSource
19
- } from "./chunk-UM5J3J4V.js";
19
+ } from "./chunk-HU5LSYL2.js";
20
20
  import {
21
21
  getApiKey,
22
22
  getBaseUrl
23
- } from "./chunk-2UMNXADU.js";
23
+ } from "./chunk-OSJEMXSR.js";
24
24
  import {
25
25
  ACTION_BODY_MAX,
26
26
  AGENT_LABELS_BY_TYPE,
@@ -44,7 +44,7 @@ import {
44
44
  resolveAutoResolveOrigin,
45
45
  resolvePolicy,
46
46
  toToolLookup
47
- } from "./chunk-SOUZBGYJ.js";
47
+ } from "./chunk-T4QY64OR.js";
48
48
 
49
49
  // src/policy.ts
50
50
  import { createHash } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-FU2DFEAZ.js";
3
+ } from "./chunk-USQDVBEV.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -1,4 +1,5 @@
1
1
  // src/cli/commands.ts
2
+ var NPX_LATEST_COMMAND = "npx @pushary/agent-hooks@latest";
2
3
  var COMMAND_LIST = [
3
4
  {
4
5
  name: "cowork",
@@ -132,6 +133,10 @@ var COMMAND_OPTIONS = {
132
133
  ["--bundle", "Write a redacted diagnostics file you can attach to a support thread"]
133
134
  ],
134
135
  suggestions: [["accept <id>", "Turn a mined suggestion into an always-allow rule"]],
136
+ upgrade: [
137
+ ["--auto=off", "Stop Pushary updating itself in the background"],
138
+ ["--auto=on", "Update in the background again: once a day at session start, within the same major version"]
139
+ ],
135
140
  bell: [
136
141
  ["(no flags)", "Turn the bell on for Claude Code"],
137
142
  ["--test", "Ring once, now, so you can hear what it does"],
@@ -232,6 +237,7 @@ var renderCommandHelp = (name, version) => {
232
237
  };
233
238
 
234
239
  export {
240
+ NPX_LATEST_COMMAND,
235
241
  COMMANDS,
236
242
  isCommandName,
237
243
  renderHelp,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-SAF6HGAA.js";
4
4
  import {
5
5
  getBaseUrl
6
- } from "./chunk-2UMNXADU.js";
6
+ } from "./chunk-OSJEMXSR.js";
7
7
 
8
8
  // src/retry.ts
9
9
  var isRetryable = (err) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  configFilePath
3
- } from "./chunk-2UMNXADU.js";
3
+ } from "./chunk-OSJEMXSR.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-SOUZBGYJ.js";
6
+ } from "./chunk-T4QY64OR.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-LPYCPFBZ.js";
3
+ } from "./chunk-2AUIEZYS.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -3,11 +3,11 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-GZ7E6VOB.js";
6
+ } from "./chunk-TIWURSLS.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
10
- } from "./chunk-EQRS3SGM.js";
10
+ } from "./chunk-66H2QQAK.js";
11
11
 
12
12
  // src/live-session.ts
13
13
  var MAX_TRANSCRIPT_QUEUE = 100;