@pushary/agent-hooks 0.61.0 → 0.62.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 (44) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/bin/pushary-claude.js +4 -4
  3. package/dist/bin/pushary-clean.js +62 -32
  4. package/dist/bin/pushary-codex-hook.js +4 -4
  5. package/dist/bin/pushary-codex.js +3 -3
  6. package/dist/bin/pushary-connect.js +6 -6
  7. package/dist/bin/pushary-daemon.js +37 -5
  8. package/dist/bin/pushary-doctor.js +113 -34
  9. package/dist/bin/pushary-gemini-hook.js +4 -4
  10. package/dist/bin/pushary-hook.js +5 -5
  11. package/dist/bin/pushary-login.js +4 -4
  12. package/dist/bin/pushary-logout.js +51 -9
  13. package/dist/bin/pushary-mode.js +4 -4
  14. package/dist/bin/pushary-notification-hook.js +2 -2
  15. package/dist/bin/pushary-permission-denied-hook.js +3 -3
  16. package/dist/bin/pushary-permission-hook.js +3 -3
  17. package/dist/bin/pushary-post-hook.js +2 -2
  18. package/dist/bin/pushary-prompt-hook.js +2 -2
  19. package/dist/bin/pushary-session-end-hook.js +2 -2
  20. package/dist/bin/pushary-session-start-hook.js +2 -2
  21. package/dist/bin/pushary-setup.js +92 -152
  22. package/dist/bin/pushary-stats.js +2 -2
  23. package/dist/bin/pushary-status.js +5 -5
  24. package/dist/bin/pushary-stop-hook.js +2 -2
  25. package/dist/bin/pushary-stopfailure-hook.js +2 -2
  26. package/dist/bin/pushary-suggestions.js +2 -2
  27. package/dist/bin/pushary-upgrade.js +176 -11
  28. package/dist/bin/pushary-wait.js +4 -4
  29. package/dist/bin/pushary.js +1 -1
  30. package/dist/{chunk-B26DNXCA.js → chunk-247C4RE5.js} +1 -0
  31. package/dist/{chunk-GX64YGU3.js → chunk-3O27ZSRE.js} +25 -0
  32. package/dist/{chunk-HI4AGGE6.js → chunk-5SO3CEGJ.js} +4 -4
  33. package/dist/chunk-E2U35RLD.js +108 -0
  34. package/dist/chunk-ER657KRJ.js +168 -0
  35. package/dist/{chunk-HUJQSP4F.js → chunk-IIZZYUWK.js} +1 -1
  36. package/dist/chunk-N7XJ4L2W.js +79 -0
  37. package/dist/{chunk-R6C4USIV.js → chunk-ONVEYEVR.js} +2 -2
  38. package/dist/{chunk-SML23YOT.js → chunk-SP7OZXCQ.js} +10 -2
  39. package/dist/chunk-UXXRRXUS.js +93 -0
  40. package/dist/{chunk-UKNAAVEE.js → chunk-YJ7YZRVV.js} +1 -1
  41. package/dist/src/index.js +3 -3
  42. package/package.json +1 -1
  43. package/dist/chunk-S4TFBJ5O.js +0 -339
  44. package/dist/{chunk-VFBYRR2N.js → chunk-HIIBSYFJ.js} +3 -3
@@ -4,24 +4,47 @@ import {
4
4
  addPusharyHooks,
5
5
  addPusharyToolPermissions
6
6
  } from "../chunk-CAJZAFVS.js";
7
+ import {
8
+ GEMINI_HOOK_BINARY,
9
+ addGeminiHooks,
10
+ addGeminiMcpServer,
11
+ hasGeminiHooks
12
+ } from "../chunk-E2U35RLD.js";
13
+ import {
14
+ CODEX_HOOK_BINARY,
15
+ addCodexHookTrust,
16
+ addCodexHooks,
17
+ addCodexMcpServer,
18
+ codexConfigToml,
19
+ codexHooksJson,
20
+ hasCodexHooks
21
+ } from "../chunk-ER657KRJ.js";
7
22
  import {
8
23
  execNpm,
9
24
  npmErrorMessage,
10
- resolveGlobalBinDir
25
+ quoteIfNeeded,
26
+ resolveGlobalBinDir,
27
+ resolveGlobalBinary
11
28
  } from "../chunk-MUEW424A.js";
