@pushary/agent-hooks 0.85.2 → 0.87.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 +17 -0
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +6 -6
- package/dist/bin/pushary-claude.js +12 -10
- package/dist/bin/pushary-clean.js +10 -9
- package/dist/bin/pushary-codex-bridge.js +5 -5
- package/dist/bin/pushary-codex-hook.js +13 -29
- package/dist/bin/pushary-codex.js +6 -4
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +4 -4
- package/dist/bin/pushary-disconnect.js +25 -10
- package/dist/bin/pushary-doctor.js +21 -15
- package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
- package/dist/bin/pushary-elicitation-hook.js +210 -0
- package/dist/bin/pushary-gemini-bridge.js +5 -5
- package/dist/bin/pushary-gemini-hook.js +10 -8
- package/dist/bin/pushary-hook.js +7 -8
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +4 -3
- package/dist/bin/pushary-permission-denied-hook.js +7 -8
- package/dist/bin/pushary-permission-hook.js +7 -8
- package/dist/bin/pushary-post-hook.js +4 -3
- package/dist/bin/pushary-prompt-hook.js +4 -3
- package/dist/bin/pushary-session-end-hook.js +4 -3
- package/dist/bin/pushary-session-start-hook.js +4 -3
- package/dist/bin/pushary-setup.js +26 -21
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +4 -3
- package/dist/bin/pushary-stopfailure-hook.js +4 -3
- package/dist/bin/pushary-upgrade.js +13 -11
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-GM74DUMS.js → chunk-2T3GX7WG.js} +1 -1
- package/dist/{chunk-XJDL3MUE.js → chunk-5646VKR4.js} +18 -20
- package/dist/{chunk-PKUNB3SN.js → chunk-7MBDFNUC.js} +1 -1
- package/dist/{chunk-K337LIBD.js → chunk-7YJS2VTG.js} +36 -4
- package/dist/{chunk-LCA2EQTK.js → chunk-A3PFIWWN.js} +1 -1
- package/dist/{chunk-LSB6PFLN.js → chunk-A4I5JGCW.js} +1 -1
- package/dist/chunk-ATBKJNWS.js +19 -0
- package/dist/{chunk-KJW6NKF7.js → chunk-B3V2H6SA.js} +39 -15
- package/dist/{chunk-T54OT7IY.js → chunk-ECEUMWBH.js} +1 -1
- package/dist/{chunk-Q4URN3E7.js → chunk-HAUTLCMA.js} +1 -1
- package/dist/{chunk-Y7E2MC3G.js → chunk-HJ44HIUW.js} +60 -31
- package/dist/{chunk-4U2YRHNF.js → chunk-IWL5LWIE.js} +2 -2
- package/dist/chunk-J3YDT4HM.js +128 -0
- package/dist/{chunk-NTIUQB3E.js → chunk-JAICP7BA.js} +1 -1
- package/dist/{chunk-HSGEEBBL.js → chunk-M7RYIZX6.js} +3 -3
- package/dist/{chunk-KZIGJLOX.js → chunk-OT4YD54Q.js} +1 -1
- package/dist/{chunk-LXWISFF3.js → chunk-QBWYNVKY.js} +1 -1
- package/dist/{chunk-4YMWLMAP.js → chunk-SJVLFIYP.js} +1 -1
- package/dist/{chunk-DPDUB4XF.js → chunk-V2XPF77V.js} +104 -163
- package/dist/{chunk-JBLRZGTT.js → chunk-WLLBFDVY.js} +1 -1
- package/dist/{chunk-JT5NQQCE.js → chunk-XW5CHAQX.js} +2 -2
- package/dist/{chunk-CWV4CMB6.js → chunk-Y3UTTFTZ.js} +1 -1
- package/dist/{chunk-GY3I353G.js → chunk-ZZE6FN7T.js} +7 -3
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +11 -11
- package/package.json +3 -1
|
@@ -1,23 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SESSION,
|
|
3
|
+
cancelQuestion,
|
|
4
|
+
isDefaultSession,
|
|
5
|
+
isPolicyConfig,
|
|
6
|
+
listPendingQuestions,
|
|
7
|
+
removePendingQuestion,
|
|
8
|
+
removePendingSession,
|
|
9
|
+
sendNotification,
|
|
10
|
+
waitForAnswer
|
|
11
|
+
} from "./chunk-J3YDT4HM.js";
|
|
1
12
|
import {
|
|
2
13
|
getMachineId
|
|
3
14
|
} from "./chunk-RN3NOEJF.js";
|
|
4
15
|
import {
|
|
5
|
-
callMcpTool,
|
|
6
16
|
withRetry
|
|
7
17
|
} from "./chunk-BSZYIAZL.js";
|
|
8
|
-
import {
|
|
9
|
-
installProxyDispatcher
|
|
10
|
-
} from "./chunk-SAF6HGAA.js";
|
|
11
18
|
import {
|
|
12
19
|
reportedInstallSource
|
|
13
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-Y3UTTFTZ.js";
|
|
14
21
|
import {
|
|
15
22
|
getApiKey,
|
|
16
23
|
getBaseUrl
|
|
17
24
|
} from "./chunk-2UMNXADU.js";
|
|
18
25
|
import {
|
|
19
26
|
ACTION_BODY_MAX,
|
|
27
|
+
AGENT_LABELS_BY_TYPE,
|
|
20
28
|
DECISION_LINE_MAX,
|
|
29
|
+
FILE_TARGET_TOOLS,
|
|
21
30
|
gateOutputFor,
|
|
22
31
|
isAutoApprove,
|
|
23
32
|
localRepoKey,
|
|
@@ -27,32 +36,13 @@ import {
|
|
|
27
36
|
redactSecretsDeep,
|
|
28
37
|
resolveAutoResolveOrigin,
|
|
29
38
|
resolvePolicy
|
|
30
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-7YJS2VTG.js";
|
|
31
40
|
|
|
32
41
|
// src/policy.ts
|
|
33
42
|
import { createHash } from "crypto";
|
|
34
43
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
35
44
|
import { join } from "path";
|
|
36
45
|
import { tmpdir } from "os";
|
|
37
|
-
|
|
38
|
-
// src/validate.ts
|
|
39
|
-
var isPolicyConfig = (data) => {
|
|
40
|
-
if (!data || typeof data !== "object") return false;
|
|
41
|
-
const d = data;
|
|
42
|
-
return Array.isArray(d.policies) && typeof d.defaultTimeoutSeconds === "number" && typeof d.defaultTimeoutAction === "string";
|
|
43
|
-
};
|
|
44
|
-
var isAskUserResponse = (data) => {
|
|
45
|
-
if (!data || typeof data !== "object") return false;
|
|
46
|
-
const d = data;
|
|
47
|
-
return typeof d.correlationId === "string" && typeof d.status === "string";
|
|
48
|
-
};
|
|
49
|
-
var isWaitForAnswerResponse = (data) => {
|
|
50
|
-
if (!data || typeof data !== "object") return false;
|
|
51
|
-
const d = data;
|
|
52
|
-
return typeof d.answered === "boolean";
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// src/policy.ts
|
|
56
46
|
var CACHE_TTL_MS = 60 * 1e3;
|
|
57
47
|
var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
|
|
58
48
|
var policyRequestFor = (agentType) => ({
|
|
@@ -188,32 +178,8 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
|
188
178
|
};
|
|
189
179
|
var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
|
|
190
180
|
|
|
191
|
-
// src/api.ts
|
|
192
|
-
import { randomUUID } from "crypto";
|
|
193
|
-
installProxyDispatcher();
|
|
194
|
-
var askUser = async (apiKey, params, timeoutMs = 15e3) => {
|
|
195
|
-
const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
|
|
196
|
-
const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
|
|
197
|
-
if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
|
|
198
|
-
return result;
|
|
199
|
-
};
|
|
200
|
-
var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4) => {
|
|
201
|
-
const result = await callMcpTool(apiKey, "wait_for_answer", {
|
|
202
|
-
correlationId,
|
|
203
|
-
timeoutMs
|
|
204
|
-
}, { timeoutMs: timeoutMs + 1e4 });
|
|
205
|
-
if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
208
|
-
var cancelQuestion = async (apiKey, correlationId) => {
|
|
209
|
-
await callMcpTool(apiKey, "cancel_question", { correlationId });
|
|
210
|
-
};
|
|
211
|
-
var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
|
|
212
|
-
await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
|
|
213
|
-
};
|
|
214
|
-
|
|
215
181
|
// ../hook-mapping/src/describe.ts
|
|
216
|
-
import { isAbsolute, relative } from "path";
|
|
182
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
217
183
|
var hookPrefixes = {
|
|
218
184
|
Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
|
|
219
185
|
PowerShell: (input) => `powershell: ${input.command ?? "(no command)"}`,
|
|
@@ -221,7 +187,11 @@ var hookPrefixes = {
|
|
|
221
187
|
Write: (input) => `write file: ${input.file_path ?? "(unknown path)"}`,
|
|
222
188
|
Edit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
|
|
223
189
|
MultiEdit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
|
|
224
|
-
Read: (input) => `read file: ${input.file_path ?? "(unknown path)"}
|
|
190
|
+
Read: (input) => `read file: ${input.file_path ?? "(unknown path)"}`,
|
|
191
|
+
// Without an entry here the fallback stringifies the whole tool input, and for
|
|
192
|
+
// ExitPlanMode that input IS the plan: the action line became a truncated dump of
|
|
193
|
+
// the plan markdown. The plan belongs in the body, not the one-line summary.
|
|
194
|
+
ExitPlanMode: () => "finished planning and wants to start coding"
|
|
225
195
|
};
|
|
226
196
|
var describeTodoProgress = (todos) => {
|
|
227
197
|
if (!Array.isArray(todos)) return "updated the plan";
|
|
@@ -265,7 +235,7 @@ var deriveToolTarget = (toolName, toolInput) => {
|
|
|
265
235
|
if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
|
|
266
236
|
return deriveCommandHead(toolInput.command);
|
|
267
237
|
}
|
|
268
|
-
if (toolName
|
|
238
|
+
if (FILE_TARGET_TOOLS.has(toolName)) {
|
|
269
239
|
const filePath = toolInput.file_path;
|
|
270
240
|
if (typeof filePath !== "string") return void 0;
|
|
271
241
|
const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
|
|
@@ -287,6 +257,21 @@ var deriveReceiptCommandHead = (command) => {
|
|
|
287
257
|
return head ? redactSecrets(head).slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
|
|
288
258
|
};
|
|
289
259
|
var RECEIPT_TARGET_MAX_LENGTH = 256;
|
|
260
|
+
var parseApplyPatchOps = (command) => {
|
|
261
|
+
if (typeof command !== "string") return [];
|
|
262
|
+
const ops = [];
|
|
263
|
+
for (const line of command.split("\n")) {
|
|
264
|
+
const file = line.match(/^\*\*\*\s+(Add|Update|Delete) File:\s+(.+?)\s*$/);
|
|
265
|
+
if (file) {
|
|
266
|
+
ops.push({ op: file[1].toLowerCase(), path: file[2] });
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const move = line.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
|
|
270
|
+
if (move) ops.push({ op: "move", path: move[1] });
|
|
271
|
+
}
|
|
272
|
+
return ops;
|
|
273
|
+
};
|
|
274
|
+
var parseApplyPatchFiles = (command) => [...new Set(parseApplyPatchOps(command).map((entry) => entry.path))];
|
|
290
275
|
var EXIT_CODE_FAILURE = /^Error: Exit code \d+/;
|
|
291
276
|
var isToolResultError = (toolResult) => {
|
|
292
277
|
try {
|
|
@@ -314,19 +299,34 @@ var relativizeReceiptPath = (filePath, cwd) => {
|
|
|
314
299
|
var deriveReceiptMeta = (toolName, toolInput, toolResult, cwd) => {
|
|
315
300
|
try {
|
|
316
301
|
const ok = !isToolResultError(toolResult);
|
|
317
|
-
if (toolName === "
|
|
318
|
-
const
|
|
302
|
+
if (toolName === "apply_patch") {
|
|
303
|
+
const files = parseApplyPatchFiles(toolInput.command);
|
|
304
|
+
const target = files.length === 1 ? files[0] : toolInput.command;
|
|
305
|
+
if (typeof target !== "string" || !target) return void 0;
|
|
306
|
+
return { kind: "edit", target: redactSecrets(target).slice(0, RECEIPT_TARGET_MAX_LENGTH), ok };
|
|
307
|
+
}
|
|
308
|
+
const canonicalTool = {
|
|
309
|
+
run_shell_command: "Bash",
|
|
310
|
+
write_file: "Write",
|
|
311
|
+
replace: "Edit"
|
|
312
|
+
}[toolName] ?? toolName;
|
|
313
|
+
const canonicalInput = canonicalTool === toolName ? toolInput : {
|
|
314
|
+
...toolInput,
|
|
315
|
+
file_path: toolInput.file_path ?? toolInput.absolute_path ?? toolInput.path
|
|
316
|
+
};
|
|
317
|
+
if (FILE_TARGET_TOOLS.has(canonicalTool)) {
|
|
318
|
+
const filePath = canonicalInput.file_path;
|
|
319
319
|
if (typeof filePath !== "string" || !filePath) return void 0;
|
|
320
320
|
return {
|
|
321
|
-
kind:
|
|
321
|
+
kind: canonicalTool === "Write" ? "write" : "edit",
|
|
322
322
|
// A path is an unlikely place for a credential but not an impossible one
|
|
323
323
|
// (a token in a temp directory name), and this field is persisted.
|
|
324
324
|
target: redactSecrets(relativizeReceiptPath(filePath, cwd)).slice(0, RECEIPT_TARGET_MAX_LENGTH),
|
|
325
325
|
ok
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
-
if (
|
|
329
|
-
const head = deriveReceiptCommandHead(
|
|
328
|
+
if (canonicalTool === "Bash" || canonicalTool === "PowerShell" || canonicalTool === "Monitor") {
|
|
329
|
+
const head = deriveReceiptCommandHead(canonicalInput.command);
|
|
330
330
|
if (!head) return void 0;
|
|
331
331
|
return {
|
|
332
332
|
kind: head === "git commit" ? "commit" : "bash",
|
|
@@ -356,6 +356,7 @@ var rawActionBody = (toolName, toolInput) => {
|
|
|
356
356
|
+ ${after ?? ""}`;
|
|
357
357
|
}
|
|
358
358
|
if (toolName === "Write" || toolName === "write_file") return firstString(toolInput.content);
|
|
359
|
+
if (toolName === "ExitPlanMode") return firstString(toolInput.plan);
|
|
359
360
|
if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
|
|
360
361
|
return firstString(toolInput.command);
|
|
361
362
|
}
|
|
@@ -383,6 +384,13 @@ var deriveBlocker = (mode, reason) => {
|
|
|
383
384
|
|
|
384
385
|
// ../hook-mapping/src/map.ts
|
|
385
386
|
import { basename } from "path";
|
|
387
|
+
var AGENT_IDENTITIES = {
|
|
388
|
+
claude: { type: "claude_code", label: AGENT_LABELS_BY_TYPE.claude_code },
|
|
389
|
+
codex: { type: "codex", label: AGENT_LABELS_BY_TYPE.codex },
|
|
390
|
+
gemini: { type: "gemini_cli", label: AGENT_LABELS_BY_TYPE.gemini_cli },
|
|
391
|
+
cursor: { type: "cursor", label: AGENT_LABELS_BY_TYPE.cursor },
|
|
392
|
+
vscode: { type: "vscode", label: AGENT_LABELS_BY_TYPE.vscode }
|
|
393
|
+
};
|
|
386
394
|
var TASK_TITLE_MAX_LENGTH = 120;
|
|
387
395
|
var STOP_SUMMARY_MAX_LENGTH = 200;
|
|
388
396
|
var SESSION_END_ACTIONS = {
|
|
@@ -411,68 +419,22 @@ var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "
|
|
|
411
419
|
var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
|
|
412
420
|
var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
|
|
413
421
|
|
|
414
|
-
// src/pending.ts
|
|
415
|
-
import { join as join2 } from "path";
|
|
416
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
417
|
-
import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2, readdirSync, unlinkSync, rmSync, statSync } from "fs";
|
|
418
|
-
var PENDING_DIR = join2(tmpdir2(), "pushary-pending");
|
|
419
|
-
var DEFAULT_SESSION = "_no_session";
|
|
420
|
-
var GRACE_MS = 10 * 60 * 1e3;
|
|
421
|
-
var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
422
|
-
var dirFor = (sessionId) => join2(PENDING_DIR, sanitize(sessionId));
|
|
423
|
-
var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
|
|
424
|
-
var savePendingQuestion = (sessionId, correlationId) => {
|
|
425
|
-
const dir = dirFor(sessionId);
|
|
426
|
-
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
427
|
-
writeFileSync2(join2(dir, correlationId), "", "utf-8");
|
|
428
|
-
};
|
|
429
|
-
var listPendingQuestions = (sessionId) => {
|
|
430
|
-
const dir = dirFor(sessionId);
|
|
431
|
-
let files;
|
|
432
|
-
try {
|
|
433
|
-
files = readdirSync(dir);
|
|
434
|
-
} catch {
|
|
435
|
-
return [];
|
|
436
|
-
}
|
|
437
|
-
if (!isDefaultSession(sessionId)) return files;
|
|
438
|
-
const cutoff = Date.now() - GRACE_MS;
|
|
439
|
-
return files.filter((name) => {
|
|
440
|
-
try {
|
|
441
|
-
return statSync(join2(dir, name)).mtimeMs < cutoff;
|
|
442
|
-
} catch {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
};
|
|
447
|
-
var removePendingQuestion = (sessionId, correlationId) => {
|
|
448
|
-
try {
|
|
449
|
-
unlinkSync(join2(dirFor(sessionId), correlationId));
|
|
450
|
-
} catch {
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
var removePendingSession = (sessionId) => {
|
|
454
|
-
try {
|
|
455
|
-
rmSync(dirFor(sessionId), { recursive: true, force: true });
|
|
456
|
-
} catch {
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
|
|
460
422
|
// src/repo.ts
|
|
461
|
-
import { existsSync as
|
|
462
|
-
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as
|
|
423
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
424
|
+
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "path";
|
|
463
425
|
var MAX_PARENT_WALK = 64;
|
|
464
426
|
var GIT_FILE_PREFIX = "gitdir:";
|
|
465
427
|
var findGitDir = (startDir) => {
|
|
466
|
-
let current =
|
|
428
|
+
let current = resolve2(startDir);
|
|
467
429
|
for (let depth = 0; depth < MAX_PARENT_WALK; depth += 1) {
|
|
468
|
-
const candidate =
|
|
430
|
+
const candidate = join2(current, ".git");
|
|
469
431
|
try {
|
|
470
|
-
if (
|
|
471
|
-
if (
|
|
432
|
+
if (existsSync2(candidate)) {
|
|
433
|
+
if (statSync(candidate).isDirectory()) return candidate;
|
|
472
434
|
const pointer = readFileSync2(candidate, "utf-8").trim();
|
|
473
435
|
if (pointer.startsWith(GIT_FILE_PREFIX)) {
|
|
474
436
|
const target = pointer.slice(GIT_FILE_PREFIX.length).trim();
|
|
475
|
-
return isAbsolute2(target) ? target :
|
|
437
|
+
return isAbsolute2(target) ? target : resolve2(current, target);
|
|
476
438
|
}
|
|
477
439
|
}
|
|
478
440
|
} catch {
|
|
@@ -485,7 +447,7 @@ var findGitDir = (startDir) => {
|
|
|
485
447
|
};
|
|
486
448
|
var configPathFor = (gitDir) => {
|
|
487
449
|
const index = gitDir.replace(/\\/g, "/").indexOf("/worktrees/");
|
|
488
|
-
return index === -1 ?
|
|
450
|
+
return index === -1 ? join2(gitDir, "config") : join2(gitDir.slice(0, index), "config");
|
|
489
451
|
};
|
|
490
452
|
var ORIGIN_SECTION = /^\[remote "origin"\]/;
|
|
491
453
|
var SECTION_HEADER = /^\[/;
|
|
@@ -493,7 +455,7 @@ var URL_LINE = /^url\s*=\s*(.+)$/;
|
|
|
493
455
|
var readOriginRemote = (gitDir) => {
|
|
494
456
|
try {
|
|
495
457
|
const configPath = configPathFor(gitDir);
|
|
496
|
-
if (!
|
|
458
|
+
if (!existsSync2(configPath)) return void 0;
|
|
497
459
|
let inOrigin = false;
|
|
498
460
|
for (const rawLine of readFileSync2(configPath, "utf-8").split("\n")) {
|
|
499
461
|
const line = rawLine.trim();
|
|
@@ -520,7 +482,7 @@ var deriveRepoKey = (cwd) => {
|
|
|
520
482
|
const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
|
|
521
483
|
return localRepoKey(basename2(root));
|
|
522
484
|
}
|
|
523
|
-
return localRepoKey(basename2(
|
|
485
|
+
return localRepoKey(basename2(resolve2(dir)));
|
|
524
486
|
} catch {
|
|
525
487
|
return void 0;
|
|
526
488
|
}
|
|
@@ -542,9 +504,9 @@ var repoKeyFor = (cwd) => {
|
|
|
542
504
|
};
|
|
543
505
|
|
|
544
506
|
// src/usage.ts
|
|
545
|
-
import { closeSync, mkdirSync
|
|
546
|
-
import { join as
|
|
547
|
-
import { tmpdir as
|
|
507
|
+
import { closeSync, mkdirSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
508
|
+
import { join as join3 } from "path";
|
|
509
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
548
510
|
var DEFAULT_PRICES = [
|
|
549
511
|
{ match: "opus-4-1", in: 15, out: 75 },
|
|
550
512
|
{ match: "opus-4-0", in: 15, out: 75 },
|
|
@@ -579,8 +541,8 @@ var estimateCostUsd = (usage, model) => {
|
|
|
579
541
|
const perTokenOut = price.out / 1e6;
|
|
580
542
|
return usage.inputTokens * perTokenIn + usage.outputTokens * perTokenOut + usage.cacheCreationTokens * perTokenIn * CACHE_WRITE_MULTIPLIER + usage.cacheReadTokens * perTokenIn * CACHE_READ_MULTIPLIER;
|
|
581
543
|
};
|
|
582
|
-
var stateDir = () => process.env.PUSHARY_USAGE_DIR?.trim() ||
|
|
583
|
-
var stateFile = (sessionId) =>
|
|
544
|
+
var stateDir = () => process.env.PUSHARY_USAGE_DIR?.trim() || join3(tmpdir2(), "pushary-usage");
|
|
545
|
+
var stateFile = (sessionId) => join3(stateDir(), sessionId.replace(/[^a-zA-Z0-9_-]/g, "_"));
|
|
584
546
|
var emptyState = () => ({ offset: 0, tokensIn: 0, tokensOut: 0, costUsd: 0, recentIds: [] });
|
|
585
547
|
var readState = (path) => {
|
|
586
548
|
try {
|
|
@@ -659,7 +621,7 @@ var extractUserText = (content) => {
|
|
|
659
621
|
};
|
|
660
622
|
var readLastUserPrompt = (transcriptPath) => {
|
|
661
623
|
try {
|
|
662
|
-
const size =
|
|
624
|
+
const size = statSync2(transcriptPath).size;
|
|
663
625
|
const start = Math.max(0, size - USER_PROMPT_TAIL_BYTES);
|
|
664
626
|
const lines = readRange(transcriptPath, start, size).toString("utf-8").split("\n");
|
|
665
627
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -682,7 +644,7 @@ var readLastUserPrompt = (transcriptPath) => {
|
|
|
682
644
|
};
|
|
683
645
|
var readNewUsage = (transcriptPath, sessionId) => {
|
|
684
646
|
try {
|
|
685
|
-
const size =
|
|
647
|
+
const size = statSync2(transcriptPath).size;
|
|
686
648
|
const path = stateFile(sessionId);
|
|
687
649
|
let state = readState(path);
|
|
688
650
|
if (size < state.offset) state = { ...emptyState(), recentIds: state.recentIds };
|
|
@@ -698,8 +660,8 @@ var readNewUsage = (transcriptPath, sessionId) => {
|
|
|
698
660
|
state.offset += lastNewline + 1;
|
|
699
661
|
}
|
|
700
662
|
}
|
|
701
|
-
|
|
702
|
-
|
|
663
|
+
mkdirSync(stateDir(), { recursive: true });
|
|
664
|
+
writeFileSync2(path, JSON.stringify(state), "utf-8");
|
|
703
665
|
if (state.tokensIn === 0 && state.tokensOut === 0) return null;
|
|
704
666
|
return {
|
|
705
667
|
tokensIn: state.tokensIn,
|
|
@@ -713,7 +675,7 @@ var readNewUsage = (transcriptPath, sessionId) => {
|
|
|
713
675
|
};
|
|
714
676
|
|
|
715
677
|
// src/codex-adapter.ts
|
|
716
|
-
import { resolve as
|
|
678
|
+
import { resolve as resolve3 } from "path";
|
|
717
679
|
var CODEX_AGENT = { type: "codex", label: "Codex" };
|
|
718
680
|
var codexAllow = () => ({ kind: "allow" });
|
|
719
681
|
var codexDeny = (reason) => ({ kind: "deny", reason });
|
|
@@ -727,21 +689,6 @@ var toCodexWire = (event, decision) => {
|
|
|
727
689
|
decision.kind === "deny" ? decision.reason : void 0
|
|
728
690
|
) ?? null;
|
|
729
691
|
};
|
|
730
|
-
var parseApplyPatchOps = (command) => {
|
|
731
|
-
if (typeof command !== "string") return [];
|
|
732
|
-
const ops = [];
|
|
733
|
-
for (const line of command.split("\n")) {
|
|
734
|
-
const file = line.match(/^\*\*\*\s+(Add|Update|Delete) File:\s+(.+?)\s*$/);
|
|
735
|
-
if (file) {
|
|
736
|
-
ops.push({ op: file[1].toLowerCase(), path: file[2] });
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
const move = line.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
|
|
740
|
-
if (move) ops.push({ op: "move", path: move[1] });
|
|
741
|
-
}
|
|
742
|
-
return ops;
|
|
743
|
-
};
|
|
744
|
-
var parseApplyPatchFiles = (command) => [...new Set(parseApplyPatchOps(command).map((entry) => entry.path))];
|
|
745
692
|
var describeApplyPatch = (command) => {
|
|
746
693
|
const ops = parseApplyPatchOps(command);
|
|
747
694
|
if (ops.length === 0) return null;
|
|
@@ -757,7 +704,7 @@ var toPolicyLookup = (toolName, toolInput, cwd) => {
|
|
|
757
704
|
const command = toolInput.command;
|
|
758
705
|
const files = parseApplyPatchFiles(command);
|
|
759
706
|
if (files.length === 1) {
|
|
760
|
-
return { tool: "Edit", input: { file_path: cwd ?
|
|
707
|
+
return { tool: "Edit", input: { file_path: cwd ? resolve3(cwd, files[0]) : files[0] } };
|
|
761
708
|
}
|
|
762
709
|
return { tool: "Edit", input: typeof command === "string" ? { file_path: command } : {} };
|
|
763
710
|
};
|
|
@@ -773,7 +720,7 @@ var toPolicyLookups = (toolName, toolInput, cwd) => {
|
|
|
773
720
|
}
|
|
774
721
|
return {
|
|
775
722
|
tool: "Edit",
|
|
776
|
-
inputs: files.map((file) => ({ file_path: cwd ?
|
|
723
|
+
inputs: files.map((file) => ({ file_path: cwd ? resolve3(cwd, file) : file }))
|
|
777
724
|
};
|
|
778
725
|
};
|
|
779
726
|
var permissionTimeoutDecision = (timeoutAction) => {
|
|
@@ -784,18 +731,18 @@ var permissionTimeoutDecision = (timeoutAction) => {
|
|
|
784
731
|
var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
|
|
785
732
|
|
|
786
733
|
// src/throttle.ts
|
|
787
|
-
import { join as
|
|
788
|
-
import { tmpdir as
|
|
734
|
+
import { join as join4 } from "path";
|
|
735
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
789
736
|
import { createHash as createHash2 } from "crypto";
|
|
790
|
-
import { existsSync as
|
|
791
|
-
var THROTTLE_DIR =
|
|
792
|
-
var markerPath = (key) =>
|
|
737
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
738
|
+
var THROTTLE_DIR = join4(tmpdir3(), "pushary-throttle");
|
|
739
|
+
var markerPath = (key) => join4(THROTTLE_DIR, createHash2("sha256").update(key).digest("hex").slice(0, 16));
|
|
793
740
|
var throttlePass = (key, windowMs) => {
|
|
794
741
|
try {
|
|
795
742
|
const path = markerPath(key);
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
743
|
+
if (existsSync3(path) && Date.now() - statSync3(path).mtimeMs < windowMs) return false;
|
|
744
|
+
mkdirSync2(THROTTLE_DIR, { recursive: true });
|
|
745
|
+
writeFileSync3(path, "", "utf-8");
|
|
799
746
|
return true;
|
|
800
747
|
} catch {
|
|
801
748
|
return false;
|
|
@@ -803,27 +750,27 @@ var throttlePass = (key, windowMs) => {
|
|
|
803
750
|
};
|
|
804
751
|
|
|
805
752
|
// src/events.ts
|
|
806
|
-
import { basename as basename3, join as
|
|
807
|
-
import { existsSync as
|
|
808
|
-
import { tmpdir as
|
|
809
|
-
var INTENT_DIR =
|
|
753
|
+
import { basename as basename3, join as join5 } from "path";
|
|
754
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
755
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
756
|
+
var INTENT_DIR = join5(tmpdir4(), "pushary-intent");
|
|
810
757
|
var INTENT_MAX_BYTES = 2048;
|
|
811
758
|
var INTENT_GRACE_MS = 10 * 60 * 1e3;
|
|
812
759
|
var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
813
|
-
var intentFile = (sessionId) =>
|
|
760
|
+
var intentFile = (sessionId) => join5(INTENT_DIR, sanitizeSession(sessionId));
|
|
814
761
|
var saveLastPrompt = (sessionId, prompt) => {
|
|
815
762
|
try {
|
|
816
763
|
const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
|
|
817
764
|
if (!trimmed) return;
|
|
818
|
-
|
|
819
|
-
|
|
765
|
+
mkdirSync3(INTENT_DIR, { recursive: true });
|
|
766
|
+
writeFileSync4(intentFile(sessionId), trimmed, "utf-8");
|
|
820
767
|
} catch {
|
|
821
768
|
}
|
|
822
769
|
};
|
|
823
770
|
var readLastPrompt = (sessionId) => {
|
|
824
771
|
try {
|
|
825
772
|
const path = intentFile(sessionId);
|
|
826
|
-
if (isDefaultSession(sessionId) &&
|
|
773
|
+
if (isDefaultSession(sessionId) && statSync4(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
|
|
827
774
|
const value = readFileSync4(path, "utf-8").trim();
|
|
828
775
|
return value.length > 0 ? value : void 0;
|
|
829
776
|
} catch {
|
|
@@ -882,7 +829,7 @@ var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
882
829
|
var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
|
|
883
830
|
const request = policyRequestFor(agentType);
|
|
884
831
|
const path = policyCacheFile(apiKey, request.agent, request.repoAware);
|
|
885
|
-
if (!
|
|
832
|
+
if (!existsSync4(path)) return null;
|
|
886
833
|
const cached = JSON.parse(readFileSync4(path, "utf-8"));
|
|
887
834
|
if (!isPolicyConfig(cached)) return null;
|
|
888
835
|
if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
@@ -1244,18 +1191,12 @@ export {
|
|
|
1244
1191
|
getPolicy,
|
|
1245
1192
|
fetchModeState,
|
|
1246
1193
|
fetchModeOverride,
|
|
1247
|
-
askUser,
|
|
1248
|
-
waitForAnswer,
|
|
1249
|
-
cancelQuestion,
|
|
1250
|
-
sendNotification,
|
|
1251
1194
|
describeToolCall,
|
|
1252
1195
|
deriveToolTarget,
|
|
1253
1196
|
scopePathFor,
|
|
1254
1197
|
deriveActionBody,
|
|
1255
1198
|
deriveAction,
|
|
1256
1199
|
deriveBlocker,
|
|
1257
|
-
DEFAULT_SESSION,
|
|
1258
|
-
savePendingQuestion,
|
|
1259
1200
|
repoKeyFor,
|
|
1260
1201
|
throttlePass,
|
|
1261
1202
|
readLastUserPrompt,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasGeminiHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZZE6FN7T.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-B3V2H6SA.js";
|
|
7
7
|
|
|
8
8
|
// src/diagnostics/wiring.ts
|
|
9
9
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
HOOK_BUDGETS
|
|
3
|
-
|
|
2
|
+
HOOK_BUDGETS,
|
|
3
|
+
HOOK_LOCATOR_BINARY
|
|
4
|
+
} from "./chunk-7YJS2VTG.js";
|
|
4
5
|
|
|
5
6
|
// src/gemini-config.ts
|
|
6
7
|
var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
|
|
@@ -23,7 +24,10 @@ var ensureRecord = (target, key) => {
|
|
|
23
24
|
var isPusharyGeminiHook = (entry) => {
|
|
24
25
|
const hooks = asRecord(entry)?.hooks;
|
|
25
26
|
if (!Array.isArray(hooks)) return false;
|
|
26
|
-
return hooks.some((hook) =>
|
|
27
|
+
return hooks.some((hook) => {
|
|
28
|
+
const command = String(asRecord(hook)?.command ?? "");
|
|
29
|
+
return command.includes(GEMINI_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
|
|
30
|
+
});
|
|
27
31
|
};
|
|
28
32
|
var addGeminiMcpServer = (settings, apiKey) => {
|
|
29
33
|
const mcpServers = ensureRecord(settings, "mcpServers");
|
package/dist/src/index.d.ts
CHANGED
|
@@ -132,13 +132,13 @@ interface AskUserResponse {
|
|
|
132
132
|
noDevices?: boolean;
|
|
133
133
|
answerUrl?: string;
|
|
134
134
|
}
|
|
135
|
-
interface
|
|
135
|
+
interface AnswerResult {
|
|
136
136
|
answered: boolean;
|
|
137
137
|
value?: string;
|
|
138
138
|
note?: string;
|
|
139
139
|
}
|
|
140
140
|
declare const askUser: (apiKey: string, params: AskUserParams, timeoutMs?: number) => Promise<AskUserResponse>;
|
|
141
|
-
declare const waitForAnswer: (apiKey: string, correlationId: string, timeoutMs?: number) => Promise<
|
|
141
|
+
declare const waitForAnswer: (apiKey: string, correlationId: string, timeoutMs?: number) => Promise<AnswerResult>;
|
|
142
142
|
declare const cancelQuestion: (apiKey: string, correlationId: string) => Promise<void>;
|
|
143
143
|
|
|
144
144
|
declare const getApiKey: () => string;
|
package/dist/src/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-VT4IVERX.js";
|
|
7
|
-
import "../chunk-PKUNB3SN.js";
|
|
3
|
+
} from "../chunk-HJ44HIUW.js";
|
|
4
|
+
import "../chunk-ATBKJNWS.js";
|
|
5
|
+
import "../chunk-7MBDFNUC.js";
|
|
8
6
|
import "../chunk-YHG74UFF.js";
|
|
9
7
|
import {
|
|
10
|
-
askUser,
|
|
11
|
-
cancelQuestion,
|
|
12
8
|
fetchModeOverride,
|
|
13
9
|
fetchModeState,
|
|
14
10
|
getPolicy,
|
|
15
11
|
handleNotification,
|
|
16
12
|
handlePostToolUse,
|
|
17
13
|
handleStop,
|
|
18
|
-
reportEvent
|
|
14
|
+
reportEvent
|
|
15
|
+
} from "../chunk-V2XPF77V.js";
|
|
16
|
+
import {
|
|
17
|
+
askUser,
|
|
18
|
+
cancelQuestion,
|
|
19
19
|
waitForAnswer
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-J3YDT4HM.js";
|
|
21
21
|
import "../chunk-RN3NOEJF.js";
|
|
22
22
|
import "../chunk-BSZYIAZL.js";
|
|
23
23
|
import "../chunk-SAF6HGAA.js";
|
|
24
|
-
import "../chunk-
|
|
24
|
+
import "../chunk-Y3UTTFTZ.js";
|
|
25
25
|
import "../chunk-7QLSKOSU.js";
|
|
26
26
|
import "../chunk-KZERVKTD.js";
|
|
27
27
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "../chunk-2UMNXADU.js";
|
|
31
31
|
import {
|
|
32
32
|
resolvePolicy
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-7YJS2VTG.js";
|
|
34
34
|
export {
|
|
35
35
|
askUser,
|
|
36
36
|
cancelQuestion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"pushary-stopfailure-hook": "./dist/bin/pushary-stopfailure-hook.js",
|
|
49
49
|
"pushary-permission-hook": "./dist/bin/pushary-permission-hook.js",
|
|
50
50
|
"pushary-permission-denied-hook": "./dist/bin/pushary-permission-denied-hook.js",
|
|
51
|
+
"pushary-elicitation-hook": "./dist/bin/pushary-elicitation-hook.js",
|
|
51
52
|
"pushary-claude": "./dist/bin/pushary-claude.js",
|
|
52
53
|
"pushary-daemon": "./dist/bin/pushary-daemon.js",
|
|
53
54
|
"pushary-codex": "./dist/bin/pushary-codex.js",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"dev": "tsup --watch",
|
|
82
83
|
"test": "node ../../scripts/ci/run-tests.mjs src bin",
|
|
83
84
|
"typecheck": "tsc --noEmit",
|
|
85
|
+
"conformance": "bun scripts/hook-conformance.ts",
|
|
84
86
|
"gen:hooks-spec": "bun scripts/generate-hooks-spec.ts"
|
|
85
87
|
},
|
|
86
88
|
"dependencies": {
|