@pushary/agent-hooks 0.61.0 → 0.62.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 (45) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/dist/bin/pushary-claude.js +4 -4
  3. package/dist/bin/pushary-clean.js +82 -35
  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 +7 -6
  7. package/dist/bin/pushary-daemon.js +37 -5
  8. package/dist/bin/pushary-doctor.js +114 -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 +5 -4
  12. package/dist/bin/pushary-logout.js +52 -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 +97 -155
  22. package/dist/bin/pushary-stats.js +2 -2
  23. package/dist/bin/pushary-status.js +6 -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 +189 -18
  28. package/dist/bin/pushary-wait.js +4 -4
  29. package/dist/bin/pushary.js +1 -1
  30. package/dist/{chunk-GX64YGU3.js → chunk-3O27ZSRE.js} +25 -0
  31. package/dist/{chunk-HI4AGGE6.js → chunk-5SO3CEGJ.js} +4 -4
  32. package/dist/chunk-6MTNS63X.js +55 -0
  33. package/dist/{chunk-B26DNXCA.js → chunk-COC6NPWG.js} +13 -59
  34. package/dist/chunk-E2U35RLD.js +108 -0
  35. package/dist/chunk-ER657KRJ.js +168 -0
  36. package/dist/{chunk-HUJQSP4F.js → chunk-IIZZYUWK.js} +1 -1
  37. package/dist/chunk-N7XJ4L2W.js +79 -0
  38. package/dist/{chunk-R6C4USIV.js → chunk-ONVEYEVR.js} +2 -2
  39. package/dist/{chunk-SML23YOT.js → chunk-SP7OZXCQ.js} +10 -2
  40. package/dist/chunk-UXXRRXUS.js +93 -0
  41. package/dist/{chunk-UKNAAVEE.js → chunk-YJ7YZRVV.js} +1 -1
  42. package/dist/src/index.js +3 -3
  43. package/package.json +1 -1
  44. package/dist/chunk-S4TFBJ5O.js +0 -339
  45. package/dist/{chunk-VFBYRR2N.js → chunk-HIIBSYFJ.js} +3 -3
@@ -4,32 +4,57 @@ 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";
22
+ import {
23
+ readJsonSafe
24
+ } from "../chunk-6MTNS63X.js";
7
25
  import {
8
26
  execNpm,
9
27
  npmErrorMessage,
10
- resolveGlobalBinDir
28
+ quoteIfNeeded,
29
+ resolveGlobalBinDir,
30
+ resolveGlobalBinary
11
31
  } from "../chunk-MUEW424A.js";
12
32
  import {
13
33
  exitOnHelpFlag
14
- } from "../chunk-HUJQSP4F.js";
15
- import "../chunk-SML23YOT.js";
34
+ } from "../chunk-IIZZYUWK.js";
35
+ import "../chunk-SP7OZXCQ.js";
16
36
  import "../chunk-DQAN3JQP.js";
17
37
  import {
18
38
  getApiKey
19
39
  } from "../chunk-2UMNXADU.js";
20
40
 
41
+ // bin/pushary-upgrade.ts
42
+ import { existsSync as existsSync2 } from "fs";
43
+ import { homedir as homedir2 } from "os";
44
+ import { join as join2 } from "path";
45
+
21
46
  // src/reapply.ts
22
47
  import { join, dirname } from "path";
23
48
  import { homedir } from "os";
24
49
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
50
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
25
51
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
26
52
  var CLAUDE_JSON = join(homedir(), ".claude.json");
27
53
  var readJson = (filePath) => {
28
- try {
29
- return JSON.parse(readFileSync(filePath, "utf-8"));
30
- } catch {
31
- return null;
32
- }
54
+ const result = readJsonSafe(filePath);
55
+ if (result.kind === "ok") return result.value;
56
+ if (result.kind === "missing") return {};
57
+ return null;
33
58
  };
