@pushary/agent-hooks 0.84.0 → 0.85.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 +63 -0
- package/README.md +24 -0
- package/dist/bin/pushary-bell-hook.js +5 -3
- package/dist/bin/pushary-bell.js +8 -7
- package/dist/bin/pushary-claude.js +29 -20
- package/dist/bin/pushary-clean.js +6 -5
- package/dist/bin/pushary-codex-bridge.js +8 -7
- package/dist/bin/pushary-codex-hook.js +11 -14
- package/dist/bin/pushary-codex.js +1 -1
- package/dist/bin/pushary-connect.js +5 -5
- package/dist/bin/pushary-daemon.js +36 -18
- package/dist/bin/pushary-disconnect.d.ts +1 -0
- package/dist/bin/pushary-disconnect.js +125 -0
- package/dist/bin/pushary-doctor.js +9 -9
- package/dist/bin/pushary-gemini-bridge.js +8 -7
- package/dist/bin/pushary-gemini-hook.js +12 -15
- package/dist/bin/pushary-hook.js +13 -20
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +3 -3
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +6 -9
- package/dist/bin/pushary-permission-denied-hook.js +11 -9
- package/dist/bin/pushary-permission-hook.js +11 -9
- package/dist/bin/pushary-post-hook.js +6 -9
- package/dist/bin/pushary-prompt-hook.js +6 -9
- package/dist/bin/pushary-session-end-hook.js +6 -6
- package/dist/bin/pushary-session-start-hook.js +6 -6
- package/dist/bin/pushary-setup.js +23 -24
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +2 -2
- package/dist/bin/pushary-stop-hook.js +6 -6
- package/dist/bin/pushary-stopfailure-hook.js +6 -6
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +13 -8
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +2 -1
- package/dist/{chunk-HZNOAJGB.js → chunk-4QPOMJUB.js} +1 -1
- package/dist/chunk-64PXDATC.js +187 -0
- package/dist/chunk-6CUUA7HO.js +59 -0
- package/dist/{chunk-UXWEE3QI.js → chunk-ADISVXZL.js} +1 -1
- package/dist/chunk-AXRATIS6.js +70 -0
- package/dist/{chunk-YJCMI5GF.js → chunk-ERYKGH4J.js} +11 -4
- package/dist/{chunk-OPMSMF5R.js → chunk-EXMUM226.js} +21 -8
- package/dist/{chunk-NWYGW7HY.js → chunk-GCKAWHFP.js} +9 -2
- package/dist/{chunk-QNBA5OXQ.js → chunk-GFM3STWO.js} +3 -3
- package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
- package/dist/{chunk-37AD5K7W.js → chunk-GXIFADGD.js} +1 -1
- package/dist/{chunk-SEAHGHCE.js → chunk-HVZIHTOO.js} +33 -0
- package/dist/chunk-PWTKKQQI.js +27 -0
- package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
- package/dist/{chunk-CCH775Y6.js → chunk-WEAQQSHQ.js} +1 -1
- package/dist/{chunk-EVJ6ULHN.js → chunk-YKW6JCWI.js} +68 -29
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +6 -4
- package/package.json +3 -2
- package/dist/chunk-UKSQWTBH.js +0 -208
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
generateSessionKey,
|
|
6
6
|
uploadTranscriptRecords,
|
|
7
7
|
wrapSessionKey
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GCKAWHFP.js";
|
|
9
9
|
|
|
10
10
|
// src/live-session.ts
|
|
11
11
|
var MAX_TRANSCRIPT_QUEUE = 100;
|
|
@@ -72,8 +72,10 @@ var LiveTranscriptRelay = class {
|
|
|
72
72
|
sessionKey = null;
|
|
73
73
|
wrappedKey = null;
|
|
74
74
|
keyUploaded = false;
|
|
75
|
+
keyRejected = false;
|
|
75
76
|
flushing = null;
|
|
76
77
|
add(value) {
|
|
78
|
+
if (this.keyRejected) return;
|
|
77
79
|
const raw = JSON.stringify(value);
|
|
78
80
|
if (raw.length > MAX_TRANSCRIPT_TEXT) return;
|
|
79
81
|
if (this.queue.length === MAX_TRANSCRIPT_QUEUE) this.queue.shift();
|
|
@@ -106,14 +108,19 @@ var LiveTranscriptRelay = class {
|
|
|
106
108
|
this.queue.splice(0, count);
|
|
107
109
|
return true;
|
|
108
110
|
}
|
|
109
|
-
const
|
|
111
|
+
const result = await uploadTranscriptRecords(this.options.apiKey, {
|
|
110
112
|
sessionId: this.sessionId,
|
|
111
113
|
machineId: this.options.machineId,
|
|
112
114
|
dataEncryptionKey: this.keyUploaded ? void 0 : this.wrappedKey ?? void 0,
|
|
113
115
|
records
|
|
114
116
|
}, this.options.baseUrl);
|
|
115
|
-
if (
|
|
116
|
-
this.
|
|
117
|
+
if (result.keyConflict) {
|
|
118
|
+
this.keyRejected = true;
|
|
119
|
+
this.queue.length = 0;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (result.ok) {
|
|
123
|
+
this.keyUploaded ||= result.keyRegistered;
|
|
117
124
|
this.queue.splice(0, count);
|
|
118
125
|
return true;
|
|
119
126
|
}
|
|
@@ -60,7 +60,7 @@ var CACHE_TTL_MS = 60 * 1e3;
|
|
|
60
60
|
var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
|
|
61
61
|
var policyRequestFor = (agentType) => ({
|
|
62
62
|
agent: agentType,
|
|
63
|
-
repoAware:
|
|
63
|
+
repoAware: ["claude_code", "codex", "gemini_cli"].includes(agentType)
|
|
64
64
|
});
|
|
65
65
|
var policyCacheFile = (apiKey, agent, repoAware = false) => {
|
|
66
66
|
const parts = [apiKey, agent, repoAware ? "repoAware" : void 0].filter(Boolean);
|
|
@@ -196,9 +196,10 @@ var readLastGoodMode = (apiKey) => {
|
|
|
196
196
|
return null;
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
var writeLastGoodMode = (apiKey, state) => {
|
|
199
|
+
var writeLastGoodMode = (apiKey, state, sessionId) => {
|
|
200
200
|
try {
|
|
201
|
-
|
|
201
|
+
const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
|
|
202
|
+
writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
|
|
202
203
|
} catch {
|
|
203
204
|
}
|
|
204
205
|
};
|
|
@@ -220,7 +221,18 @@ var degradedFallback = (apiKey, authoritative = false) => {
|
|
|
220
221
|
return unknownMode();
|
|
221
222
|
};
|
|
222
223
|
var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
|
|
223
|
-
var
|
|
224
|
+
var MODE_REUSE_MS = CACHE_TTL_MS;
|
|
225
|
+
var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
226
|
+
const last = readLastGoodMode(apiKey);
|
|
227
|
+
if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
228
|
+
if (Date.now() - last.at >= maxAgeMs) return null;
|
|
229
|
+
return { ...last.state, degraded: false };
|
|
230
|
+
};
|
|
231
|
+
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
232
|
+
if (reuseWithinMs > 0) {
|
|
233
|
+
const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
|
|
234
|
+
if (reusable) return reusable;
|
|
235
|
+
}
|
|
224
236
|
try {
|
|
225
237
|
const baseUrl = getBaseUrl();
|
|
226
238
|
const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
|
|
@@ -244,7 +256,7 @@ var fetchModeState = async (apiKey, sessionId) => {
|
|
|
244
256
|
scope: isScopeContract(data.scope) ? data.scope : null,
|
|
245
257
|
degraded: false
|
|
246
258
|
};
|
|
247
|
-
writeLastGoodMode(apiKey, state);
|
|
259
|
+
writeLastGoodMode(apiKey, state, sessionId);
|
|
248
260
|
return state;
|
|
249
261
|
} catch {
|
|
250
262
|
return degradedFallback(apiKey);
|
|
@@ -1053,7 +1065,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1053
1065
|
const isError = isToolResultError(toolResult);
|
|
1054
1066
|
const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
|
|
1055
1067
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1056
|
-
const liveMode = await fetchModeState(getApiKey(), input.session_id);
|
|
1068
|
+
const liveMode = await fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS);
|
|
1057
1069
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1058
1070
|
let additionalContext;
|
|
1059
1071
|
if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
|
|
@@ -1239,7 +1251,7 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1239
1251
|
} catch {
|
|
1240
1252
|
return;
|
|
1241
1253
|
}
|
|
1242
|
-
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
1254
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1243
1255
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1244
1256
|
await sendNotification(apiKey, {
|
|
1245
1257
|
title: `${agent.label} is waiting`,
|
|
@@ -1302,7 +1314,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1302
1314
|
return;
|
|
1303
1315
|
}
|
|
1304
1316
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
1305
|
-
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
1317
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1306
1318
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1307
1319
|
const reason = describeStopFailure(input.error);
|
|
1308
1320
|
await sendNotification(apiKey, {
|
|
@@ -1318,6 +1330,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1318
1330
|
};
|
|
1319
1331
|
|
|
1320
1332
|
export {
|
|
1333
|
+
policyRequestFor,
|
|
1321
1334
|
getPolicy,
|
|
1322
1335
|
resolveAutoResolveOrigin,
|
|
1323
1336
|
resolvePolicy,
|
|
@@ -101,9 +101,16 @@ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) =>
|
|
|
101
101
|
body: JSON.stringify(params),
|
|
102
102
|
signal: AbortSignal.timeout(15e3)
|
|
103
103
|
});
|
|
104
|
-
|
|
104
|
+
const body = await res.json().catch(() => null);
|
|
105
|
+
const keyRegistered = body?.keyRegistered === true;
|
|
106
|
+
const keyConflict = body?.keyConflict === true;
|
|
107
|
+
return {
|
|
108
|
+
ok: res.ok && !keyConflict && (params.dataEncryptionKey === void 0 || keyRegistered),
|
|
109
|
+
keyRegistered,
|
|
110
|
+
keyConflict
|
|
111
|
+
};
|
|
105
112
|
} catch {
|
|
106
|
-
return false;
|
|
113
|
+
return { ok: false, keyRegistered: false, keyConflict: false };
|
|
107
114
|
}
|
|
108
115
|
};
|
|
109
116
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-64PXDATC.js";
|
|
4
4
|
import {
|
|
5
5
|
isGatingMoment,
|
|
6
6
|
recordKeylessMoment
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
denyReasonFrom,
|
|
10
10
|
isDeferAnswer,
|
|
11
11
|
resolveGate
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-GXIFADGD.js";
|
|
13
13
|
import {
|
|
14
14
|
DEFAULT_SESSION,
|
|
15
15
|
askUser,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
sendNotification,
|
|
30
30
|
throttlePass,
|
|
31
31
|
waitForAnswer
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-EXMUM226.js";
|
|
33
33
|
import {
|
|
34
34
|
getMachineId
|
|
35
35
|
} from "./chunk-RN3NOEJF.js";
|
|
@@ -53,6 +53,7 @@ var COMMAND_LIST = [
|
|
|
53
53
|
]
|
|
54
54
|
},
|
|
55
55
|
{ name: "doctor", module: "pushary-doctor", summary: "Verify your Pushary installation is working" },
|
|
56
|
+
{ name: "disconnect", module: "pushary-disconnect", summary: "Turn off one agent (claude|codex|gemini|cursor)" },
|
|
56
57
|
{ name: "clean", module: "pushary-clean", summary: "Remove all Pushary configuration (--yes for non-interactive)" },
|
|
57
58
|
{ name: "mode", module: "pushary-mode", summary: "Switch approval mode (push_only, push_first, terminal_only)" },
|
|
58
59
|
{ name: "wait", module: "pushary-wait", summary: 'Show or set the "wait for your phone" ladder (pushary wait 45)' },
|
|
@@ -139,6 +139,38 @@ var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
|
|
|
139
139
|
return stored !== codexHookTrustHash(definition, command);
|
|
140
140
|
}).map((definition) => definition.event);
|
|
141
141
|
};
|
|
142
|
+
var removeCodexMcpServer = (config) => {
|
|
143
|
+
const servers = asRecord(config.mcp_servers);
|
|
144
|
+
if (!servers?.pushary) return false;
|
|
145
|
+
delete servers.pushary;
|
|
146
|
+
if (Object.keys(servers).length === 0) delete config.mcp_servers;
|
|
147
|
+
return true;
|
|
148
|
+
};
|
|
149
|
+
var removeCodexHookTrust = (config, hooksJsonPath) => {
|
|
150
|
+
const state = asRecord(asRecord(config.hooks)?.state);
|
|
151
|
+
if (!state) return false;
|
|
152
|
+
let changed = false;
|
|
153
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
154
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event);
|
|
155
|
+
if (key in state) {
|
|
156
|
+
delete state[key];
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (changed && Object.keys(state).length === 0) {
|
|
161
|
+
const hooks = asRecord(config.hooks);
|
|
162
|
+
if (hooks) {
|
|
163
|
+
delete hooks.state;
|
|
164
|
+
if (Object.keys(hooks).length === 0) delete config.hooks;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return changed;
|
|
168
|
+
};
|
|
169
|
+
var removeCodexSettings = (config, hooksJsonPath) => {
|
|
170
|
+
const mcp = removeCodexMcpServer(config);
|
|
171
|
+
const trust = removeCodexHookTrust(config, hooksJsonPath);
|
|
172
|
+
return mcp || trust;
|
|
173
|
+
};
|
|
142
174
|
var addCodexHookTrust = (config, hooksJsonPath, command) => {
|
|
143
175
|
const hooks = ensureRecord(config, "hooks");
|
|
144
176
|
const state = ensureRecord(hooks, "state");
|
|
@@ -183,6 +215,7 @@ export {
|
|
|
183
215
|
hasCodexHooks,
|
|
184
216
|
missingCodexHookEvents,
|
|
185
217
|
untrustedCodexHookEvents,
|
|
218
|
+
removeCodexSettings,
|
|
186
219
|
addCodexHookTrust,
|
|
187
220
|
claudeSettings,
|
|
188
221
|
claudeJson,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
quoteIfNeeded
|
|
3
|
+
} from "./chunk-VT4IVERX.js";
|
|
4
|
+
|
|
5
|
+
// src/hook-command.ts
|
|
6
|
+
var SHELL = "/bin/sh -c";
|
|
7
|
+
var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
|
|
8
|
+
var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
|
|
9
|
+
var RESOLVE = {
|
|
10
|
+
native: `${LOCATE}; exec "$B"`,
|
|
11
|
+
node: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`
|
|
12
|
+
};
|
|
13
|
+
var guardHookCommand = (target, platform = process.platform) => {
|
|
14
|
+
if (target.kind === "node") {
|
|
15
|
+
const script = quoteIfNeeded(target.script);
|
|
16
|
+
return platform === "win32" ? `node ${script}` : `${SHELL} '[ -f "$0" ] || exit 0; command -v node >/dev/null 2>&1 || exit 0; exec node "$0"' ${script}`;
|
|
17
|
+
}
|
|
18
|
+
const path = quoteIfNeeded(target.path);
|
|
19
|
+
return platform === "win32" ? path : `${SHELL} '${RESOLVE[target.runtime]}' ${path}`;
|
|
20
|
+
};
|
|
21
|
+
var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
|
|
22
|
+
var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
guardBinary,
|
|
26
|
+
guardNodeScript
|
|
27
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pusharyDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HVZIHTOO.js";
|
|
4
4
|
import {
|
|
5
5
|
readJsonSafe
|
|
6
6
|
} from "./chunk-6MTNS63X.js";
|
|
@@ -91,7 +91,11 @@ var startProcessOwnership = (identity, options = {}) => {
|
|
|
91
91
|
proof = { ...identity, port: address.port, token };
|
|
92
92
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
93
93
|
pruneProofs(dir);
|
|
94
|
-
writeFileSync(path, JSON.stringify(proof), {
|
|
94
|
+
writeFileSync(path, JSON.stringify(proof), {
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
mode: 384,
|
|
97
|
+
flag: options.exclusive ? "wx" : "w"
|
|
98
|
+
});
|
|
95
99
|
resolve();
|
|
96
100
|
} catch {
|
|
97
101
|
server.close();
|
|
@@ -113,41 +117,76 @@ var startProcessOwnership = (identity, options = {}) => {
|
|
|
113
117
|
}
|
|
114
118
|
};
|
|
115
119
|
};
|
|
120
|
+
var withSpawnProcessOwnership = async (spawnId, run, options = {}) => {
|
|
121
|
+
if (!spawnId) return run();
|
|
122
|
+
const sessionId = `spawn:${spawnId}`;
|
|
123
|
+
const ownership = startProcessOwnership({
|
|
124
|
+
sessionId,
|
|
125
|
+
pid: process.pid,
|
|
126
|
+
spawnId
|
|
127
|
+
}, { ...options, exclusive: true });
|
|
128
|
+
await ownership.ready;
|
|
129
|
+
if (!await ownsLiveProcess({ sessionId, pid: process.pid, spawnId }, options)) {
|
|
130
|
+
ownership.stop();
|
|
131
|
+
throw new Error("Could not establish spawned process ownership");
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return await run();
|
|
135
|
+
} finally {
|
|
136
|
+
ownership.stop();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
116
139
|
var ownsLiveProcess = async (request, options = {}) => {
|
|
117
140
|
const stored = readJsonSafe(proofPath(ownershipDir(options), request.sessionId));
|
|
118
141
|
if (stored.kind !== "ok" || !isProof(stored.value)) return false;
|
|
119
142
|
const proof = stored.value;
|
|
120
143
|
if (proof.sessionId !== request.sessionId || proof.pid !== request.pid) return false;
|
|
121
144
|
if (request.spawnId && proof.spawnId !== request.spawnId) return false;
|
|
122
|
-
return
|
|
123
|
-
let settled = false;
|
|
124
|
-
let raw = "";
|
|
125
|
-
const socket = createConnection({ host: "127.0.0.1", port: proof.port });
|
|
126
|
-
const finish = (owned) => {
|
|
127
|
-
if (settled) return;
|
|
128
|
-
settled = true;
|
|
129
|
-
socket.destroy();
|
|
130
|
-
resolve(owned);
|
|
131
|
-
};
|
|
132
|
-
socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
|
|
133
|
-
socket.on("error", () => finish(false));
|
|
134
|
-
socket.on("connect", () => socket.write(proof.token));
|
|
135
|
-
socket.on("data", (chunk) => {
|
|
136
|
-
raw += String(chunk);
|
|
137
|
-
if (raw.length > MAX_PROOF_BYTES) finish(false);
|
|
138
|
-
});
|
|
139
|
-
socket.on("end", () => {
|
|
140
|
-
try {
|
|
141
|
-
const live = JSON.parse(raw);
|
|
142
|
-
finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
|
|
143
|
-
} catch {
|
|
144
|
-
finish(false);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
145
|
+
return verifyProof(proof, options);
|
|
148
146
|
};
|
|
147
|
+
var findLiveProcessBySpawnId = async (spawnId, options = {}) => {
|
|
148
|
+
try {
|
|
149
|
+
const dir = ownershipDir(options);
|
|
150
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
151
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
152
|
+
const stored = readJsonSafe(join(dir, entry.name));
|
|
153
|
+
if (stored.kind !== "ok" || !isProof(stored.value) || stored.value.spawnId !== spawnId) continue;
|
|
154
|
+
if (await verifyProof(stored.value, options)) return stored.value.pid;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
};
|
|
160
|
+
var verifyProof = (proof, options) => new Promise((resolve) => {
|
|
161
|
+
let settled = false;
|
|
162
|
+
let raw = "";
|
|
163
|
+
const socket = createConnection({ host: "127.0.0.1", port: proof.port });
|
|
164
|
+
const finish = (owned) => {
|
|
165
|
+
if (settled) return;
|
|
166
|
+
settled = true;
|
|
167
|
+
socket.destroy();
|
|
168
|
+
resolve(owned);
|
|
169
|
+
};
|
|
170
|
+
socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
|
|
171
|
+
socket.on("error", () => finish(false));
|
|
172
|
+
socket.on("connect", () => socket.write(proof.token));
|
|
173
|
+
socket.on("data", (chunk) => {
|
|
174
|
+
raw += String(chunk);
|
|
175
|
+
if (raw.length > MAX_PROOF_BYTES) finish(false);
|
|
176
|
+
});
|
|
177
|
+
socket.on("end", () => {
|
|
178
|
+
try {
|
|
179
|
+
const live = JSON.parse(raw);
|
|
180
|
+
finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
|
|
181
|
+
} catch {
|
|
182
|
+
finish(false);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
149
186
|
|
|
150
187
|
export {
|
|
151
188
|
startProcessOwnership,
|
|
152
|
-
|
|
189
|
+
withSpawnProcessOwnership,
|
|
190
|
+
ownsLiveProcess,
|
|
191
|
+
findLiveProcessBySpawnId
|
|
153
192
|
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ interface ModeState {
|
|
|
39
39
|
readonly scope?: ScopeContract | null;
|
|
40
40
|
readonly degraded?: boolean;
|
|
41
41
|
}
|
|
42
|
-
declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
|
|
42
|
+
declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
|
|
43
43
|
declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
|
|
44
44
|
|
|
45
45
|
interface UsageReport {
|
package/dist/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-GFM3STWO.js";
|
|
4
|
+
import "../chunk-64PXDATC.js";
|
|
5
|
+
import "../chunk-PWTKKQQI.js";
|
|
6
|
+
import "../chunk-VT4IVERX.js";
|
|
5
7
|
import "../chunk-NJ7JT26G.js";
|
|
6
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-GXIFADGD.js";
|
|
7
9
|
import {
|
|
8
10
|
askUser,
|
|
9
11
|
cancelQuestion,
|
|
@@ -16,7 +18,7 @@ import {
|
|
|
16
18
|
reportEvent,
|
|
17
19
|
resolvePolicy,
|
|
18
20
|
waitForAnswer
|
|
19
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-EXMUM226.js";
|
|
20
22
|
import "../chunk-RN3NOEJF.js";
|
|
21
23
|
import "../chunk-BSZYIAZL.js";
|
|
22
24
|
import "../chunk-SAF6HGAA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"pushary-suggestions": "./dist/bin/pushary-suggestions.js",
|
|
69
69
|
"pushary-upgrade": "./dist/bin/pushary-upgrade.js",
|
|
70
70
|
"pushary-bell": "./dist/bin/pushary-bell.js",
|
|
71
|
-
"pushary-bell-hook": "./dist/bin/pushary-bell-hook.js"
|
|
71
|
+
"pushary-bell-hook": "./dist/bin/pushary-bell-hook.js",
|
|
72
|
+
"pushary-disconnect": "./dist/bin/pushary-disconnect.js"
|
|
72
73
|
},
|
|
73
74
|
"files": [
|
|
74
75
|
"dist",
|
package/dist/chunk-UKSQWTBH.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HOOK_BUDGETS
|
|
3
|
-
} from "./chunk-MQMPG5X4.js";
|
|
4
|
-
|
|
5
|
-
// src/claude-config.ts
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
|
|
8
|
-
var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
|
|
9
|
-
var PRETOOLUSE_GATED_TOOLS = ["Bash", "Write", "Edit", "MultiEdit", "NotebookEdit", "PowerShell", "Monitor"];
|
|
10
|
-
var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"]);
|
|
11
|
-
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
12
|
-
var ensureRecord = (target, key) => {
|
|
13
|
-
const existing = asRecord(target[key]);
|
|
14
|
-
if (existing) return existing;
|
|
15
|
-
const created = {};
|
|
16
|
-
target[key] = created;
|
|
17
|
-
return created;
|
|
18
|
-
};
|
|
19
|
-
var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
|
|
20
|
-
var isPusharyHook = (entry) => {
|
|
21
|
-
const hooks = asRecord(entry)?.hooks;
|
|
22
|
-
if (!Array.isArray(hooks)) return false;
|
|
23
|
-
return hooks.some((hook) => {
|
|
24
|
-
const command = String(asRecord(hook)?.command ?? "");
|
|
25
|
-
return command.includes("pushary-hook") || command.includes("pushary-post-hook") || command.includes("pushary-stop-hook") || command.includes("pushary-prompt-hook") || command.includes("pushary-notification-hook") || command.includes("pushary-session-start-hook") || command.includes("pushary-session-end-hook") || command.includes("pushary-stopfailure-hook") || command.includes("pushary-permission-hook") || command.includes("pushary-permission-denied-hook");
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
var addClaudeMcpServer = (config, apiKey) => {
|
|
29
|
-
const mcpServers = ensureRecord(config, "mcpServers");
|
|
30
|
-
mcpServers.pushary = {
|
|
31
|
-
type: "http",
|
|
32
|
-
url: PUSHARY_MCP_URL,
|
|
33
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
var removeClaudeMcpServers = (config) => {
|
|
37
|
-
let changed = false;
|
|
38
|
-
const removeFrom = (target) => {
|
|
39
|
-
const mcpServers = asRecord(target.mcpServers);
|
|
40
|
-
if (!mcpServers?.pushary) return;
|
|
41
|
-
delete mcpServers.pushary;
|
|
42
|
-
if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
|
|
43
|
-
changed = true;
|
|
44
|
-
};
|
|
45
|
-
removeFrom(config);
|
|
46
|
-
const projects = asRecord(config.projects);
|
|
47
|
-
if (projects) {
|
|
48
|
-
for (const project of Object.values(projects)) {
|
|
49
|
-
const projectConfig = asRecord(project);
|
|
50
|
-
if (projectConfig) removeFrom(projectConfig);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return changed;
|
|
54
|
-
};
|
|
55
|
-
var addPusharyToolPermissions = (settings) => {
|
|
56
|
-
const permissions = ensureRecord(settings, "permissions");
|
|
57
|
-
const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
|
|
58
|
-
const filtered = allow.filter((rule) => !isPusharyPermission(rule));
|
|
59
|
-
if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
|
|
60
|
-
filtered.push(PUSHARY_PERMISSION_RULE);
|
|
61
|
-
}
|
|
62
|
-
permissions.allow = filtered;
|
|
63
|
-
};
|
|
64
|
-
var addPusharyHooks = (settings, binDir) => {
|
|
65
|
-
const resolve = (name) => {
|
|
66
|
-
const path = binDir ? join(binDir, name) : name;
|
|
67
|
-
return /\s/.test(path) ? `"${path}"` : path;
|
|
68
|
-
};
|
|
69
|
-
const hooks = ensureRecord(settings, "hooks");
|
|
70
|
-
const SHELL_AND_FILE_TOOLS = PRETOOLUSE_GATED_TOOLS.join("|");
|
|
71
|
-
const preToolUse = (Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []).filter((entry) => !isPusharyHook(entry));
|
|
72
|
-
preToolUse.push({
|
|
73
|
-
matcher: `${SHELL_AND_FILE_TOOLS}|AskUserQuestion`,
|
|
74
|
-
hooks: [{
|
|
75
|
-
type: "command",
|
|
76
|
-
command: resolve("pushary-hook"),
|
|
77
|
-
timeout: HOOK_BUDGETS.claude.budgetSeconds
|
|
78
|
-
}]
|
|
79
|
-
});
|
|
80
|
-
hooks.PreToolUse = preToolUse;
|
|
81
|
-
const postToolUse = (Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : []).filter((entry) => !isPusharyHook(entry));
|
|
82
|
-
postToolUse.push({
|
|
83
|
-
matcher: `${SHELL_AND_FILE_TOOLS}|TodoWrite`,
|
|
84
|
-
hooks: [{
|
|
85
|
-
type: "command",
|
|
86
|
-
command: resolve("pushary-post-hook"),
|
|
87
|
-
timeout: 10
|
|
88
|
-
}]
|
|
89
|
-
});
|
|
90
|
-
hooks.PostToolUse = postToolUse;
|
|
91
|
-
const stop = (Array.isArray(hooks.Stop) ? hooks.Stop : []).filter((entry) => !isPusharyHook(entry));
|
|
92
|
-
stop.push({
|
|
93
|
-
hooks: [{
|
|
94
|
-
type: "command",
|
|
95
|
-
command: resolve("pushary-stop-hook"),
|
|
96
|
-
timeout: 10
|
|
97
|
-
}]
|
|
98
|
-
});
|
|
99
|
-
hooks.Stop = stop;
|
|
100
|
-
const userPromptSubmit = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).filter((entry) => !isPusharyHook(entry));
|
|
101
|
-
userPromptSubmit.push({
|
|
102
|
-
hooks: [{
|
|
103
|
-
type: "command",
|
|
104
|
-
command: resolve("pushary-prompt-hook"),
|
|
105
|
-
timeout: 10
|
|
106
|
-
}]
|
|
107
|
-
});
|
|
108
|
-
hooks.UserPromptSubmit = userPromptSubmit;
|
|
109
|
-
const notification = (Array.isArray(hooks.Notification) ? hooks.Notification : []).filter((entry) => !isPusharyHook(entry));
|
|
110
|
-
notification.push({
|
|
111
|
-
matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed",
|
|
112
|
-
hooks: [{
|
|
113
|
-
type: "command",
|
|
114
|
-
command: resolve("pushary-notification-hook"),
|
|
115
|
-
timeout: 10
|
|
116
|
-
}]
|
|
117
|
-
});
|
|
118
|
-
hooks.Notification = notification;
|
|
119
|
-
const sessionStart = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).filter((entry) => !isPusharyHook(entry));
|
|
120
|
-
sessionStart.push({
|
|
121
|
-
matcher: "startup|resume|clear",
|
|
122
|
-
hooks: [{
|
|
123
|
-
type: "command",
|
|
124
|
-
command: resolve("pushary-session-start-hook"),
|
|
125
|
-
timeout: 10
|
|
126
|
-
}]
|
|
127
|
-
});
|
|
128
|
-
hooks.SessionStart = sessionStart;
|
|
129
|
-
const sessionEnd = (Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []).filter((entry) => !isPusharyHook(entry));
|
|
130
|
-
sessionEnd.push({
|
|
131
|
-
hooks: [{
|
|
132
|
-
type: "command",
|
|
133
|
-
command: resolve("pushary-session-end-hook"),
|
|
134
|
-
timeout: 10
|
|
135
|
-
}]
|
|
136
|
-
});
|
|
137
|
-
hooks.SessionEnd = sessionEnd;
|
|
138
|
-
const stopFailure = (Array.isArray(hooks.StopFailure) ? hooks.StopFailure : []).filter((entry) => !isPusharyHook(entry));
|
|
139
|
-
stopFailure.push({
|
|
140
|
-
hooks: [{
|
|
141
|
-
type: "command",
|
|
142
|
-
command: resolve("pushary-stopfailure-hook"),
|
|
143
|
-
timeout: 10
|
|
144
|
-
}]
|
|
145
|
-
});
|
|
146
|
-
hooks.StopFailure = stopFailure;
|
|
147
|
-
const permissionRequest = (Array.isArray(hooks.PermissionRequest) ? hooks.PermissionRequest : []).filter((entry) => !isPusharyHook(entry));
|
|
148
|
-
permissionRequest.push({
|
|
149
|
-
hooks: [{
|
|
150
|
-
type: "command",
|
|
151
|
-
command: resolve("pushary-permission-hook"),
|
|
152
|
-
timeout: HOOK_BUDGETS.claude.budgetSeconds
|
|
153
|
-
}]
|
|
154
|
-
});
|
|
155
|
-
hooks.PermissionRequest = permissionRequest;
|
|
156
|
-
const permissionDenied = (Array.isArray(hooks.PermissionDenied) ? hooks.PermissionDenied : []).filter((entry) => !isPusharyHook(entry));
|
|
157
|
-
permissionDenied.push({
|
|
158
|
-
hooks: [{
|
|
159
|
-
type: "command",
|
|
160
|
-
command: resolve("pushary-permission-denied-hook"),
|
|
161
|
-
timeout: 60
|
|
162
|
-
}]
|
|
163
|
-
});
|
|
164
|
-
hooks.PermissionDenied = permissionDenied;
|
|
165
|
-
};
|
|
166
|
-
var removePusharySettings = (settings) => {
|
|
167
|
-
let changed = removeClaudeMcpServers(settings);
|
|
168
|
-
const permissions = asRecord(settings.permissions);
|
|
169
|
-
if (permissions && Array.isArray(permissions.allow)) {
|
|
170
|
-
const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
|
|
171
|
-
if (filtered.length !== permissions.allow.length) {
|
|
172
|
-
if (filtered.length === 0) {
|
|
173
|
-
delete permissions.allow;
|
|
174
|
-
} else {
|
|
175
|
-
permissions.allow = filtered;
|
|
176
|
-
}
|
|
177
|
-
if (Object.keys(permissions).length === 0) delete settings.permissions;
|
|
178
|
-
changed = true;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const hooks = asRecord(settings.hooks);
|
|
182
|
-
if (hooks) {
|
|
183
|
-
for (const key of ["PreToolUse", "PostToolUse", "Stop", "StopFailure", "UserPromptSubmit", "Notification", "SessionStart", "SessionEnd", "PermissionRequest", "PermissionDenied"]) {
|
|
184
|
-
const entries = hooks[key];
|
|
185
|
-
if (!Array.isArray(entries)) continue;
|
|
186
|
-
const filtered = entries.filter((entry) => !isPusharyHook(entry));
|
|
187
|
-
if (filtered.length !== entries.length) {
|
|
188
|
-
if (filtered.length === 0) {
|
|
189
|
-
delete hooks[key];
|
|
190
|
-
} else {
|
|
191
|
-
hooks[key] = filtered;
|
|
192
|
-
}
|
|
193
|
-
changed = true;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
197
|
-
}
|
|
198
|
-
return changed;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
export {
|
|
202
|
-
PRETOOLUSE_HANDLED_TOOLS,
|
|
203
|
-
addClaudeMcpServer,
|
|
204
|
-
removeClaudeMcpServers,
|
|
205
|
-
addPusharyToolPermissions,
|
|
206
|
-
addPusharyHooks,
|
|
207
|
-
removePusharySettings
|
|
208
|
-
};
|