@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.
- package/dist/bin/pushary-clean.js +5 -3
- package/dist/bin/pushary-codex.js +17 -10
- package/dist/bin/pushary-doctor.js +39 -3
- package/dist/bin/pushary-hook.js +3 -2
- package/dist/bin/pushary-post-hook.js +2 -2
- package/dist/bin/pushary-setup.js +72 -35
- package/dist/bin/pushary-stop-hook.js +2 -2
- package/dist/chunk-5GFUI5N6.js +132 -0
- package/dist/chunk-AB4KX4XT.js +109 -0
- package/dist/chunk-IBWCHA5M.js +10 -0
- package/dist/chunk-M2N5DYWN.js +247 -0
- package/dist/chunk-OF5WIOYS.js +129 -0
- package/dist/chunk-RSHN2AQ7.js +29 -0
- package/dist/chunk-W5KRWUNE.js +262 -0
- package/dist/src/index.d.ts +13 -17
- package/dist/src/index.js +6 -3
- package/package.json +5 -4
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
removeClaudeMcpServers,
|
|
4
4
|
removePusharySettings
|
|
5
|
-
} from "../chunk-
|
|
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
|
-
|
|
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-
|
|
4
|
+
} from "../chunk-AB4KX4XT.js";
|
|
5
5
|
import {
|
|
6
6
|
askUser,
|
|
7
|
+
getMachineId,
|
|
7
8
|
waitForAnswer
|
|
8
|
-
} from "../chunk-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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();
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
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
|
|
|
@@ -3,7 +3,14 @@ import {
|
|
|
3
3
|
addClaudeMcpServer,
|
|
4
4
|
addPusharyHooks,
|
|
5
5
|
addPusharyToolPermissions
|
|
6
|
-
} from "../chunk-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 &&
|
|
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 &&
|
|
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 || !
|
|
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(
|
|
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);
|
|
@@ -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
|
+
};
|