@pushary/agent-hooks 0.8.3 → 0.9.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.
@@ -2,13 +2,15 @@
2
2
  import {
3
3
  removeClaudeMcpServers,
4
4
  removePusharySettings
5
- } from "../chunk-AC4UYAGX.js";
5
+ } from "../chunk-5GFUI5N6.js";
6
+ import {
7
+ execNpm
8
+ } from "../chunk-RSHN2AQ7.js";
6
9
 
7
10
  // bin/pushary-clean.ts
8
11
  import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
9
12
  import { join } from "path";
10
13
  import { homedir } from "os";
11
- import { execSync } from "child_process";
12
14
  import { confirm } from "@inquirer/prompts";
13
15
  import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
14
16
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
@@ -124,7 +126,7 @@ var main = async () => {
124
126
  }
125
127
  }
126
128
  try {
127
- execSync("npm uninstall -g @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
129
+ execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
128
130
  console.log(` ${check} Global package ${dim("(uninstalled)")}`);
129
131
  } catch {
130
132
  console.log(` ${skip} Global package ${dim("(not installed)")}`);
@@ -1,23 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-6SRXMZAN.js";
4
+ } from "../chunk-AB4KX4XT.js";
5
5
  import {
6
6
  askUser,
7
+ getMachineId,
7
8
  waitForAnswer
8
- } from "../chunk-7PTU7TGE.js";
9
+ } from "../chunk-OF5WIOYS.js";
9
10
  import "../chunk-3MIR7ODJ.js";
10
11
  import {
11
12
  getApiKey
12
13
  } from "../chunk-VUNL35KE.js";
13
14
 
14
15
  // bin/pushary-codex.ts
15
- import { hostname } from "os";
16
16
  import { basename } from "path";
17
+ var readStdin = async () => {
18
+ let raw = "";
19
+ for await (const chunk of process.stdin) raw += chunk;
20
+ return raw;
21
+ };
17
22
  var main = async () => {
18
- let rawInput = "";
19
- for await (const chunk of process.stdin) {
20
- rawInput += chunk;
23
+ const argvPayload = process.argv.slice(2).find((a) => a.trim().startsWith("{"));
24
+ let rawInput = argvPayload ?? "";
25
+ if (!rawInput.trim()) {
26
+ rawInput = await readStdin();
21
27
  }
22
28
  if (!rawInput.trim()) {
23
29
  process.exit(0);
@@ -37,7 +43,7 @@ var main = async () => {
37
43
  agentType: "codex",
38
44
  agentName,
39
45
  action: event.message ?? "Turn complete",
40
- machineId: hostname()
46
+ machineId: getMachineId()
41
47
  });
42
48
  process.stdout.write(JSON.stringify({ acknowledged: true }));
43
49
  }
@@ -49,13 +55,14 @@ var main = async () => {
49
55
  agentType: "codex",
50
56
  agentName,
51
57
  action: description,
52
- machineId: hostname()
58
+ machineId: getMachineId()
53
59
  });
54
60
  const result = await askUser(apiKey, {
55
61
  question: `Allow: ${description}?`,
56
62
  type: "confirm",
57
63
  context: `Codex wants to run this in ${projectName}`,
58
- agentName
64
+ agentName,
65
+ machineId: getMachineId()
59
66
  });
60
67
  const deadline = Date.now() + 12e4;
61
68
  while (Date.now() < deadline) {
@@ -69,7 +76,7 @@ var main = async () => {
69
76
  agentType: "codex",
70
77
  agentName,
71
78
  action: approved ? `Approved: ${description}` : `Denied: ${description}`,
72
- machineId: hostname()
79
+ machineId: getMachineId()
73
80
  });
74
81
  process.exit(0);
