@pushary/agent-hooks 0.5.1 → 0.7.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.
@@ -10,6 +10,7 @@ import { join } from "path";
10
10
  import { homedir } from "os";
11
11
  import { execSync } from "child_process";
12
12
  import { confirm } from "@inquirer/prompts";
13
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
13
14
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
14
15
  var bold = (s) => `\x1B[1m${s}\x1B[0m`;
15
16
  var green = (s) => `\x1B[32m${s}\x1B[0m`;
@@ -21,7 +22,7 @@ var CLAUDE_SETTINGS_LOCAL = join(homedir(), ".claude", "settings.local.json");
21
22
  var CLAUDE_JSON = join(homedir(), ".claude.json");
22
23
  var SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
23
24
  var CURSOR_MCP = join(".cursor", "mcp.json");
24
- var SHELL_FILES = [".zshrc", ".bashrc"].map((f) => join(homedir(), f));
25
+ var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
25
26
  var readJson = (path) => {
26
27
  try {
27
28
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -91,26 +92,19 @@ var main = async () => {
91
92
  }
92
93
  const codexConfig = join(homedir(), ".codex", "config.toml");
93
94
  try {
94
- let config = readFileSync(codexConfig, "utf-8");
95
+ const config = readFileSync(codexConfig, "utf-8");
95
96
  const hadPushary = config.includes("pushary");
96
97
  if (hadPushary) {
97
- const lines = config.split("\n");
98
- const cleaned = [];
99
- let skipping = false;
100
- for (const line of lines) {
101
- if (/^\[mcp_servers\.pushary(?:\.|]$)/.test(line.trim())) {
102
- skipping = true;
103
- continue;
104
- }
105
- if (skipping && line.startsWith("[") && !/^\[mcp_servers\.pushary/.test(line.trim())) {
106
- skipping = false;
107
- }
108
- if (skipping) continue;
109
- if (line.includes("pushary-codex")) continue;
110
- cleaned.push(line);
98
+ const parsed = parseTOML(config);
99
+ const mcpServers = parsed.mcp_servers ?? {};
100
+ delete mcpServers.pushary;
101
+ if (Object.keys(mcpServers).length === 0) delete parsed.mcp_servers;
102
+ else parsed.mcp_servers = mcpServers;
103
+ if (Array.isArray(parsed.notify)) {
104
+ parsed.notify = parsed.notify.filter((n) => typeof n !== "string" || !n.includes("pushary-codex"));
105
+ if (parsed.notify.length === 0) delete parsed.notify;
111
106
  }
112
- config = cleaned.join("\n").replace(/\n{3,}/g, "\n\n");
113
- writeFileSync(codexConfig, config, "utf-8");
107
+ writeFileSync(codexConfig, stringifyTOML(parsed), "utf-8");
114
108
  console.log(` ${check} Codex config ${dim("(cleaned)")}`);
115
109
  } else {
116
110
  console.log(` ${skip} Codex config ${dim("(no pushary entries)")}`);
@@ -130,7 +124,7 @@ var main = async () => {
130
124
  }
131
125
  }
132
126
  try {
133
- execSync("npm uninstall -g @pushary/agent-hooks", { stdio: "ignore" });
127
+ execSync("npm uninstall -g @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
134
128
  console.log(` ${check} Global package ${dim("(uninstalled)")}`);
135
129
  } catch {
136
130
  console.log(` ${skip} Global package ${dim("(not installed)")}`);
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-P4JH2Q7Z.js";
4
+ } from "../chunk-5JEDLXEC.js";
5
5
  import {
6
6
  askUser,
7
7
  waitForAnswer
8
- } from "../chunk-KTP2EPVB.js";
8
+ } from "../chunk-EMPL27ZV.js";
9
+ import "../chunk-3MIR7ODJ.js";
9
10
  import {
10
11
  getApiKey
11
- } from "../chunk-VIST7ACL.js";
12
+ } from "../chunk-VUNL35KE.js";
12
13
 
13
14
  // bin/pushary-codex.ts
14
15
  import { hostname } from "os";
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  callMcpTool,
4
4
  sendMcpRequest
5
- } from "../chunk-VIST7ACL.js";
5
+ } from "../chunk-3MIR7ODJ.js";
6
+ import "../chunk-VUNL35KE.js";
6
7
 
7
8
  // bin/pushary-doctor.ts
8
9
  import { existsSync, readFileSync } from "fs";
@@ -20,6 +21,7 @@ var fail = red("\u2717");
20
21
  var warn = yellow("!");
21
22
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
22
23
  var SKILL_PATH = join(homedir(), ".claude", "skills", "pushary", "SKILL.md");
24
+ var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
23
25
  var readJson = (path) => {
24
26
  try {
25
27
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -39,8 +41,28 @@ var main = async () => {
39
41
  console.log(` ${bold("Pushary Doctor")}`);
40
42
  console.log();
41
43
  console.log(` ${dim("Configuration")}`);
42
- const apiKey = process.env.PUSHARY_API_KEY;
43
- check(!!apiKey, "API key in environment", apiKey ? `pk_${apiKey.split(".")[0]?.slice(3, 7)}...` : "PUSHARY_API_KEY not set");
44
+ let apiKey = process.env.PUSHARY_API_KEY;
45
+ let keyFromProfile = false;
46
+ if (!apiKey) {
47
+ for (const f of SHELL_FILES) {
48
+ try {
49
+ const content = readFileSync(f, "utf-8");
50
+ const match = content.match(/export\s+PUSHARY_API_KEY=['"](pk_[a-f0-9]+\.[a-f0-9]+)['"]/);
51
+ if (match) {
52
+ apiKey = match[1];
53
+ keyFromProfile = true;
54
+ break;
55
+ }
56
+ } catch {
57
+ }
58
+ }
59
+ }
60
+ if (keyFromProfile) {
61
+ console.log(` ${pass} API key in shell profile ${dim(`(pk_${apiKey.split(".")[0]?.slice(3, 7)}...)`)}`);
62
+ console.log(` ${warn} Not loaded in this shell \u2014 run ${cyan("source ~/.zshrc")} or open a new terminal`);
63
+ } else {
64
+ check(!!apiKey, "API key in environment", apiKey ? `pk_${apiKey.split(".")[0]?.slice(3, 7)}...` : "PUSHARY_API_KEY not set");
65
+ }
44
66
  const CLAUDE_JSON = join(homedir(), ".claude.json");
45
67
  const claudeJson = readJson(CLAUDE_JSON);
46
68
  const mcpServers = claudeJson?.mcpServers ?? {};
@@ -61,7 +83,7 @@ var main = async () => {
61
83
  const permissions = settings.permissions;
62
84
  const hasWildcard = permissions?.allow?.some((r) => r === "mcp__pushary__*" || r === "MCP(pushary:*)") ?? false;
63
85
  check(hasWildcard, "Claude Code: Pushary tools auto-allowed", hasWildcard ? "mcp__pushary__*" : "missing");
64
- const hasLegacyPerms = permissions?.allow?.some((r) => r.startsWith("mcp__pushary__")) ?? false;
86
+ const hasLegacyPerms = permissions?.allow?.some((r) => r.startsWith("mcp__pushary__") && r !== "mcp__pushary__*") ?? false;
65
87
  if (hasLegacyPerms) {
66
88
  console.log(` ${warn} Legacy individual permissions detected ${dim("(run pushary clean, then setup again)")}`);
67
89
  }
@@ -89,7 +111,7 @@ var main = async () => {
89
111
  let globalVersion = "";
90
112
  try {
91
113
  const { execSync } = await import("child_process");
92
- globalVersion = execSync("npm list -g @pushary/agent-hooks --depth=0 2>/dev/null", { encoding: "utf-8" }).match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
114
+ globalVersion = execSync("npm list -g @pushary/agent-hooks --depth=0 2>/dev/null", { encoding: "utf-8", timeout: 1e4 }).match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
93
115
  } catch {
94
116
  }
95
117
  check(!!globalVersion, "Global package installed", globalVersion || "not found");
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-DF3BM6BF.js";
5
- import "../chunk-KTP2EPVB.js";
6
- import "../chunk-VIST7ACL.js";
4
+ } from "../chunk-C5TFTNHG.js";
5
+ import "../chunk-EMPL27ZV.js";
6
+ import "../chunk-3MIR7ODJ.js";
7
+ import "../chunk-VUNL35KE.js";
7
8
 
8
9
  // bin/pushary-hook.ts
9
10
  var main = async () => {
@@ -25,14 +26,19 @@ var main = async () => {
25
26
  }
26
27
  try {
27
28
  const output = await handlePreToolUse(input);
28
- if (output) {
29
- process.stdout.write(JSON.stringify(output));
30
- }
29
+ process.stdout.write(JSON.stringify(output));
31
30
  } catch (err) {
32
31
  process.stderr.write(
33
32
  `[pushary-hook] Error: ${err instanceof Error ? err.message : String(err)}
34
33
  `
35
34
  );
35
+ process.stdout.write(JSON.stringify({
36
+ hookSpecificOutput: {
37
+ hookEventName: "PreToolUse",
38
+ permissionDecision: "ask",
39
+ permissionDecisionReason: "Pushary hook error, falling back to terminal"
40
+ }
41
+ }));
36
42
  }
37
43
  };
38
44
  main();
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getApiKey,
4
+ getBaseUrl
5
+ } from "../chunk-VUNL35KE.js";
6
+
7
+ // bin/pushary-mode.ts
8
+ var VALID_MODES = ["push_only", "push_first", "terminal_only", "notify_only"];
9
+ var dim = (s) => `\x1B[2m${s}\x1B[0m`;
10
+ var green = (s) => `\x1B[32m${s}\x1B[0m`;
11
+ var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
12
+ var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
13
+ var parseDuration = (value) => {
14
+ const match = value.match(/^(\d+)(m|h)$/);
15
+ if (!match) return null;
16
+ const num = Number(match[1]);
17
+ return match[2] === "h" ? num * 3600 : num * 60;
18
+ };
19
+ var main = async () => {
20
+ const apiKey = getApiKey();
21
+ const baseUrl = getBaseUrl();
22
+ const mode = process.argv[2];
23
+ const forFlag = process.argv[3];
24
+ const forValue = process.argv[4];
25
+ const headers = {
26
+ "Authorization": `Bearer ${apiKey}`,
27
+ "Content-Type": "application/json"
28
+ };
29
+ if (!mode || mode === "status") {
30
+ const res2 = await fetch(`${baseUrl}/api/mcp/mode`, { headers });
31
+ const data2 = await res2.json();
32
+ if (!data2.override) {
33
+ console.log(` Mode: ${cyan("default")} ${dim("(using per-tool policies)")}`);
34
+ } else {
35
+ console.log(` Mode: ${green(data2.override.mode)}`);
36
+ if (data2.override.expiresAt) {
37
+ console.log(` Expires: ${new Date(data2.override.expiresAt).toLocaleString()}`);
38
+ } else {
39
+ console.log(` ${dim("Sticky (no expiry)")}`);
40
+ }
41
+ }
42
+ return;
43
+ }
44
+ if (mode === "clear" || mode === "reset") {
45
+ await fetch(`${baseUrl}/api/mcp/mode`, { method: "DELETE", headers });
46
+ console.log(` ${green("\u2713")} Mode override cleared \u2014 using per-tool policies`);
47
+ return;
48
+ }
49
+ if (!VALID_MODES.includes(mode)) {
50
+ console.log(` ${yellow("!")} Invalid mode: ${mode}`);
51
+ console.log(` Valid modes: ${VALID_MODES.join(", ")}`);
52
+ console.log(` ${dim("Usage: pushary mode <mode> [--for <duration>]")}`);
53
+ console.log(` ${dim("Example: pushary mode push_only --for 30m")}`);
54
+ return;
55
+ }
56
+ let ttlSeconds;
57
+ if (forFlag === "--for" && forValue) {
58
+ const parsed = parseDuration(forValue);
59
+ if (!parsed) {
60
+ console.log(` ${yellow("!")} Invalid duration: ${forValue} ${dim("(use e.g. 30m, 1h)")}`);
61
+ return;
62
+ }
63
+ ttlSeconds = parsed;
64
+ }
65
+ const res = await fetch(`${baseUrl}/api/mcp/mode`, {
66
+ method: "PUT",
67
+ headers,
68
+ body: JSON.stringify({ mode, ttlSeconds })
69
+ });
70
+ if (!res.ok) {
71
+ const err = await res.json();
72
+ console.log(` ${yellow("!")} Failed: ${err.error ?? res.statusText}`);
73
+ return;
74
+ }
75
+ const data = await res.json();
76
+ console.log(` ${green("\u2713")} Mode set to ${cyan(data.override.mode)}`);
77
+ if (data.override.expiresAt) {
78
+ console.log(` Expires: ${new Date(data.override.expiresAt).toLocaleString()}`);
79
+ }
80
+ };
81
+ main().catch((err) => {
82
+ console.error(` ${yellow("!")} ${err instanceof Error ? err.message : err}`);
83
+ process.exit(1);
84
+ });
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-P4JH2Q7Z.js";
5
- import "../chunk-KTP2EPVB.js";
6
- import "../chunk-VIST7ACL.js";
4
+ } from "../chunk-5JEDLXEC.js";
5
+ import "../chunk-EMPL27ZV.js";
6
+ import "../chunk-3MIR7ODJ.js";
7
+ import "../chunk-VUNL35KE.js";
7
8
 
8
9
  // bin/pushary-post-hook.ts
9
10
  var main = async () => {
@@ -12,13 +12,14 @@ import { homedir } from "os";
12
12
  import { execSync } from "child_process";
13
13
  import { checkbox, input, confirm } from "@inquirer/prompts";
14
14
  import { fileURLToPath } from "url";
15
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
15
16
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
16
17
  var CLAUDE_JSON = join(homedir(), ".claude.json");
17
18
  var CURSOR_MCP = join(".cursor", "mcp.json");
18
19
  var CURSOR_RULES_DIR = join(".cursor", "rules");
19
20
  var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
20
21
  var CODEX_SKILL_DIR = join(homedir(), ".codex", "skills", "pushary");
21
- var SHELL_FILES = [".zshrc", ".bashrc"].map((f) => join(homedir(), f));
22
+ var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
22
23
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
23
24
  var bold = (s) => `\x1B[1m${s}\x1B[0m`;
24
25
  var green = (s) => `\x1B[32m${s}\x1B[0m`;
@@ -37,7 +38,16 @@ var writeJson = (path, data) => {
37
38
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
38
39
  writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
39
40
  };
40
- var formatError = (err) => err instanceof Error ? err.message : String(err);
41
+ var formatError = (err) => {
42
+ if (err instanceof Error) {
43
+ const errWithSignal = err;
44
+ if (errWithSignal.killed && errWithSignal.signal === "SIGTERM") {
45
+ return "timed out \u2014 check your network connection";
46
+ }
47
+ return err.message;
48
+ }
49
+ return String(err);
50
+ };
41
51
  var spinner = async (label, fn, options = {}) => {
42
52
  const frames = [" ", ". ", ".. ", "..."];
43
53
  let i = 0;
@@ -82,7 +92,7 @@ var checkForUpdates = async (current) => {
82
92
  };
83
93
  var installGlobally = async () => {
84
94
  await spinner("Installing pushary-hook globally", async () => {
85
- execSync("npm install -g @pushary/agent-hooks@latest", { stdio: "ignore" });
95
+ execSync("npm install -g @pushary/agent-hooks@latest", { stdio: "ignore", timeout: 12e4 });
86
96
  });
87
97
  };
88
98
  var _cachedSkillContent = null;
@@ -150,7 +160,7 @@ var findPython310Plus = () => {
150
160
  const candidates = ["python3.13", "python3.12", "python3.11", "python3.10", "python3", "python"];
151
161
  for (const py of candidates) {
152
162
  try {
153
- const version = execSync(`${py} --version 2>&1`, { encoding: "utf-8", stdio: "pipe" }).trim();
163
+ const version = execSync(`${py} --version 2>&1`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
154
164
  const match = version.match(/Python (\d+)\.(\d+)/);
155
165
  if (match && (Number(match[1]) > 3 || Number(match[1]) === 3 && Number(match[2]) >= 10)) {
156
166
  return py;
@@ -161,7 +171,7 @@ var findPython310Plus = () => {
161
171
  return null;
162
172
  };
163
173
  var installPythonPlugin = (pythonBin) => {
164
- execSync(`${pythonBin} -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe" });
174
+ execSync(`${pythonBin} -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
165
175
  };
166
176
  var setupHermes = async (_apiKey) => {
167
177
  console.log(`
@@ -170,7 +180,7 @@ var setupHermes = async (_apiKey) => {
170
180
  const whichCmd = process.platform === "win32" ? "where" : "which";
171
181
  const hasHermes = (() => {
172
182
  try {
173
- execSync(`${whichCmd} hermes`, { stdio: "ignore" });
183
+ execSync(`${whichCmd} hermes`, { stdio: "ignore", timeout: 5e3 });
174
184
  return true;
175
185
  } catch {
176
186
  return false;
@@ -183,7 +193,7 @@ var setupHermes = async (_apiKey) => {
183
193
  }
184
194
  await spinner("Installing hermes-plugin-pushary", async () => {
185
195
  try {
186
- execSync("uv pip install hermes-plugin-pushary", { stdio: "pipe" });
196
+ execSync("uv pip install hermes-plugin-pushary", { stdio: "pipe", timeout: 12e4 });
187
197
  return;
188
198
  } catch {
189
199
  }
@@ -191,8 +201,8 @@ var setupHermes = async (_apiKey) => {
191
201
  if (!python) {
192
202
  if (process.platform === "darwin") {
193
203
  try {
194
- execSync("which brew", { stdio: "ignore" });
195
- execSync("brew install python@3.12", { stdio: "pipe" });
204
+ execSync("which brew", { stdio: "ignore", timeout: 5e3 });
205
+ execSync("brew install python@3.12", { stdio: "pipe", timeout: 3e5 });
196
206
  python = findPython310Plus();
197
207
  } catch {
198
208
  }
@@ -204,8 +214,8 @@ var setupHermes = async (_apiKey) => {
204
214
  ["which pacman", "sudo pacman -S --noconfirm python python-pip"]
205
215
  ]) {
206
216
  try {
207
- execSync(check2, { stdio: "ignore" });
208
- execSync(install, { stdio: "pipe" });
217
+ execSync(check2, { stdio: "ignore", timeout: 5e3 });
218
+ execSync(install, { stdio: "pipe", timeout: 3e5 });
209
219
  python = findPython310Plus();
210
220
  if (python) break;
211
221
  } catch {
@@ -219,7 +229,7 @@ var setupHermes = async (_apiKey) => {
219
229
  }
220
230
  for (const pip of ["pip3", "pip"]) {
221
231
  try {
222
- execSync(`${pip} install hermes-plugin-pushary`, { stdio: "pipe" });
232
+ execSync(`${pip} install hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
223
233
  return;
224
234
  } catch {
225
235
  }
@@ -227,7 +237,7 @@ var setupHermes = async (_apiKey) => {
227
237
  throw new Error("Python 3.10+ not found and could not be installed");
228
238
  });
229
239
  await spinner("Enabling plugin", async () => {
230
- execSync("hermes plugins enable pushary", { stdio: "ignore" });
240
+ execSync("hermes plugins enable pushary", { stdio: "ignore", timeout: 1e4 });
231
241
  });
232
242
  console.log();
233
243
  console.log(` ${dim("What this configured:")}`);
@@ -241,7 +251,7 @@ var setupCodex = async (_apiKey) => {
241
251
  const whichCmd = process.platform === "win32" ? "where" : "which";
242
252
  const hasCodex = (() => {
243
253
  try {
244
- execSync(`${whichCmd} codex`, { stdio: "ignore" });
254
+ execSync(`${whichCmd} codex`, { stdio: "ignore", timeout: 5e3 });
245
255
  return true;
246
256
  } catch {
247
257
  return false;
@@ -258,61 +268,41 @@ var setupCodex = async (_apiKey) => {
258
268
  try {
259
269
  execSync(
260
270
  "codex mcp add pushary --url https://pushary.com/api/mcp/mcp --bearer-token-env-var PUSHARY_API_KEY",
261
- { stdio: "ignore" }
271
+ { stdio: "ignore", timeout: 15e3 }
262
272
  );
263
273
  } catch {
264
274
  }
265
275
  });
266
276
  await spinner("Auto-allowing all Pushary tools", async () => {
267
- let config = "";
277
+ let raw = "";
268
278
  try {
269
- config = readFileSync(codexConfig, "utf-8");
279
+ raw = readFileSync(codexConfig, "utf-8");
270
280
  } catch {
271
281
  }
272
- const lines = config.split("\n");
273
- const cleaned = [];
274
- let skippingToolSection = false;
275
- for (const line of lines) {
276
- if (/^\[mcp_servers\.pushary\.tools\./.test(line.trim())) {
277
- skippingToolSection = true;
278
- continue;
279
- }
280
- if (skippingToolSection) {
281
- if (line.startsWith("[") || line.trim() === "") {
282
- skippingToolSection = false;
283
- if (line.startsWith("[")) {
284
- cleaned.push(line);
285
- continue;
286
- }
287
- } else {
288
- continue;
289
- }
290
- }
291
- cleaned.push(line);
292
- }
293
- config = cleaned.join("\n");
294
- if (!config.includes("default_tools_approval_mode")) {
295
- config = config.replace(
296
- /(\[mcp_servers\.pushary\]\n)/,
297
- '$1default_tools_approval_mode = "approve"\n'
298
- );
299
- }
300
- writeFileSync(codexConfig, config, "utf-8");
282
+ const config = raw ? parseTOML(raw) : {};
283
+ const mcpServers = config.mcp_servers ?? {};
284
+ const pushary = mcpServers.pushary ?? {};
285
+ pushary.default_tools_approval_mode = "approve";
286
+ delete pushary.tools;
287
+ mcpServers.pushary = pushary;
288
+ config.mcp_servers = mcpServers;
289
+ writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
301
290
  });
302
291
  await spinner("Adding notify handler for Codex events", async () => {
303
- const globalPrefix = execSync("npm prefix -g", { encoding: "utf-8" }).trim();
292
+ const globalPrefix = execSync("npm prefix -g", { encoding: "utf-8", timeout: 5e3 }).trim();
304
293
  const pusharyCodexPath = join(globalPrefix, "bin", "pushary-codex");
305
294
  if (!existsSync(pusharyCodexPath)) throw new Error("pushary-codex not found at " + pusharyCodexPath);
306
- let config = "";
295
+ let raw = "";
307
296
  try {
308
- config = readFileSync(codexConfig, "utf-8");
297
+ raw = readFileSync(codexConfig, "utf-8");
309
298
  } catch {
310
299
  }
311
- if (!config.includes("pushary-codex")) {
312
- const notifyLine = `
313
- notify = ["${pusharyCodexPath}"]
314
- `;
315
- appendFileSync(codexConfig, notifyLine, "utf-8");
300
+ const config = raw ? parseTOML(raw) : {};
301
+ const notify = Array.isArray(config.notify) ? config.notify : [];
302
+ if (!notify.some((n) => typeof n === "string" && n.includes("pushary-codex"))) {
303
+ notify.push(pusharyCodexPath);
304
+ config.notify = notify;
305
+ writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
316
306
  }
317
307
  });
318
308
  await installSkillToDir(CODEX_SKILL_DIR, "Installing Pushary skill");
@@ -452,9 +442,10 @@ var main = async () => {
452
442
  console.log(` ${green(bold("Setup complete."))}`);
453
443
  console.log();
454
444
  console.log(` ${dim("Next:")}`);
455
- console.log(` ${dim("1.")} Enable notifications on your phone at ${cyan("pushary.com")}`);
456
- console.log(` ${dim("2.")} Restart your agent to load the new config`);
457
- console.log(` ${dim("3.")} Run ${cyan("npx @pushary/agent-hooks doctor")} to verify`);
445
+ console.log(` ${dim("1.")} Load your API key: ${cyan("source ~/.zshrc")} ${dim("(or open a new terminal)")}`);
446
+ console.log(` ${dim("2.")} Enable notifications on your phone at ${cyan("pushary.com")}`);
447
+ console.log(` ${dim("3.")} Restart your agent to load the new config`);
448
+ console.log(` ${dim("4.")} Run ${cyan("npx @pushary/agent-hooks doctor")} to verify`);
458
449
  console.log();
459
450
  };
460
451
  main().catch((err) => {
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-P4JH2Q7Z.js";
5
- import "../chunk-KTP2EPVB.js";
6
- import "../chunk-VIST7ACL.js";
4
+ } from "../chunk-5JEDLXEC.js";
5
+ import "../chunk-EMPL27ZV.js";
6
+ import "../chunk-3MIR7ODJ.js";
7
+ import "../chunk-VUNL35KE.js";
7
8
 
8
9
  // bin/pushary-stop-hook.ts
9
10
  var main = async () => {
@@ -10,6 +10,8 @@ if (command === "setup") {
10
10
  await import("./pushary-clean.js");
11
11
  } else if (command === "doctor") {
12
12
  await import("./pushary-doctor.js");
13
+ } else if (command === "mode") {
14
+ await import("./pushary-mode.js");
13
15
  } else {
14
16
  console.log(`
15
17
  Pushary Agent Hooks
@@ -18,11 +20,12 @@ Commands:
18
20
  setup Configure Claude Code, Codex, Hermes, or Cursor with Pushary
19
21
  doctor Verify your Pushary installation is working
20
22
  clean Remove all Pushary configuration
23
+ mode Switch approval mode (push_only, push_first, terminal_only)
21
24
  hook Run as a PreToolUse hook (reads stdin, writes stdout)
22
25
 
23
26
  Usage:
24
27
  npx @pushary/agent-hooks@latest setup
25
28
  npx @pushary/agent-hooks@latest doctor
26
- npx @pushary/agent-hooks@latest clean
29
+ npx @pushary/agent-hooks@latest mode push_only --for 30m
27
30
  `);
28
31
  }