@pushary/agent-hooks 1.1.0 → 1.3.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/data/SKILL.md +20 -8
  3. package/data/cursor-plugin/skills/pushary/SKILL.md +19 -7
  4. package/data/vscode-plugin/skills/pushary/SKILL.md +19 -7
  5. package/dist/bin/pushary-bell-hook.js +18 -18
  6. package/dist/bin/pushary-bell.js +6 -6
  7. package/dist/bin/pushary-claude.js +18 -18
  8. package/dist/bin/pushary-clean.js +19 -19
  9. package/dist/bin/pushary-codex-bridge.js +10 -10
  10. package/dist/bin/pushary-codex-hook.js +22 -22
  11. package/dist/bin/pushary-codex.js +6 -6
  12. package/dist/bin/pushary-connect.js +6 -6
  13. package/dist/bin/pushary-cowork.js +3 -3
  14. package/dist/bin/pushary-daemon.js +22 -22
  15. package/dist/bin/pushary-disconnect.js +10 -10
  16. package/dist/bin/pushary-doctor.js +23 -23
  17. package/dist/bin/pushary-elicitation-hook.js +17 -17
  18. package/dist/bin/pushary-gemini-bridge.js +9 -9
  19. package/dist/bin/pushary-gemini-hook.js +19 -19
  20. package/dist/bin/pushary-hook.js +26 -26
  21. package/dist/bin/pushary-login.js +7 -7
  22. package/dist/bin/pushary-logout.js +4 -4
  23. package/dist/bin/pushary-mode.js +3 -3
  24. package/dist/bin/pushary-notification-hook.js +20 -20
  25. package/dist/bin/pushary-opencode-hook.js +19 -19
  26. package/dist/bin/pushary-permission-denied-hook.js +24 -24
  27. package/dist/bin/pushary-permission-hook.js +25 -25
  28. package/dist/bin/pushary-post-hook.js +20 -20
  29. package/dist/bin/pushary-prompt-hook.js +20 -20
  30. package/dist/bin/pushary-session-end-hook.js +20 -20
  31. package/dist/bin/pushary-session-start-hook.js +22 -22
  32. package/dist/bin/pushary-setup.js +26 -26
  33. package/dist/bin/pushary-stats.js +2 -2
  34. package/dist/bin/pushary-status.js +10 -10
  35. package/dist/bin/pushary-stop-hook.js +20 -20
  36. package/dist/bin/pushary-stopfailure-hook.js +20 -20
  37. package/dist/bin/pushary-transcript-register.js +16 -16
  38. package/dist/bin/pushary-transcripts.js +4 -4
  39. package/dist/bin/pushary-upgrade.js +14 -14
  40. package/dist/bin/pushary-wait.js +3 -3
  41. package/dist/{chunk-SA6UB3GD.js → chunk-2PLEJ7SY.js} +1 -1
  42. package/dist/{chunk-RZTH4MIY.js → chunk-3AIFWTVB.js} +2 -2
  43. package/dist/{chunk-MZUXTN7X.js → chunk-4V24U44H.js} +2 -2
  44. package/dist/{chunk-2MOQUNFL.js → chunk-A2URMNFM.js} +1 -1
  45. package/dist/{chunk-IH7YUPFN.js → chunk-A5ZWYQFV.js} +3 -3
  46. package/dist/{chunk-BBUZD7MU.js → chunk-B3S4SF3D.js} +1 -1
  47. package/dist/{chunk-76ZCJZBK.js → chunk-BL4SQNO6.js} +1 -1
  48. package/dist/{chunk-SM54HHVY.js → chunk-BNDHXPTQ.js} +21 -14
  49. package/dist/{chunk-OYYQHJAO.js → chunk-BYGEZI7B.js} +1 -1
  50. package/dist/{chunk-LUYYB6V2.js → chunk-DON24PNT.js} +1 -1
  51. package/dist/{chunk-AH5OO2YZ.js → chunk-DVO4SZMJ.js} +3 -3
  52. package/dist/{chunk-S2BNTXP6.js → chunk-E7WBNGJS.js} +1 -1
  53. package/dist/{chunk-XIHKSZHL.js → chunk-EJZCV5G4.js} +1 -1
  54. package/dist/{chunk-FO4HHPMO.js → chunk-EKQAKSGI.js} +1 -1
  55. package/dist/{chunk-PWIMJP4U.js → chunk-EU6JH7KU.js} +6 -6
  56. package/dist/{chunk-PQG6NTPT.js → chunk-EYMMU5HC.js} +1 -1
  57. package/dist/{chunk-PUDQJ772.js → chunk-FGVNU7XD.js} +1 -1
  58. package/dist/{chunk-INBTKFT6.js → chunk-IAEVVUXO.js} +1 -1
  59. package/dist/{chunk-SLEWDZOA.js → chunk-JYEGOYQC.js} +1 -1
  60. package/dist/{chunk-MR2PWE62.js → chunk-K4TAUYUJ.js} +7 -8
  61. package/dist/{chunk-J7PJALS2.js → chunk-K7UIAE7I.js} +1 -1
  62. package/dist/{chunk-QACXZYDA.js → chunk-KPNGPRYU.js} +4 -4
  63. package/dist/{chunk-6JUCFMSF.js → chunk-KS4P6F3R.js} +3 -3
  64. package/dist/{chunk-ISELLVBP.js → chunk-MU275U5A.js} +1 -1
  65. package/dist/{chunk-LQ3BHPPZ.js → chunk-N43EQPJK.js} +5 -1
  66. package/dist/{chunk-YM4G6CFN.js → chunk-OE6U2AXM.js} +3 -3
  67. package/dist/{chunk-E3WQMDCE.js → chunk-POAFXBNH.js} +1 -1
  68. package/dist/{chunk-EWQZISYV.js → chunk-RHLUJE4H.js} +1 -1
  69. package/dist/{chunk-PHJOSAXW.js → chunk-RVDHN2TJ.js} +2 -2
  70. package/dist/{chunk-EZ3GX3NS.js → chunk-UVXNYYHG.js} +5 -5
  71. package/dist/{chunk-VQYARC2K.js → chunk-WPJS3OZH.js} +109 -21
  72. package/dist/{chunk-WTT24AUA.js → chunk-WXXDHGTZ.js} +29 -5
  73. package/dist/{chunk-DKTFZCLQ.js → chunk-X3KIGNIP.js} +6 -6
  74. package/dist/{chunk-IPDBFJ2M.js → chunk-XBJW4MV5.js} +1 -1
  75. package/dist/{chunk-UP7NZXJU.js → chunk-XBO42HKH.js} +1 -1
  76. package/dist/{chunk-DZYIAMKR.js → chunk-XEXOHUJ6.js} +4 -4
  77. package/dist/{chunk-VACFOGF3.js → chunk-YF4JDCU4.js} +1 -1
  78. package/dist/{chunk-6T4OINZB.js → chunk-ZHL62DRA.js} +3 -3
  79. package/dist/{reapply-STSGYPF2.js → reapply-FUCAKR6Z.js} +8 -8
  80. package/dist/src/index.js +12 -12
  81. package/package.json +1 -1