75
82
  }
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ execNpm
4
+ } from "../chunk-RSHN2AQ7.js";
2
5
  import {
3
6
  callMcpTool,
4
7
  sendMcpRequest
@@ -9,6 +12,7 @@ import "../chunk-VUNL35KE.js";
9
12
  import { existsSync, readFileSync } from "fs";
10
13
  import { join } from "path";
11
14
  import { homedir } from "os";
15
+ import { execSync } from "child_process";
12
16
  import { confirm } from "@inquirer/prompts";
13
17
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
14
18
  var bold = (s) => `\x1B[1m${s}\x1B[0m`;
@@ -29,6 +33,28 @@ var readJson = (path) => {
29
33
  return null;
30
34
  }
31
35
  };
36
+ var commandResolves = (command) => {
37
+ if (command.includes("/") || command.includes("\\")) return existsSync(command);
38
+ try {
39
+ const whichCmd = process.platform === "win32" ? "where" : "which";
40
+ execSync(`${whichCmd} ${command}`, { stdio: "ignore", timeout: 5e3 });
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ };
46
+ var extractHookCommand = (entries, needle) => {
47
+ if (!Array.isArray(entries)) return null;
48
+ for (const entry of entries) {
49
+ const hookList = entry.hooks;
50
+ if (!Array.isArray(hookList)) continue;
51
+ for (const candidate of hookList) {
52
+ const command = candidate.command;
53
+ if (typeof command === "string" && command.includes(needle)) return command;
54
+ }
55
+ }
56
+ return null;
57
+ };
32
58
  var results = [];
33
59
  var check = (passed, label, detail) => {
34
60
  results.push({ passed, label, detail });
@@ -80,6 +106,11 @@ var main = async () => {
80
106
  check(hasPreHook, "Claude Code: PreToolUse hook");
81
107
  check(hasPostHook, "Claude Code: PostToolUse hook");
82
108
  check(hasStopHook, "Claude Code: Stop hook");
109
+ const preHookCommand = extractHookCommand(hooks?.PreToolUse, "pushary-hook");
110
+ if (preHookCommand) {
111
+ const resolves = commandResolves(preHookCommand);
112
+ check(resolves, "Claude Code: hook command resolves", resolves ? preHookCommand : `not on PATH \u2014 ${preHookCommand}`);
113
+ }
83
114
  const permissions = settings.permissions;
84
115
  const hasWildcard = permissions?.allow?.some((r) => r === "mcp__pushary__*" || r === "MCP(pushary:*)") ?? false;
85
116
  check(hasWildcard, "Claude Code: Pushary tools auto-allowed", hasWildcard ? "mcp__pushary__*" : "missing");
@@ -103,16 +134,21 @@ var main = async () => {
103
134
  console.log(` ${warn} Codex: per-tool approval overrides detected ${dim("(redundant with default_tools_approval_mode)")}`);
104
135
  }
105
136
  }
106
- check(codexConfig.includes("pushary-codex"), "Codex: notify handler configured");
137
+ const codexNotifyPath = codexConfig.match(/["']([^"']*pushary-codex[^"']*)["']/)?.[1] ?? null;
138
+ check(!!codexNotifyPath, "Codex: notify handler configured");
139
+ if (codexNotifyPath) {
140
+ const resolves = commandResolves(codexNotifyPath);
141
+ check(resolves, "Codex: notify handler resolves", resolves ? codexNotifyPath : `not found \u2014 ${codexNotifyPath}`);
142
+ }
107
143
  const codexSkillPath = join(homedir(), ".codex", "skills", "pushary", "SKILL.md");
108
144
  check(existsSync(codexSkillPath), "Codex: skill installed");
109
145
  }
110
146
  check(existsSync(SKILL_PATH), "Skill installed", existsSync(SKILL_PATH) ? SKILL_PATH : "not found");
111
147
  let globalVersion = "";
112
148
  try {
113
- const { execSync } = await import("child_process");
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] ?? "";
149
+ globalVersion = execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
115
150
  } catch {
151
+ globalVersion = "";
116
152
  }
117
153
  check(!!globalVersion, "Global package installed", globalVersion || "not found");
118
154
  console.log();
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-M6S3M6E3.js";
5
- import "../chunk-7PTU7TGE.js";
4
+ } from "../chunk-W5KRWUNE.js";
5
+ import "../chunk-IBWCHA5M.js";
6
+ import "../chunk-OF5WIOYS.js";
6
7
  import "../chunk-3MIR7ODJ.js";