34
59
  var writeJson = (filePath, data) => {
35
60
  const dir = dirname(filePath);
@@ -45,10 +70,13 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
45
70
  const claudeJson = readJson(claudeJsonPath);
46
71
  const configured = settings != null && hasPusharyClaudeHooks(settings) || claudeJson != null && hasPusharyMcp(claudeJson);
47
72
  if (!configured) return { reapplied: false, hooks: false, mcp: false };
73
+ const settingsWritable = settings != null;
74
+ const claudeJsonWritable = claudeJson != null;
48
75
  let hooks = false;
49
76
  let mcp = false;
50
77
  try {
51
- const s = settings ?? {};
78
+ if (!settingsWritable) throw new Error("settings.json could not be parsed");
79
+ const s = settings;
52
80
  addPusharyToolPermissions(s);
53
81
  addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"));
54
82
  writeJson(settingsPath, s);
@@ -57,7 +85,8 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
57
85
  }
58
86
  if (apiKey) {
59
87
  try {
60
- const cj = claudeJson ?? {};
88
+ if (!claudeJsonWritable) throw new Error(".claude.json could not be parsed");
89
+ const cj = claudeJson;
61
90
  addClaudeMcpServer(cj, apiKey);
62
91
  writeJson(claudeJsonPath, cj);
63
92
  mcp = true;
@@ -66,9 +95,130 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
66
95
  }
67
96
  return { reapplied: hooks || mcp, hooks, mcp };
68
97
  };
98
+ var readToml = (filePath) => {
99
+ try {
100
+ const raw = readFileSync(filePath, "utf-8");
101
+ return raw.trim() === "" ? {} : parseTOML(raw);
102
+ } catch {
103
+ return null;
104
+ }
105
+ };
106
+ var reapplyCodexSettings = (apiKey, paths = {}) => {
107
+ const configPath = paths.configPath ?? codexConfigToml();
108
+ const hooksPath = paths.hooksPath ?? codexHooksJson();
109
+ const config = readToml(configPath);
110
+ const hooksConfig = readJson(hooksPath);
111
+ const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
112
+ if (!configured) return { reapplied: false, hooks: false, mcp: false };
113
+ const command = quoteIfNeeded(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
114
+ let hooks = false;
115
+ let mcp = false;
116
+ if (hooksConfig != null) {
117
+ try {
118
+ addCodexHooks(hooksConfig, command);
119
+ writeJson(hooksPath, hooksConfig);
120
+ hooks = true;
121
+ } catch {
122
+ }
123
+ }
124
+ if (config != null) {
125
+ try {
126
+ if (hooks) addCodexHookTrust(config, hooksPath, command);
127
+ if (apiKey) {
128
+ addCodexMcpServer(config, apiKey);
129
+ mcp = true;
130
+ }
131
+ if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
132
+ } catch {
133
+ }
134
+ }
135
+ return { reapplied: hooks || mcp, hooks, mcp };
136
+ };
137
+ var reapplyGeminiSettings = (apiKey, paths = {}) => {
138
+ const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
139
+ const settings = readJson(settingsPath);
140
+ if (settings == null) return { reapplied: false, hooks: false, mcp: false };
141
+ const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
142
+ if (!configured) return { reapplied: false, hooks: false, mcp: false };
143
+ let hooks = false;
144
+ let mcp = false;
145
+ try {
146
+ addGeminiHooks(settings, quoteIfNeeded(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
147
+ hooks = true;
148
+ if (apiKey) {
149
+ addGeminiMcpServer(settings, apiKey);
150
+ mcp = true;
151
+ }
152
+ writeJson(settingsPath, settings);
153
+ } catch {
154
+ }
155
+ return { reapplied: hooks || mcp, hooks, mcp };
156
+ };
157
+ var reapplyCursorPlugin = (apiKey, paths = {}) => {
158
+ const mcpPath = paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json");
159
+ if (!apiKey) return { reapplied: false, hooks: false, mcp: false };
160
+ const mcpConfig = readJson(mcpPath);
161
+ if (mcpConfig == null) return { reapplied: false, hooks: false, mcp: false };
162
+ const servers = mcpConfig.mcpServers;
163
+ if (!servers?.pushary) return { reapplied: false, hooks: false, mcp: false };
164
+ try {
165
+ addClaudeMcpServer(mcpConfig, apiKey);
166
+ writeJson(mcpPath, mcpConfig);
167
+ return { reapplied: true, hooks: false, mcp: true };
168
+ } catch {
169
+ return { reapplied: false, hooks: false, mcp: false };
170
+ }
171
+ };
172
+ var reapplyAllAgents = (apiKey) => {
173
+ const runners = [
174
+ { label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
175
+ { label: "Codex", run: () => reapplyCodexSettings(apiKey) },
176
+ { label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
177
+ { label: "Cursor", run: () => reapplyCursorPlugin(apiKey) }
178
+ ];
179
+ const done = [];
180
+ for (const runner of runners) {
181
+ try {
182
+ const result = runner.run();
183
+ if (result.reapplied) done.push({ label: runner.label, result });
184
+ } catch {
185
+ }
186
+ }
187
+ return done;
188
+ };
69
189
 
70
190
  // bin/pushary-upgrade.ts
71
191
  exitOnHelpFlag("upgrade");
192
+ var detectInstallModes = () => {
193
+ const candidates = [
194
+ {
195
+ label: "Cursor plugin",
196
+ upgrade: "updates itself through Cursor",
197
+ present: existsSync2(join2(homedir2(), ".cursor", "plugins", "local", "pushary"))
198
+ },
199
+ {
200
+ label: "Codex config",
201
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents codex",
202
+ present: existsSync2(codexConfigToml())
203
+ },
204
+ {
205
+ label: "Claude Code config",
206
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents claude_code",
207
+ present: existsSync2(join2(homedir2(), ".claude.json"))
208
+ },
209
+ {
210
+ label: "Gemini config",
211
+ upgrade: "npx @pushary/agent-hooks@latest setup --agents gemini_cli",
212
+ present: existsSync2(join2(homedir2(), ".gemini", "settings.json"))
213
+ },
214
+ {
215
+ label: "Hermes plugin (pip)",
216
+ upgrade: "pip install --upgrade pushary-hermes",
217
+ present: existsSync2(join2(homedir2(), ".hermes"))
218
+ }
219
+ ];
220
+ return candidates.filter((candidate) => candidate.present).map(({ label, upgrade }) => ({ label, upgrade }));
221
+ };
72
222
  var getInstalledVersion = () => {
73
223
  try {
74
224
  return execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
@@ -100,8 +250,20 @@ var isNewer = (a, b) => {
100
250
  var main = async () => {
101
251
  const installed = getInstalledVersion();
102
252
  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");
253
+ const found = detectInstallModes();
254
+ console.log("No global install of @pushary/agent-hooks found, so there is nothing to upgrade here.");
255
+ console.log();
256
+ if (found.length === 0) {
257
+ console.log("Nothing on this machine looks Pushary-configured either.");
258
+ console.log(" Set it up with: npx @pushary/agent-hooks@latest setup");
259
+ } else {
260
+ console.log("What this machine does have:");
261
+ for (const mode of found) {
262
+ console.log(` ${mode.label.padEnd(22)} ${mode.upgrade}`);
263
+ }
264
+ }
265
+ console.log();
266
+ console.log("Check the result with: pushary doctor");
105
267
  return;
106
268
  }
107
269
  const latest = await fetchLatestVersion();
@@ -122,14 +284,23 @@ var main = async () => {
122
284
  } catch {
123
285
  apiKey = void 0;
124
286
  }
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.");
287
+ const refreshed = reapplyAllAgents(apiKey);
288
+ console.log();
289
+ if (refreshed.length > 0) {
290
+ console.log("Refreshed so the new hook set is active now:");
291
+ for (const agent of refreshed) {
292
+ const parts = [agent.result.hooks ? "hooks" : null, agent.result.mcp ? "MCP key" : null].filter(Boolean).join(" + ");
293
+ console.log(` ${agent.label.padEnd(14)} ${parts}`);
129
294
  }
130
- } catch {
295
+ if (!apiKey) {
296
+ console.log();
297
+ console.log("No key was readable here, so MCP entries kept the key they already had.");
298
+ }
299
+ } else {
300
+ console.log("No agent config on this machine needed refreshing.");
131
301
  }
132
- console.log("Done. Your hooks point at the global install and settings were refreshed.");
302
+ console.log();
303
+ console.log("Done. Your hooks point at the global install.");
133
304
  console.log("Verify with: pushary doctor");
134
305
  };
135
306
  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 = {
@@ -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,55 @@
1
+ // src/cli/fsx.ts
2
+ import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ var readJsonSafe = (filePath) => {
5
+ let raw;
6
+ try {
7
+ raw = readFileSync(filePath, "utf-8");
8
+ } catch (err) {
9
+ const error = err;
10
+ if (error.code === "ENOENT") return { kind: "missing" };
11
+ return { kind: "unreadable", error };
12
+ }
13
+ if (raw.trim() === "") return { kind: "ok", value: {} };
14
+ try {
15
+ return { kind: "ok", value: JSON.parse(raw) };
16
+ } catch (err) {
17
+ return { kind: "unparseable", error: err, raw };
18
+ }
19
+ };
20
+ var stamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
21
+ var backupFile = (filePath) => {
22
+ const target = `${filePath}.pushary-backup-${stamp()}`;
23
+ copyFileSync(filePath, target);
24
+ return target;
25
+ };
26
+ var writeFileAtomic = (filePath, contents, mode) => {
27
+ const dir = dirname(filePath);
28
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
29
+ const temp = join(dir, `.${process.pid}-${stamp()}.pushary-tmp`);
30
+ try {
31
+ writeFileSync(temp, contents, mode === void 0 ? "utf-8" : { encoding: "utf-8", mode });
32
+ const handle = openSync(temp, "r+");
33
+ try {
34
+ fsyncSync(handle);
35
+ } finally {
36
+ closeSync(handle);
37
+ }
38
+ renameSync(temp, filePath);
39
+ } catch (err) {
40
+ try {
41
+ if (existsSync(temp)) unlinkSync(temp);
42
+ } catch {
43
+ }
44
+ throw err;
45
+ }
46
+ };
47
+ var writeJsonAtomic = (filePath, data, mode) => writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
48
+ `, mode);
49
+
50
+ export {
51
+ readJsonSafe,
52
+ backupFile,
53
+ writeFileAtomic,
54
+ writeJsonAtomic
55
+ };
@@ -1,3 +1,9 @@
1
+ import {
2
+ backupFile,
3
+ readJsonSafe,
4
+ writeFileAtomic,
5
+ writeJsonAtomic
6
+ } from "./chunk-6MTNS63X.js";
1
7
  import {
2
8
  resolveShellRc
3
9
  } from "./chunk-BC3VCZ3E.js";
@@ -5,59 +11,10 @@ import {
5
11
  configFilePath
6
12
  } from "./chunk-2UMNXADU.js";
7
13
 
8
- // src/cli/fsx.ts
9
- import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
10
- import { dirname, join } from "path";
11
- var readJsonSafe = (filePath) => {
12
- let raw;
13
- try {
14
- raw = readFileSync(filePath, "utf-8");
15
- } catch (err) {
16
- const error = err;
17
- if (error.code === "ENOENT") return { kind: "missing" };
18
- return { kind: "unreadable", error };
19
- }
20
- if (raw.trim() === "") return { kind: "ok", value: {} };
21
- try {
22
- return { kind: "ok", value: JSON.parse(raw) };
23
- } catch (err) {
24
- return { kind: "unparseable", error: err, raw };
25
- }
26
- };
27
- var stamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28
- var backupFile = (filePath) => {
29
- const target = `${filePath}.pushary-backup-${stamp()}`;
30
- copyFileSync(filePath, target);
31
- return target;
32
- };
33
- var writeFileAtomic = (filePath, contents, mode) => {
34
- const dir = dirname(filePath);
35
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
36
- const temp = join(dir, `.${process.pid}-${stamp()}.pushary-tmp`);
37
- try {
38
- writeFileSync(temp, contents, mode === void 0 ? "utf-8" : { encoding: "utf-8", mode });
39
- const handle = openSync(temp, "r+");
40
- try {
41
- fsyncSync(handle);
42
- } finally {
43
- closeSync(handle);
44
- }
45
- renameSync(temp, filePath);
46
- } catch (err) {
47
- try {
48
- if (existsSync(temp)) unlinkSync(temp);
49
- } catch {
50
- }
51
- throw err;
52
- }
53
- };
54
- var writeJsonAtomic = (filePath, data, mode) => writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
55
- `, mode);
56
-
57
14
  // src/keystore.ts
58
- import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
15
+ import { chmodSync, existsSync, readFileSync, realpathSync, writeFileSync } from "fs";
59
16
  import { homedir } from "os";
60
- import { join as join2 } from "path";
17
+ import { join } from "path";
61
18
  var KEY_FILE_MODE = 384;
62
19
  var FALLBACK_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
63
20
  var OUR_EXPORT = /^[ \t]*export[ \t]+PUSHARY_API_KEY=(['"]?)(pk_[a-f0-9]+\.[a-f0-9]+)\1[ \t]*$/;
@@ -86,7 +43,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
86
43
  const target = realPathOf(rcPath);
87
44
  let content = "";
88
45
  try {
89
- content = readFileSync2(target, "utf-8");
46
+ content = readFileSync(target, "utf-8");
90
47
  } catch (err) {
91
48
  const error = err;
92
49
  if (error.code !== "ENOENT") {
@@ -141,7 +98,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
141
98
  var resolveRcTarget = () => {
142
99
  const resolved = resolveShellRc();
143
100
  if (resolved) return { path: resolved.path, shell: resolved.shell };
144
- const fallback = FALLBACK_RCS.map((name) => join2(homedir(), name)).find(existsSync2);
101
+ const fallback = FALLBACK_RCS.map((name) => join(homedir(), name)).find(existsSync);
145
102
  return fallback ? { path: fallback, shell: null } : null;
146
103
  };
147
104
  var writeKey = (apiKey, options = {}) => {
@@ -181,13 +138,13 @@ var clearKey = (options = {}) => {
181
138
  if (target) {
182
139
  try {
183
140
  const real = realpathSync(target.path);
184
- const lines = readFileSync2(real, "utf8").split("\n");
141
+ const lines = readFileSync(real, "utf8").split("\n");
185
142
  const next = lines.map((line) => {
186
143
  if (!OUR_EXPORT.test(line)) return line;
187
144
  rcLinesDisabled += 1;
188
145
  return `# ${line.trim()} # removed by pushary logout`;
189
146
  });
190
- if (rcLinesDisabled > 0) writeFileSync2(real, next.join("\n"));
147
+ if (rcLinesDisabled > 0) writeFileSync(real, next.join("\n"));
191
148
  } catch {
192
149
  rcLinesDisabled = 0;
193
150
  }
@@ -223,12 +180,9 @@ var readKeySource = () => {
223
180
  };
224
181
 
225
182
  export {
226
- readJsonSafe,
227
- backupFile,
228
- writeFileAtomic,
229
- writeJsonAtomic,
230
183
  KEY_FILE_MODE,
231
184
  exportLine,
185
+ keyPrefix,
232
186
  writeKey,
233
187
  clearKey,
234
188
  readKeySource
@@ -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
+ };