@@ -256,7 +256,8 @@ var BRIDGE_HOOK_SOURCES = Object.freeze(Object.values(AGENTS).flatMap((agent) =>
256
256
 
257
257
  // ../contracts/src/agent-wiring.ts
258
258
  var COMPATIBLE_AGENT_TYPES = ["qwen_code", "codebuddy", "qoder", "droid", "devin"];
259
- var CANONICAL_PATH_KEYS = ["file_path", "path"];
259
+ var CANONICAL_PATH_KEYS = ["file_path", "path", "notebook_path"];
260
+ var CANONICAL_COMMAND_KEYS = ["command"];
260
261
  var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
261
262
  var PATH_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit", "Read"]);
262
263
  var PHONE_ANSWERED_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
@@ -271,17 +272,17 @@ var canonicalToolName = (profile, toolName) => profile.toolAliases[toolName] ??
271
272
  var toToolLookup = (profile, toolName, toolInput, resolvePath = (path) => path) => {
272
273
  const tool = canonicalToolName(profile, toolName);
273
274
  if (SHELL_TOOLS.has(tool)) {
274
- const command = firstString(toolInput, ["command"]);
275
- if (!command) return { tool, input: toolInput, pathless: false };
275
+ const command = firstString(toolInput, [...profile.shellCommandKeys, ...CANONICAL_COMMAND_KEYS]);
276
+ if (!command) return { tool, input: toolInput, unreadable: true };
276
277
  const context = Object.fromEntries(
277
278
  profile.shellContextKeys.flatMap((key) => toolInput[key] === void 0 ? [] : [[key, toolInput[key]]])
278
279
  );
279
- return { tool, input: { command, ...context }, pathless: false };
280
+ return { tool, input: { command, ...context }, unreadable: false };
280
281
  }
281
- if (!PATH_TOOLS.has(tool)) return { tool, input: toolInput, pathless: false };
282
+ if (!PATH_TOOLS.has(tool)) return { tool, input: toolInput, unreadable: false };
282
283
  const filePath = firstString(toolInput, [...profile.pathInputKeys, ...CANONICAL_PATH_KEYS]);
283
- if (!filePath) return { tool, input: toolInput, pathless: true };
284
- return { tool, input: { ...toolInput, file_path: resolvePath(filePath) }, pathless: false };
284
+ if (!filePath) return { tool, input: toolInput, unreadable: true };
285
+ return { tool, input: { ...toolInput, file_path: resolvePath(filePath) }, unreadable: false };
285
286
  };
286
287
  var renderPreToolUseVerdict = (profile, verdict) => {
287
288
  if (profile.wire === "permission_decision") return verdict;
@@ -331,6 +332,7 @@ var QWEN_CODE = {
331
332
  },
332
333
  extraMatchers: ["mcp__.*"],
333
334
  pathInputKeys: ["absolute_path"],
335
+ shellCommandKeys: [],
334
336
  shellContextKeys: ["directory"],
335
337
  events: without("TeammateIdle", "Elicitation", "PermissionDenied"),
336
338
  wire: "permission_decision",
@@ -348,6 +350,7 @@ var CODEBUDDY = {
348
350
  toolAliases: {},
349
351
  extraMatchers: ["mcp__.*"],
350
352
  pathInputKeys: [],
353
+ shellCommandKeys: [],
351
354
  shellContextKeys: [],
352
355
  events: without("Elicitation", "PermissionDenied"),
353
356
  wire: "permission_decision",
@@ -365,6 +368,7 @@ var QODER = {
365
368
  toolAliases: {},
366
369
  extraMatchers: ["mcp__.*"],
367
370
  pathInputKeys: [],
371
+ shellCommandKeys: [],
368
372
  shellContextKeys: [],
369
373
  events: without("Elicitation", "PermissionDenied"),
370
374
  wire: "permission_decision",
@@ -387,6 +391,7 @@ var DROID = {
387
391
  },
388
392
  extraMatchers: ["mcp__.*"],
389
393
  pathInputKeys: [],
394
+ shellCommandKeys: [],
390
395
  shellContextKeys: [],
391
396
  events: ["PreToolUse", "PostToolUse", "UserPromptSubmit", "Notification", "Stop", "SubagentStop", "PreCompact", "SessionStart", "SessionEnd"],
392
397
  wire: "permission_decision",
@@ -412,6 +417,7 @@ var DEVIN = {
412
417
  },
413
418
  extraMatchers: ["mcp__.*"],
414
419
  pathInputKeys: [],
420
+ shellCommandKeys: [],
415
421
  shellContextKeys: [],
416
422
  events: ["PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop", "SessionStart", "SessionEnd"],
417
423
  wire: "legacy_decision",
@@ -422,7 +428,6 @@ var DEVIN = {
422
428
  var HOOK_COMPATIBLE_PROFILES = Object.freeze([QWEN_CODE, CODEBUDDY, QODER, DROID, DEVIN]);
423
429
  var hookCompatibleProfile = (agentType) => HOOK_COMPATIBLE_PROFILES.find((profile) => profile.agentType === agentType);
424
430
  var hookCompatibleProfileBySource = (source) => HOOK_COMPATIBLE_PROFILES.find((profile) => profile.source === source);
425
- var compatibleConfigDir = (profile) => profile.configFile.slice(0, -1);
426
431
  var compatibleTargetFiles = (profile) => {
427
432
  const seen = /* @__PURE__ */ new Map();
428
433
  for (const file of [profile.configFile, profile.mcp?.file, profile.permissionRuleFile, profile.instructions?.file]) {
@@ -688,7 +693,71 @@ var compiledGlob = (glob) => {
688
693
  globCache.set(glob, compiled);
689
694
  return compiled;
690
695
  };
691
- var matchToolPattern = (pattern, toolName, arg) => {
696
+ var PATTERN_INTENT = {
697
+ grant: "grant",
698
+ restrict: "restrict"
699
+ };
700
+ var SHELL_ESCAPE = "\\";
701
+ var SHELL_DOUBLE_QUOTE = '"';
702
+ var SHELL_SUBSTITUTION_SIGIL = "$";
703
+ var SHELL_GROUP_OPEN = "(";
704
+ var SHELL_QUOTES = /* @__PURE__ */ new Set(["'", SHELL_DOUBLE_QUOTE]);
705
+ var SHELL_SEGMENT_SEPARATORS = /* @__PURE__ */ new Set([";", "\n", "&", "|"]);
706
+ var SHELL_DOUBLED_SEPARATORS = /* @__PURE__ */ new Set(["&", "|"]);
707
+ var SHELL_UNDECOMPOSABLE = /* @__PURE__ */ new Set(["`", "<", ">"]);
708
+ var shellCommandSegments = (command) => {
709
+ const segments = [];
710
+ let current = "";
711
+ let quote = null;
712
+ for (let index = 0; index < command.length; index += 1) {
713
+ const char = command[index];
714
+ const next = command[index + 1];
715
+ if (quote !== null) {
716
+ if (char === SHELL_ESCAPE && quote === SHELL_DOUBLE_QUOTE && next !== void 0) {
717
+ current += char + next;
718
+ index += 1;
719
+ continue;
720
+ }
721
+ if (char === quote) quote = null;
722
+ current += char;
723
+ continue;
724
+ }
725
+ if (char === SHELL_ESCAPE && next !== void 0) {
726
+ current += char + next;
727
+ index += 1;
728
+ continue;
729
+ }
730
+ if (SHELL_QUOTES.has(char)) {
731
+ quote = char;
732
+ current += char;
733
+ continue;
734
+ }
735
+ if (SHELL_UNDECOMPOSABLE.has(char)) return null;
736
+ if (char === SHELL_SUBSTITUTION_SIGIL && next === SHELL_GROUP_OPEN) return null;
737
+ if (SHELL_SEGMENT_SEPARATORS.has(char)) {
738
+ if (next === char && SHELL_DOUBLED_SEPARATORS.has(char)) index += 1;
739
+ segments.push(current);
740
+ current = "";
741
+ continue;
742
+ }
743
+ current += char;
744
+ }
745
+ if (quote !== null) return null;
746
+ segments.push(current);
747
+ const present = segments.map((segment) => segment.trim()).filter((segment) => segment.length > 0);
748
+ return present.length > 0 ? present : null;
749
+ };
750
+ var everySegmentMatches = (command, matches) => {
751
+ const segments = shellCommandSegments(command);
752
+ return segments !== null && segments.every(matches);
753
+ };
754
+ var anySegmentMatches = (command, matches) => (shellCommandSegments(command) ?? [command]).some(matches);
755
+ var shellSegmentsMatch = (command, intent, matches) => intent === PATTERN_INTENT.restrict ? anySegmentMatches(command, matches) : everySegmentMatches(command, matches);
756
+ var rankWholeOrSegments = (toolName, arg, intent, matches) => {
757
+ const matched = toolName in POLICY_COMMAND_KEYS ? shellSegmentsMatch(arg, intent, matches) : matches(arg);
758
+ return matched ? "prefix" : "none";
759
+ };
760
+ var matchToolPattern = (pattern, toolName, arg, intent = PATTERN_INTENT.grant) => {
692
761
  const open = pattern.indexOf("(");
693
762
  if (open === -1 || !pattern.endsWith(")")) {
694
763
  return pattern === toolName ? "tool" : "none";
@@ -696,12 +765,14 @@ var matchToolPattern = (pattern, toolName, arg) => {
696
765
  if (pattern.slice(0, open) !== toolName || arg === void 0) return "none";
697
766
  const inner = pattern.slice(open + 1, -1);
698
767
  if (inner.endsWith(":*")) {
699
- return arg.startsWith(inner.slice(0, -2)) ? "prefix" : "none";
768
+ const prefix = inner.slice(0, -2);
769
+ return rankWholeOrSegments(toolName, arg, intent, (segment) => segment.startsWith(prefix));
700
770
  }
701
771
  if (inner.includes("*")) {
702
772
  if (arg.length > GLOB_MAX_ARG_LENGTH) return "none";
703
773
  const compiled = compiledGlob(inner);
704
- return compiled && matchGlobTokens(compiled, arg) ? "prefix" : "none";
774
+ if (!compiled) return "none";
775
+ return rankWholeOrSegments(toolName, arg, intent, (segment) => matchGlobTokens(compiled, segment));
705
776
  }
706
777
  return arg === inner ? "exact" : "none";
707
778
  };
@@ -748,11 +819,11 @@ var policyArgForms = (toolName, toolInput, cwd) => {
748
819
  }
749
820
  return forms;
750
821
  };
751
- var matchToolPatternAny = (pattern, toolName, args) => {
822
+ var matchToolPatternAny = (pattern, toolName, args, intent = PATTERN_INTENT.grant) => {
752
823
  if (args.length === 0) return matchToolPattern(pattern, toolName);
753
824
  let best = "none";
754
825
  for (const arg of args) {
755
- const rank = matchToolPattern(pattern, toolName, arg);
826
+ const rank = matchToolPattern(pattern, toolName, arg, intent);
756
827
  if (matchRankWeight(rank) > matchRankWeight(best)) best = rank;
757
828
  }
758
829
  return best;
@@ -995,6 +1066,7 @@ var verdictForPolicy = (policy) => {
995
1066
  if (isAutoApprove(policy)) return "allow";
996
1067
  return "requires_human";
997
1068
  };
1069
+ var policyIntent = (policy) => policy.timeoutAction === "deny" ? PATTERN_INTENT.restrict : PATTERN_INTENT.grant;
998
1070
  var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
999
1071
  let best;
1000
1072
  let bestWeight = 0;
@@ -1003,7 +1075,7 @@ var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
1003
1075
  for (const candidate of policies) {
1004
1076
  if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
1005
1077
  if (candidate.sessionId && candidate.sessionId !== sessionId) continue;
1006
- const rank = matchToolPatternAny(candidate.tool, toolName, args);
1078
+ const rank = matchToolPatternAny(candidate.tool, toolName, args, policyIntent(candidate));
1007
1079
  if (rank === "none") continue;
1008
1080
  const weight = matchRankWeight(rank);
1009
1081
  const scoped = candidate.sessionId ? 2 : candidate.repoKey ? 1 : 0;
@@ -1253,6 +1325,8 @@ var ANSWER_SOURCES = [
1253
1325
  "sandbox"
1254
1326
  ];
1255
1327
  var isAnswerSource = (value) => typeof value === "string" && ANSWER_SOURCES.includes(value);
1328
+ var SETTLED_OUTCOMES = ["approve", "deny"];
1329
+ var isSettledOutcome = (value) => typeof value === "string" && SETTLED_OUTCOMES.includes(value);
1256
1330
  var classifyDecisionRisk = (toolName, toolTarget) => {
1257
1331
  const parts = [toolName, toolTarget ?? ""];
1258
1332
  const matchesAny = (patterns) => parts.some((part) => patterns.some((re) => re.test(part)));
@@ -1300,6 +1374,13 @@ var CAPABILITY_PATTERNS = {
1300
1374
  };
1301
1375
  var DECISION_EPISODE_SCHEMA_VERSION = 1;
1302
1376
  var DECISION_FEATURE_EXTRACTOR = `pushary-hooks/schema-${DECISION_EPISODE_SCHEMA_VERSION}`;
1377
+ var DECISION_ORIGIN = {
1378
+ human: "human",
1379
+ autopilot: "autopilot",
1380
+ safeReadonly: "safe_readonly",
1381
+ policyTimeout: "policy_timeout",
1382
+ killSwitch: "kill_switch"
1383
+ };
1303
1384
  var EPISODE_CAPABILITIES = [...POLICY_CAPABILITIES, "other"];
1304
1385
  var NETWORK_COMMAND_PATTERNS = [
1305
1386
  /\bcurl\b/i,
@@ -1373,9 +1454,8 @@ var deriveBlastRadius = (capability, riskClass, targetClass) => {
1373
1454
  return "low";
1374
1455
  };
1375
1456
  var deriveLabelTier = (origin, decision) => {
1376
- if (origin === "autopilot") return "none";
1377
- if (origin === "human" && (decision === "approve" || decision === "deny")) return "bronze";
1378
- return "bronze";
1457
+ if (origin !== DECISION_ORIGIN.human) return "none";
1458
+ return isSettledOutcome(decision) ? "bronze" : "none";
1379
1459
  };
1380
1460
  var buildDecisionEpisodeFeatures = (input) => {
1381
1461
  const isCommand = isCommandTool(input.toolName);
@@ -1423,6 +1503,7 @@ var matchesGlobPath = (glob, path) => {
1423
1503
  const compiled = compiledGlob(glob);
1424
1504
  return compiled ? matchGlobTokens(compiled, path) : false;
1425
1505
  };
1506
+ var SCOPE_PATH_MAX_LENGTH = 200;
1426
1507
  var evaluateScope = (contract, path) => {
1427
1508
  if (!contract) return { outcome: "no_contract" };
1428
1509
  if (typeof path !== "string" || path.length === 0) return { outcome: "in_scope" };
@@ -1436,6 +1517,7 @@ var evaluateScope = (contract, path) => {
1436
1517
  return { outcome: "out_of_scope", path };
1437
1518
  };
1438
1519
  var scopeRequiresHuman = (verdict) => verdict.outcome === "out_of_scope" || verdict.outcome === "off_limits";
1520
+ var widenablePath = (path) => path.length > 0 && path.length <= SCOPE_PATH_MAX_LENGTH ? path : void 0;
1439
1521
  var scopeChangeReason = (verdict) => {
1440
1522
  if (verdict.outcome === "out_of_scope") {
1441
1523
  return `Outside the scope you approved: ${verdict.path}`;
@@ -1465,9 +1547,9 @@ var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessi
1465
1547
  const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
1466
1548
  const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
1467
1549
  if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
1468
- return "safe_readonly";
1550
+ return DECISION_ORIGIN.safeReadonly;
1469
1551
  }
1470
- return "policy_timeout";
1552
+ return DECISION_ORIGIN.autopilot;
1471
1553
  };
1472
1554
  var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
1473
1555
  const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
@@ -1625,7 +1707,7 @@ var resolveGate = (input) => {
1625
1707
  kind: "gate",
1626
1708
  policy,
1627
1709
  scopeReason,
1628
- scopePath: scopeReason && "path" in scopeVerdict ? scopeVerdict.path : void 0
1710
+ scopePath: scopeReason && "path" in scopeVerdict ? widenablePath(scopeVerdict.path) : void 0
1629
1711
  };
1630
1712
  };
1631
1713
  var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
@@ -1707,6 +1789,7 @@ var claudePermissionRequestOutput = (decision, reason, updatedInput) => ({
1707
1789
  decision: decision === "allow" ? { behavior: "allow", ...updatedInput ? { updatedInput } : {} } : { behavior: "deny", ...reason ? { message: reason } : {} }
1708
1790
  }
1709
1791
  });
1792
+ var COMPATIBLE_GATE_EVENT = "PreToolUse";
1710
1793
  var gateOutputFor = (source, event, decision, reason) => {
1711
1794
  let output;
1712
1795
  if (source === "claude" || source === "vscode") {
@@ -1744,6 +1827,11 @@ var gateOutputFor = (source, event, decision, reason) => {
1744
1827
  user_message: "Command denied via Pushary.",
1745
1828
  agent_message: reason ?? "Denied from Pushary"
1746
1829
  };
1830
+ } else {
1831
+ const profile = hookCompatibleProfileBySource(source);
1832
+ if (profile && event === COMPATIBLE_GATE_EVENT) {
1833
+ output = renderPreToolUseVerdict(profile, claudePreToolUseOutput(decision, reason));
1834
+ }
1747
1835
  }
1748
1836
  return output;
1749
1837
  };
@@ -1904,12 +1992,12 @@ export {
1904
1992
  MANAGED_PROVIDERS,
1905
1993
  COMPATIBLE_AGENT_TYPES,
1906
1994
  PHONE_ANSWERED_TOOLS,
1995
+ canonicalToolName,
1907
1996
  toToolLookup,
1908
1997
  renderPreToolUseVerdict,
1909
1998
  HOOK_COMPATIBLE_PROFILES,
1910
1999
  hookCompatibleProfile,
1911
2000
  hookCompatibleProfileBySource,
1912
- compatibleConfigDir,
1913
2001
  compatibleTargetFiles,
1914
2002
  hookWritePath,
1915
2003
  isHookActivationReport,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  NOTIFICATION_PUSH_TYPES,
3
3
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
4
- } from "./chunk-INBTKFT6.js";
4
+ } from "./chunk-IAEVVUXO.js";
5
5
  import {
6
6
  cancelQuestion,
7
7
  isPolicyConfig,
@@ -10,13 +10,13 @@ import {
10
10
  } from "./chunk-LGXUGYUH.js";
11
11
  import {
12
12
  getMachineId
13
- } from "./chunk-J7PJALS2.js";
13
+ } from "./chunk-K7UIAE7I.js";
14
14
  import {
15
15
  withRetry
16
16
  } from "./chunk-EYRGBYZG.js";
17
17
  import {
18
18
  reportedInstallSource
19
- } from "./chunk-E3WQMDCE.js";
19
+ } from "./chunk-POAFXBNH.js";
20
20
  import {
21
21
  getApiKey,
22
22
  getBaseUrl
@@ -31,7 +31,9 @@ import {
31
31
  HOOK_SOURCES,
32
32
  PENDING_COMMAND_PREFIX,
33
33
  REPO_AWARE_AGENTS,
34
+ canonicalToolName,
34
35
  gateOutputFor,
36
+ hookCompatibleProfileBySource,
35
37
  isAutoApprove,
36
38
  localRepoKey,
37
39
  modeStateFromResponse,
@@ -40,8 +42,9 @@ import {
40
42
  redactSecrets,
41
43
  redactSecretsDeep,
42
44
  resolveAutoResolveOrigin,
43
- resolvePolicy
44
- } from "./chunk-VQYARC2K.js";
45
+ resolvePolicy,
46
+ toToolLookup
47
+ } from "./chunk-WPJS3OZH.js";
45
48
 
46
49
  // src/policy.ts
47
50
  import { createHash } from "crypto";
@@ -349,6 +352,25 @@ var OPENCODE_TOOL_NAMES = {
349
352
  websearch: "WebSearch",
350
353
  task: "Task"
351
354
  };
355
+ var PATCH_ARG_KEYS = ["command", "patch", "input"];
356
+ var patchedFilesIn = (input) => [...new Set(PATCH_ARG_KEYS.flatMap((key) => parseApplyPatchFiles(input[key])))];
357
+ var lookupsForInput = (profile, toolName, input, absolute) => {
358
+ const lookup = toToolLookup(profile, toolName, input, absolute);
359
+ if (!lookup.unreadable) return [lookup];
360
+ const files = patchedFilesIn(input);
361
+ if (files.length === 0) return [lookup];
362
+ return files.slice(0, GATE_MAX_TOOL_INPUTS + 1).map((file) => toToolLookup(profile, toolName, { ...input, file_path: absolute(file) }, absolute));
363
+ };
364
+ var compatibleGateInputs = (profile, toolName, toolInputs, cwd) => {
365
+ const absolute = (path) => cwd && !isAbsolute(path) ? resolve(cwd, path) : path;
366
+ const lookups = toolInputs.flatMap((input) => lookupsForInput(profile, toolName, input, absolute));
367
+ if (lookups.length > GATE_MAX_TOOL_INPUTS) return null;
368
+ if (lookups.some((lookup) => lookup.unreadable)) return null;
369
+ return {
370
+ toolName: lookups[0]?.tool ?? canonicalToolName(profile, toolName),
371
+ toolInputs: lookups.map((lookup) => lookup.input)
372
+ };
373
+ };
352
374
  var canonicalGateInputs = (source, toolName, toolInputs, cwd) => {
353
375
  if (source === "codex" && toolName === "apply_patch" || source === "vscode" && ["apply_patch", "applyPatch", "copilot_applyPatch"].includes(toolName)) {
354
376
  const paths = [...new Set(toolInputs.flatMap((input) => parseApplyPatchFiles(input.command ?? input.input ?? input.patch)))];
@@ -419,6 +441,8 @@ var canonicalGateInputs = (source, toolName, toolInputs, cwd) => {
419
441
  return typeof path === "string" ? { ...input, file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path } : input;
420
442
  }) };
421
443
  }
444
+ const profile = hookCompatibleProfileBySource(source);
445
+ if (profile) return compatibleGateInputs(profile, toolName, toolInputs, cwd);
422
446
  if (source !== "gemini") return { toolName, toolInputs };
423
447
  if (toolName === "run_shell_command") {
424
448
  return { toolName: "Bash", toolInputs: toolInputs.map((input) => ({ command: input.command })) };
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  activeIdentity
3
- } from "./chunk-SM54HHVY.js";
3
+ } from "./chunk-BNDHXPTQ.js";
4
4
  import {
5
5
  blockedActionFingerprint,
6
6
  defersToNativeMode,
7
7
  forgetBlockedActionHint,
8
8
  hasBlockedActionHint,
9
9
  recordBlockedActionHint
10
- } from "./chunk-IH7YUPFN.js";
10
+ } from "./chunk-A5ZWYQFV.js";
11
11
  import {
12
12
  isGatingMoment,
13
13
  recordKeylessMoment
14
- } from "./chunk-VACFOGF3.js";
14
+ } from "./chunk-YF4JDCU4.js";
15
15
  import {
16
16
  denyReasonFrom,
17
17
  isDeferAnswer
@@ -34,7 +34,7 @@ import {
34
34
  scopePathFor,
35
35
  settleQuestionsForToolUse,
36
36
  throttlePass
37
- } from "./chunk-WTT24AUA.js";
37
+ } from "./chunk-WXXDHGTZ.js";
38
38
  import {
39
39
  DECISION_STOP_REASON,
40
40
  askUser,
@@ -49,7 +49,7 @@ import {
49
49
  } from "./chunk-LGXUGYUH.js";
50
50
  import {
51
51
  getMachineId
52
- } from "./chunk-J7PJALS2.js";
52
+ } from "./chunk-K7UIAE7I.js";
53
53
  import {
54
54
  getApiKey,
55
55
  getBaseUrl
@@ -73,7 +73,7 @@ import {
73
73
  parseAgentQuestions,
74
74
  resolveGate,
75
75
  stoppedOutput
76
- } from "./chunk-VQYARC2K.js";
76
+ } from "./chunk-WPJS3OZH.js";
77
77
 
78
78
  // src/decision-episode.ts
79
79
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-YM4G6CFN.js";
3
+ } from "./chunk-OE6U2AXM.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveInstallSourceFor
3
- } from "./chunk-E3WQMDCE.js";
3
+ } from "./chunk-POAFXBNH.js";
4
4
  import {
5
5
  backupFile,
6
6
  readJsonSafe,
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AGENT_CHOICES
3
- } from "./chunk-OYYQHJAO.js";
3
+ } from "./chunk-BYGEZI7B.js";
4
4
  import {
5
5
  isBridgeLocator
6
- } from "./chunk-SLEWDZOA.js";
6
+ } from "./chunk-JYEGOYQC.js";
7
7
  import {
8
8
  pusharyHookExecutable
9
- } from "./chunk-AH5OO2YZ.js";
9
+ } from "./chunk-DVO4SZMJ.js";
10
10
  import {
11
11
  BELL_HOOK_BINARY
12
- } from "./chunk-SA6UB3GD.js";
12
+ } from "./chunk-2PLEJ7SY.js";
13
13
 
14
14
  // src/setup/hook-ownership.ts
15
15
  var commandsIn = (value) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-VQYARC2K.js";
3
+ } from "./chunk-WPJS3OZH.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  supportedClaudeEvents
4
- } from "./chunk-YM4G6CFN.js";
4
+ } from "./chunk-OE6U2AXM.js";
5
5
  import {
6
6
  guardBinary,
7
7
  holdBudgetOf,
8
8
  isProtectedByScope,
9
9
  registerHookEntry
10
- } from "./chunk-EWQZISYV.js";
10
+ } from "./chunk-RHLUJE4H.js";
11
11
  import {
12
12
  HOOK_LOCATOR_BINARY,
13
13
  withHookHoldBudget
14
- } from "./chunk-VQYARC2K.js";
14
+ } from "./chunk-WPJS3OZH.js";
15
15
 