7
8
  import "../chunk-VUNL35KE.js";
8
9
 
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-6SRXMZAN.js";
5
- import "../chunk-7PTU7TGE.js";
4
+ } from "../chunk-AB4KX4XT.js";
5
+ import "../chunk-OF5WIOYS.js";
6
6
  import "../chunk-3MIR7ODJ.js";
7
7
  import "../chunk-VUNL35KE.js";
8
8
 
@@ -3,7 +3,14 @@ import {
3
3
  addClaudeMcpServer,
4
4
  addPusharyHooks,
5
5
  addPusharyToolPermissions
6
- } from "../chunk-AC4UYAGX.js";
6
+ } from "../chunk-5GFUI5N6.js";
7
+ import {
8
+ execNpm,
9
+ npmErrorMessage
10
+ } from "../chunk-RSHN2AQ7.js";
11
+ import {
12
+ isValidApiKey
13
+ } from "../chunk-IBWCHA5M.js";
7
14
 
8
15
  // bin/pushary-setup.ts
9
16
  import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from "fs";
@@ -34,6 +41,15 @@ var parseKeyFlag = () => {
34
41
  }
35
42
  return void 0;
36
43
  };
44
+ var isInstalled = (command) => {
45
+ const whichCmd = process.platform === "win32" ? "where" : "which";
46
+ try {
47
+ execSync(`${whichCmd} ${command}`, { stdio: "ignore", timeout: 5e3 });
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ };
37
53
  var readJson = (path) => {
38
54
  try {
39
55
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -100,7 +116,11 @@ var checkForUpdates = async (current) => {
100
116
  };
101
117
  var installGlobally = async () => {
102
118
  await spinner("Installing pushary-hook globally", async () => {
103
- execSync("npm install -g @pushary/agent-hooks@latest", { stdio: "ignore", timeout: 12e4 });
119
+ try {
120
+ execNpm("install -g --no-workspaces @pushary/agent-hooks@latest", { timeout: 12e4 });
121
+ } catch (err) {
122
+ throw new Error(npmErrorMessage(err));
123
+ }
104
124
  });
105
125
  };
106
126
  var _cachedSkillContent = null;
@@ -151,7 +171,13 @@ var setupClaudeCode = async (apiKey) => {
151
171
  });
152
172
  await installGlobally();
153
173
  await spinner("Adding hooks (PreToolUse, PostToolUse, Stop)", async () => {
154
- addPusharyHooks(settings);
174
+ let binDir;
175
+ try {
176
+ binDir = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin");
177
+ } catch {
178
+ binDir = void 0;
179
+ }
180
+ addPusharyHooks(settings, binDir);
155
181
  });
156
182
  await spinner(`Writing ${CLAUDE_SETTINGS}`, async () => {
157
183
  writeJson(CLAUDE_SETTINGS, settings);
@@ -179,22 +205,22 @@ var findPython310Plus = () => {
179
205
  return null;
180
206
  };
181
207
  var installPythonPlugin = (pythonBin) => {
182
- execSync(`${pythonBin} -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
208
+ try {
209
+ execSync(`${pythonBin} -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
210
+ } catch (err) {
211
+ const msg = npmErrorMessage(err);
212
+ if (!msg.includes("externally-managed-environment")) throw err;
213
+ execSync(
214
+ `${pythonBin} -m pip install --upgrade --user --break-system-packages hermes-plugin-pushary`,
215
+ { stdio: "pipe", timeout: 12e4 }
216
+ );
217
+ }
183
218
  };
184
219
  var setupHermes = async (_apiKey) => {
185
220
  console.log(`
186
221
  ${bold("Setting up Hermes Agent")}
187
222
  `);
188
- const whichCmd = process.platform === "win32" ? "where" : "which";
189
- const hasHermes = (() => {
190
- try {
191
- execSync(`${whichCmd} hermes`, { stdio: "ignore", timeout: 5e3 });
192
- return true;
193
- } catch {
194
- return false;
195
- }
196
- })();
197
- if (!hasHermes) {
223
+ if (!isInstalled("hermes")) {
198
224
  console.log(` ${yellow("!")} Hermes CLI not found. Skipping.`);
199
225
  console.log(` ${dim("Install Hermes and re-run setup to configure.")}`);
200
226
  return;
@@ -205,6 +231,13 @@ var setupHermes = async (_apiKey) => {
205
231
  return;
206
232
  } catch {
207
233
  }
234
+ if (isInstalled("pipx")) {
235
+ try {
236
+ execSync("pipx inject hermes hermes-plugin-pushary", { stdio: "pipe", timeout: 12e4 });
237
+ return;
238
+ } catch {
239
+ }
240
+ }
208
241
  let python = findPython310Plus();
209
242
  if (!python) {
210
243
  if (process.platform === "darwin") {
@@ -239,7 +272,14 @@ var setupHermes = async (_apiKey) => {
239
272
  try {
240
273
  execSync(`${pip} install hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
241
274
  return;
242
- } catch {
275
+ } catch (err) {
276
+ if (npmErrorMessage(err).includes("externally-managed-environment")) {
277
+ try {
278
+ execSync(`${pip} install --user --break-system-packages hermes-plugin-pushary`, { stdio: "pipe", timeout: 12e4 });
279
+ return;
280
+ } catch {
281
+ }
282
+ }
243
283
  }
244
284
  }
245
285
  throw new Error("Python 3.10+ not found and could not be installed");
@@ -256,16 +296,7 @@ var setupCodex = async (_apiKey) => {
256
296
  console.log(`
257
297
  ${bold("Setting up Codex")}
258
298
  `);
259
- const whichCmd = process.platform === "win32" ? "where" : "which";
260
- const hasCodex = (() => {
261
- try {
262
- execSync(`${whichCmd} codex`, { stdio: "ignore", timeout: 5e3 });
263
- return true;
264
- } catch {
265
- return false;
266
- }
267
- })();
268
- if (!hasCodex) {
299
+ if (!isInstalled("codex")) {
269
300
  console.log(` ${yellow("!")} Codex CLI not found. Skipping.`);
270
301
  console.log(` ${dim("Install Codex and re-run setup to configure.")}`);
271
302
  return;
@@ -297,7 +328,7 @@ var setupCodex = async (_apiKey) => {
297
328
  writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
298
329
  });
299
330
  await spinner("Adding notify handler for Codex events", async () => {
300
- const globalPrefix = execSync("npm prefix -g", { encoding: "utf-8", timeout: 5e3 }).trim();
331
+ const globalPrefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
301
332
  const pusharyCodexPath = join(globalPrefix, "bin", "pushary-codex");
302
333
  if (!existsSync(pusharyCodexPath)) throw new Error("pushary-codex not found at " + pusharyCodexPath);
303
334
  let raw = "";
@@ -402,16 +433,15 @@ var main = async () => {
402
433
  console.log(` ${dim("Push notifications for AI coding agents")}`);
403
434
  console.log();
404
435
  await checkForUpdates(version);
405
- const keyPattern = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
406
436
  const flagKey = parseKeyFlag();
407
437
  const envKey = process.env.PUSHARY_API_KEY?.trim();
408
438
  let trimmedKey;
409
- if (flagKey && keyPattern.test(flagKey)) {
439
+ if (flagKey && isValidApiKey(flagKey)) {
410
440
  const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
411
441
  console.log(` ${check} Using API key: ${dim(masked)}`);
412
442
  console.log();
413
443
  trimmedKey = flagKey;
414
- } else if (envKey && keyPattern.test(envKey)) {
444
+ } else if (envKey && isValidApiKey(envKey)) {
415
445
  const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
416
446
  console.log(` ${check} Found API key in environment: ${dim(masked)}`);
417
447
  console.log();
@@ -429,7 +459,7 @@ var main = async () => {
429
459
  const apiKey = await input({ message: "API key:" });
430
460
  trimmedKey = apiKey.trim();
431
461
  }
432
- if (!trimmedKey || !keyPattern.test(trimmedKey)) {
462
+ if (!trimmedKey || !isValidApiKey(trimmedKey)) {
433
463
  console.log(`
434
464
  ${yellow("!")} Invalid key format. Expected: ${dim("pk_xxx.xxx")}`);
435
465
  console.log(` ${dim("Copy your key from")} ${cyan("https://pushary.com/dashboard/agent/settings")}`);
@@ -437,13 +467,20 @@ var main = async () => {
437
467
  `);
438
468
  process.exit(1);
439
469
  }
470
+ const detected = {
471
+ claude_code: isInstalled("claude"),
472
+ codex: isInstalled("codex"),
473
+ hermes: isInstalled("hermes"),
474
+ cursor: isInstalled("cursor")
475
+ };
476
+ const hint = Object.values(detected).some(Boolean) ? "(detected agents pre-selected)" : "(space = toggle, enter = confirm)";
440
477
  const agents = await checkbox({
441
- message: "Which agents do you use? " + dim("(space = toggle, enter = confirm)"),
478
+ message: "Which agents do you use? " + dim(hint),
442
479
  choices: [
443
- { name: `Claude Code ${dim("MCP + hooks + auto-allowed tools")}`, value: "claude_code" },
444
- { name: `Codex ${dim("MCP + notify handler + auto-allowed tools")}`, value: "codex" },
445
- { name: `Hermes ${dim("native plugin + auto-error notifications")}`, value: "hermes" },
446
- { name: `Cursor ${dim("MCP server")}`, value: "cursor" }
480
+ { name: `Claude Code ${dim("MCP + hooks + auto-allowed tools")}`, value: "claude_code", checked: detected.claude_code },
481
+ { name: `Codex ${dim("MCP + notify handler + auto-allowed tools")}`, value: "codex", checked: detected.codex },
482
+ { name: `Hermes ${dim("native plugin + auto-error notifications")}`, value: "hermes", checked: detected.hermes },
483
+ { name: `Cursor ${dim("MCP server")}`, value: "cursor", checked: detected.cursor }
447
484
  ]
448
485
  });
449
486
  await saveApiKey(trimmedKey);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-6SRXMZAN.js";
5
- import "../chunk-7PTU7TGE.js";
4
+ } from "../chunk-AB4KX4XT.js";
5
+ import "../chunk-OF5WIOYS.js";
6
6
  import "../chunk-3MIR7ODJ.js";
7
7
  import "../chunk-VUNL35KE.js";
8
8
 
@@ -0,0 +1,132 @@
1
+ // src/claude-config.ts
2
+ import { join } from "path";
3
+ var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
4
+ var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
5
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6
+ var ensureRecord = (target, key) => {
7
+ const existing = asRecord(target[key]);
8
+ if (existing) return existing;
9
+ const created = {};
10
+ target[key] = created;
11
+ return created;
12
+ };
13
+ var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
14
+ var isPusharyHook = (entry) => {
15
+ const hooks = asRecord(entry)?.hooks;
16
+ if (!Array.isArray(hooks)) return false;
17
+ return hooks.some((hook) => {
18
+ const command = String(asRecord(hook)?.command ?? "");
19
+ return command.includes("pushary-hook") || command.includes("pushary-post-hook") || command.includes("pushary-stop-hook");
20
+ });
21
+ };
22
+ var addClaudeMcpServer = (config, apiKey) => {
23
+ const mcpServers = ensureRecord(config, "mcpServers");
24
+ mcpServers.pushary = {
25
+ type: "http",
26
+ url: PUSHARY_MCP_URL,
27
+ headers: { Authorization: `Bearer ${apiKey}` }
28
+ };
29
+ };
30
+ var removeClaudeMcpServers = (config) => {
31
+ let changed = false;
32
+ const removeFrom = (target) => {
33
+ const mcpServers = asRecord(target.mcpServers);
34
+ if (!mcpServers?.pushary) return;
35
+ delete mcpServers.pushary;
36
+ if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
37
+ changed = true;
38
+ };
39
+ removeFrom(config);
40
+ const projects = asRecord(config.projects);
41
+ if (projects) {
42
+ for (const project of Object.values(projects)) {
43
+ const projectConfig = asRecord(project);
44
+ if (projectConfig) removeFrom(projectConfig);
45
+ }
46
+ }
47
+ return changed;
48
+ };
49
+ var addPusharyToolPermissions = (settings) => {
50
+ const permissions = ensureRecord(settings, "permissions");
51
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
52
+ const filtered = allow.filter((rule) => !isPusharyPermission(rule));
53
+ if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
54
+ filtered.push(PUSHARY_PERMISSION_RULE);
55
+ }
56
+ permissions.allow = filtered;
57
+ };
58
+ var addPusharyHooks = (settings, binDir) => {
59
+ const resolve = (name) => binDir ? join(binDir, name) : name;
60
+ const hooks = ensureRecord(settings, "hooks");
61
+ const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
62
+ preToolUse.push({
63
+ matcher: "Bash|Write|Edit",
64
+ hooks: [{
65
+ type: "command",
66
+ command: resolve("pushary-hook"),
67
+ timeout: 120
68
+ }]
69
+ });
70
+ hooks.PreToolUse = preToolUse;
71
+ const postToolUse = (Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : []).filter((entry) => !isPusharyHook(entry));
72
+ postToolUse.push({
73
+ matcher: "Bash|Write|Edit",
74
+ hooks: [{
75
+ type: "command",
76
+ command: resolve("pushary-post-hook"),
77
+ timeout: 10
78
+ }]
79
+ });
80
+ hooks.PostToolUse = postToolUse;
81
+ const stop = (Array.isArray(hooks.Stop) ? hooks.Stop : []).filter((entry) => !isPusharyHook(entry));
82
+ stop.push({
83
+ hooks: [{
84
+ type: "command",
85
+ command: resolve("pushary-stop-hook"),
86
+ timeout: 10
87
+ }]
88
+ });
89
+ hooks.Stop = stop;
90
+ };
91
+ var removePusharySettings = (settings) => {
92
+ let changed = removeClaudeMcpServers(settings);
93
+ const permissions = asRecord(settings.permissions);
94
+ if (permissions && Array.isArray(permissions.allow)) {
95
+ const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
96
+ if (filtered.length !== permissions.allow.length) {
97
+ if (filtered.length === 0) {
98
+ delete permissions.allow;
99
+ } else {
100
+ permissions.allow = filtered;
101
+ }
102
+ if (Object.keys(permissions).length === 0) delete settings.permissions;
103
+ changed = true;
104
+ }
105
+ }
106
+ const hooks = asRecord(settings.hooks);
107
+ if (hooks) {
108
+ for (const key of ["PreToolUse", "PostToolUse", "Stop"]) {
109
+ const entries = hooks[key];
110
+ if (!Array.isArray(entries)) continue;
111
+ const filtered = entries.filter((entry) => !isPusharyHook(entry));
112
+ if (filtered.length !== entries.length) {
113
+ if (filtered.length === 0) {
114
+ delete hooks[key];
115
+ } else {
116
+ hooks[key] = filtered;
117
+ }
118
+ changed = true;
119
+ }
120
+ }
121
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
122
+ }
123
+ return changed;
124
+ };
125
+
126
+ export {
127
+ addClaudeMcpServer,
128
+ removeClaudeMcpServers,
129
+ addPusharyToolPermissions,
130
+ addPusharyHooks,
131
+ removePusharySettings
132
+ };
@@ -0,0 +1,109 @@
1
+ import {
2
+ DEFAULT_SESSION,
3
+ cancelQuestion,
4
+ describeToolCall,
5
+ getMachineId,
6
+ isDefaultSession,
7
+ listPendingQuestions,
8
+ removePendingQuestion,
9
+ removePendingSession
10
+ } from "./chunk-OF5WIOYS.js";
11
+ import {
12
+ withRetry
13
+ } from "./chunk-3MIR7ODJ.js";
14
+ import {
15
+ getApiKey,
16
+ getBaseUrl
17
+ } from "./chunk-VUNL35KE.js";
18
+
19
+ // src/events.ts
20
+ import { basename } from "path";
21
+ var cleanupPendingQuestions = async (sessionId) => {
22
+ try {
23
+ const files = listPendingQuestions(sessionId);
24
+ const apiKey = getApiKey();
25
+ for (const correlationId of files) {
26
+ try {
27
+ await cancelQuestion(apiKey, correlationId);
28
+ } catch {
29
+ }
30
+ removePendingQuestion(sessionId, correlationId);
31
+ }
32
+ if (!isDefaultSession(sessionId)) removePendingSession(sessionId);
33
+ } catch {
34
+ }
35
+ };
36
+ var reportEvent = async (event) => {
37
+ const apiKey = getApiKey();
38
+ const baseUrl = getBaseUrl();
39
+ await withRetry(async () => {
40
+ await fetch(`${baseUrl}/api/agent/event`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ "Authorization": `Bearer ${apiKey}`
45
+ },
46
+ body: JSON.stringify({
47
+ ...event,
48
+ machineId: event.machineId ?? getMachineId()
49
+ }),
50
+ signal: AbortSignal.timeout(1e4)
51
+ });
52
+ }, { maxAttempts: 2, baseDelayMs: 300 });
53
+ };
54
+ var handlePostToolUse = async (input) => {
55
+ try {
56
+ const projectName = basename(input.cwd ?? process.cwd());
57
+ const action = describeToolCall(input.tool_name, input.tool_input, "event");
58
+ const isError = input.tool_result && ("error" in input.tool_result || "is_error" in input.tool_result);
59
+ await Promise.allSettled([
60
+ cleanupPendingQuestions(input.session_id || DEFAULT_SESSION),
61
+ reportEvent({
62
+ event: isError ? "tool_error" : "tool_complete",
63
+ agentType: "claude_code",
64
+ agentName: `Claude Code - ${projectName}`,
65
+ action,
66
+ sessionId: input.session_id,
67
+ error: isError ? String(input.tool_result?.error ?? input.tool_result?.stderr ?? "").slice(0, 500) : void 0
68
+ })
69
+ ]);
70
+ } catch {
71
+ }
72
+ };
73
+ var handleStop = async (input) => {
74
+ try {
75
+ const projectName = basename(input.cwd ?? process.cwd());
76
+ await Promise.allSettled([
77
+ cleanupPendingQuestions(input.session_id || DEFAULT_SESSION),
78
+ reportEvent({
79
+ event: "session_end",
80
+ agentType: "claude_code",
81
+ agentName: `Claude Code - ${projectName}`,
82
+ action: "Session ended",
83
+ sessionId: input.session_id
84
+ })
85
+ ]);
86
+ } catch {
87
+ }
88
+ };
89
+ var handleNotification = async (input) => {
90
+ try {
91
+ const projectName = basename(input.cwd ?? process.cwd());
92
+ await reportEvent({
93
+ event: input.type === "error" ? "error" : "notification",
94
+ agentType: "claude_code",
95
+ agentName: `Claude Code - ${projectName}`,
96
+ action: input.title ?? input.message ?? "Notification",
97
+ sessionId: input.session_id,
98
+ error: input.type === "error" ? input.message : void 0
99
+ });
100
+ } catch {
101
+ }
102
+ };
103
+
104
+ export {
105
+ reportEvent,
106
+ handlePostToolUse,
107
+ handleStop,
108
+ handleNotification
109
+ };
@@ -0,0 +1,10 @@
1
+ // ../contracts/src/index.ts
2
+ var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
3
+ var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
4
+ var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
5
+ var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
6
+
7
+ export {
8
+ isApprovalMode,
9
+ isValidApiKey
10
+ };