@pushary/agent-hooks 0.68.0 → 0.70.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 +138 -0
- package/README.md +3 -2
- package/data/cursor-plugin/scripts/pushary-gate.mjs +70 -15
- package/data/vscode-plugin/scripts/pushary-gate.mjs +59 -11
- package/dist/bin/pushary-claude.js +21 -11
- package/dist/bin/pushary-clean.js +60 -22
- package/dist/bin/pushary-codex-hook.js +38 -23
- package/dist/bin/pushary-codex.js +1 -1
- package/dist/bin/pushary-connect.js +6 -4
- package/dist/bin/pushary-daemon.js +3 -3
- package/dist/bin/pushary-doctor.js +120 -28
- package/dist/bin/pushary-gemini-hook.js +32 -16
- package/dist/bin/pushary-hook.js +5 -5
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +2 -2
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +1 -1
- 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 +1 -1
- package/dist/bin/pushary-prompt-hook.js +1 -1
- package/dist/bin/pushary-session-end-hook.js +1 -1
- package/dist/bin/pushary-session-start-hook.js +1 -1
- package/dist/bin/pushary-setup.js +90 -13
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +4 -4
- package/dist/bin/pushary-stop-hook.js +1 -1
- package/dist/bin/pushary-stopfailure-hook.js +1 -1
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-JGH37QRB.js → chunk-7OYGFJYZ.js} +1 -1
- package/dist/chunk-B7R4YSAB.js +38 -0
- package/dist/{chunk-ATP6UQPH.js → chunk-BBK3TTU2.js} +1 -1
- package/dist/{chunk-XOUYM27W.js → chunk-FZIQSDTQ.js} +207 -113
- package/dist/chunk-GD275GEX.js +66 -0
- package/dist/{chunk-SONXGCZY.js → chunk-HU43EZ5Q.js} +45 -27
- package/dist/{chunk-KB4ODLGB.js → chunk-I4BYZIX4.js} +2 -0
- package/dist/{chunk-YMFZUXL5.js → chunk-MGFZXUUR.js} +57 -4
- package/dist/{chunk-YZT2CBC3.js → chunk-NQTRA7H5.js} +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-YHG74UFF.js +0 -12
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
} from "./chunk-CAJZAFVS.js";
|
|
4
4
|
import {
|
|
5
5
|
denyReasonFrom,
|
|
6
|
-
isDeferAnswer
|
|
7
|
-
|
|
6
|
+
isDeferAnswer,
|
|
7
|
+
resolveGate
|
|
8
|
+
} from "./chunk-GD275GEX.js";
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_SESSION,
|
|
10
11
|
askUser,
|
|
@@ -19,13 +20,12 @@ import {
|
|
|
19
20
|
readLastPrompt,
|
|
20
21
|
readLastUserPrompt,
|
|
21
22
|
repoKeyFor,
|
|
22
|
-
resolvePolicy,
|
|
23
23
|
savePendingQuestion,
|
|
24
24
|
scopePathFor,
|
|
25
25
|
sendNotification,
|
|
26
26
|
throttlePass,
|
|
27
27
|
waitForAnswer
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FZIQSDTQ.js";
|
|
29
29
|
import {
|
|
30
30
|
isGatingMoment,
|
|
31
31
|
recordKeylessMoment
|
|
@@ -33,11 +33,8 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
buildDecisionEpisodeFeatures,
|
|
35
35
|
effectiveWaitSeconds,
|
|
36
|
-
evaluateScope,
|
|
37
36
|
hookWaitClamped,
|
|
38
|
-
hookWaitDeadline
|
|
39
|
-
scopeChangeReason,
|
|
40
|
-
scopeRequiresHuman
|
|
37
|
+
hookWaitDeadline
|
|
41
38
|
} from "./chunk-DQAN3JQP.js";
|
|
42
39
|
import {
|
|
43
40
|
getMachineId
|
|
@@ -383,6 +380,18 @@ var handleKeyless = (input) => {
|
|
|
383
380
|
}
|
|
384
381
|
return void 0;
|
|
385
382
|
};
|
|
383
|
+
var claudeGateInput = (modeState, config, input) => {
|
|
384
|
+
const scopePath = modeState.scope ? scopePathFor(input.tool_name, input.tool_input, input.cwd) : void 0;
|
|
385
|
+
return {
|
|
386
|
+
modeState,
|
|
387
|
+
config,
|
|
388
|
+
toolName: input.tool_name,
|
|
389
|
+
toolInputs: [input.tool_input ?? {}],
|
|
390
|
+
scopePaths: scopePath ? [scopePath] : [],
|
|
391
|
+
cwd: input.cwd,
|
|
392
|
+
repoKey: repoKeyFor(input.cwd)
|
|
393
|
+
};
|
|
394
|
+
};
|
|
386
395
|
var handlePreToolUse = async (input) => {
|
|
387
396
|
if (input.tool_name.startsWith("mcp__pushary__")) return void 0;
|
|
388
397
|
let apiKey;
|
|
@@ -394,26 +403,26 @@ var handlePreToolUse = async (input) => {
|
|
|
394
403
|
try {
|
|
395
404
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
396
405
|
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
406
|
+
const verdict = resolveGate(claudeGateInput(modeState, policy, input));
|
|
407
|
+
if (verdict.kind === "kill") return deny(verdict.reason);
|
|
400
408
|
if (input.tool_name === "AskUserQuestion") {
|
|
401
409
|
return handleAskUserQuestion(apiKey, input);
|
|
402
410
|
}
|
|
403
411
|
if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
|
|
404
412
|
return void 0;
|
|
405
413
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
414
|
+
switch (verdict.kind) {
|
|
415
|
+
case "allow":
|
|
416
|
+
return allow();
|
|
417
|
+
case "deny":
|
|
418
|
+
return deny(verdict.reason);
|
|
419
|
+
// State we could not establish, with an auto-approve on the table. No
|
|
420
|
+
// opinion, so Claude Code's own flow runs as if the hook were absent.
|
|
421
|
+
case "defer":
|
|
422
|
+
return void 0;
|
|
423
|
+
default:
|
|
424
|
+
return dispatchModeHandler(apiKey, input, verdict.policy, getMachineId(), modeState.trainingConsent, verdict.scopeReason, verdict.scopePath);
|
|
415
425
|
}
|
|
416
|
-
return dispatchModeHandler(apiKey, input, toolPolicy, getMachineId(), modeState.trainingConsent, scopeReason, scopePath);
|
|
417
426
|
} catch {
|
|
418
427
|
return void 0;
|
|
419
428
|
}
|
|
@@ -452,13 +461,22 @@ var handlePermissionRequest = async (input) => {
|
|
|
452
461
|
try {
|
|
453
462
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
454
463
|
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
464
|
+
const verdict = resolveGate(claudeGateInput(modeState, policy, input));
|
|
465
|
+
switch (verdict.kind) {
|
|
466
|
+
case "kill":
|
|
467
|
+
return permReqDeny(verdict.reason);
|
|
468
|
+
case "allow":
|
|
469
|
+
return permReqAllow();
|
|
470
|
+
case "deny":
|
|
471
|
+
return permReqDeny(verdict.reason);
|
|
472
|
+
// No opinion, so Claude Code's own dialog shows.
|
|
473
|
+
case "defer":
|
|
474
|
+
return void 0;
|
|
475
|
+
default:
|
|
476
|
+
return toPermissionRequestOutput(
|
|
477
|
+
await dispatchModeHandler(apiKey, input, verdict.policy, getMachineId(), modeState.trainingConsent, verdict.scopeReason, verdict.scopePath)
|
|
478
|
+
);
|
|
460
479
|
}
|
|
461
|
-
return toPermissionRequestOutput(await dispatchModeHandler(apiKey, input, toolPolicy, getMachineId(), modeState.trainingConsent));
|
|
462
480
|
} catch {
|
|
463
481
|
return void 0;
|
|
464
482
|
}
|
|
@@ -45,6 +45,27 @@ var describeReach = (channels) => {
|
|
|
45
45
|
if (verdict.kind === "unattributable") return `${summary}, and no way to tell whose`;
|
|
46
46
|
return summary;
|
|
47
47
|
};
|
|
48
|
+
var describeAppRepair = (verdict) => {
|
|
49
|
+
switch (verdict.kind) {
|
|
50
|
+
case "yours":
|
|
51
|
+
return null;
|
|
52
|
+
case "nobody":
|
|
53
|
+
return [
|
|
54
|
+
"Open the Pushary app, sign in, and allow notifications.",
|
|
55
|
+
"If this terminal was never paired with your phone, run: npx @pushary/agent-hooks@latest setup --connect app"
|
|
56
|
+
];
|
|
57
|
+
case "others":
|
|
58
|
+
return [
|
|
59
|
+
`Reachable devices belong to other members of this workspace (${verdict.otherMembers}).`,
|
|
60
|
+
"Sign in on the Pushary app with your own account so approvals can reach you."
|
|
61
|
+
];
|
|
62
|
+
case "unattributable":
|
|
63
|
+
return [
|
|
64
|
+
"Something is reachable here, but not identifiably yours.",
|
|
65
|
+
"Sign in on the Pushary app so your approvals are routed to your device."
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
48
69
|
|
|
49
70
|
// src/cli/human.ts
|
|
50
71
|
var stream = process.stdout;
|
|
@@ -324,7 +345,12 @@ var printQr = (url) => new Promise((resolve, reject) => {
|
|
|
324
345
|
reject(err);
|
|
325
346
|
}
|
|
326
347
|
});
|
|
327
|
-
var phoneFailure = (detail) => ({
|
|
348
|
+
var phoneFailure = (detail, repair) => ({
|
|
349
|
+
reachesYou: false,
|
|
350
|
+
outcome: null,
|
|
351
|
+
detail,
|
|
352
|
+
...repair ? { repair } : {}
|
|
353
|
+
});
|
|
328
354
|
var waitForDevice = async (apiKey, baseline) => {
|
|
329
355
|
const deadline = Date.now() + CONNECT_TIMEOUT_MS;
|
|
330
356
|
let consecutiveFailures = 0;
|
|
@@ -469,9 +495,11 @@ var confirmAppConnection = async (apiKey) => {
|
|
|
469
495
|
const channels = probe.status;
|
|
470
496
|
const ownedApp = channels.yours?.appDevices ?? channels.appDevices;
|
|
471
497
|
if (channels.precise && ownedApp === 0) {
|
|
472
|
-
|
|
473
|
-
console.log(` ${
|
|
474
|
-
|
|
498
|
+
const repair = describeAppRepair(reachVerdict(channels)) ?? [];
|
|
499
|
+
console.log(` ${yellow("!")} No device of yours is registered for notifications yet.`);
|
|
500
|
+
for (const line of repair) console.log(` ${dim(line)}`);
|
|
501
|
+
console.log(` ${dim("Then run")} ${cyan("pushary doctor")}${dim(".")}`);
|
|
502
|
+
return phoneFailure("app_not_registered", repair);
|
|
475
503
|
}
|
|
476
504
|
return sendAndConfirm(apiKey, channels);
|
|
477
505
|
};
|
|
@@ -533,6 +561,7 @@ var cancelPairing = async (pairId) => {
|
|
|
533
561
|
} catch {
|
|
534
562
|
}
|
|
535
563
|
};
|
|
564
|
+
var PAIR_MAX_CONSECUTIVE_FAILURES = 8;
|
|
536
565
|
var claimPairing = async (pairId) => {
|
|
537
566
|
try {
|
|
538
567
|
const res = await fetch(`${apiBase()}/api/mobile/pair/claim?id=${encodeURIComponent(pairId)}`, {
|
|
@@ -544,6 +573,17 @@ var claimPairing = async (pairId) => {
|
|
|
544
573
|
return null;
|
|
545
574
|
}
|
|
546
575
|
};
|
|
576
|
+
var ackPairing = async (pairId) => {
|
|
577
|
+
try {
|
|
578
|
+
await fetch(`${apiBase()}/api/mobile/pair/ack`, {
|
|
579
|
+
method: "POST",
|
|
580
|
+
headers: { "Content-Type": "application/json" },
|
|
581
|
+
body: JSON.stringify({ pairId }),
|
|
582
|
+
signal: AbortSignal.timeout(5e3)
|
|
583
|
+
});
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
};
|
|
547
587
|
var buildPairLinks = (pairId, publicKeyB64, baseUrl = apiBase()) => {
|
|
548
588
|
const id = encodeURIComponent(pairId);
|
|
549
589
|
const pk = encodeURIComponent(publicKeyB64);
|
|
@@ -593,10 +633,22 @@ var connectViaAppPairing = async (options = {}) => {
|
|
|
593
633
|
void Promise.allSettled([Promise.resolve(flushed), cancelPairing(pairId)]).finally(() => process.exit(EXIT_ABORTED));
|
|
594
634
|
};
|
|
595
635
|
process.once("SIGINT", onInterrupt);
|
|
636
|
+
let consecutiveFailures = 0;
|
|
596
637
|
try {
|
|
597
638
|
while (Date.now() < deadline) {
|
|
598
639
|
if (cancelled) return null;
|
|
599
640
|
const claim = await claimPairing(pairId);
|
|
641
|
+
if (claim === null) {
|
|
642
|
+
consecutiveFailures += 1;
|
|
643
|
+
if (consecutiveFailures >= PAIR_MAX_CONSECUTIVE_FAILURES) {
|
|
644
|
+
stop(yellow("!"), "Can't reach pushary.com to finish pairing. Check your connection, then re-run setup.");
|
|
645
|
+
await cancelPairing(pairId);
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
await sleep(PAIR_POLL_INTERVAL_MS);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
consecutiveFailures = 0;
|
|
600
652
|
if (claim?.status === "authorized") {
|
|
601
653
|
let apiKey;
|
|
602
654
|
try {
|
|
@@ -606,6 +658,7 @@ var connectViaAppPairing = async (options = {}) => {
|
|
|
606
658
|
await cancelPairing(pairId);
|
|
607
659
|
return null;
|
|
608
660
|
}
|
|
661
|
+
await ackPairing(pairId);
|
|
609
662
|
stop(check, "App connected");
|
|
610
663
|
return { apiKey, handle: claim.siteSlug };
|
|
611
664
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ interface ModeState {
|
|
|
36
36
|
readonly relayUrl: string | null;
|
|
37
37
|
readonly trainingConsent?: boolean;
|
|
38
38
|
readonly scope?: ScopeContract | null;
|
|
39
|
+
readonly degraded?: boolean;
|
|
39
40
|
}
|
|
40
41
|
declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
|
|
41
42
|
declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
|
package/dist/src/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-HU43EZ5Q.js";
|
|
4
4
|
import "../chunk-CAJZAFVS.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-GD275GEX.js";
|
|
6
6
|
import {
|
|
7
7
|
askUser,
|
|
8
8
|
cancelQuestion,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
reportEvent,
|
|
16
16
|
resolvePolicy,
|
|
17
17
|
waitForAnswer
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-FZIQSDTQ.js";
|
|
19
19
|
import "../chunk-WLGEY4UX.js";
|
|
20
20
|
import "../chunk-BSZYIAZL.js";
|
|
21
21
|
import "../chunk-SAF6HGAA.js";
|
package/package.json
CHANGED
package/dist/chunk-YHG74UFF.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// src/answer.ts
|
|
2
|
-
var denyReasonFrom = (value, note) => {
|
|
3
|
-
const trimmed = note?.trim();
|
|
4
|
-
if (trimmed) return `Denied from your phone: ${trimmed}`;
|
|
5
|
-
return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
|
|
6
|
-
};
|
|
7
|
-
var isDeferAnswer = (value) => value === "defer";
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
denyReasonFrom,
|
|
11
|
-
isDeferAnswer
|
|
12
|
-
};
|