@pushary/agent-hooks 1.9.7 → 1.9.9
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 +18 -0
- package/README.md +3 -3
- package/data/cursor-plugin/README.md +3 -7
- package/data/vscode-plugin/README.md +1 -1
- package/dist/bin/pushary-bell-hook.js +22 -24
- package/dist/bin/pushary-bell.js +13 -13
- package/dist/bin/pushary-claude.js +28 -30
- package/dist/bin/pushary-clean.js +113 -94
- package/dist/bin/pushary-codex-bridge.js +20 -20
- package/dist/bin/pushary-codex-hook.js +36 -38
- package/dist/bin/pushary-codex.js +13 -13
- package/dist/bin/pushary-connect.js +48 -22
- package/dist/bin/pushary-cowork.js +5 -5
- package/dist/bin/pushary-daemon.js +50 -40
- package/dist/bin/pushary-disconnect.js +188 -56
- package/dist/bin/pushary-doctor.js +105 -85
- package/dist/bin/pushary-elicitation-hook.js +25 -27
- package/dist/bin/pushary-gemini-bridge.js +13 -13
- package/dist/bin/pushary-gemini-hook.js +31 -33
- package/dist/bin/pushary-hook.js +36 -39
- package/dist/bin/pushary-login.js +12 -12
- package/dist/bin/pushary-logout.js +105 -42
- package/dist/bin/pushary-mcp.js +9 -9
- package/dist/bin/pushary-mode.js +28 -31
- package/dist/bin/pushary-notification-hook.js +29 -32
- package/dist/bin/pushary-opencode-hook.js +30 -32
- package/dist/bin/pushary-permission-denied-hook.js +30 -33
- package/dist/bin/pushary-permission-hook.js +33 -36
- package/dist/bin/pushary-post-hook.js +30 -33
- package/dist/bin/pushary-prompt-hook.js +29 -32
- package/dist/bin/pushary-session-end-hook.js +29 -32
- package/dist/bin/pushary-session-start-hook.js +33 -36
- package/dist/bin/pushary-setup.js +168 -160
- package/dist/bin/pushary-stats.js +5 -5
- package/dist/bin/pushary-status.js +65 -19
- package/dist/bin/pushary-stop-hook.js +29 -32
- package/dist/bin/pushary-stopfailure-hook.js +29 -32
- package/dist/bin/pushary-suggestions.js +19 -21
- package/dist/bin/pushary-transcript-register.js +18 -20
- package/dist/bin/pushary-transcripts.js +7 -7
- package/dist/bin/pushary-upgrade.js +50 -26
- package/dist/bin/pushary-wait.js +23 -19
- package/dist/bin/pushary.js +15 -14
- package/dist/{chunk-4FK6RZVJ.js → chunk-462EQA6K.js} +1 -1
- package/dist/{chunk-STQRUH3S.js → chunk-5DCDQ5VW.js} +59 -15
- package/dist/{chunk-JXISQ7DT.js → chunk-5N5L6DNN.js} +3 -3
- package/dist/{chunk-QR6VE5ME.js → chunk-5RGA636G.js} +79 -17
- package/dist/{chunk-L76HTPU6.js → chunk-6AYSKVLL.js} +3 -3
- package/dist/{chunk-NIBFSARF.js → chunk-6EBGOHDJ.js} +33 -13
- package/dist/{chunk-QQ4HBXZC.js → chunk-6W5M3MSE.js} +84 -673
- package/dist/{chunk-R3YW55MY.js → chunk-AI7RMFVV.js} +29 -26
- package/dist/{chunk-AUUXS44H.js → chunk-APP43AW7.js} +1 -1
- package/dist/{chunk-RQGODFQN.js → chunk-AZOKPWVI.js} +1 -1
- package/dist/{chunk-3GNUXXDZ.js → chunk-B5NMSP3V.js} +2 -2
- package/dist/{chunk-4D2AYNSE.js → chunk-BNSKVIAW.js} +2 -2
- package/dist/chunk-BYLKCZ2G.js +367 -0
- package/dist/{chunk-X5NXPCIC.js → chunk-BZLHTMT6.js} +89 -7
- package/dist/{chunk-BTQGAXZ3.js → chunk-COQ64C4F.js} +10 -10
- package/dist/{chunk-N4EUSDKX.js → chunk-DCCSULXK.js} +14 -14
- package/dist/{chunk-JWKBDSPF.js → chunk-DP3WX7FW.js} +2 -2
- package/dist/chunk-E5HOHIUV.js +52 -0
- package/dist/{chunk-GU3EJCVV.js → chunk-EA5NWDK7.js} +4 -0
- package/dist/chunk-EIAEUG7G.js +46 -0
- package/dist/{chunk-EB4XGVBG.js → chunk-EUW7JL2K.js} +0 -32
- package/dist/{chunk-T7Y5EADL.js → chunk-F7R3SZ4Q.js} +2 -2
- package/dist/{chunk-H37L7TT3.js → chunk-G7RIDXAT.js} +2 -2
- package/dist/{chunk-W2KI5KPD.js → chunk-H5T27W7M.js} +2 -2
- package/dist/chunk-HBWYMRR5.js +94 -0
- package/dist/{chunk-CBQACHKY.js → chunk-IIVZZMSO.js} +1 -1
- package/dist/{chunk-Z6HPS67T.js → chunk-IRHABAQT.js} +1 -1
- package/dist/{chunk-QTO6HEZD.js → chunk-K545B734.js} +2 -2
- package/dist/{chunk-KYO45G5L.js → chunk-KOWFRUIT.js} +1 -1
- package/dist/{chunk-QFEM7R6T.js → chunk-KQFTBQ66.js} +11 -11
- package/dist/chunk-LENBKYRB.js +50 -0
- package/dist/{chunk-JCWZ23MJ.js → chunk-M3STIAGU.js} +3 -3
- package/dist/{chunk-6DJTDHBI.js → chunk-MD4SVNT6.js} +1 -1
- package/dist/{chunk-7264S5HZ.js → chunk-MLGIVVSG.js} +1 -1
- package/dist/{chunk-H7MMU64M.js → chunk-NGKUXZID.js} +2 -2
- package/dist/{chunk-JWQZSYCK.js → chunk-NUVGDBZC.js} +1 -1
- package/dist/{chunk-AQ55ZSSS.js → chunk-OAYWO5NP.js} +1 -1
- package/dist/chunk-Q6C6ILUS.js +49 -0
- package/dist/{chunk-MBK7N46B.js → chunk-QIOIO7VN.js} +6 -6
- package/dist/{chunk-KFKL3FHS.js → chunk-QRXPWW3M.js} +4 -3
- package/dist/{chunk-7OIWWZAD.js → chunk-QTIUJZRX.js} +3 -3
- package/dist/{chunk-XWAYAO3V.js → chunk-R5RIVOPL.js} +2 -2
- package/dist/{chunk-ADSQDYYG.js → chunk-REU2VQPH.js} +54 -5
- package/dist/{chunk-GKI6LFAV.js → chunk-RQ32ADJP.js} +2 -2
- package/dist/{chunk-VXRXFU6Q.js → chunk-SZ4Q6ZVS.js} +14 -7
- package/dist/{chunk-KT5E2RLS.js → chunk-T6ZN4TEF.js} +1 -1
- package/dist/{chunk-UFVGSEWH.js → chunk-TO3PS646.js} +2 -2
- package/dist/chunk-UAP5DS6P.js +599 -0
- package/dist/{chunk-MZPXVH4A.js → chunk-UF6RBP5V.js} +204 -28
- package/dist/{chunk-AVNKVF7N.js → chunk-UPIZSCYD.js} +4 -4
- package/dist/{chunk-3PVDOCFI.js → chunk-VHPFNLRS.js} +5 -5
- package/dist/{chunk-HPRZJKRO.js → chunk-VIIHC42X.js} +1 -1
- package/dist/{chunk-D6S5EKXA.js → chunk-WRPYCIV2.js} +8 -12
- package/dist/{chunk-ZUTU43FT.js → chunk-Z23ZBYDW.js} +6 -4
- package/dist/{chunk-SBLPHUEB.js → chunk-ZGADIZDS.js} +8 -6
- package/dist/{global-upgrade-E2HUEB4B.js → global-upgrade-H272QOGD.js} +6 -6
- package/dist/{paths-QVTEGNZV.js → paths-HMVOTFUN.js} +2 -2
- package/dist/reapply-Q5NHDTVU.js +55 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +19 -20
- package/dist/{upgrade-lock-JCIDZMHF.js → upgrade-lock-L3GHFIRW.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-4YO4T5FV.js +0 -149
- package/dist/chunk-ANEAA37E.js +0 -197
- package/dist/chunk-MNWRUGMH.js +0 -86
- package/dist/chunk-RQSOEB4S.js +0 -44
- package/dist/chunk-SD3272LS.js +0 -143
- package/dist/reapply-5QVKM6LA.js +0 -38
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
activeHookSource,
|
|
3
|
-
activeIdentity
|
|
4
|
-
} from "./chunk-MZPXVH4A.js";
|
|
5
1
|
import {
|
|
6
2
|
defersToNativeMode,
|
|
7
3
|
reportMachineDenial
|
|
8
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-B5NMSP3V.js";
|
|
9
5
|
import {
|
|
10
6
|
isGatingMoment,
|
|
11
7
|
recordKeylessMoment
|
|
12
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IIVZZMSO.js";
|
|
13
9
|
import {
|
|
14
10
|
announceSubscriptionRequired,
|
|
15
11
|
denyReasonFrom,
|
|
16
12
|
isDeferAnswer,
|
|
17
13
|
oncePerSession
|
|
18
14
|
} from "./chunk-5OP5MQI7.js";
|
|
15
|
+
import {
|
|
16
|
+
activeHookSource,
|
|
17
|
+
activeIdentity
|
|
18
|
+
} from "./chunk-UF6RBP5V.js";
|
|
19
19
|
import {
|
|
20
20
|
UNNAMED_ACTION,
|
|
21
21
|
approvalAwaitingRetry,
|
|
@@ -39,13 +39,11 @@ import {
|
|
|
39
39
|
reportEvent,
|
|
40
40
|
scopePathFor,
|
|
41
41
|
settleQuestionsForToolUse
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-KQFTBQ66.js";
|
|
43
43
|
import {
|
|
44
44
|
DEFAULT_SESSION
|
|
45
45
|
} from "./chunk-N6IOSZOE.js";
|
|
46
46
|
import {
|
|
47
|
-
DECISION_STOP_REASON,
|
|
48
|
-
UNVERIFIABLE_APPROVAL_REASON,
|
|
49
47
|
askUser,
|
|
50
48
|
cancelQuestion,
|
|
51
49
|
claimBlockedAction,
|
|
@@ -55,24 +53,28 @@ import {
|
|
|
55
53
|
requestGate,
|
|
56
54
|
sendNotification,
|
|
57
55
|
waitForQuestion
|
|
58
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-WRPYCIV2.js";
|
|
59
57
|
import {
|
|
60
58
|
SubscriptionRequiredError
|
|
61
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-6AYSKVLL.js";
|
|
62
60
|
import {
|
|
63
61
|
getMachineId
|
|
64
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-KOWFRUIT.js";
|
|
65
63
|
import {
|
|
66
64
|
getApiKey,
|
|
67
65
|
getBaseUrl
|
|
68
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-IRHABAQT.js";
|
|
69
67
|
import {
|
|
70
68
|
NPX_LATEST_COMMAND
|
|
71
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-5DCDQ5VW.js";
|
|
72
70
|
import {
|
|
73
71
|
AGENT_QUESTION_STRATEGY,
|
|
72
|
+
DECISION_STOP_REASON,
|
|
74
73
|
GATE_BLOCK_REASON_MAX_LENGTH,
|
|
75
74
|
GATE_WIRE,
|
|
75
|
+
PLAN_DECLINED_REASON,
|
|
76
|
+
UNVERIFIABLE_APPROVAL_REASON,
|
|
77
|
+
WITHDRAWN_APPROVAL_REASON,
|
|
76
78
|
agentQuestionStrategy,
|
|
77
79
|
agentRulesAllowEveryInput,
|
|
78
80
|
buildDecisionEpisodeFeatures,
|
|
@@ -82,8 +84,9 @@ import {
|
|
|
82
84
|
claudePermissionRequestOutput,
|
|
83
85
|
claudePermissionRulesFromSettings,
|
|
84
86
|
claudePreToolUseOutput,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
claudeStoppedPermissionRequestOutput,
|
|
88
|
+
claudeStoppedPreToolUseOutput,
|
|
89
|
+
editGoesToTheAgentsPrompt,
|
|
87
90
|
effectiveWaitSeconds,
|
|
88
91
|
hookHoldWindowMs,
|
|
89
92
|
hookWaitClamped,
|
|
@@ -94,9 +97,9 @@ import {
|
|
|
94
97
|
isPusharyOwnTool,
|
|
95
98
|
parseAgentQuestionAnswers,
|
|
96
99
|
parseAgentQuestions,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} from "./chunk-
|
|
100
|
+
questionHandedToTheTerminal,
|
|
101
|
+
resolveGate
|
|
102
|
+
} from "./chunk-5RGA636G.js";
|
|
100
103
|
|
|
101
104
|
// src/claude-permission-rules.ts
|
|
102
105
|
import { readFileSync, statSync } from "fs";
|
|
@@ -174,7 +177,7 @@ var allow = (reason) => claudePreToolUseOutput("allow", reason);
|
|
|
174
177
|
var deny = (reason) => claudePreToolUseOutput("deny", reason);
|
|
175
178
|
var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
|
|
176
179
|
var ask = (reason) => claudePreToolUseOutput("ask", reason);
|
|
177
|
-
var halted = (reason) =>
|
|
180
|
+
var halted = (reason) => claudeStoppedPreToolUseOutput(reason);
|
|
178
181
|
var outcomeOfFailedAsk = (error, sessionId, failure) => {
|
|
179
182
|
if (!(error instanceof SubscriptionRequiredError)) return failure;
|
|
180
183
|
announceSubscriptionRequired(error.upgradeUrl, sessionId);
|
|
@@ -218,7 +221,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
|
|
|
218
221
|
return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value, answer.note));
|
|
219
222
|
}
|
|
220
223
|
if (timeoutAction === "wait" || clampCut || deadline < requestedDeadline) {
|
|
221
|
-
return ask(
|
|
224
|
+
return ask(WITHDRAWN_APPROVAL_REASON);
|
|
222
225
|
}
|
|
223
226
|
switch (timeoutAction) {
|
|
224
227
|
case "approve":
|
|
@@ -261,7 +264,7 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
|
|
|
261
264
|
signals.humanDecision = true;
|
|
262
265
|
return answer.value === "yes" ? allow() : deny(denyReasonFrom(answer.value, answer.note));
|
|
263
266
|
}
|
|
264
|
-
return ask(
|
|
267
|
+
return ask(WITHDRAWN_APPROVAL_REASON);
|
|
265
268
|
};
|
|
266
269
|
var handleNotifyOnly = async (apiKey, description, projectName, sessionId, machineId) => {
|
|
267
270
|
try {
|
|
@@ -448,7 +451,7 @@ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline)
|
|
|
448
451
|
return answers;
|
|
449
452
|
};
|
|
450
453
|
var handleAskUserQuestion = async (apiKey, input, modeState) => {
|
|
451
|
-
if (modeState
|
|
454
|
+
if (questionHandedToTheTerminal(input.tool_name, modeState)) return void 0;
|
|
452
455
|
const parsed = parseQuestions(input.tool_input ?? {});
|
|
453
456
|
if (!parsed) return void 0;
|
|
454
457
|
const projectName = basename(input.cwd ?? process.cwd());
|
|
@@ -494,7 +497,7 @@ var handleExitPlanMode = async (apiKey, input) => {
|
|
|
494
497
|
return void 0;
|
|
495
498
|
}
|
|
496
499
|
if (value === PLAN_APPROVE) return allow();
|
|
497
|
-
return deny(
|
|
500
|
+
return deny(PLAN_DECLINED_REASON);
|
|
498
501
|
};
|
|
499
502
|
var handleKeyless = (input) => {
|
|
500
503
|
try {
|
|
@@ -615,7 +618,7 @@ var handlePreToolUse = async (input, canonical) => {
|
|
|
615
618
|
return void 0;
|
|
616
619
|
}
|
|
617
620
|
if (claudeAllowlistOwnsCall(gateInput, verdict, input.permission_mode, claudePermissionRulesFor(input))) return void 0;
|
|
618
|
-
if (
|
|
621
|
+
if (editGoesToTheAgentsPrompt({ source: ACTIVE_SOURCE, event: "PreToolUse", permissionMode: input.permission_mode }, gateInput, verdict)) return handleTerminalOnly();
|
|
619
622
|
switch (verdict.kind) {
|
|
620
623
|
case "allow":
|
|
621
624
|
return allow();
|
|
@@ -635,7 +638,7 @@ var handlePreToolUse = async (input, canonical) => {
|
|
|
635
638
|
};
|
|
636
639
|
var permReqAllow = (updatedInput) => claudePermissionRequestOutput("allow", void 0, updatedInput);
|
|
637
640
|
var permReqDeny = (message) => claudePermissionRequestOutput("deny", message);
|
|
638
|
-
var permReqHalted = (reason) =>
|
|
641
|
+
var permReqHalted = (reason) => claudeStoppedPermissionRequestOutput(reason);
|
|
639
642
|
var toPermissionRequestOutput = (hookOutput) => {
|
|
640
643
|
if (!hookOutput) return void 0;
|
|
641
644
|
const d = hookOutput.hookSpecificOutput;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
permissionDeniedAction,
|
|
6
6
|
releaseThrottle,
|
|
7
7
|
reportEvent
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KQFTBQ66.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_SESSION
|
|
11
11
|
} from "./chunk-N6IOSZOE.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
machineDecisionFor,
|
|
14
14
|
shouldDeferToNativeMode,
|
|
15
15
|
wireVerdictFor
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-5RGA636G.js";
|
|
17
17
|
|
|
18
18
|
// src/machine-decision-audit.ts
|
|
19
19
|
import { basename } from "path";
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLAUDE_HOOK_EVENTS,
|
|
3
|
+
GEMINI_HOOK_EVENTS,
|
|
4
|
+
hasGeminiHooks
|
|
5
|
+
} from "./chunk-M3STIAGU.js";
|
|
6
|
+
import {
|
|
7
|
+
AGENT_CHOICES
|
|
8
|
+
} from "./chunk-ZGADIZDS.js";
|
|
9
|
+
import {
|
|
10
|
+
BELL_HOOK_BINARY
|
|
11
|
+
} from "./chunk-MLGIVVSG.js";
|
|
12
|
+
import {
|
|
13
|
+
CODEX_HOOK_EVENTS,
|
|
14
|
+
hasCodexHooks,
|
|
15
|
+
holdBudgetOf
|
|
16
|
+
} from "./chunk-Z23ZBYDW.js";
|
|
17
|
+
import {
|
|
18
|
+
CURSOR_GATE_EVENTS,
|
|
19
|
+
HOOK_LOCATOR_BINARY,
|
|
20
|
+
hookCommandHoldBudget
|
|
21
|
+
} from "./chunk-5RGA636G.js";
|
|
22
|
+
|
|
23
|
+
// src/diagnostics/bridge.ts
|
|
24
|
+
import { execFileSync } from "child_process";
|
|
25
|
+
import { existsSync, readFileSync } from "fs";
|
|
26
|
+
import { homedir } from "os";
|
|
27
|
+
import { dirname, join } from "path";
|
|
28
|
+
var BRIDGE_BUNDLE_IDENTIFIER = "com.pushary.app";
|
|
29
|
+
var BRIDGE_HELPER_SUFFIX = "/Contents/Helpers/pushary-bridge";
|
|
30
|
+
var BRIDGE_BUNDLE_ROOTS = ["/Applications/Pushary.app", "$HOME/Applications/Pushary.app"];
|
|
31
|
+
var expandHome = (path, home) => path.replaceAll("$HOME", home);
|
|
32
|
+
var parseBridgeStub = (contents, stubPath) => {
|
|
33
|
+
const helperSuffix = contents.match(/^H=(\S+)$/m)?.[1] ?? BRIDGE_HELPER_SUFFIX;
|
|
34
|
+
const cachePath = contents.match(/^C="([^"\n]+)"$/m)?.[1] ?? join(dirname(stubPath), ".bridge-cache");
|
|
35
|
+
const rootsLine = contents.match(/^for P in ((?:"[^"\n]*"\s*)+); do$/m)?.[1];
|
|
36
|
+
const bundleRoots = rootsLine ? [...rootsLine.matchAll(/"([^"]*)"/g)].map((match) => match[1]) : BRIDGE_BUNDLE_ROOTS;
|
|
37
|
+
const bundleIdentifier = contents.match(/kMDItemCFBundleIdentifier == "([^"]+)"/)?.[1] ?? BRIDGE_BUNDLE_IDENTIFIER;
|
|
38
|
+
return { helperSuffix, cachePath, bundleRoots, bundleIdentifier };
|
|
39
|
+
};
|
|
40
|
+
var resolveBridgeStub = (stubPath, probe) => {
|
|
41
|
+
const contents = probe.readFile(stubPath);
|
|
42
|
+
if (contents === null) return { kind: "no-stub", stub: stubPath };
|
|
43
|
+
const shape = parseBridgeStub(contents, stubPath);
|
|
44
|
+
const helperIn = (bundle) => `${bundle}${shape.helperSuffix}`;
|
|
45
|
+
const looked = [];
|
|
46
|
+
for (const root of shape.bundleRoots.map((root2) => expandHome(root2, probe.home))) {
|
|
47
|
+
looked.push(root);
|
|
48
|
+
if (probe.exists(helperIn(root))) return { kind: "bundle", helper: helperIn(root), via: "root" };
|
|
49
|
+
}
|
|
50
|
+
const cached = shape.cachePath ? probe.readFile(shape.cachePath)?.split("\n")[0]?.trim() : void 0;
|
|
51
|
+
if (cached) {
|
|
52
|
+
looked.push(cached);
|
|
53
|
+
if (probe.exists(helperIn(cached))) return { kind: "bundle", helper: helperIn(cached), via: "cache" };
|
|
54
|
+
}
|
|
55
|
+
for (const found of probe.mdfind(shape.bundleIdentifier)) {
|
|
56
|
+
looked.push(found);
|
|
57
|
+
if (probe.exists(helperIn(found))) return { kind: "bundle", helper: helperIn(found), via: "spotlight" };
|
|
58
|
+
}
|
|
59
|
+
return { kind: "missing", stub: stubPath, looked };
|
|
60
|
+
};
|
|
61
|
+
var BRIDGE_GONE_MESSAGE = "the Mac app is gone but its hooks remain; run pushary setup or reinstall the app";
|
|
62
|
+
var describeBridgeResolution = (resolution) => {
|
|
63
|
+
switch (resolution.kind) {
|
|
64
|
+
case "bundle":
|
|
65
|
+
return `${resolution.helper} (${resolution.via === "root" ? "installed" : resolution.via === "cache" ? "cached path" : "found by Spotlight"})`;
|
|
66
|
+
case "missing":
|
|
67
|
+
return BRIDGE_GONE_MESSAGE;
|
|
68
|
+
case "no-stub":
|
|
69
|
+
return `${resolution.stub} is not there; ${BRIDGE_GONE_MESSAGE}`;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var isBridgeLocator = (executable) => executable !== null && executable.split(/[/\\]/).pop() === HOOK_LOCATOR_BINARY;
|
|
73
|
+
var spotlight = (bundleIdentifier) => {
|
|
74
|
+
if (process.platform !== "darwin") return [];
|
|
75
|
+
try {
|
|
76
|
+
return execFileSync("/usr/bin/mdfind", [`kMDItemCFBundleIdentifier == "${bundleIdentifier}"`], {
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
timeout: 5e3,
|
|
79
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
80
|
+
}).split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var readOrNull = (path) => {
|
|
86
|
+
try {
|
|
87
|
+
return readFileSync(path, "utf-8");
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var liveBridgeProbe = () => ({
|
|
93
|
+
exists: existsSync,
|
|
94
|
+
readFile: readOrNull,
|
|
95
|
+
mdfind: spotlight,
|
|
96
|
+
home: homedir()
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// src/diagnostics/wiring.ts
|
|
100
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
101
|
+
var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
|
|
102
|
+
var EXACT_HOOK_BINARY = /^pushary-[a-z-]*(hook|gate)$/;
|
|
103
|
+
var pusharyHookExecutable = (command) => {
|
|
104
|
+
const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
105
|
+
for (const raw of tokens.reverse()) {
|
|
106
|
+
const token = raw[0] === '"' && raw.at(-1) === '"' || raw[0] === "'" && raw.at(-1) === "'" ? raw.slice(1, -1) : raw;
|
|
107
|
+
const name = token.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "") ?? "";
|
|
108
|
+
if (name === HOOK_LOCATOR_BINARY || EXACT_HOOK_BINARY.test(name)) return token;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
var claudeWired = ({ claudeJson, settings, skillExists }) => {
|
|
113
|
+
if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
|
|
114
|
+
const s = record(settings);
|
|
115
|
+
if (s) {
|
|
116
|
+
const hooks = JSON.stringify(s.hooks ?? {});
|
|
117
|
+
if (CLAUDE_HOOK_BINARY.test(hooks) || hooks.includes(HOOK_LOCATOR_BINARY)) return true;
|
|
118
|
+
const allow = record(s.permissions)?.allow;
|
|
119
|
+
if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
|
|
120
|
+
}
|
|
121
|
+
return skillExists;
|
|
122
|
+
};
|
|
123
|
+
var codexWired = ({ config, hooks, skillExists }) => {
|
|
124
|
+
if (record(record(config)?.mcp_servers)?.pushary) return true;
|
|
125
|
+
const h = record(hooks);
|
|
126
|
+
if (h && hasCodexHooks(h)) return true;
|
|
127
|
+
return skillExists;
|
|
128
|
+
};
|
|
129
|
+
var geminiWired = ({ settings }) => {
|
|
130
|
+
const s = record(settings);
|
|
131
|
+
if (!s) return false;
|
|
132
|
+
if (record(s.mcpServers)?.pushary) return true;
|
|
133
|
+
return hasGeminiHooks(s);
|
|
134
|
+
};
|
|
135
|
+
var cursorWired = ({ userHooks, mcp, pluginDirExists }) => {
|
|
136
|
+
if (pluginDirExists) return true;
|
|
137
|
+
if (record(record(mcp)?.mcpServers)?.pushary) return true;
|
|
138
|
+
const hooks = record(record(userHooks)?.hooks);
|
|
139
|
+
if (!hooks) return false;
|
|
140
|
+
return Object.values(hooks).some((entries) => Array.isArray(entries) && entries.some((entry) => pusharyHookExecutable(String(record(entry)?.command ?? "")) !== null));
|
|
141
|
+
};
|
|
142
|
+
var cursorMcpAuthorization = (pluginMcp, userMcp) => {
|
|
143
|
+
const pluginServers = record(record(pluginMcp)?.mcpServers);
|
|
144
|
+
const usePlugin = pluginServers !== null && Object.hasOwn(pluginServers, "pushary");
|
|
145
|
+
const servers = usePlugin ? pluginServers : record(record(userMcp)?.mcpServers);
|
|
146
|
+
const headers = record(record(servers?.pushary)?.headers);
|
|
147
|
+
return {
|
|
148
|
+
authorization: typeof headers?.Authorization === "string" ? headers.Authorization : null,
|
|
149
|
+
source: usePlugin ? "plugin mcp.json" : "~/.cursor/mcp.json"
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
var LONG_HOLD_SECONDS = 600;
|
|
153
|
+
var toSeconds = (timeout, unit) => {
|
|
154
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout)) return null;
|
|
155
|
+
return unit === "milliseconds" ? timeout / 1e3 : timeout;
|
|
156
|
+
};
|
|
157
|
+
var registeredHooks = (entries, shape, unit) => {
|
|
158
|
+
if (!Array.isArray(entries)) return [];
|
|
159
|
+
return entries.flatMap((entry) => {
|
|
160
|
+
const container = record(entry);
|
|
161
|
+
if (!container) return [];
|
|
162
|
+
const hooks = shape === "nested" ? Array.isArray(container.hooks) ? container.hooks : [] : [container];
|
|
163
|
+
return hooks.flatMap((hook) => {
|
|
164
|
+
const command = record(hook)?.command;
|
|
165
|
+
if (typeof command !== "string" || command.trim() === "") return [];
|
|
166
|
+
return [{ command, timeoutSeconds: toSeconds(record(hook)?.timeout, unit) }];
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
var holdersOnEvent = (event, hooks) => {
|
|
171
|
+
if (!hooks.some((hook) => pusharyHookExecutable(hook.command) !== null)) return [];
|
|
172
|
+
return hooks.filter((hook) => pusharyHookExecutable(hook.command) === null).map((hook) => ({ event, command: hook.command, timeoutSeconds: hook.timeoutSeconds }));
|
|
173
|
+
};
|
|
174
|
+
var otherHolders = (hooks, events, shape, unit) => {
|
|
175
|
+
const table = record(hooks);
|
|
176
|
+
if (!table) return [];
|
|
177
|
+
return events.flatMap((event) => holdersOnEvent(event, registeredHooks(table[event], shape, unit)));
|
|
178
|
+
};
|
|
179
|
+
var approvalEvents = (table) => table.filter((hook) => hook.delivery === "approval").map((hook) => hook.event);
|
|
180
|
+
var CLAUDE_GATE_EVENTS = approvalEvents(CLAUDE_HOOK_EVENTS);
|
|
181
|
+
var CODEX_GATE_EVENTS = approvalEvents(CODEX_HOOK_EVENTS);
|
|
182
|
+
var GEMINI_GATE_EVENTS = approvalEvents(GEMINI_HOOK_EVENTS);
|
|
183
|
+
var claudeOtherHolders = (settings) => otherHolders(record(settings)?.hooks, CLAUDE_GATE_EVENTS, "nested", "seconds");
|
|
184
|
+
var codexOtherHolders = (hooks) => otherHolders(record(hooks)?.hooks, CODEX_GATE_EVENTS, "nested", "seconds");
|
|
185
|
+
var geminiOtherHolders = (settings) => otherHolders(record(settings)?.hooks, GEMINI_GATE_EVENTS, "nested", "milliseconds");
|
|
186
|
+
var cursorOtherHolders = (userHooks) => otherHolders(record(userHooks)?.hooks, CURSOR_GATE_EVENTS, "flat", "seconds");
|
|
187
|
+
var holdEvents = (table) => table.flatMap((hook) => {
|
|
188
|
+
const seconds = holdBudgetOf(hook);
|
|
189
|
+
return seconds === void 0 ? [] : [{ event: hook.event, seconds }];
|
|
190
|
+
});
|
|
191
|
+
var unprovenHolds = (hooks, events) => {
|
|
192
|
+
const table = record(hooks);
|
|
193
|
+
if (!table) return [];
|
|
194
|
+
return events.filter(({ event, seconds }) => registeredHooks(table[event], "nested", "seconds").some((hook) => pusharyHookExecutable(hook.command) !== null && hookCommandHoldBudget(hook.command) !== seconds)).map(({ event }) => event);
|
|
195
|
+
};
|
|
196
|
+
var claudeUnprovenHolds = (settings) => unprovenHolds(record(settings)?.hooks, holdEvents(CLAUDE_HOOK_EVENTS));
|
|
197
|
+
var codexUnprovenHolds = (hooks) => unprovenHolds(record(hooks)?.hooks, holdEvents(CODEX_HOOK_EVENTS));
|
|
198
|
+
var holdsLong = (holder) => holder.timeoutSeconds !== null && holder.timeoutSeconds > LONG_HOLD_SECONDS;
|
|
199
|
+
var SHELLS = /* @__PURE__ */ new Set(["/bin/sh", "/bin/bash", "/bin/zsh", "/usr/bin/env"]);
|
|
200
|
+
var holderExecutable = (command) => {
|
|
201
|
+
const pathLike = command.match(/[^\s"']*[/\\][^\s"']*/g) ?? [];
|
|
202
|
+
const target = pathLike.find((token) => !SHELLS.has(token));
|
|
203
|
+
return target ?? command.trim().split(/\s+/)[0] ?? command;
|
|
204
|
+
};
|
|
205
|
+
var describeHoldTimeout = (holder) => {
|
|
206
|
+
const seconds = holder.timeoutSeconds;
|
|
207
|
+
if (seconds === null) return "no timeout set, so the agent default applies";
|
|
208
|
+
if (seconds >= 3600 && seconds % 3600 === 0) return `${seconds / 3600}h timeout`;
|
|
209
|
+
if (seconds >= 60 && seconds % 60 === 0) return `${seconds / 60}m timeout`;
|
|
210
|
+
return `${seconds}s timeout`;
|
|
211
|
+
};
|
|
212
|
+
var describeOtherHolder = (holder) => `another hook also registered on ${holder.event}: ${holderExecutable(holder.command)} (${describeHoldTimeout(holder)})`;
|
|
213
|
+
var describeHoldSentence = (agentLabel) => `${agentLabel} waits for every hook on an event, so the terminal cannot move until that one returns, however fast your phone answered.`;
|
|
214
|
+
|
|
215
|
+
// src/setup/hook-ownership.ts
|
|
216
|
+
var commandsIn = (value) => {
|
|
217
|
+
if (Array.isArray(value)) return value.flatMap(commandsIn);
|
|
218
|
+
if (!value || typeof value !== "object") return [];
|
|
219
|
+
return Object.entries(value).flatMap(([key, item]) => key === "command" && typeof item === "string" ? [item] : commandsIn(item));
|
|
220
|
+
};
|
|
221
|
+
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
222
|
+
var hasPaidPusharyCommand = (value) => commandsIn(value).some((command) => {
|
|
223
|
+
const executable = pusharyHookExecutable(command);
|
|
224
|
+
const name = executable?.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "");
|
|
225
|
+
return executable !== null && !isBridgeLocator(executable) && name !== BELL_HOOK_BINARY;
|
|
226
|
+
});
|
|
227
|
+
var hasAppCommand = (value) => commandsIn(value).some((command) => {
|
|
228
|
+
const executable = pusharyHookExecutable(command);
|
|
229
|
+
return executable !== null && isBridgeLocator(executable);
|
|
230
|
+
});
|
|
231
|
+
var mcpBearerKey = (document, containerKey, headersKey) => {
|
|
232
|
+
const servers = isRecord(document?.[containerKey]) ? document[containerKey] : null;
|
|
233
|
+
const pushary = isRecord(servers?.pushary) ? servers.pushary : null;
|
|
234
|
+
const headers = isRecord(pushary?.[headersKey]) ? pushary[headersKey] : null;
|
|
235
|
+
const authorization = headers?.Authorization;
|
|
236
|
+
const match = typeof authorization === "string" ? authorization.trim().match(/^Bearer\s+(\S+)$/) : null;
|
|
237
|
+
return match?.[1] ?? null;
|
|
238
|
+
};
|
|
239
|
+
var captureMcpServer = (document, containerKey) => {
|
|
240
|
+
const container = document[containerKey];
|
|
241
|
+
const servers = isRecord(container) ? container : null;
|
|
242
|
+
if (!servers && Object.hasOwn(document, containerKey)) {
|
|
243
|
+
return { present: false, containerValue: structuredClone(container) };
|
|
244
|
+
}
|
|
245
|
+
return servers && Object.hasOwn(servers, "pushary") ? { present: true, value: structuredClone(servers.pushary) } : { present: false };
|
|
246
|
+
};
|
|
247
|
+
var restoreMcpServer = (document, containerKey, snapshot) => {
|
|
248
|
+
const before = JSON.stringify(document);
|
|
249
|
+
const existing = isRecord(document[containerKey]) ? document[containerKey] : null;
|
|
250
|
+
if (snapshot.present) {
|
|
251
|
+
const servers = existing ?? {};
|
|
252
|
+
servers.pushary = structuredClone(snapshot.value);
|
|
253
|
+
document[containerKey] = servers;
|
|
254
|
+
} else if (Object.hasOwn(snapshot, "containerValue")) {
|
|
255
|
+
document[containerKey] = structuredClone(snapshot.containerValue);
|
|
256
|
+
} else if (existing && Object.hasOwn(existing, "pushary")) {
|
|
257
|
+
delete existing.pushary;
|
|
258
|
+
if (Object.keys(existing).length === 0) delete document[containerKey];
|
|
259
|
+
}
|
|
260
|
+
return JSON.stringify(document) !== before;
|
|
261
|
+
};
|
|
262
|
+
var captureHookTakeover = (agent, document) => {
|
|
263
|
+
const hooks = isRecord(document?.hooks) ? document.hooks : null;
|
|
264
|
+
if (!hooks) return null;
|
|
265
|
+
const captured = {};
|
|
266
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
267
|
+
if (!Array.isArray(entries)) continue;
|
|
268
|
+
const appEntries = entries.flatMap((value, index) => isRecord(value) && hasAppCommand(value) ? [{ index, value: structuredClone(value) }] : []);
|
|
269
|
+
if (appEntries.length > 0) captured[event] = appEntries;
|
|
270
|
+
}
|
|
271
|
+
return Object.keys(captured).length > 0 ? { version: 1, agent, hooks: captured } : null;
|
|
272
|
+
};
|
|
273
|
+
var isHookTakeoverReceipt = (value) => {
|
|
274
|
+
if (!isRecord(value) || value.version !== 1 || typeof value.agent !== "string" || !AGENT_CHOICES.includes(value.agent) || !isRecord(value.hooks)) return false;
|
|
275
|
+
const expectsMcp = ["claude_code", "codex", "gemini_cli", "vscode"].includes(value.agent);
|
|
276
|
+
if (value.mcpServer !== void 0 !== expectsMcp) return false;
|
|
277
|
+
if (value.codexTrust !== void 0 !== (value.agent === "codex")) return false;
|
|
278
|
+
if (value.codexTrust !== void 0 && !isRecord(value.codexTrust)) return false;
|
|
279
|
+
if (value.mcpServer !== void 0 && (!isRecord(value.mcpServer) || typeof value.mcpServer.present !== "boolean" || value.mcpServer.present && !Object.hasOwn(value.mcpServer, "value"))) return false;
|
|
280
|
+
return Object.values(value.hooks).every((entries) => Array.isArray(entries) && entries.length > 0 && entries.every((item) => isRecord(item) && Number.isInteger(item.index) && item.index >= 0 && isRecord(item.value)));
|
|
281
|
+
};
|
|
282
|
+
var restoreHookTakeover = (document, receipt) => {
|
|
283
|
+
const before = JSON.stringify(document);
|
|
284
|
+
const appOwnsNow = hookOwnerOf(document) === "app";
|
|
285
|
+
const hooks = isRecord(document.hooks) ? document.hooks : {};
|
|
286
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
287
|
+
if (!Array.isArray(value)) continue;
|
|
288
|
+
const kept = value.filter((entry) => !hasPaidPusharyCommand(entry));
|
|
289
|
+
if (kept.length === 0) delete hooks[event];
|
|
290
|
+
else hooks[event] = kept;
|
|
291
|
+
}
|
|
292
|
+
if (!appOwnsNow) {
|
|
293
|
+
for (const [event, snapshots] of Object.entries(receipt.hooks)) {
|
|
294
|
+
const entries = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
295
|
+
for (const snapshot of snapshots) {
|
|
296
|
+
entries.splice(Math.min(snapshot.index, entries.length), 0, structuredClone(snapshot.value));
|
|
297
|
+
}
|
|
298
|
+
hooks[event] = entries;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
document.hooks = hooks;
|
|
302
|
+
return JSON.stringify(document) !== before;
|
|
303
|
+
};
|
|
304
|
+
var hookOwnerOf = (document, appAvailable = true, hooksKeyPath = ["hooks"]) => {
|
|
305
|
+
const hooks = hooksKeyPath.reduce(
|
|
306
|
+
(node, key) => node && typeof node === "object" ? node[key] : void 0,
|
|
307
|
+
document
|
|
308
|
+
);
|
|
309
|
+
return hookOwnerOfCommands(commandsIn(hooks), appAvailable);
|
|
310
|
+
};
|
|
311
|
+
var hookOwnerOfCommands = (commands, appAvailable = true) => {
|
|
312
|
+
const executables = commands.map(pusharyHookExecutable).filter((executable) => executable !== null);
|
|
313
|
+
if (appAvailable && executables.some(isBridgeLocator)) return "app";
|
|
314
|
+
if (executables.some((executable) => !isBridgeLocator(executable))) return "cli";
|
|
315
|
+
return "none";
|
|
316
|
+
};
|
|
317
|
+
var planHooks = (context) => {
|
|
318
|
+
if (context.owner !== "app") return "install";
|
|
319
|
+
if (context.takeOverHooks) return "install";
|
|
320
|
+
return "keep";
|
|
321
|
+
};
|
|
322
|
+
var describeAppOwnership = (agentName) => `The Pushary Mac app owns ${agentName}'s hooks.`;
|
|
323
|
+
var KEEP_HINT = "Kept with the app's account. Pass --take-over-hooks to have the CLI own them instead.";
|
|
324
|
+
var OWNER_LABELS = {
|
|
325
|
+
app: "the Pushary Mac app",
|
|
326
|
+
cli: "the CLI"
|
|
327
|
+
};
|
|
328
|
+
var describeHookOwnership = (entries, names) => {
|
|
329
|
+
const owned = entries.filter((entry) => entry.owner !== "none");
|
|
330
|
+
if (owned.length === 0) return null;
|
|
331
|
+
const parts = owned.map((entry) => `${names[entry.agent]} by ${OWNER_LABELS[entry.owner]}${entry.kept ? " (kept)" : ""}`);
|
|
332
|
+
return `Hooks: ${parts.join(", ")}.`;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export {
|
|
336
|
+
resolveBridgeStub,
|
|
337
|
+
describeBridgeResolution,
|
|
338
|
+
isBridgeLocator,
|
|
339
|
+
liveBridgeProbe,
|
|
340
|
+
pusharyHookExecutable,
|
|
341
|
+
claudeWired,
|
|
342
|
+
codexWired,
|
|
343
|
+
geminiWired,
|
|
344
|
+
cursorWired,
|
|
345
|
+
cursorMcpAuthorization,
|
|
346
|
+
claudeOtherHolders,
|
|
347
|
+
codexOtherHolders,
|
|
348
|
+
geminiOtherHolders,
|
|
349
|
+
cursorOtherHolders,
|
|
350
|
+
claudeUnprovenHolds,
|
|
351
|
+
codexUnprovenHolds,
|
|
352
|
+
holdsLong,
|
|
353
|
+
describeOtherHolder,
|
|
354
|
+
describeHoldSentence,
|
|
355
|
+
mcpBearerKey,
|
|
356
|
+
captureMcpServer,
|
|
357
|
+
restoreMcpServer,
|
|
358
|
+
captureHookTakeover,
|
|
359
|
+
isHookTakeoverReceipt,
|
|
360
|
+
restoreHookTakeover,
|
|
361
|
+
hookOwnerOf,
|
|
362
|
+
hookOwnerOfCommands,
|
|
363
|
+
planHooks,
|
|
364
|
+
describeAppOwnership,
|
|
365
|
+
KEEP_HINT,
|
|
366
|
+
describeHookOwnership
|
|
367
|
+
};
|