@pushary/agent-hooks 0.29.0 → 0.31.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.
- package/dist/bin/pushary-clean.js +84 -6
- package/dist/bin/pushary-codex-hook.js +22 -1
- package/dist/bin/pushary-doctor.js +29 -5
- package/dist/bin/pushary-gemini-hook.js +22 -1
- package/dist/bin/pushary-hook.js +2 -2
- package/dist/bin/pushary-setup.js +42 -39
- package/dist/bin/pushary-stats.js +1 -1
- package/dist/bin/pushary-upgrade.js +1 -1
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-GPLKEAFG.js → chunk-CY5YP34L.js} +4 -4
- package/dist/{chunk-QNEYHDKR.js → chunk-H3LQRYMW.js} +4 -1
- package/dist/{chunk-RSHN2AQ7.js → chunk-J7JWI3KU.js} +21 -1
- package/dist/{chunk-WLF3NCHS.js → chunk-R5AJNXZS.js} +17 -15
- package/dist/{chunk-XY6OKUQ4.js → chunk-WUNVKFFY.js} +1 -1
- package/dist/src/index.js +2 -2
- package/package.json +1 -1
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
removeClaudeMcpServers,
|
|
4
4
|
removePusharySettings
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-H3LQRYMW.js";
|
|
6
6
|
import {
|
|
7
7
|
removeCodexHooks,
|
|
8
8
|
removeGeminiSettings,
|
|
9
9
|
removeInstructionBlock
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-WUNVKFFY.js";
|
|
11
11
|
import {
|
|
12
12
|
execNpm
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-J7JWI3KU.js";
|
|
14
14
|
import "../chunk-Z5PL3K7C.js";
|
|
15
15
|
|
|
16
16
|
// bin/pushary-clean.ts
|
|
17
|
-
import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
|
|
18
18
|
import { join } from "path";
|
|
19
|
-
import { homedir } from "os";
|
|
19
|
+
import { homedir, tmpdir } from "os";
|
|
20
|
+
import { execSync } from "child_process";
|
|
20
21
|
import { confirm } from "@inquirer/prompts";
|
|
21
22
|
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
22
23
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
@@ -31,7 +32,25 @@ var CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
|
31
32
|
var SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
|
|
32
33
|
var CURSOR_MCP = join(".cursor", "mcp.json");
|
|
33
34
|
var CURSOR_PLUGIN_DIR = join(homedir(), ".cursor", "plugins", "local", "pushary");
|
|
35
|
+
var CURSOR_USER_HOOKS = join(homedir(), ".cursor", "hooks.json");
|
|
36
|
+
var PUSHARY_DIR = join(homedir(), ".pushary");
|
|
34
37
|
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
|
|
38
|
+
var resolveHermesPython = () => {
|
|
39
|
+
try {
|
|
40
|
+
const launcher = execSync("command -v hermes", { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
|
|
41
|
+
if (launcher) {
|
|
42
|
+
const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
|
|
43
|
+
if (shebang.startsWith("#!")) {
|
|
44
|
+
const interpreter = shebang.slice(2).trim().split(/\s+/)[0];
|
|
45
|
+
if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
const venvPython = join(homedir(), ".hermes", "hermes-agent", "venv", "bin", "python3");
|
|
51
|
+
if (existsSync(venvPython)) return venvPython;
|
|
52
|
+
return null;
|
|
53
|
+
};
|
|
35
54
|
var readJson = (path) => {
|
|
36
55
|
try {
|
|
37
56
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -61,7 +80,12 @@ var main = async () => {
|
|
|
61
80
|
console.log(` ${bold("Pushary Clean")}`);
|
|
62
81
|
console.log(` ${dim("Removes all Pushary configuration")}`);
|
|
63
82
|
console.log();
|
|
64
|
-
const
|
|
83
|
+
const assumeYes = process.argv.includes("--yes") || process.argv.includes("-y");
|
|
84
|
+
if (!assumeYes && !process.stdin.isTTY) {
|
|
85
|
+
console.log(` ${yellow("!")} Non-interactive shell. Re-run with --yes to confirm removal.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const proceed = assumeYes || await confirm({ message: "Remove all Pushary configuration?", default: false });
|
|
65
89
|
if (!proceed) {
|
|
66
90
|
console.log(` ${dim("Cancelled.")}`);
|
|
67
91
|
process.exit(0);
|
|
@@ -99,6 +123,23 @@ var main = async () => {
|
|
|
99
123
|
} else {
|
|
100
124
|
console.log(` ${skip} Cursor plugin ${dim("(not installed)")}`);
|
|
101
125
|
}
|
|
126
|
+
const cursorHooks = readJson(CURSOR_USER_HOOKS);
|
|
127
|
+
if (cursorHooks) {
|
|
128
|
+
const hooks = cursorHooks.hooks ?? {};
|
|
129
|
+
const existing = Array.isArray(hooks.beforeShellExecution) ? hooks.beforeShellExecution : [];
|
|
130
|
+
const others = existing.filter((h) => !String(h.command ?? "").includes("pushary-gate"));
|
|
131
|
+
if (others.length !== existing.length) {
|
|
132
|
+
if (others.length === 0) delete hooks.beforeShellExecution;
|
|
133
|
+
else hooks.beforeShellExecution = others;
|
|
134
|
+
cursorHooks.hooks = hooks;
|
|
135
|
+
writeJson(CURSOR_USER_HOOKS, cursorHooks);
|
|
136
|
+
console.log(` ${check} Cursor gate ${dim("(removed from ~/.cursor/hooks.json)")}`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(` ${skip} Cursor gate ${dim("(no pushary entries)")}`);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
console.log(` ${skip} Cursor gate ${dim("(no hooks.json)")}`);
|
|
142
|
+
}
|
|
102
143
|
if (existsSync(SKILL_DIR)) {
|
|
103
144
|
rmSync(SKILL_DIR, { recursive: true });
|
|
104
145
|
console.log(` ${check} Skill directory ${dim("(removed)")}`);
|
|
@@ -170,6 +211,24 @@ var main = async () => {
|
|
|
170
211
|
} else {
|
|
171
212
|
console.log(` ${skip} Gemini GEMINI.md ${dim("(no pushary block)")}`);
|
|
172
213
|
}
|
|
214
|
+
const hermesPython = resolveHermesPython();
|
|
215
|
+
if (hermesPython) {
|
|
216
|
+
const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = [x for x in e if x != "pushary"]; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = [x for x in d if x != "clarify"]; c["agent"] = a; save_config(c)';
|
|
217
|
+
try {
|
|
218
|
+
execSync(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 });
|
|
219
|
+
console.log(` ${check} Hermes config ${dim("(plugin disabled, clarify toolset restored)")}`);
|
|
220
|
+
} catch {
|
|
221
|
+
console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
execSync(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 });
|
|
225
|
+
console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
|
|
226
|
+
} catch {
|
|
227
|
+
console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
console.log(` ${skip} Hermes ${dim("(not found)")}`);
|
|
231
|
+
}
|
|
173
232
|
for (const shellFile of SHELL_FILES) {
|
|
174
233
|
try {
|
|
175
234
|
const content = readFileSync(shellFile, "utf-8");
|
|
@@ -181,6 +240,25 @@ var main = async () => {
|
|
|
181
240
|
} catch {
|
|
182
241
|
}
|
|
183
242
|
}
|
|
243
|
+
if (existsSync(PUSHARY_DIR)) {
|
|
244
|
+
rmSync(PUSHARY_DIR, { recursive: true });
|
|
245
|
+
console.log(` ${check} Local state ${dim("(~/.pushary removed: stored API key, ledger)")}`);
|
|
246
|
+
} else {
|
|
247
|
+
console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
let swept = 0;
|
|
251
|
+
for (const f of readdirSync(tmpdir())) {
|
|
252
|
+
if (!f.startsWith("pushary-")) continue;
|
|
253
|
+
try {
|
|
254
|
+
rmSync(join(tmpdir(), f), { recursive: true, force: true });
|
|
255
|
+
swept++;
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (swept > 0) console.log(` ${check} Temp state ${dim(`(${swept} pushary entries swept)`)}`);
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
184
262
|
try {
|
|
185
263
|
execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
|
|
186
264
|
console.log(` ${check} Global package ${dim("(uninstalled)")}`);
|
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
denyReasonFrom,
|
|
4
4
|
isDeferAnswer
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
|
+
import {
|
|
7
|
+
isGatingMoment,
|
|
8
|
+
recordKeylessMoment
|
|
9
|
+
} from "../chunk-R5AJNXZS.js";
|
|
6
10
|
import {
|
|
7
11
|
CODEX_AGENT,
|
|
8
12
|
DEFAULT_SESSION,
|
|
@@ -201,8 +205,25 @@ var decidePermissionRequest = async (input) => {
|
|
|
201
205
|
}
|
|
202
206
|
};
|
|
203
207
|
var decidePreToolUse = async (input) => {
|
|
208
|
+
let apiKey;
|
|
209
|
+
try {
|
|
210
|
+
apiKey = getApiKey();
|
|
211
|
+
} catch {
|
|
212
|
+
try {
|
|
213
|
+
const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
|
|
214
|
+
if (isGatingMoment(lookup.tool, lookup.input)) {
|
|
215
|
+
recordKeylessMoment({
|
|
216
|
+
ts: Date.now(),
|
|
217
|
+
tool: lookup.tool,
|
|
218
|
+
project: basename(input.cwd ?? process.cwd()),
|
|
219
|
+
sessionId: input.session_id
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
return codexPass();
|
|
225
|
+
}
|
|
204
226
|
try {
|
|
205
|
-
const apiKey = getApiKey();
|
|
206
227
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
207
228
|
if (modeState.kill) return codexDeny(KILL_REASON);
|
|
208
229
|
const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
|
|
@@ -11,10 +11,13 @@ import {
|
|
|
11
11
|
missingGeminiHookEvents,
|
|
12
12
|
readCodexMcpAuth,
|
|
13
13
|
untrustedCodexHookEvents
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-WUNVKFFY.js";
|
|
15
15
|
import {
|
|
16
16
|
execNpm
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-J7JWI3KU.js";
|
|
18
|
+
import {
|
|
19
|
+
readLedgerSummary
|
|
20
|
+
} from "../chunk-R5AJNXZS.js";
|
|
18
21
|
import {
|
|
19
22
|
callMcpTool,
|
|
20
23
|
sendMcpRequest
|
|
@@ -317,12 +320,29 @@ var main = async () => {
|
|
|
317
320
|
globalVersion = "";
|
|
318
321
|
}
|
|
319
322
|
check(!!globalVersion, "Global package installed", globalVersion || "not found");
|
|
323
|
+
if (globalVersion) {
|
|
324
|
+
try {
|
|
325
|
+
const res = await fetch("https://registry.npmjs.org/@pushary/agent-hooks/latest", {
|
|
326
|
+
signal: AbortSignal.timeout(5e3)
|
|
327
|
+
});
|
|
328
|
+
const latest = res.ok ? (await res.json()).version ?? "" : "";
|
|
329
|
+
if (latest) {
|
|
330
|
+
const upToDate = latest === globalVersion;
|
|
331
|
+
check(upToDate, "Global package up to date", upToDate ? globalVersion : `${globalVersion} installed, ${latest} available \u2014 run npx @pushary/agent-hooks@latest upgrade`);
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
320
336
|
console.log();
|
|
321
337
|
console.log(` ${dim("Connectivity")}`);
|
|
322
338
|
if (!apiKey) {
|
|
323
339
|
check(false, "MCP server reachable", "skipped \u2014 no API key");
|
|
324
340
|
check(false, "API key valid", "skipped");
|
|
325
341
|
check(false, "MCP handshake", "skipped");
|
|
342
|
+
const keylessSummary = readLedgerSummary(7);
|
|
343
|
+
if (keylessSummary.count > 0) {
|
|
344
|
+
check(false, "Keyless approval moments (7d)", `${keylessSummary.count} counted locally \u2014 npx @pushary/agent-hooks@latest stats`);
|
|
345
|
+
}
|
|
326
346
|
} else {
|
|
327
347
|
let sessionId = "";
|
|
328
348
|
try {
|
|
@@ -379,7 +399,7 @@ var main = async () => {
|
|
|
379
399
|
const msg = err instanceof Error ? err.message : "network error";
|
|
380
400
|
check(false, "Push notification sent", msg);
|
|
381
401
|
}
|
|
382
|
-
const testQuestion = await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false });
|
|
402
|
+
const testQuestion = process.stdin.isTTY ? await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false }) : false;
|
|
383
403
|
if (testQuestion) {
|
|
384
404
|
console.log();
|
|
385
405
|
console.log(` ${dim("Question Roundtrip")}`);
|
|
@@ -418,7 +438,8 @@ var main = async () => {
|
|
|
418
438
|
if (apiKey) {
|
|
419
439
|
try {
|
|
420
440
|
const res = await fetch(`${getBaseUrl()}/api/mcp/policy`, {
|
|
421
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
441
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
442
|
+
signal: AbortSignal.timeout(1e4)
|
|
422
443
|
});
|
|
423
444
|
if (res.ok) {
|
|
424
445
|
const policy = await res.json();
|
|
@@ -453,4 +474,7 @@ var main = async () => {
|
|
|
453
474
|
}
|
|
454
475
|
console.log();
|
|
455
476
|
};
|
|
456
|
-
main()
|
|
477
|
+
main().catch((err) => {
|
|
478
|
+
console.error(` doctor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
});
|
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
denyReasonFrom,
|
|
4
4
|
isDeferAnswer
|
|
5
5
|
} from "../chunk-KQYIHZ5E.js";
|
|
6
|
+
import {
|
|
7
|
+
isGatingMoment,
|
|
8
|
+
recordKeylessMoment
|
|
9
|
+
} from "../chunk-R5AJNXZS.js";
|
|
6
10
|
import {
|
|
7
11
|
DEFAULT_SESSION,
|
|
8
12
|
askUser,
|
|
@@ -181,8 +185,25 @@ var handlePushFirst = async (apiKey, input, lookup, pushFirstSeconds) => {
|
|
|
181
185
|
return geminiPass();
|
|
182
186
|
};
|
|
183
187
|
var decideBeforeTool = async (input) => {
|
|
188
|
+
let apiKey;
|
|
189
|
+
try {
|
|
190
|
+
apiKey = getApiKey();
|
|
191
|
+
} catch {
|
|
192
|
+
try {
|
|
193
|
+
const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
|
|
194
|
+
if (isGatingMoment(lookup.tool, lookup.input)) {
|
|
195
|
+
recordKeylessMoment({
|
|
196
|
+
ts: Date.now(),
|
|
197
|
+
tool: lookup.tool,
|
|
198
|
+
project: basename(input.cwd ?? process.cwd()),
|
|
199
|
+
sessionId: input.session_id
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
return geminiPass();
|
|
205
|
+
}
|
|
184
206
|
try {
|
|
185
|
-
const apiKey = getApiKey();
|
|
186
207
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
187
208
|
if (modeState.kill) return geminiDeny(KILL_REASON);
|
|
188
209
|
const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {});
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-WLF3NCHS.js";
|
|
4
|
+
} from "../chunk-CY5YP34L.js";
|
|
6
5
|
import "../chunk-KQYIHZ5E.js";
|
|
6
|
+
import "../chunk-R5AJNXZS.js";
|
|
7
7
|
import "../chunk-IAOXM7X5.js";
|
|
8
8
|
import "../chunk-DWED7BS3.js";
|
|
9
9
|
import "../chunk-Z5PL3K7C.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
addClaudeMcpServer,
|
|
4
4
|
addPusharyHooks,
|
|
5
5
|
addPusharyToolPermissions
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-H3LQRYMW.js";
|
|
7
7
|
import {
|
|
8
8
|
GEMINI_HOOK_BINARY,
|
|
9
9
|
addCodexHookTrust,
|
|
@@ -14,11 +14,14 @@ import {
|
|
|
14
14
|
renderAgentInstructions,
|
|
15
15
|
renderProjectAgentInstructions,
|
|
16
16
|
writeInstructionBlock
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-WUNVKFFY.js";
|
|
18
18
|
import {
|
|
19
19
|
execNpm,
|
|
20
|
-
npmErrorMessage
|
|
21
|
-
|
|
20
|
+
npmErrorMessage,
|
|
21
|
+
quoteIfNeeded,
|
|
22
|
+
resolveGlobalBinDir,
|
|
23
|
+
resolveGlobalBinary
|
|
24
|
+
} from "../chunk-J7JWI3KU.js";
|
|
22
25
|
import {
|
|
23
26
|
reportEvent
|
|
24
27
|
} from "../chunk-IAOXM7X5.js";
|
|
@@ -32,7 +35,7 @@ import "../chunk-NKXSILEW.js";
|
|
|
32
35
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
|
|
33
36
|
import { join, dirname, basename } from "path";
|
|
34
37
|
import { homedir } from "os";
|
|
35
|
-
import { execSync } from "child_process";
|
|
38
|
+
import { execSync as execSync2 } from "child_process";
|
|
36
39
|
import { checkbox, input, confirm } from "@inquirer/prompts";
|
|
37
40
|
import { fileURLToPath } from "url";
|
|
38
41
|
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
@@ -306,6 +309,20 @@ var connectViaAppPairing = async () => {
|
|
|
306
309
|
}
|
|
307
310
|
};
|
|
308
311
|
|
|
312
|
+
// src/skills-cli.ts
|
|
313
|
+
import { execSync } from "child_process";
|
|
314
|
+
var installSkillViaSkillsCli = (agent) => {
|
|
315
|
+
try {
|
|
316
|
+
execSync(`npx -y skills@latest add Pushary/pushary-skill@pushary -y -g --agent ${agent}`, {
|
|
317
|
+
stdio: "pipe",
|
|
318
|
+
timeout: 9e4
|
|
319
|
+
});
|
|
320
|
+
return true;
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
309
326
|
// bin/pushary-setup.ts
|
|
310
327
|
var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
|
|
311
328
|
var CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
@@ -349,7 +366,7 @@ var parseConnectFlag = () => {
|
|
|
349
366
|
var isInstalled = (command) => {
|
|
350
367
|
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
351
368
|
try {
|
|
352
|
-
|
|
369
|
+
execSync2(`${whichCmd} ${command}`, { stdio: "ignore", timeout: 5e3 });
|
|
353
370
|
return true;
|
|
354
371
|
} catch {
|
|
355
372
|
return false;
|
|
@@ -441,11 +458,13 @@ var fetchSkillContent = async () => {
|
|
|
441
458
|
_cachedSkillContent = readFileSync(source, "utf-8");
|
|
442
459
|
return _cachedSkillContent;
|
|
443
460
|
};
|
|
444
|
-
var
|
|
445
|
-
|
|
461
|
+
var skillsCliInUse = () => isInstalled("skills") || existsSync(join(homedir(), ".agents", "skills"));
|
|
462
|
+
var installSkill = async (agent, fallbackDir) => {
|
|
463
|
+
await spinner("Installing Pushary skill", async () => {
|
|
464
|
+
if (skillsCliInUse() && installSkillViaSkillsCli(agent)) return;
|
|
446
465
|
const content = await fetchSkillContent();
|
|
447
|
-
if (!existsSync(
|
|
448
|
-
writeFileSync(join(
|
|
466
|
+
if (!existsSync(fallbackDir)) mkdirSync(fallbackDir, { recursive: true });
|
|
467
|
+
writeFileSync(join(fallbackDir, "SKILL.md"), content, "utf-8");
|
|
449
468
|
});
|
|
450
469
|
};
|
|
451
470
|
var setupClaudeCode = async (apiKey) => {
|
|
@@ -463,18 +482,12 @@ var setupClaudeCode = async (apiKey) => {
|
|
|
463
482
|
});
|
|
464
483
|
await installGlobally();
|
|
465
484
|
await spinner("Adding hooks (PreToolUse, PostToolUse, UserPromptSubmit, Stop)", async () => {
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
binDir = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin");
|
|
469
|
-
} catch {
|
|
470
|
-
binDir = void 0;
|
|
471
|
-
}
|
|
472
|
-
addPusharyHooks(settings, binDir);
|
|
485
|
+
addPusharyHooks(settings, resolveGlobalBinDir("pushary-hook"));
|
|
473
486
|
});
|
|
474
487
|
await spinner(`Writing ${CLAUDE_SETTINGS}`, async () => {
|
|
475
488
|
writeJson(CLAUDE_SETTINGS, settings);
|
|
476
489
|
});
|
|
477
|
-
await
|
|
490
|
+
await installSkill("claude-code", CLAUDE_SKILL_DIR);
|
|
478
491
|
console.log();
|
|
479
492
|
console.log(` ${dim2("What this configured:")}`);
|
|
480
493
|
console.log(` ${dim2("\u2022")} MCP server: your agent can send notifications and ask questions`);
|
|
@@ -484,7 +497,7 @@ var setupClaudeCode = async (apiKey) => {
|
|
|
484
497
|
};
|
|
485
498
|
var resolveHermesPython = () => {
|
|
486
499
|
try {
|
|
487
|
-
const launcher =
|
|
500
|
+
const launcher = execSync2("command -v hermes", { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).trim();
|
|
488
501
|
if (launcher) {
|
|
489
502
|
const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
|
|
490
503
|
if (shebang.startsWith("#!")) {
|
|
@@ -500,23 +513,23 @@ var resolveHermesPython = () => {
|
|
|
500
513
|
};
|
|
501
514
|
var ensurePip = (python) => {
|
|
502
515
|
try {
|
|
503
|
-
|
|
516
|
+
execSync2(`"${python}" -m pip --version`, { stdio: "ignore", timeout: 15e3 });
|
|
504
517
|
return;
|
|
505
518
|
} catch {
|
|
506
519
|
}
|
|
507
520
|
try {
|
|
508
|
-
|
|
521
|
+
execSync2(`"${python}" -m ensurepip --upgrade`, { stdio: "pipe", timeout: 6e4 });
|
|
509
522
|
} catch {
|
|
510
523
|
}
|
|
511
524
|
};
|
|
512
525
|
var enablePusharyPlugin = (python) => {
|
|
513
526
|
const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = (e + ["pushary"]) if "pushary" not in e else e; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = (d + ["clarify"]) if "clarify" not in d else d; c["agent"] = a; save_config(c)';
|
|
514
527
|
try {
|
|
515
|
-
|
|
528
|
+
execSync2(`"${python}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 });
|
|
516
529
|
return;
|
|
517
530
|
} catch {
|
|
518
531
|
}
|
|
519
|
-
|
|
532
|
+
execSync2("hermes plugins enable pushary", { stdio: "ignore", timeout: 1e4 });
|
|
520
533
|
};
|
|
521
534
|
var setupHermes = async (_apiKey) => {
|
|
522
535
|
console.log(`
|
|
@@ -535,7 +548,7 @@ var setupHermes = async (_apiKey) => {
|
|
|
535
548
|
}
|
|
536
549
|
await spinner("Installing hermes-plugin-pushary into Hermes\u2019 environment", async () => {
|
|
537
550
|
ensurePip(python);
|
|
538
|
-
|
|
551
|
+
execSync2(`"${python}" -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 18e4 });
|
|
539
552
|
});
|
|
540
553
|
await spinner("Enabling plugin + routing questions to push", async () => {
|
|
541
554
|
enablePusharyPlugin(python);
|
|
@@ -564,7 +577,7 @@ var compareCodexVersion = (a, b) => {
|
|
|
564
577
|
};
|
|
565
578
|
var readCodexVersion = () => {
|
|
566
579
|
try {
|
|
567
|
-
return parseCodexVersion(
|
|
580
|
+
return parseCodexVersion(execSync2("codex --version", { encoding: "utf-8", stdio: "pipe", timeout: 1e4 }));
|
|
568
581
|
} catch {
|
|
569
582
|
return null;
|
|
570
583
|
}
|
|
@@ -591,9 +604,7 @@ var removeCodexNotifyEntry = (codexConfig) => {
|
|
|
591
604
|
writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
|
|
592
605
|
};
|
|
593
606
|
var addCodexNotifyEntry = (codexConfig) => {
|
|
594
|
-
const
|
|
595
|
-
const pusharyCodexPath = join(globalPrefix, "bin", "pushary-codex");
|
|
596
|
-
if (!existsSync(pusharyCodexPath)) throw new Error("pushary-codex not found at " + pusharyCodexPath);
|
|
607
|
+
const pusharyCodexPath = resolveGlobalBinary("pushary-codex") ?? "pushary-codex";
|
|
597
608
|
let raw = "";
|
|
598
609
|
try {
|
|
599
610
|
raw = readFileSync(codexConfig, "utf-8");
|
|
@@ -633,10 +644,8 @@ var setupCodex = async (apiKey) => {
|
|
|
633
644
|
const trustAuto = codexTrustAutoSupported(codexVersion);
|
|
634
645
|
let trusted = false;
|
|
635
646
|
if (hooksSupported) {
|
|
636
|
-
const
|
|
637
|
-
const hookCommand = join(globalPrefix, "bin", "pushary-codex-hook");
|
|
647
|
+
const hookCommand = quoteIfNeeded(resolveGlobalBinary("pushary-codex-hook") ?? "pushary-codex-hook");
|
|
638
648
|
await spinner("Adding native hooks (~/.codex/hooks.json)", async () => {
|
|
639
|
-
if (!existsSync(hookCommand)) throw new Error("pushary-codex-hook not found at " + hookCommand);
|
|
640
649
|
const hooksConfig = readJson(CODEX_HOOKS_JSON);
|
|
641
650
|
addCodexHooks(hooksConfig, hookCommand);
|
|
642
651
|
writeJson(CODEX_HOOKS_JSON, hooksConfig);
|
|
@@ -665,7 +674,7 @@ var setupCodex = async (apiKey) => {
|
|
|
665
674
|
addCodexNotifyEntry(codexConfig);
|
|
666
675
|
});
|
|
667
676
|
}
|
|
668
|
-
await
|
|
677
|
+
await installSkill("codex", CODEX_SKILL_DIR);
|
|
669
678
|
await spinner("Teaching Codex to ask via push (~/.codex/AGENTS.md)", async () => {
|
|
670
679
|
writeInstructionBlock(CODEX_AGENTS_MD, renderAgentInstructions("Codex"));
|
|
671
680
|
});
|
|
@@ -822,13 +831,7 @@ var setupGemini = async (apiKey) => {
|
|
|
822
831
|
addGeminiMcpServer(settings, apiKey);
|
|
823
832
|
});
|
|
824
833
|
await spinner("Adding hooks (BeforeTool, AfterTool, BeforeAgent, SessionStart, SessionEnd)", async () => {
|
|
825
|
-
|
|
826
|
-
try {
|
|
827
|
-
const candidate = join(execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim(), "bin", GEMINI_HOOK_BINARY);
|
|
828
|
-
if (existsSync(candidate)) hookCommand = candidate;
|
|
829
|
-
} catch {
|
|
830
|
-
}
|
|
831
|
-
addGeminiHooks(settings, hookCommand);
|
|
834
|
+
addGeminiHooks(settings, quoteIfNeeded(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
|
|
832
835
|
});
|
|
833
836
|
await spinner(`Writing ${GEMINI_SETTINGS}`, async () => {
|
|
834
837
|
writeJson(GEMINI_SETTINGS, settings);
|
package/dist/bin/pushary.js
CHANGED
|
@@ -25,7 +25,7 @@ Pushary Agent Hooks
|
|
|
25
25
|
Commands:
|
|
26
26
|
setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
|
|
27
27
|
doctor Verify your Pushary installation is working
|
|
28
|
-
clean Remove all Pushary configuration
|
|
28
|
+
clean Remove all Pushary configuration (--yes for non-interactive)
|
|
29
29
|
mode Switch approval mode (push_only, push_first, terminal_only)
|
|
30
30
|
wait Show or set the "wait for your phone" ladder (pushary wait 45)
|
|
31
31
|
stats Show the approval moments your agents hit while not connected
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isGatingMoment,
|
|
3
|
-
recordKeylessMoment
|
|
4
|
-
} from "./chunk-WLF3NCHS.js";
|
|
5
1
|
import {
|
|
6
2
|
denyReasonFrom,
|
|
7
3
|
isDeferAnswer
|
|
8
4
|
} from "./chunk-KQYIHZ5E.js";
|
|
5
|
+
import {
|
|
6
|
+
isGatingMoment,
|
|
7
|
+
recordKeylessMoment
|
|
8
|
+
} from "./chunk-R5AJNXZS.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_SESSION,
|
|
11
11
|
askUser,
|
|
@@ -60,7 +60,10 @@ var addPusharyToolPermissions = (settings) => {
|
|
|
60
60
|
permissions.allow = filtered;
|
|
61
61
|
};
|
|
62
62
|
var addPusharyHooks = (settings, binDir) => {
|
|
63
|
-
const resolve = (name) =>
|
|
63
|
+
const resolve = (name) => {
|
|
64
|
+
const path = binDir ? join(binDir, name) : name;
|
|
65
|
+
return /\s/.test(path) ? `"${path}"` : path;
|
|
66
|
+
};
|
|
64
67
|
const hooks = ensureRecord(settings, "hooks");
|
|
65
68
|
const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
|
|
66
69
|
preToolUse.push({
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/npm.ts
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
3
5
|
var cleanNpmEnv = () => {
|
|
4
6
|
const env = {};
|
|
5
7
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -22,8 +24,26 @@ var execNpm = (args, options = {}) => {
|
|
|
22
24
|
env: { ...cleanNpmEnv(), ...options.env ?? {} }
|
|
23
25
|
});
|
|
24
26
|
};
|
|
27
|
+
var resolveGlobalBinary = (name) => {
|
|
28
|
+
try {
|
|
29
|
+
const prefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
|
|
30
|
+
const candidates = process.platform === "win32" ? [join(prefix, `${name}.cmd`), join(prefix, name)] : [join(prefix, "bin", name)];
|
|
31
|
+
return candidates.find(existsSync);
|
|
32
|
+
} catch {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var resolveGlobalBinDir = (probe) => {
|
|
37
|
+
if (process.platform === "win32") return void 0;
|
|
38
|
+
const resolved = resolveGlobalBinary(probe);
|
|
39
|
+
return resolved ? resolved.slice(0, resolved.length - probe.length - 1) : void 0;
|
|
40
|
+
};
|
|
41
|
+
var quoteIfNeeded = (path) => /\s/.test(path) ? `"${path}"` : path;
|
|
25
42
|
|
|
26
43
|
export {
|
|
27
44
|
npmErrorMessage,
|
|
28
|
-
execNpm
|
|
45
|
+
execNpm,
|
|
46
|
+
resolveGlobalBinary,
|
|
47
|
+
resolveGlobalBinDir,
|
|
48
|
+
quoteIfNeeded
|
|
29
49
|
};
|
|
@@ -30,26 +30,28 @@ var recordKeylessMoment = (entry) => {
|
|
|
30
30
|
var readLedgerSummary = (days = 7) => {
|
|
31
31
|
const sinceMs = Date.now() - days * 864e5;
|
|
32
32
|
const summary = { count: 0, sessions: 0, byTool: {}, sinceMs };
|
|
33
|
-
let raw;
|
|
34
|
-
try {
|
|
35
|
-
raw = readFileSync(ledgerPath(), "utf-8");
|
|
36
|
-
} catch {
|
|
37
|
-
return summary;
|
|
38
|
-
}
|
|
39
33
|
const sessions = /* @__PURE__ */ new Set();
|
|
40
|
-
for (const
|
|
41
|
-
|
|
42
|
-
let entry;
|
|
34
|
+
for (const path of [`${ledgerPath()}.1`, ledgerPath()]) {
|
|
35
|
+
let raw;
|
|
43
36
|
try {
|
|
44
|
-
|
|
37
|
+
raw = readFileSync(path, "utf-8");
|
|
45
38
|
} catch {
|
|
46
39
|
continue;
|
|
47
40
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
for (const line of raw.split("\n")) {
|
|
42
|
+
if (!line.trim()) continue;
|
|
43
|
+
let entry;
|
|
44
|
+
try {
|
|
45
|
+
entry = JSON.parse(line);
|
|
46
|
+
} catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (typeof entry.ts !== "number" || entry.ts < sinceMs) continue;
|
|
50
|
+
summary.count += 1;
|
|
51
|
+
const tool = typeof entry.tool === "string" && entry.tool ? entry.tool : "Other";
|
|
52
|
+
summary.byTool[tool] = (summary.byTool[tool] ?? 0) + 1;
|
|
53
|
+
sessions.add(entry.sessionId || `${entry.project ?? "unknown"}:no-session`);
|
|
54
|
+
}
|
|
53
55
|
}
|
|
54
56
|
summary.sessions = sessions.size;
|
|
55
57
|
return summary;
|
|
@@ -254,7 +254,7 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
|
|
|
254
254
|
|
|
255
255
|
Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out with no answer, retry once with a longer timeout before falling back to asking in the terminal.`;
|
|
256
256
|
var renderAgentInstructions = (label) => instructionBody(label);
|
|
257
|
-
var KEYLESS_FALLBACK_LINE = "This repo
|
|
257
|
+
var KEYLESS_FALLBACK_LINE = "This repo uses Pushary phone approvals. Your teammates get agent questions on their lock screens; you do not have a key yet, so questions will fall back to the terminal. Get your own key (7-day trial): https://pushary.com/sign-up?utm_source=agents-md&utm_medium=teammate";
|
|
258
258
|
var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
|
|
259
259
|
|
|
260
260
|
If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
|
package/dist/src/index.js
CHANGED