16
16
  // src/claude-config.ts
17
17
  import { join } from "path";
@@ -8,15 +8,15 @@ import {
8
8
  reapplyGeminiSettings,
9
9
  reapplyVsCodePlugin,
10
10
  repairCodexHolds
11
- } from "./chunk-EZ3GX3NS.js";
12
- import "./chunk-IPDBFJ2M.js";
13
- import "./chunk-6T4OINZB.js";
14
- import "./chunk-AH5OO2YZ.js";
15
- import "./chunk-YM4G6CFN.js";
16
- import "./chunk-INBTKFT6.js";
17
- import "./chunk-EWQZISYV.js";
11
+ } from "./chunk-UVXNYYHG.js";
12
+ import "./chunk-XBJW4MV5.js";
13
+ import "./chunk-ZHL62DRA.js";
14
+ import "./chunk-DVO4SZMJ.js";
15
+ import "./chunk-OE6U2AXM.js";
16
+ import "./chunk-IAEVVUXO.js";
17
+ import "./chunk-RHLUJE4H.js";
18
18
  import "./chunk-4LL6LG5X.js";
19
- import "./chunk-VQYARC2K.js";
19
+ import "./chunk-WPJS3OZH.js";
20
20
  export {
21
21
  inspectConfiguredManagedProviders,
22
22
  reapplyAllAgents,
package/dist/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-DKTFZCLQ.js";
4
- import "../chunk-SM54HHVY.js";
5
- import "../chunk-6T4OINZB.js";
6
- import "../chunk-IH7YUPFN.js";
7
- import "../chunk-VACFOGF3.js";
3
+ } from "../chunk-X3KIGNIP.js";
4
+ import "../chunk-BNDHXPTQ.js";
5
+ import "../chunk-ZHL62DRA.js";
6
+ import "../chunk-A5ZWYQFV.js";
7
+ import "../chunk-YF4JDCU4.js";
8
8
  import "../chunk-YHG74UFF.js";