12
29
  import {
13
30
  exitOnHelpFlag
14
- } from "../chunk-HUJQSP4F.js";
15
- import "../chunk-SML23YOT.js";
31
+ } from "../chunk-IIZZYUWK.js";
32
+ import "../chunk-SP7OZXCQ.js";
16
33
  import "../chunk-DQAN3JQP.js";
17
34
  import {
18
35
  getApiKey
19
36
  } from "../chunk-2UMNXADU.js";
20
37
 
38
+ // bin/pushary-upgrade.ts
39
+ import { existsSync as existsSync2 } from "fs";
40
+ import { homedir as homedir2 } from "os";
41
+ import { join as join2 } from "path";
42
+
21
43
  // src/reapply.ts
22
44
  import { join, dirname } from "path";
23
45
  import { homedir } from "os";
24
46
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
47
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
25
48
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
26
49
  var CLAUDE_JSON = join(homedir(), ".claude.json");
27
50
  var readJson = (filePath) => {
@@ -66,9 +89,130 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
66
89
  }
67
90
  return { reapplied: hooks || mcp, hooks, mcp };
68
91
  };
92
+ var readToml = (filePath) => {
93
+ try {
94
+ const raw = readFileSync(filePath, "utf-8");
95
+ return raw.trim() === "" ? {} : parseTOML(raw);
96
+ } catch {
97
+ return null;
98
+ }
99
+ };
100
+ var reapplyCodexSettings = (apiKey, paths = {}) => {
101
+ const configPath = paths.configPath ?? codexConfigToml();
102
+ const hooksPath = paths.hooksPath ?? codexHooksJson();
103
+ const config = readToml(configPath);
104
+ const hooksConfig = readJson(hooksPath);
105
+ const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
106
+ if (!configured) return { reapplied: false, hooks: false, mcp: false };
107
+ const command = quoteIfNeeded(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
108
+ let hooks = false;
109
+ let mcp = false;
110
+ if (hooksConfig != null) {
111
+ try {
112
+ addCodexHooks(hooksConfig, command);
113
+ writeJson(hooksPath, hooksConfig);
114
+ hooks = true;
115
+ } catch {
116
+ }
117
+ }
118
+ if (config != null) {
119
+ try {
120
+ if (hooks) addCodexHookTrust(config, hooksPath, command);
121
+ if (apiKey) {
122
+ addCodexMcpServer(config, apiKey);
123
+ mcp = true;
124
+ }
125
+ if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
126
+ } catch {
127
+ }
128
+ }
129
+ return { reapplied: hooks || mcp, hooks, mcp };
130
+ };
131
+ var reapplyGeminiSettings = (apiKey, paths = {}) => {
132
+ const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
133
+ const settings = readJson(settingsPath);
134
+ if (settings == null) return { reapplied: false, hooks: false, mcp: false };
135
+ const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
136
+ if (!configured) return { reapplied: false, hooks: false, mcp: false };
137
+ let hooks = false;
138
+ let mcp = false;
139
+ try {
140
+ addGeminiHooks(settings, quoteIfNeeded(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
141
+ hooks = true;
142
+ if (apiKey) {
143
+ addGeminiMcpServer(settings, apiKey);
144
+ mcp = true;
145
+ }
146
+ writeJson(settingsPath, settings);
147
+ } catch {
148
+ }
149
+ return { reapplied: hooks || mcp, hooks, mcp };
150
+ };
151
+ var reapplyCursorPlugin = (apiKey, paths = {}) => {
152
+ const mcpPath = paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json");
153
+ if (!apiKey) return { reapplied: false, hooks: false, mcp: false };
154
+ const mcpConfig = readJson(mcpPath);
155
+ if (mcpConfig == null) return { reapplied: false, hooks: false, mcp: false };
156
+ const servers = mcpConfig.mcpServers;
157
+ if (!servers?.pushary) return { reapplied: false, hooks: false, mcp: false };
158
+ try {
159
+ addClaudeMcpServer(mcpConfig, apiKey);
160
+ writeJson(mcpPath, mcpConfig);
161
+ return { reapplied: true, hooks: false, mcp: true };
162
+ } catch {
163
+ return { reapplied: false, hooks: false, mcp: false };
164
+ }
165
+ };
166
+ var reapplyAllAgents = (apiKey) => {
167
+ const runners = [
168
+ { label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
169
+ { label: "Codex", run: () => reapplyCodexSettings(apiKey) },
170
+ { label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
171
+ { label: "Cursor", run: () => reapplyCursorPlugin(apiKey) }
172
+ ];
173
+ const done = [];
174
+ for (const runner of runners) {
175
+ try {
176
+ const result = runner.run();
177
+ if (result.reapplied) done.push({ label: runner.label, result });
178
+ } catch {
179
+ }
180
+ }
181
+ return done;
182
+ };
69
183
 
70
184
  // bin/pushary-upgrade.ts
71
185
  exitOnHelpFlag("upgrade");
186
+ var detectInstallModes = () => {
187
+ const candidates = [
188
+ {
189
+ label: "Cursor plugin",
190
+ upgrade: "updates itself through Cursor",
191
+ present: existsSync2(join2(homedir2(), ".cursor", "plugins", "local", "pushary"))
192
+ },
193
+ {
194
+ label: "Codex config",
195
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents codex",
196
+ present: existsSync2(codexConfigToml())
197
+ },
198
+ {
199
+ label: "Claude Code config",
200
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents claude_code",
201
+ present: existsSync2(join2(homedir2(), ".claude.json"))
202
+ },
203
+ {
204
+ label: "Gemini config",
205
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents gemini_cli",
206
+ present: existsSync2(join2(homedir2(), ".gemini", "settings.json"))
207
+ },
208
+ {
209
+ label: "Hermes plugin (pip)",
210
+ upgrade: "pip install --upgrade pushary-hermes",
211
+ present: existsSync2(join2(homedir2(), ".hermes"))
212
+ }
213
+ ];
214
+ return candidates.filter((candidate) => candidate.present).map(({ label, upgrade }) => ({ label, upgrade }));
215
+ };
72
216
  var getInstalledVersion = () => {
73
217
  try {
74
218
  return execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
@@ -100,8 +244,20 @@ var isNewer = (a, b) => {
100
244
  var main = async () => {
101
245
  const installed = getInstalledVersion();
102
246
  if (!installed) {
103
- console.log("No global install of @pushary/agent-hooks found, so there is nothing to upgrade.");
104
- console.log("Plugin installs update on their own. To set up the CLI hooks: npx @pushary/agent-hooks@latest setup");
247
+ const found = detectInstallModes();
248
+ console.log("No global install of @pushary/agent-hooks found, so there is nothing to upgrade here.");
249
+ console.log();
250
+ if (found.length === 0) {
251
+ console.log("Nothing on this machine looks Pushary-configured either.");
252
+ console.log(" Set it up with: npx @pushary/agent-hooks@latest setup");
253
+ } else {
254
+ console.log("What this machine does have:");
255
+ for (const mode of found) {
256
+ console.log(` ${mode.label.padEnd(22)} ${mode.upgrade}`);
257
+ }
258
+ }
259
+ console.log();
260
+ console.log("Check the result with: pushary doctor");
105
261
  return;
106
262
  }
107
263
  const latest = await fetchLatestVersion();
@@ -122,14 +278,23 @@ var main = async () => {
122
278
  } catch {
123
279
  apiKey = void 0;
124
280
  }
125
- try {
126
- const result = reapplyClaudeSettings(apiKey);
127
- if (result.reapplied) {
128
- console.log("Refreshed Claude Code hooks and settings so the new hook set is active now.");
281
+ const refreshed = reapplyAllAgents(apiKey);
282
+ console.log();
283
+ if (refreshed.length > 0) {
284
+ console.log("Refreshed so the new hook set is active now:");
285
+ for (const agent of refreshed) {
286
+ const parts = [agent.result.hooks ? "hooks" : null, agent.result.mcp ? "MCP key" : null].filter(Boolean).join(" + ");
287
+ console.log(` ${agent.label.padEnd(14)} ${parts}`);
129
288
  }
130
- } catch {
289
+ if (!apiKey) {
290
+ console.log();
291
+ console.log("No key was readable here, so MCP entries kept the key they already had.");
292
+ }
293
+ } else {
294
+ console.log("No agent config on this machine needed refreshing.");
131
295
  }
132
- console.log("Done. Your hooks point at the global install and settings were refreshed.");
296
+ console.log();
297
+ console.log("Done. Your hooks point at the global install.");
133
298
  console.log("Verify with: pushary doctor");
134
299
  };
135
300
  main();
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  exitOnFailedResponse
4
- } from "../chunk-R6C4USIV.js";
4
+ } from "../chunk-ONVEYEVR.js";
5
5
  import {
6
6
  describeWaitLadder
7
7
  } from "../chunk-IIENZE2J.js";
8
- import "../chunk-UKNAAVEE.js";
8
+ import "../chunk-YJ7YZRVV.js";
9
9
  import {
10
10
  createIo
11
11
  } from "../chunk-TX7KBKT7.js";
12
12
  import "../chunk-7QLSKOSU.js";
13
13
  import {
14
14
  exitOnHelpFlag
15
- } from "../chunk-HUJQSP4F.js";
15
+ } from "../chunk-IIZZYUWK.js";
16
16
  import {
17
17
  EXIT
18
- } from "../chunk-SML23YOT.js";
18
+ } from "../chunk-SP7OZXCQ.js";
19
19
  import "../chunk-DQAN3JQP.js";
20
20
  import {
21
21
  getApiKey,
@@ -5,7 +5,7 @@ import {
5
5
  getPackageVersion,
6
6
  isCommandName,
7
7
  renderHelp
8
- } from "../chunk-SML23YOT.js";
8
+ } from "../chunk-SP7OZXCQ.js";
9
9
 
10
10
  // bin/pushary.ts
11
11
  var LOADERS = {
@@ -229,6 +229,7 @@ export {
229
229
  writeJsonAtomic,
230
230
  KEY_FILE_MODE,
231
231
  exportLine,
232
+ keyPrefix,
232
233
  writeKey,
233
234
  clearKey,
234
235
  readKeySource
@@ -155,6 +155,7 @@ var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
155
155
  var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
156
156
  var check = green("\u2713");
157
157
  var apiBase = () => process.env.PUSHARY_API_URL?.trim() || process.env.PUSHARY_BASE_URL?.trim() || "https://pushary.com";
158
+ var EXIT_ABORTED = 130;
158
159
  var CONNECT_POLL_INTERVAL_MS = 2500;
159
160
  var CONNECT_TIMEOUT_MS = 18e4;
160
161
  var EMPTY_CHANNELS = {
@@ -463,6 +464,17 @@ var startPairing = async (cliPublicKey) => {
463
464
  return null;
464
465
  }
465
466
  };
467
+ var cancelPairing = async (pairId) => {
468
+ try {
469
+ await fetch(`${apiBase()}/api/mobile/pair/start`, {
470
+ method: "DELETE",
471
+ headers: { "Content-Type": "application/json" },
472
+ body: JSON.stringify({ pairId }),
473
+ signal: AbortSignal.timeout(5e3)
474
+ });
475
+ } catch {
476
+ }
477
+ };
466
478
  var claimPairing = async (pairId) => {
467
479
  try {
468
480
  const res = await fetch(`${apiBase()}/api/mobile/pair/claim?id=${encodeURIComponent(pairId)}`, {
@@ -504,8 +516,16 @@ var connectViaAppPairing = async () => {
504
516
  const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
505
517
  return `Waiting for the app to authorize ${dim(`(${left}s)`)}`;
506
518
  });
519
+ let cancelled = false;
520
+ const onInterrupt = () => {
521
+ cancelled = true;
522
+ stop(yellow("!"), "Cancelled");
523
+ void cancelPairing(pairId).finally(() => process.exit(EXIT_ABORTED));
524
+ };
525
+ process.once("SIGINT", onInterrupt);
507
526
  try {
508
527
  while (Date.now() < deadline) {
528
+ if (cancelled) return null;
509
529
  const claim = await claimPairing(pairId);
510
530
  if (claim?.status === "authorized") {
511
531
  let apiKey;
@@ -513,6 +533,7 @@ var connectViaAppPairing = async () => {
513
533
  apiKey = openSealedKey(claim.sealed, keypair);
514
534
  } catch {
515
535
  stop(yellow("!"), "Pairing failed to decrypt \u2014 re-run setup to try again");
536
+ await cancelPairing(pairId);
516
537
  return null;
517
538
  }
518
539
  stop(check, "App connected");
@@ -525,10 +546,14 @@ var connectViaAppPairing = async () => {
525
546
  await sleep(PAIR_POLL_INTERVAL_MS);
526
547
  }
527
548
  stop(yellow("!"), "Timed out waiting for the app");
549
+ await cancelPairing(pairId);
528
550
  return null;
529
551
  } catch (err) {
530
552
  stop(yellow("!"), `Pairing error ${dim(`(${err instanceof Error ? err.message : "network"})`)}`);
553
+ await cancelPairing(pairId);
531
554
  return null;
555
+ } finally {
556
+ process.off("SIGINT", onInterrupt);
532
557
  }
533
558
  };
534
559
 
@@ -25,10 +25,7 @@ import {
25
25
  sendNotification,
26
26
  throttlePass,
27
27
  waitForAnswer
28
- } from "./chunk-VFBYRR2N.js";
29
- import {
30
- getMachineId
31
- } from "./chunk-RN3NOEJF.js";
28
+ } from "./chunk-HIIBSYFJ.js";
32
29
  import {
33
30
  isGatingMoment,
34
31
  recordKeylessMoment
@@ -42,6 +39,9 @@ import {
42
39
  scopeChangeReason,
43
40
  scopeRequiresHuman
44
41
  } from "./chunk-DQAN3JQP.js";
42
+ import {
43
+ getMachineId
44
+ } from "./chunk-RN3NOEJF.js";
45
45
  import {
46
46
  getApiKey,
47
47
  getBaseUrl
@@ -0,0 +1,108 @@
1
+ import {
2
+ HOOK_BUDGETS
3
+ } from "./chunk-DQAN3JQP.js";
4
+
5
+ // src/gemini-config.ts
6
+ var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
7
+ var GEMINI_MCP_URL = "https://pushary.com/api/mcp/mcp";
8
+ var GEMINI_HOOK_EVENTS = [
9
+ { event: "BeforeTool", matcher: "run_shell_command|write_file|replace", timeoutMs: HOOK_BUDGETS.gemini.budgetSeconds * 1e3 },
10
+ { event: "AfterTool", matcher: "run_shell_command|write_file|replace", timeoutMs: 1e4 },
11
+ { event: "BeforeAgent", timeoutMs: 1e4 },
12
+ { event: "SessionStart", timeoutMs: 1e4 },
13
+ { event: "SessionEnd", timeoutMs: 1e4 }
14
+ ];
15
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
16
+ var ensureRecord = (target, key) => {
17
+ const existing = asRecord(target[key]);
18
+ if (existing) return existing;
19
+ const created = {};
20
+ target[key] = created;
21
+ return created;
22
+ };
23
+ var isPusharyGeminiHook = (entry) => {
24
+ const hooks = asRecord(entry)?.hooks;
25
+ if (!Array.isArray(hooks)) return false;
26
+ return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(GEMINI_HOOK_BINARY));
27
+ };
28
+ var addGeminiMcpServer = (settings, apiKey) => {
29
+ const mcpServers = ensureRecord(settings, "mcpServers");
30
+ mcpServers.pushary = {
31
+ httpUrl: GEMINI_MCP_URL,
32
+ headers: { Authorization: `Bearer ${apiKey}` },
33
+ trust: true
34
+ };
35
+ };
36
+ var addGeminiHooks = (settings, command) => {
37
+ const hooks = ensureRecord(settings, "hooks");
38
+ for (const definition of GEMINI_HOOK_EVENTS) {
39
+ const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
40
+ const entries = existing.filter((entry) => !isPusharyGeminiHook(entry));
41
+ entries.push({
42
+ ...definition.matcher ? { matcher: definition.matcher } : {},
43
+ hooks: [{
44
+ name: "pushary",
45
+ type: "command",
46
+ command,
47
+ timeout: definition.timeoutMs
48
+ }]
49
+ });
50
+ hooks[definition.event] = entries;
51
+ }
52
+ };
53
+ var removeGeminiHooks = (settings) => {
54
+ const hooks = asRecord(settings.hooks);
55
+ if (!hooks) return false;
56
+ let changed = false;
57
+ for (const definition of GEMINI_HOOK_EVENTS) {
58
+ const entries = hooks[definition.event];
59
+ if (!Array.isArray(entries)) continue;
60
+ const filtered = entries.filter((entry) => !isPusharyGeminiHook(entry));
61
+ if (filtered.length !== entries.length) {
62
+ if (filtered.length === 0) {
63
+ delete hooks[definition.event];
64
+ } else {
65
+ hooks[definition.event] = filtered;
66
+ }
67
+ changed = true;
68
+ }
69
+ }
70
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
71
+ return changed;
72
+ };
73
+ var removeGeminiMcpServer = (settings) => {
74
+ const mcpServers = asRecord(settings.mcpServers);
75
+ if (!mcpServers?.pushary) return false;
76
+ delete mcpServers.pushary;
77
+ if (Object.keys(mcpServers).length === 0) delete settings.mcpServers;
78
+ return true;
79
+ };
80
+ var removeGeminiSettings = (settings) => {
81
+ const mcpRemoved = removeGeminiMcpServer(settings);
82
+ const hooksRemoved = removeGeminiHooks(settings);
83
+ return mcpRemoved || hooksRemoved;
84
+ };
85
+ var hasGeminiHooks = (settings) => {
86
+ const hooks = asRecord(settings.hooks);
87
+ if (!hooks) return false;
88
+ return GEMINI_HOOK_EVENTS.some((definition) => {
89
+ const entries = hooks[definition.event];
90
+ return Array.isArray(entries) && entries.some(isPusharyGeminiHook);
91
+ });
92
+ };
93
+ var missingGeminiHookEvents = (settings) => {
94
+ const hooks = asRecord(settings.hooks);
95
+ return GEMINI_HOOK_EVENTS.filter((definition) => {
96
+ const entries = hooks?.[definition.event];
97
+ return !Array.isArray(entries) || !entries.some(isPusharyGeminiHook);
98
+ }).map((definition) => definition.event);
99
+ };
100
+
101
+ export {
102
+ GEMINI_HOOK_BINARY,
103
+ addGeminiMcpServer,
104
+ addGeminiHooks,
105
+ removeGeminiSettings,
106
+ hasGeminiHooks,
107
+ missingGeminiHookEvents
108
+ };
@@ -0,0 +1,168 @@
1
+ import {
2
+ HOOK_BUDGETS
3
+ } from "./chunk-DQAN3JQP.js";
4
+
5
+ // src/codex-config.ts
6
+ import { createHash } from "crypto";
7
+ import { homedir } from "os";
8
+ import { join } from "path";
9
+ var CODEX_HOOK_BINARY = "pushary-codex-hook";
10
+ var codexHome = () => codexHomeFrom(homedir());
11
+ var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join(home, ".codex");
12
+ var codexConfigToml = () => join(codexHome(), "config.toml");
13
+ var codexHooksJson = () => join(codexHome(), "hooks.json");
14
+ var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
15
+ var codexSkillDir = () => join(codexHome(), "skills", "pushary");
16
+ var CODEX_HOOK_EVENTS = [
17
+ { event: "PermissionRequest", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
18
+ { event: "PreToolUse", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
19
+ { event: "PostToolUse", matcher: "Bash|apply_patch", timeout: 10 },
20
+ { event: "UserPromptSubmit", timeout: 10 },
21
+ { event: "Stop", timeout: 10 },
22
+ { event: "SessionStart", matcher: "startup|resume", timeout: 10 }
23
+ ];
24
+ var CODEX_EVENT_KEY = {
25
+ PermissionRequest: "permission_request",
26
+ PreToolUse: "pre_tool_use",
27
+ PostToolUse: "post_tool_use",
28
+ UserPromptSubmit: "user_prompt_submit",
29
+ Stop: "stop",
30
+ SessionStart: "session_start"
31
+ };
32
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
33
+ var ensureRecord = (target, key) => {
34
+ const existing = asRecord(target[key]);
35
+ if (existing) return existing;
36
+ const created = {};
37
+ target[key] = created;
38
+ return created;
39
+ };
40
+ var CODEX_MCP_URL = "https://pushary.com/api/mcp/mcp";
41
+ var addCodexMcpServer = (config, apiKey) => {
42
+ const servers = ensureRecord(config, "mcp_servers");
43
+ const pushary = ensureRecord(servers, "pushary");
44
+ pushary.url = CODEX_MCP_URL;
45
+ ensureRecord(pushary, "http_headers").Authorization = `Bearer ${apiKey}`;
46
+ pushary.default_tools_approval_mode = "approve";
47
+ delete pushary.bearer_token_env_var;
48
+ delete pushary.tools;
49
+ };
50
+ var readCodexMcpAuth = (config) => {
51
+ const pushary = asRecord(asRecord(config.mcp_servers)?.pushary);
52
+ if (!pushary) return void 0;
53
+ const authorization = asRecord(pushary.http_headers)?.Authorization;
54
+ if (typeof authorization === "string" && authorization.startsWith("Bearer ")) {
55
+ return { method: "embedded", key: authorization.slice("Bearer ".length).trim() };
56
+ }
57
+ if (typeof pushary.bearer_token_env_var === "string") {
58
+ return { method: "env", envVar: pushary.bearer_token_env_var };
59
+ }
60
+ if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
61
+ return void 0;
62
+ };
63
+ var isPusharyCodexHook = (entry) => {
64
+ const hooks = asRecord(entry)?.hooks;
65
+ if (!Array.isArray(hooks)) return false;
66
+ return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(CODEX_HOOK_BINARY));
67
+ };
68
+ var addCodexHooks = (config, command) => {
69
+ const hooks = ensureRecord(config, "hooks");
70
+ for (const definition of CODEX_HOOK_EVENTS) {
71
+ const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
72
+ const entries = existing.filter((entry) => !isPusharyCodexHook(entry));
73
+ entries.push({
74
+ ...definition.matcher ? { matcher: definition.matcher } : {},
75
+ hooks: [{
76
+ type: "command",
77
+ command,
78
+ timeout: definition.timeout,
79
+ ...definition.statusMessage ? { statusMessage: definition.statusMessage } : {}
80
+ }]
81
+ });
82
+ hooks[definition.event] = entries;
83
+ }
84
+ };
85
+ var removeCodexHooks = (config) => {
86
+ const hooks = asRecord(config.hooks);
87
+ if (!hooks) return false;
88
+ let changed = false;
89
+ for (const definition of CODEX_HOOK_EVENTS) {
90
+ const entries = hooks[definition.event];
91
+ if (!Array.isArray(entries)) continue;
92
+ const filtered = entries.filter((entry) => !isPusharyCodexHook(entry));
93
+ if (filtered.length !== entries.length) {
94
+ if (filtered.length === 0) {
95
+ delete hooks[definition.event];
96
+ } else {
97
+ hooks[definition.event] = filtered;
98
+ }
99
+ changed = true;
100
+ }
101
+ }
102
+ if (Object.keys(hooks).length === 0) delete config.hooks;
103
+ return changed;
104
+ };
105
+ var hasCodexHooks = (config) => {
106
+ const hooks = asRecord(config.hooks);
107
+ if (!hooks) return false;
108
+ return CODEX_HOOK_EVENTS.some((definition) => {
109
+ const entries = hooks[definition.event];
110
+ return Array.isArray(entries) && entries.some(isPusharyCodexHook);
111
+ });
112
+ };
113
+ var missingCodexHookEvents = (config) => {
114
+ const hooks = asRecord(config.hooks);
115
+ return CODEX_HOOK_EVENTS.filter((definition) => {
116
+ const entries = hooks?.[definition.event];
117
+ return !Array.isArray(entries) || !entries.some(isPusharyCodexHook);
118
+ }).map((definition) => definition.event);
119
+ };
120
+ var canonicalJson = (value) => {
121
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
122
+ if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
123
+ const obj = value;
124
+ return "{" + Object.keys(obj).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(obj[key])).join(",") + "}";
125
+ };
126
+ var codexHookTrustHash = (definition, command) => {
127
+ const hook = { async: false, command, timeout: definition.timeout, type: "command" };
128
+ if (definition.statusMessage) hook.statusMessage = definition.statusMessage;
129
+ const identity = { event_name: CODEX_EVENT_KEY[definition.event], hooks: [hook] };
130
+ if (definition.matcher) identity.matcher = definition.matcher;
131
+ return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
132
+ };
133
+ var codexHookStateKey = (hooksJsonPath, event) => `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:0:0`;
134
+ var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
135
+ const state = asRecord(asRecord(config.hooks)?.state);
136
+ return CODEX_HOOK_EVENTS.filter((definition) => {
137
+ const entry = asRecord(state?.[codexHookStateKey(hooksJsonPath, definition.event)]);
138
+ const stored = entry?.trusted_hash;
139
+ return stored !== codexHookTrustHash(definition, command);
140
+ }).map((definition) => definition.event);
141
+ };
142
+ var addCodexHookTrust = (config, hooksJsonPath, command) => {
143
+ const hooks = ensureRecord(config, "hooks");
144
+ const state = ensureRecord(hooks, "state");
145
+ for (const definition of CODEX_HOOK_EVENTS) {
146
+ const key = codexHookStateKey(hooksJsonPath, definition.event);
147
+ const existing = asRecord(state[key]) ?? {};
148
+ state[key] = { ...existing, trusted_hash: codexHookTrustHash(definition, command) };
149
+ }
150
+ };
151
+
152
+ export {
153
+ CODEX_HOOK_BINARY,
154
+ codexHome,
155
+ codexHomeFrom,
156
+ codexConfigToml,
157
+ codexHooksJson,
158
+ codexAgentsMd,
159
+ codexSkillDir,
160
+ addCodexMcpServer,
161
+ readCodexMcpAuth,
162
+ addCodexHooks,
163
+ removeCodexHooks,
164
+ hasCodexHooks,
165
+ missingCodexHookEvents,
166
+ untrustedCodexHookEvents,
167
+ addCodexHookTrust
168
+ };
@@ -2,7 +2,7 @@ import {
2
2
  EXIT,
3
3
  getPackageVersion,
4
4
  renderCommandHelp
5
- } from "./chunk-SML23YOT.js";
5
+ } from "./chunk-SP7OZXCQ.js";
6
6
 
7
7
  // src/cli/usage.ts
8
8
  var exitOnHelpFlag = (command) => {