@pushary/agent-hooks 0.61.0 → 0.62.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/CHANGELOG.md +48 -0
- package/dist/bin/pushary-claude.js +4 -4
- package/dist/bin/pushary-clean.js +62 -32
- package/dist/bin/pushary-codex-hook.js +4 -4
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +37 -5
- package/dist/bin/pushary-doctor.js +113 -34
- package/dist/bin/pushary-gemini-hook.js +4 -4
- package/dist/bin/pushary-hook.js +5 -5
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +51 -9
- package/dist/bin/pushary-mode.js +4 -4
- package/dist/bin/pushary-notification-hook.js +2 -2
- package/dist/bin/pushary-permission-denied-hook.js +3 -3
- package/dist/bin/pushary-permission-hook.js +3 -3
- package/dist/bin/pushary-post-hook.js +2 -2
- package/dist/bin/pushary-prompt-hook.js +2 -2
- package/dist/bin/pushary-session-end-hook.js +2 -2
- package/dist/bin/pushary-session-start-hook.js +2 -2
- package/dist/bin/pushary-setup.js +92 -152
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +2 -2
- package/dist/bin/pushary-stopfailure-hook.js +2 -2
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +176 -11
- package/dist/bin/pushary-wait.js +4 -4
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-B26DNXCA.js → chunk-247C4RE5.js} +1 -0
- package/dist/{chunk-GX64YGU3.js → chunk-3O27ZSRE.js} +25 -0
- package/dist/{chunk-HI4AGGE6.js → chunk-5SO3CEGJ.js} +4 -4
- package/dist/chunk-E2U35RLD.js +108 -0
- package/dist/chunk-ER657KRJ.js +168 -0
- package/dist/{chunk-HUJQSP4F.js → chunk-IIZZYUWK.js} +1 -1
- package/dist/chunk-N7XJ4L2W.js +79 -0
- package/dist/{chunk-R6C4USIV.js → chunk-ONVEYEVR.js} +2 -2
- package/dist/{chunk-SML23YOT.js → chunk-SP7OZXCQ.js} +10 -2
- package/dist/chunk-UXXRRXUS.js +93 -0
- package/dist/{chunk-UKNAAVEE.js → chunk-YJ7YZRVV.js} +1 -1
- package/dist/src/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-S4TFBJ5O.js +0 -339
- package/dist/{chunk-VFBYRR2N.js → chunk-HIIBSYFJ.js} +3 -3
|
@@ -4,24 +4,47 @@ import {
|
|
|
4
4
|
addPusharyHooks,
|
|
5
5
|
addPusharyToolPermissions
|
|
6
6
|
} from "../chunk-CAJZAFVS.js";
|
|
7
|
+
import {
|
|
8
|
+
GEMINI_HOOK_BINARY,
|
|
9
|
+
addGeminiHooks,
|
|
10
|
+
addGeminiMcpServer,
|
|
11
|
+
hasGeminiHooks
|
|
12
|
+
} from "../chunk-E2U35RLD.js";
|
|
13
|
+
import {
|
|
14
|
+
CODEX_HOOK_BINARY,
|
|
15
|
+
addCodexHookTrust,
|
|
16
|
+
addCodexHooks,
|
|
17
|
+
addCodexMcpServer,
|
|
18
|
+
codexConfigToml,
|
|
19
|
+
codexHooksJson,
|
|
20
|
+
hasCodexHooks
|
|
21
|
+
} from "../chunk-ER657KRJ.js";
|
|
7
22
|
import {
|
|
8
23
|
execNpm,
|
|
9
24
|
npmErrorMessage,
|
|
10
|
-
|
|
25
|
+
quoteIfNeeded,
|
|
26
|
+
resolveGlobalBinDir,
|
|
27
|
+
resolveGlobalBinary
|
|
11
28
|
} from "../chunk-MUEW424A.js";
|
|
12
29
|
import {
|
|
13
30
|
exitOnHelpFlag
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
31
|
+
} from "../chunk-IIZZYUWK.js";
|
|
32
|
+
import "../chunk-SP7OZXCQ.js";
|
|
16
33
|
import "../chunk-DQAN3JQP.js";
|
|
17
34
|
import {
|
|
18
35
|
getApiKey
|
|
19
36
|
} from "../chunk-2UMNXADU.js";
|
|
20
37
|
|
|
38
|
+
// bin/pushary-upgrade.ts
|
|
39
|
+
import { existsSync as existsSync2 } from "fs";
|
|
40
|
+
import { homedir as homedir2 } from "os";
|
|
41
|
+
import { join as join2 } from "path";
|
|
42
|
+
|
|
21
43
|
// src/reapply.ts
|
|
22
44
|
import { join, dirname } from "path";
|
|
23
45
|
import { homedir } from "os";
|
|
24
46
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
47
|
+
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
25
48
|
var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
|
|
26
49
|
var CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
27
50
|
var readJson = (filePath) => {
|
|
@@ -66,9 +89,130 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
|
|
|
66
89
|
}
|
|
67
90
|
return { reapplied: hooks || mcp, hooks, mcp };
|
|
68
91
|
};
|
|
92
|
+
var readToml = (filePath) => {
|
|
93
|
+
try {
|
|
94
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
95
|
+
return raw.trim() === "" ? {} : parseTOML(raw);
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var reapplyCodexSettings = (apiKey, paths = {}) => {
|
|
101
|
+
const configPath = paths.configPath ?? codexConfigToml();
|
|
102
|
+
const hooksPath = paths.hooksPath ?? codexHooksJson();
|
|
103
|
+
const config = readToml(configPath);
|
|
104
|
+
const hooksConfig = readJson(hooksPath);
|
|
105
|
+
const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
|
|
106
|
+
if (!configured) return { reapplied: false, hooks: false, mcp: false };
|
|
107
|
+
const command = quoteIfNeeded(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
|
|
108
|
+
let hooks = false;
|
|
109
|
+
let mcp = false;
|
|
110
|
+
if (hooksConfig != null) {
|
|
111
|
+
try {
|
|
112
|
+
addCodexHooks(hooksConfig, command);
|
|
113
|
+
writeJson(hooksPath, hooksConfig);
|
|
114
|
+
hooks = true;
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (config != null) {
|
|
119
|
+
try {
|
|
120
|
+
if (hooks) addCodexHookTrust(config, hooksPath, command);
|
|
121
|
+
if (apiKey) {
|
|
122
|
+
addCodexMcpServer(config, apiKey);
|
|
123
|
+
mcp = true;
|
|
124
|
+
}
|
|
125
|
+
if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { reapplied: hooks || mcp, hooks, mcp };
|
|
130
|
+
};
|
|
131
|
+
var reapplyGeminiSettings = (apiKey, paths = {}) => {
|
|
132
|
+
const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
|
|
133
|
+
const settings = readJson(settingsPath);
|
|
134
|
+
if (settings == null) return { reapplied: false, hooks: false, mcp: false };
|
|
135
|
+
const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
|
|
136
|
+
if (!configured) return { reapplied: false, hooks: false, mcp: false };
|
|
137
|
+
let hooks = false;
|
|
138
|
+
let mcp = false;
|
|
139
|
+
try {
|
|
140
|
+
addGeminiHooks(settings, quoteIfNeeded(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
|
|
141
|
+
hooks = true;
|
|
142
|
+
if (apiKey) {
|
|
143
|
+
addGeminiMcpServer(settings, apiKey);
|
|
144
|
+
mcp = true;
|
|
145
|
+
}
|
|
146
|
+
writeJson(settingsPath, settings);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
return { reapplied: hooks || mcp, hooks, mcp };
|
|
150
|
+
};
|
|
151
|
+
var reapplyCursorPlugin = (apiKey, paths = {}) => {
|
|
152
|
+
const mcpPath = paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json");
|
|
153
|
+
if (!apiKey) return { reapplied: false, hooks: false, mcp: false };
|
|
154
|
+
const mcpConfig = readJson(mcpPath);
|
|
155
|
+
if (mcpConfig == null) return { reapplied: false, hooks: false, mcp: false };
|
|
156
|
+
const servers = mcpConfig.mcpServers;
|
|
157
|
+
if (!servers?.pushary) return { reapplied: false, hooks: false, mcp: false };
|
|
158
|
+
try {
|
|
159
|
+
addClaudeMcpServer(mcpConfig, apiKey);
|
|
160
|
+
writeJson(mcpPath, mcpConfig);
|
|
161
|
+
return { reapplied: true, hooks: false, mcp: true };
|
|
162
|
+
} catch {
|
|
163
|
+
return { reapplied: false, hooks: false, mcp: false };
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var reapplyAllAgents = (apiKey) => {
|
|
167
|
+
const runners = [
|
|
168
|
+
{ label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
|
|
169
|
+
{ label: "Codex", run: () => reapplyCodexSettings(apiKey) },
|
|
170
|
+
{ label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
|
|
171
|
+
{ label: "Cursor", run: () => reapplyCursorPlugin(apiKey) }
|
|
172
|
+
];
|
|
173
|
+
const done = [];
|
|
174
|
+
for (const runner of runners) {
|
|
175
|
+
try {
|
|
176
|
+
const result = runner.run();
|
|
177
|
+
if (result.reapplied) done.push({ label: runner.label, result });
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return done;
|
|
182
|
+
};
|
|
69
183
|
|
|
70
184
|
// bin/pushary-upgrade.ts
|
|
71
185
|
exitOnHelpFlag("upgrade");
|
|
186
|
+
var detectInstallModes = () => {
|
|
187
|
+
const candidates = [
|
|
188
|
+
{
|
|
189
|
+
label: "Cursor plugin",
|
|
190
|
+
upgrade: "updates itself through Cursor",
|
|
191
|
+
present: existsSync2(join2(homedir2(), ".cursor", "plugins", "local", "pushary"))
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
label: "Codex config",
|
|
195
|
+
upgrade: "npx @pushary/agent-hooks@latest setup --agents codex",
|
|
196
|
+
present: existsSync2(codexConfigToml())
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
label: "Claude Code config",
|
|
200
|
+
upgrade: "npx @pushary/agent-hooks@latest setup --agents claude_code",
|
|
201
|
+
present: existsSync2(join2(homedir2(), ".claude.json"))
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
label: "Gemini config",
|
|
205
|
+
upgrade: "npx @pushary/agent-hooks@latest setup --agents gemini_cli",
|
|
206
|
+
present: existsSync2(join2(homedir2(), ".gemini", "settings.json"))
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
label: "Hermes plugin (pip)",
|
|
210
|
+
upgrade: "pip install --upgrade pushary-hermes",
|
|
211
|
+
present: existsSync2(join2(homedir2(), ".hermes"))
|
|
212
|
+
}
|
|
213
|
+
];
|
|
214
|
+
return candidates.filter((candidate) => candidate.present).map(({ label, upgrade }) => ({ label, upgrade }));
|
|
215
|
+
};
|
|
72
216
|
var getInstalledVersion = () => {
|
|
73
217
|
try {
|
|
74
218
|
return execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
|
|
@@ -100,8 +244,20 @@ var isNewer = (a, b) => {
|
|
|
100
244
|
var main = async () => {
|
|
101
245
|
const installed = getInstalledVersion();
|
|
102
246
|
if (!installed) {
|
|
103
|
-
|
|
104
|
-
console.log("
|
|
247
|
+
const found = detectInstallModes();
|
|
248
|
+
console.log("No global install of @pushary/agent-hooks found, so there is nothing to upgrade here.");
|
|
249
|
+
console.log();
|
|
250
|
+
if (found.length === 0) {
|
|
251
|
+
console.log("Nothing on this machine looks Pushary-configured either.");
|
|
252
|
+
console.log(" Set it up with: npx @pushary/agent-hooks@latest setup");
|
|
253
|
+
} else {
|
|
254
|
+
console.log("What this machine does have:");
|
|
255
|
+
for (const mode of found) {
|
|
256
|
+
console.log(` ${mode.label.padEnd(22)} ${mode.upgrade}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
console.log();
|
|
260
|
+
console.log("Check the result with: pushary doctor");
|
|
105
261
|
return;
|
|
106
262
|
}
|
|
107
263
|
const latest = await fetchLatestVersion();
|
|
@@ -122,14 +278,23 @@ var main = async () => {
|
|
|
122
278
|
} catch {
|
|
123
279
|
apiKey = void 0;
|
|
124
280
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
281
|
+
const refreshed = reapplyAllAgents(apiKey);
|
|
282
|
+
console.log();
|
|
283
|
+
if (refreshed.length > 0) {
|
|
284
|
+
console.log("Refreshed so the new hook set is active now:");
|
|
285
|
+
for (const agent of refreshed) {
|
|
286
|
+
const parts = [agent.result.hooks ? "hooks" : null, agent.result.mcp ? "MCP key" : null].filter(Boolean).join(" + ");
|
|
287
|
+
console.log(` ${agent.label.padEnd(14)} ${parts}`);
|
|
129
288
|
}
|
|
130
|
-
|
|
289
|
+
if (!apiKey) {
|
|
290
|
+
console.log();
|
|
291
|
+
console.log("No key was readable here, so MCP entries kept the key they already had.");
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
console.log("No agent config on this machine needed refreshing.");
|
|
131
295
|
}
|
|
132
|
-
console.log(
|
|
296
|
+
console.log();
|
|
297
|
+
console.log("Done. Your hooks point at the global install.");
|
|
133
298
|
console.log("Verify with: pushary doctor");
|
|
134
299
|
};
|
|
135
300
|
main();
|
package/dist/bin/pushary-wait.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
exitOnFailedResponse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-ONVEYEVR.js";
|
|
5
5
|
import {
|
|
6
6
|
describeWaitLadder
|
|
7
7
|
} from "../chunk-IIENZE2J.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-YJ7YZRVV.js";
|
|
9
9
|
import {
|
|
10
10
|
createIo
|
|
11
11
|
} from "../chunk-TX7KBKT7.js";
|
|
12
12
|
import "../chunk-7QLSKOSU.js";
|
|
13
13
|
import {
|
|
14
14
|
exitOnHelpFlag
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-IIZZYUWK.js";
|
|
16
16
|
import {
|
|
17
17
|
EXIT
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-SP7OZXCQ.js";
|
|
19
19
|
import "../chunk-DQAN3JQP.js";
|
|
20
20
|
import {
|
|
21
21
|
getApiKey,
|
package/dist/bin/pushary.js
CHANGED
|
@@ -155,6 +155,7 @@ var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
|
155
155
|
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
156
156
|
var check = green("\u2713");
|
|
157
157
|
var apiBase = () => process.env.PUSHARY_API_URL?.trim() || process.env.PUSHARY_BASE_URL?.trim() || "https://pushary.com";
|
|
158
|
+
var EXIT_ABORTED = 130;
|
|
158
159
|
var CONNECT_POLL_INTERVAL_MS = 2500;
|
|
159
160
|
var CONNECT_TIMEOUT_MS = 18e4;
|
|
160
161
|
var EMPTY_CHANNELS = {
|
|
@@ -463,6 +464,17 @@ var startPairing = async (cliPublicKey) => {
|
|
|
463
464
|
return null;
|
|
464
465
|
}
|
|
465
466
|
};
|
|
467
|
+
var cancelPairing = async (pairId) => {
|
|
468
|
+
try {
|
|
469
|
+
await fetch(`${apiBase()}/api/mobile/pair/start`, {
|
|
470
|
+
method: "DELETE",
|
|
471
|
+
headers: { "Content-Type": "application/json" },
|
|
472
|
+
body: JSON.stringify({ pairId }),
|
|
473
|
+
signal: AbortSignal.timeout(5e3)
|
|
474
|
+
});
|
|
475
|
+
} catch {
|
|
476
|
+
}
|
|
477
|
+
};
|
|
466
478
|
var claimPairing = async (pairId) => {
|
|
467
479
|
try {
|
|
468
480
|
const res = await fetch(`${apiBase()}/api/mobile/pair/claim?id=${encodeURIComponent(pairId)}`, {
|
|
@@ -504,8 +516,16 @@ var connectViaAppPairing = async () => {
|
|
|
504
516
|
const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
|
|
505
517
|
return `Waiting for the app to authorize ${dim(`(${left}s)`)}`;
|
|
506
518
|
});
|
|
519
|
+
let cancelled = false;
|
|
520
|
+
const onInterrupt = () => {
|
|
521
|
+
cancelled = true;
|
|
522
|
+
stop(yellow("!"), "Cancelled");
|
|
523
|
+
void cancelPairing(pairId).finally(() => process.exit(EXIT_ABORTED));
|
|
524
|
+
};
|
|
525
|
+
process.once("SIGINT", onInterrupt);
|
|
507
526
|
try {
|
|
508
527
|
while (Date.now() < deadline) {
|
|
528
|
+
if (cancelled) return null;
|
|
509
529
|
const claim = await claimPairing(pairId);
|
|
510
530
|
if (claim?.status === "authorized") {
|
|
511
531
|
let apiKey;
|
|
@@ -513,6 +533,7 @@ var connectViaAppPairing = async () => {
|
|
|
513
533
|
apiKey = openSealedKey(claim.sealed, keypair);
|
|
514
534
|
} catch {
|
|
515
535
|
stop(yellow("!"), "Pairing failed to decrypt \u2014 re-run setup to try again");
|
|
536
|
+
await cancelPairing(pairId);
|
|
516
537
|
return null;
|
|
517
538
|
}
|
|
518
539
|
stop(check, "App connected");
|
|
@@ -525,10 +546,14 @@ var connectViaAppPairing = async () => {
|
|
|
525
546
|
await sleep(PAIR_POLL_INTERVAL_MS);
|
|
526
547
|
}
|
|
527
548
|
stop(yellow("!"), "Timed out waiting for the app");
|
|
549
|
+
await cancelPairing(pairId);
|
|
528
550
|
return null;
|
|
529
551
|
} catch (err) {
|
|
530
552
|
stop(yellow("!"), `Pairing error ${dim(`(${err instanceof Error ? err.message : "network"})`)}`);
|
|
553
|
+
await cancelPairing(pairId);
|
|
531
554
|
return null;
|
|
555
|
+
} finally {
|
|
556
|
+
process.off("SIGINT", onInterrupt);
|
|
532
557
|
}
|
|
533
558
|
};
|
|
534
559
|
|
|
@@ -25,10 +25,7 @@ import {
|
|
|
25
25
|
sendNotification,
|
|
26
26
|
throttlePass,
|
|
27
27
|
waitForAnswer
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
getMachineId
|
|
31
|
-
} from "./chunk-RN3NOEJF.js";
|
|
28
|
+
} from "./chunk-HIIBSYFJ.js";
|
|
32
29
|
import {
|
|
33
30
|
isGatingMoment,
|
|
34
31
|
recordKeylessMoment
|
|
@@ -42,6 +39,9 @@ import {
|
|
|
42
39
|
scopeChangeReason,
|
|
43
40
|
scopeRequiresHuman
|
|
44
41
|
} from "./chunk-DQAN3JQP.js";
|
|
42
|
+
import {
|
|
43
|
+
getMachineId
|
|
44
|
+
} from "./chunk-RN3NOEJF.js";
|
|
45
45
|
import {
|
|
46
46
|
getApiKey,
|
|
47
47
|
getBaseUrl
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOOK_BUDGETS
|
|
3
|
+
} from "./chunk-DQAN3JQP.js";
|
|
4
|
+
|
|
5
|
+
// src/gemini-config.ts
|
|
6
|
+
var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
|
|
7
|
+
var GEMINI_MCP_URL = "https://pushary.com/api/mcp/mcp";
|
|
8
|
+
var GEMINI_HOOK_EVENTS = [
|
|
9
|
+
{ event: "BeforeTool", matcher: "run_shell_command|write_file|replace", timeoutMs: HOOK_BUDGETS.gemini.budgetSeconds * 1e3 },
|
|
10
|
+
{ event: "AfterTool", matcher: "run_shell_command|write_file|replace", timeoutMs: 1e4 },
|
|
11
|
+
{ event: "BeforeAgent", timeoutMs: 1e4 },
|
|
12
|
+
{ event: "SessionStart", timeoutMs: 1e4 },
|
|
13
|
+
{ event: "SessionEnd", timeoutMs: 1e4 }
|
|
14
|
+
];
|
|
15
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
16
|
+
var ensureRecord = (target, key) => {
|
|
17
|
+
const existing = asRecord(target[key]);
|
|
18
|
+
if (existing) return existing;
|
|
19
|
+
const created = {};
|
|
20
|
+
target[key] = created;
|
|
21
|
+
return created;
|
|
22
|
+
};
|
|
23
|
+
var isPusharyGeminiHook = (entry) => {
|
|
24
|
+
const hooks = asRecord(entry)?.hooks;
|
|
25
|
+
if (!Array.isArray(hooks)) return false;
|
|
26
|
+
return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(GEMINI_HOOK_BINARY));
|
|
27
|
+
};
|
|
28
|
+
var addGeminiMcpServer = (settings, apiKey) => {
|
|
29
|
+
const mcpServers = ensureRecord(settings, "mcpServers");
|
|
30
|
+
mcpServers.pushary = {
|
|
31
|
+
httpUrl: GEMINI_MCP_URL,
|
|
32
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
33
|
+
trust: true
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
var addGeminiHooks = (settings, command) => {
|
|
37
|
+
const hooks = ensureRecord(settings, "hooks");
|
|
38
|
+
for (const definition of GEMINI_HOOK_EVENTS) {
|
|
39
|
+
const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
|
|
40
|
+
const entries = existing.filter((entry) => !isPusharyGeminiHook(entry));
|
|
41
|
+
entries.push({
|
|
42
|
+
...definition.matcher ? { matcher: definition.matcher } : {},
|
|
43
|
+
hooks: [{
|
|
44
|
+
name: "pushary",
|
|
45
|
+
type: "command",
|
|
46
|
+
command,
|
|
47
|
+
timeout: definition.timeoutMs
|
|
48
|
+
}]
|
|
49
|
+
});
|
|
50
|
+
hooks[definition.event] = entries;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var removeGeminiHooks = (settings) => {
|
|
54
|
+
const hooks = asRecord(settings.hooks);
|
|
55
|
+
if (!hooks) return false;
|
|
56
|
+
let changed = false;
|
|
57
|
+
for (const definition of GEMINI_HOOK_EVENTS) {
|
|
58
|
+
const entries = hooks[definition.event];
|
|
59
|
+
if (!Array.isArray(entries)) continue;
|
|
60
|
+
const filtered = entries.filter((entry) => !isPusharyGeminiHook(entry));
|
|
61
|
+
if (filtered.length !== entries.length) {
|
|
62
|
+
if (filtered.length === 0) {
|
|
63
|
+
delete hooks[definition.event];
|
|
64
|
+
} else {
|
|
65
|
+
hooks[definition.event] = filtered;
|
|
66
|
+
}
|
|
67
|
+
changed = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
71
|
+
return changed;
|
|
72
|
+
};
|
|
73
|
+
var removeGeminiMcpServer = (settings) => {
|
|
74
|
+
const mcpServers = asRecord(settings.mcpServers);
|
|
75
|
+
if (!mcpServers?.pushary) return false;
|
|
76
|
+
delete mcpServers.pushary;
|
|
77
|
+
if (Object.keys(mcpServers).length === 0) delete settings.mcpServers;
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
var removeGeminiSettings = (settings) => {
|
|
81
|
+
const mcpRemoved = removeGeminiMcpServer(settings);
|
|
82
|
+
const hooksRemoved = removeGeminiHooks(settings);
|
|
83
|
+
return mcpRemoved || hooksRemoved;
|
|
84
|
+
};
|
|
85
|
+
var hasGeminiHooks = (settings) => {
|
|
86
|
+
const hooks = asRecord(settings.hooks);
|
|
87
|
+
if (!hooks) return false;
|
|
88
|
+
return GEMINI_HOOK_EVENTS.some((definition) => {
|
|
89
|
+
const entries = hooks[definition.event];
|
|
90
|
+
return Array.isArray(entries) && entries.some(isPusharyGeminiHook);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
var missingGeminiHookEvents = (settings) => {
|
|
94
|
+
const hooks = asRecord(settings.hooks);
|
|
95
|
+
return GEMINI_HOOK_EVENTS.filter((definition) => {
|
|
96
|
+
const entries = hooks?.[definition.event];
|
|
97
|
+
return !Array.isArray(entries) || !entries.some(isPusharyGeminiHook);
|
|
98
|
+
}).map((definition) => definition.event);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
GEMINI_HOOK_BINARY,
|
|
103
|
+
addGeminiMcpServer,
|
|
104
|
+
addGeminiHooks,
|
|
105
|
+
removeGeminiSettings,
|
|
106
|
+
hasGeminiHooks,
|
|
107
|
+
missingGeminiHookEvents
|
|
108
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOOK_BUDGETS
|
|
3
|
+
} from "./chunk-DQAN3JQP.js";
|
|
4
|
+
|
|
5
|
+
// src/codex-config.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var CODEX_HOOK_BINARY = "pushary-codex-hook";
|
|
10
|
+
var codexHome = () => codexHomeFrom(homedir());
|
|
11
|
+
var codexHomeFrom = (home) => process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
12
|
+
var codexConfigToml = () => join(codexHome(), "config.toml");
|
|
13
|
+
var codexHooksJson = () => join(codexHome(), "hooks.json");
|
|
14
|
+
var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
|
|
15
|
+
var codexSkillDir = () => join(codexHome(), "skills", "pushary");
|
|
16
|
+
var CODEX_HOOK_EVENTS = [
|
|
17
|
+
{ event: "PermissionRequest", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
|
|
18
|
+
{ event: "PreToolUse", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
|
|
19
|
+
{ event: "PostToolUse", matcher: "Bash|apply_patch", timeout: 10 },
|
|
20
|
+
{ event: "UserPromptSubmit", timeout: 10 },
|
|
21
|
+
{ event: "Stop", timeout: 10 },
|
|
22
|
+
{ event: "SessionStart", matcher: "startup|resume", timeout: 10 }
|
|
23
|
+
];
|
|
24
|
+
var CODEX_EVENT_KEY = {
|
|
25
|
+
PermissionRequest: "permission_request",
|
|
26
|
+
PreToolUse: "pre_tool_use",
|
|
27
|
+
PostToolUse: "post_tool_use",
|
|
28
|
+
UserPromptSubmit: "user_prompt_submit",
|
|
29
|
+
Stop: "stop",
|
|
30
|
+
SessionStart: "session_start"
|
|
31
|
+
};
|
|
32
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
33
|
+
var ensureRecord = (target, key) => {
|
|
34
|
+
const existing = asRecord(target[key]);
|
|
35
|
+
if (existing) return existing;
|
|
36
|
+
const created = {};
|
|
37
|
+
target[key] = created;
|
|
38
|
+
return created;
|
|
39
|
+
};
|
|
40
|
+
var CODEX_MCP_URL = "https://pushary.com/api/mcp/mcp";
|
|
41
|
+
var addCodexMcpServer = (config, apiKey) => {
|
|
42
|
+
const servers = ensureRecord(config, "mcp_servers");
|
|
43
|
+
const pushary = ensureRecord(servers, "pushary");
|
|
44
|
+
pushary.url = CODEX_MCP_URL;
|
|
45
|
+
ensureRecord(pushary, "http_headers").Authorization = `Bearer ${apiKey}`;
|
|
46
|
+
pushary.default_tools_approval_mode = "approve";
|
|
47
|
+
delete pushary.bearer_token_env_var;
|
|
48
|
+
delete pushary.tools;
|
|
49
|
+
};
|
|
50
|
+
var readCodexMcpAuth = (config) => {
|
|
51
|
+
const pushary = asRecord(asRecord(config.mcp_servers)?.pushary);
|
|
52
|
+
if (!pushary) return void 0;
|
|
53
|
+
const authorization = asRecord(pushary.http_headers)?.Authorization;
|
|
54
|
+
if (typeof authorization === "string" && authorization.startsWith("Bearer ")) {
|
|
55
|
+
return { method: "embedded", key: authorization.slice("Bearer ".length).trim() };
|
|
56
|
+
}
|
|
57
|
+
if (typeof pushary.bearer_token_env_var === "string") {
|
|
58
|
+
return { method: "env", envVar: pushary.bearer_token_env_var };
|
|
59
|
+
}
|
|
60
|
+
if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
|
|
61
|
+
return void 0;
|
|
62
|
+
};
|
|
63
|
+
var isPusharyCodexHook = (entry) => {
|
|
64
|
+
const hooks = asRecord(entry)?.hooks;
|
|
65
|
+
if (!Array.isArray(hooks)) return false;
|
|
66
|
+
return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(CODEX_HOOK_BINARY));
|
|
67
|
+
};
|
|
68
|
+
var addCodexHooks = (config, command) => {
|
|
69
|
+
const hooks = ensureRecord(config, "hooks");
|
|
70
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
71
|
+
const existing = Array.isArray(hooks[definition.event]) ? hooks[definition.event] : [];
|
|
72
|
+
const entries = existing.filter((entry) => !isPusharyCodexHook(entry));
|
|
73
|
+
entries.push({
|
|
74
|
+
...definition.matcher ? { matcher: definition.matcher } : {},
|
|
75
|
+
hooks: [{
|
|
76
|
+
type: "command",
|
|
77
|
+
command,
|
|
78
|
+
timeout: definition.timeout,
|
|
79
|
+
...definition.statusMessage ? { statusMessage: definition.statusMessage } : {}
|
|
80
|
+
}]
|
|
81
|
+
});
|
|
82
|
+
hooks[definition.event] = entries;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var removeCodexHooks = (config) => {
|
|
86
|
+
const hooks = asRecord(config.hooks);
|
|
87
|
+
if (!hooks) return false;
|
|
88
|
+
let changed = false;
|
|
89
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
90
|
+
const entries = hooks[definition.event];
|
|
91
|
+
if (!Array.isArray(entries)) continue;
|
|
92
|
+
const filtered = entries.filter((entry) => !isPusharyCodexHook(entry));
|
|
93
|
+
if (filtered.length !== entries.length) {
|
|
94
|
+
if (filtered.length === 0) {
|
|
95
|
+
delete hooks[definition.event];
|
|
96
|
+
} else {
|
|
97
|
+
hooks[definition.event] = filtered;
|
|
98
|
+
}
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(hooks).length === 0) delete config.hooks;
|
|
103
|
+
return changed;
|
|
104
|
+
};
|
|
105
|
+
var hasCodexHooks = (config) => {
|
|
106
|
+
const hooks = asRecord(config.hooks);
|
|
107
|
+
if (!hooks) return false;
|
|
108
|
+
return CODEX_HOOK_EVENTS.some((definition) => {
|
|
109
|
+
const entries = hooks[definition.event];
|
|
110
|
+
return Array.isArray(entries) && entries.some(isPusharyCodexHook);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
var missingCodexHookEvents = (config) => {
|
|
114
|
+
const hooks = asRecord(config.hooks);
|
|
115
|
+
return CODEX_HOOK_EVENTS.filter((definition) => {
|
|
116
|
+
const entries = hooks?.[definition.event];
|
|
117
|
+
return !Array.isArray(entries) || !entries.some(isPusharyCodexHook);
|
|
118
|
+
}).map((definition) => definition.event);
|
|
119
|
+
};
|
|
120
|
+
var canonicalJson = (value) => {
|
|
121
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
122
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
|
|
123
|
+
const obj = value;
|
|
124
|
+
return "{" + Object.keys(obj).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(obj[key])).join(",") + "}";
|
|
125
|
+
};
|
|
126
|
+
var codexHookTrustHash = (definition, command) => {
|
|
127
|
+
const hook = { async: false, command, timeout: definition.timeout, type: "command" };
|
|
128
|
+
if (definition.statusMessage) hook.statusMessage = definition.statusMessage;
|
|
129
|
+
const identity = { event_name: CODEX_EVENT_KEY[definition.event], hooks: [hook] };
|
|
130
|
+
if (definition.matcher) identity.matcher = definition.matcher;
|
|
131
|
+
return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
|
|
132
|
+
};
|
|
133
|
+
var codexHookStateKey = (hooksJsonPath, event) => `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:0:0`;
|
|
134
|
+
var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
|
|
135
|
+
const state = asRecord(asRecord(config.hooks)?.state);
|
|
136
|
+
return CODEX_HOOK_EVENTS.filter((definition) => {
|
|
137
|
+
const entry = asRecord(state?.[codexHookStateKey(hooksJsonPath, definition.event)]);
|
|
138
|
+
const stored = entry?.trusted_hash;
|
|
139
|
+
return stored !== codexHookTrustHash(definition, command);
|
|
140
|
+
}).map((definition) => definition.event);
|
|
141
|
+
};
|
|
142
|
+
var addCodexHookTrust = (config, hooksJsonPath, command) => {
|
|
143
|
+
const hooks = ensureRecord(config, "hooks");
|
|
144
|
+
const state = ensureRecord(hooks, "state");
|
|
145
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
146
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event);
|
|
147
|
+
const existing = asRecord(state[key]) ?? {};
|
|
148
|
+
state[key] = { ...existing, trusted_hash: codexHookTrustHash(definition, command) };
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
CODEX_HOOK_BINARY,
|
|
154
|
+
codexHome,
|
|
155
|
+
codexHomeFrom,
|
|
156
|
+
codexConfigToml,
|
|
157
|
+
codexHooksJson,
|
|
158
|
+
codexAgentsMd,
|
|
159
|
+
codexSkillDir,
|
|
160
|
+
addCodexMcpServer,
|
|
161
|
+
readCodexMcpAuth,
|
|
162
|
+
addCodexHooks,
|
|
163
|
+
removeCodexHooks,
|
|
164
|
+
hasCodexHooks,
|
|
165
|
+
missingCodexHookEvents,
|
|
166
|
+
untrustedCodexHookEvents,
|
|
167
|
+
addCodexHookTrust
|
|
168
|
+
};
|