9
- import "../chunk-YM4G6CFN.js";
9
+ import "../chunk-OE6U2AXM.js";
10
10
  import {
11
11
  fetchAccountPublicKey
12
12
  } from "../chunk-EQRS3SGM.js";
@@ -18,20 +18,20 @@ import {
18
18
  handlePostToolUse,
19
19
  handleStop,
20
20
  reportEvent
21
- } from "../chunk-WTT24AUA.js";
22
- import "../chunk-INBTKFT6.js";
21
+ } from "../chunk-WXXDHGTZ.js";
22
+ import "../chunk-IAEVVUXO.js";
23
23
  import {
24
24
  askUser,
25
25
  cancelQuestion,
26
26
  waitForAnswer
27
27
  } from "../chunk-LGXUGYUH.js";
28
- import "../chunk-J7PJALS2.js";
29
- import "../chunk-EWQZISYV.js";
28
+ import "../chunk-K7UIAE7I.js";
29
+ import "../chunk-RHLUJE4H.js";
30
30
  import "../chunk-EYRGBYZG.js";
31
31
  import "../chunk-SAF6HGAA.js";
32
32
  import "../chunk-7QLSKOSU.js";
33
33
  import "../chunk-KZERVKTD.js";
34
- import "../chunk-E3WQMDCE.js";
34
+ import "../chunk-POAFXBNH.js";
35
35
  import "../chunk-4LL6LG5X.js";
36
36
  import {
37
37
  getApiKey,
@@ -39,7 +39,7 @@ import {
39
39
  } from "../chunk-2UMNXADU.js";
40
40
  import {
41
41
  resolvePolicy
42
- } from "../chunk-VQYARC2K.js";
42
+ } from "../chunk-WPJS3OZH.js";
43
43
  export {
44
44
  askUser,
45
45
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",