@pushary/agent-hooks 0.22.0 → 0.24.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 (91) hide show
  1. package/data/cursor-plugin/README.md +5 -1
  2. package/data/cursor-plugin/mcp.json +1 -1
  3. package/dist/bin/pushary-clean.js +1 -1
  4. package/dist/bin/pushary-codex-hook.js +2 -4
  5. package/dist/bin/pushary-codex.js +1 -2
  6. package/dist/bin/pushary-doctor.js +2 -2
  7. package/dist/bin/pushary-gemini-hook.js +2 -4
  8. package/dist/bin/pushary-hook.js +2 -3
  9. package/dist/bin/pushary-post-hook.js +1 -2
  10. package/dist/bin/pushary-prompt-hook.js +1 -2
  11. package/dist/bin/pushary-setup.js +39 -3
  12. package/dist/bin/pushary-stop-hook.js +1 -2
  13. package/dist/{chunk-BHAMEPOP.js → chunk-JPRRYC5X.js} +5 -0
  14. package/dist/{chunk-5LTQNC7R.js → chunk-OQZH5PVA.js} +242 -33
  15. package/dist/{chunk-3LC4YUMA.js → chunk-U4SSWRVM.js} +1 -1
  16. package/dist/src/index.d.ts +15 -10
  17. package/dist/src/index.js +2 -3
  18. package/package.json +1 -1
  19. package/dist/chunk-22CV7V7A.js +0 -38
  20. package/dist/chunk-2BE2IPMO.js +0 -315
  21. package/dist/chunk-2CRLBMOX.js +0 -244
  22. package/dist/chunk-2HMNOZPY.js +0 -109
  23. package/dist/chunk-2I6DLXJN.js +0 -219
  24. package/dist/chunk-2Q6XPZ4Q.js +0 -395
  25. package/dist/chunk-3MIR7ODJ.js +0 -112
  26. package/dist/chunk-3QZESA66.js +0 -222
  27. package/dist/chunk-4Z4MB37G.js +0 -96
  28. package/dist/chunk-5GFUI5N6.js +0 -132
  29. package/dist/chunk-5JEDLXEC.js +0 -99
  30. package/dist/chunk-5QHQVXWN.js +0 -174
  31. package/dist/chunk-6A3WCJO2.js +0 -416
  32. package/dist/chunk-6QWFMVCD.js +0 -328
  33. package/dist/chunk-6SRXMZAN.js +0 -99
  34. package/dist/chunk-7PTU7TGE.js +0 -96
  35. package/dist/chunk-AB4KX4XT.js +0 -109
  36. package/dist/chunk-AC4UYAGX.js +0 -136
  37. package/dist/chunk-ACE77TKQ.js +0 -328
  38. package/dist/chunk-BYNYYZWW.js +0 -412
  39. package/dist/chunk-C5TFTNHG.js +0 -244
  40. package/dist/chunk-CH53PBQN.js +0 -265
  41. package/dist/chunk-DF3BM6BF.js +0 -195
  42. package/dist/chunk-DKZVKEOW.js +0 -175
  43. package/dist/chunk-EMPL27ZV.js +0 -96
  44. package/dist/chunk-EQJXMEIR.js +0 -175
  45. package/dist/chunk-FXYTRZHC.js +0 -177
  46. package/dist/chunk-GTJGZBTQ.js +0 -328
  47. package/dist/chunk-HJMFYDWY.js +0 -222
  48. package/dist/chunk-HRQEECB6.js +0 -315
  49. package/dist/chunk-HWEMAAOY.js +0 -370
  50. package/dist/chunk-IBWCHA5M.js +0 -10
  51. package/dist/chunk-KTM4ZQ3W.js +0 -96
  52. package/dist/chunk-KTP2EPVB.js +0 -27
  53. package/dist/chunk-KYARP7KP.js +0 -97
  54. package/dist/chunk-M2N5DYWN.js +0 -247
  55. package/dist/chunk-M5F2L4KF.js +0 -177
  56. package/dist/chunk-M6S3M6E3.js +0 -244
  57. package/dist/chunk-N7VXDBQU.js +0 -6
  58. package/dist/chunk-O6A5RHWY.js +0 -122
  59. package/dist/chunk-ODUXELPM.js +0 -219
  60. package/dist/chunk-OF5WIOYS.js +0 -129
  61. package/dist/chunk-OP4QAN23.js +0 -99
  62. package/dist/chunk-P4JH2Q7Z.js +0 -102
  63. package/dist/chunk-PMD5JSV3.js +0 -242
  64. package/dist/chunk-PSSHNBEL.js +0 -177
  65. package/dist/chunk-QEPRH7JB.js +0 -315
  66. package/dist/chunk-QRXWPZKN.js +0 -308
  67. package/dist/chunk-QY4L6XHN.js +0 -384
  68. package/dist/chunk-RNWPCELY.js +0 -176
  69. package/dist/chunk-RSHN2AQ7.js +0 -29
  70. package/dist/chunk-RZLVE57X.js +0 -38
  71. package/dist/chunk-SDCIKREA.js +0 -241
  72. package/dist/chunk-SDREQWNI.js +0 -175
  73. package/dist/chunk-SH26ZOHU.js +0 -159
  74. package/dist/chunk-TRLBBLSS.js +0 -176
  75. package/dist/chunk-U6OXZPYC.js +0 -384
  76. package/dist/chunk-UIBTFZUD.js +0 -314
  77. package/dist/chunk-USUCPCUC.js +0 -156
  78. package/dist/chunk-V4GLWPD7.js +0 -174
  79. package/dist/chunk-VC6U3QGF.js +0 -142
  80. package/dist/chunk-VIST7ACL.js +0 -88
  81. package/dist/chunk-VUNL35KE.js +0 -16
  82. package/dist/chunk-VWBNI4SC.js +0 -174
  83. package/dist/chunk-W5KRWUNE.js +0 -262
  84. package/dist/chunk-WCGKLHCL.js +0 -154
  85. package/dist/chunk-WNXGIEX7.js +0 -219
  86. package/dist/chunk-XPVSZIA3.js +0 -315
  87. package/dist/chunk-Y633ZIJF.js +0 -384
  88. package/dist/chunk-YCL4GUFR.js +0 -222
  89. package/dist/chunk-YTMKB44I.js +0 -220
  90. package/dist/chunk-ZWBS3T7Q.js +0 -244
  91. package/dist/pushary-mode-T7XOPI6Z.js +0 -83
@@ -48,7 +48,7 @@ Add this to `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` for ever
48
48
  "pushary": {
49
49
  "type": "http",
50
50
  "url": "https://pushary.com/api/mcp/mcp",
51
- "headers": { "Authorization": "Bearer ${PUSHARY_API_KEY}" }
51
+ "headers": { "Authorization": "Bearer ${env:PUSHARY_API_KEY}" }
52
52
  }
53
53
  }
54
54
  }
@@ -67,6 +67,10 @@ source ~/.zshrc
67
67
 
68
68
  Install the Pushary app on your phone (or turn on web push) so the agent can reach you.
69
69
 
70
+ ## Troubleshooting
71
+
72
+ macOS GUI apps do not read `.zshrc`. If Settings > MCP shows pushary in red after you set the env var, launch Cursor from a terminal where `PUSHARY_API_KEY` is exported, or set the variable system-wide so GUI apps see it.
73
+
70
74
  ## What is in the plugin
71
75
 
72
76
  | Part | File | What it does |
@@ -4,7 +4,7 @@
4
4
  "type": "http",
5
5
  "url": "https://pushary.com/api/mcp/mcp",
6
6
  "headers": {
7
- "Authorization": "Bearer ${PUSHARY_API_KEY}"
7
+ "Authorization": "Bearer ${env:PUSHARY_API_KEY}"
8
8
  }
9
9
  }
10
10
  }
@@ -8,7 +8,7 @@ import {
8
8
  removeCodexHooks,
9
9
  removeGeminiSettings,
10
10
  removeInstructionBlock
11
- } from "../chunk-BHAMEPOP.js";
11
+ } from "../chunk-JPRRYC5X.js";
12
12
 
13
13
  // bin/pushary-clean.ts
14
14
  import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
@@ -5,6 +5,7 @@ import {
5
5
  } from "../chunk-KQYIHZ5E.js";
6
6
  import {
7
7
  CODEX_AGENT,
8
+ DECISION_LINE_MAX,
8
9
  DEFAULT_SESSION,
9
10
  askUser,
10
11
  codexAllow,
@@ -31,10 +32,7 @@ import {
31
32
  toCodexWire,
32
33
  toPolicyLookup,
33
34
  waitForAnswer
34
- } from "../chunk-M4GDBGXF.js";
35
- import {
36
- DECISION_LINE_MAX
37
- } from "../chunk-WPVL6VIT.js";
35
+ } from "../chunk-OQZH5PVA.js";
38
36
  import "../chunk-DWED7BS3.js";
39
37
  import {
40
38
  getApiKey
@@ -4,8 +4,7 @@ import {
4
4
  getMachineId,
5
5
  reportEvent,
6
6
  waitForAnswer
7
- } from "../chunk-M4GDBGXF.js";
8
- import "../chunk-WPVL6VIT.js";
7
+ } from "../chunk-OQZH5PVA.js";
9
8
  import "../chunk-DWED7BS3.js";
10
9
  import {
11
10
  getApiKey
@@ -8,7 +8,7 @@ import {
8
8
  missingCodexHookEvents,
9
9
  missingGeminiHookEvents,
10
10
  untrustedCodexHookEvents
11
- } from "../chunk-BHAMEPOP.js";
11
+ } from "../chunk-JPRRYC5X.js";
12
12
  import {
13
13
  callMcpTool,
14
14
  sendMcpRequest
@@ -283,7 +283,7 @@ var main = async () => {
283
283
  const cursorAuth = cursorServers.pushary?.headers?.Authorization;
284
284
  if (!cursorAuth) {
285
285
  check(false, "Cursor: API key linked", "no Authorization in plugin mcp.json \u2014 re-run setup");
286
- } else if (cursorAuth.includes("${PUSHARY_API_KEY}")) {
286
+ } else if (cursorAuth.includes("PUSHARY_API_KEY")) {
287
287
  check(false, "Cursor: API key linked", "plugin relies on $PUSHARY_API_KEY; re-run setup to embed the key so the gate works when Cursor is launched from the GUI");
288
288
  } else {
289
289
  check(true, "Cursor: API key linked", "embedded in plugin mcp.json");
@@ -4,6 +4,7 @@ import {
4
4
  isDeferAnswer
5
5
  } from "../chunk-KQYIHZ5E.js";
6
6
  import {
7
+ DECISION_LINE_MAX,
7
8
  DEFAULT_SESSION,
8
9
  askUser,
9
10
  deriveActionBody,
@@ -23,10 +24,7 @@ import {
23
24
  savePendingQuestion,
24
25
  sendNotification,
25
26
  waitForAnswer
26
- } from "../chunk-M4GDBGXF.js";
27
- import {
28
- DECISION_LINE_MAX
29
- } from "../chunk-WPVL6VIT.js";
27
+ } from "../chunk-OQZH5PVA.js";
30
28
  import "../chunk-DWED7BS3.js";
31
29
  import {
32
30
  getApiKey
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-FGZZRCOG.js";
4
+ } from "../chunk-U4SSWRVM.js";
5
5
  import "../chunk-KQYIHZ5E.js";
6
- import "../chunk-M4GDBGXF.js";
7
- import "../chunk-WPVL6VIT.js";
6
+ import "../chunk-OQZH5PVA.js";
8
7
  import "../chunk-DWED7BS3.js";
9
8
  import "../chunk-NKXSILEW.js";
10
9
 
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-M4GDBGXF.js";
5
- import "../chunk-WPVL6VIT.js";
4
+ } from "../chunk-OQZH5PVA.js";
6
5
  import "../chunk-DWED7BS3.js";
7
6
  import "../chunk-NKXSILEW.js";
8
7
 
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-M4GDBGXF.js";
5
- import "../chunk-WPVL6VIT.js";
4
+ } from "../chunk-OQZH5PVA.js";
6
5
  import "../chunk-DWED7BS3.js";
7
6
  import "../chunk-NKXSILEW.js";
8
7
 
@@ -13,11 +13,15 @@ import {
13
13
  execNpm,
14
14
  npmErrorMessage,
15
15
  renderAgentInstructions,
16
+ renderProjectAgentInstructions,
16
17
  writeInstructionBlock
17
- } from "../chunk-BHAMEPOP.js";
18
+ } from "../chunk-JPRRYC5X.js";
18
19
  import {
19
- isValidApiKey
20
- } from "../chunk-WPVL6VIT.js";
20
+ isValidApiKey,
21
+ reportEvent
22
+ } from "../chunk-OQZH5PVA.js";
23
+ import "../chunk-DWED7BS3.js";
24
+ import "../chunk-NKXSILEW.js";
21
25
 
22
26
  // bin/pushary-setup.ts
23
27
  import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
@@ -857,6 +861,26 @@ var AGENT_SETUP = {
857
861
  cursor: setupCursor,
858
862
  custom: setupCustom
859
863
  };
864
+ var PROJECT_INSTRUCTION_TARGETS = {
865
+ claude_code: { file: "CLAUDE.md", label: "Claude Code" },
866
+ codex: { file: "AGENTS.md", label: "Codex" }
867
+ };
868
+ var offerProjectInstructions = async (agents) => {
869
+ const targets = agents.map((agent) => PROJECT_INSTRUCTION_TARGETS[agent]).filter((target) => target !== void 0);
870
+ if (targets.length === 0 || !process.stdout.isTTY) return;
871
+ console.log();
872
+ const wanted = await confirm({
873
+ message: "Add Pushary instructions to this project's CLAUDE.md/AGENTS.md so your whole team's agents can use phone approvals?",
874
+ default: false
875
+ });
876
+ if (!wanted) return;
877
+ for (const target of targets) {
878
+ await spinner(`Writing managed block to ${join(process.cwd(), target.file)}`, async () => {
879
+ writeInstructionBlock(join(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
880
+ }, { optional: true });
881
+ }
882
+ console.log(` ${dim2("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
883
+ };
860
884
  var main = async () => {
861
885
  const version = getPackageVersion();
862
886
  console.log();
@@ -939,9 +963,11 @@ var main = async () => {
939
963
  ${dim2("No agents selected. API key saved.")}`);
940
964
  } else {
941
965
  const failed = [];
966
+ const completed = [];
942
967
  for (const agent of agents) {
943
968
  try {
944
969
  await AGENT_SETUP[agent](trimmedKey);
970
+ completed.push(agent);
945
971
  } catch (err) {
946
972
  failed.push(agent.replace("_", " "));
947
973
  console.log(` ${yellow2("!")} ${agent.replace("_", " ")} setup failed: ${formatError(err)}`);
@@ -952,6 +978,16 @@ var main = async () => {
952
978
  console.log();
953
979
  console.log(` ${yellow2("!")} Failed: ${failed.join(", ")} ${dim2("(others completed successfully)")}`);
954
980
  }
981
+ await offerProjectInstructions(agents);
982
+ if (completed.length > 0) {
983
+ try {
984
+ await reportEvent(
985
+ { event: "agent_setup_completed", agentType: "setup", agents: completed },
986
+ { maxAttempts: 1, timeoutMs: 2e3 }
987
+ );
988
+ } catch {
989
+ }
990
+ }
955
991
  }
956
992
  if (connectMode !== "app") {
957
993
  const skipPhone = process.argv.includes("--skip-phone");
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-M4GDBGXF.js";
5
- import "../chunk-WPVL6VIT.js";
4
+ } from "../chunk-OQZH5PVA.js";
6
5
  import "../chunk-DWED7BS3.js";
7
6
  import "../chunk-NKXSILEW.js";
8
7
 
@@ -227,6 +227,10 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
227
227
 
228
228
  Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out with no answer, retry once with a longer timeout before falling back to asking in the terminal.`;
229
229
  var renderAgentInstructions = (label) => instructionBody(label);
230
+ var KEYLESS_FALLBACK_LINE = "This repo is set up with Pushary phone approvals. No PUSHARY_API_KEY found for you, so remote notifications are off; questions will fall back to the terminal. Setup: https://pushary.com/docs?utm_source=agents-md&utm_medium=keyless-fallback";
231
+ var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
232
+
233
+ If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
230
234
  var wrap = (body) => `${PUSHARY_BEGIN}
231
235
  ${body}
232
236
  ${PUSHARY_END}`;
@@ -315,6 +319,7 @@ export {
315
319
  hasGeminiHooks,
316
320
  missingGeminiHookEvents,
317
321
  renderAgentInstructions,
322
+ renderProjectAgentInstructions,
318
323
  writeInstructionBlock,
319
324
  removeInstructionBlock,
320
325
  hasInstructionBlock,
@@ -1,12 +1,3 @@
1
- import {
2
- ACTION_BODY_MAX,
3
- DECISION_LINE_MAX,
4
- extractPolicyArg,
5
- isApprovalMode,
6
- isSafeReadOnlyCommand,
7
- matchRankWeight,
8
- matchToolPattern
9
- } from "./chunk-WPVL6VIT.js";
10
1
  import {
11
2
  callMcpTool,
12
3
  withRetry
@@ -16,6 +7,156 @@ import {
16
7
  getBaseUrl
17
8
  } from "./chunk-NKXSILEW.js";
18
9
 
10
+ // ../contracts/src/index.ts
11
+ var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
12
+ var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
13
+ var MATCH_RANKS = ["none", "tool", "prefix", "exact"];
14
+ var matchRankWeight = (rank) => MATCH_RANKS.indexOf(rank);
15
+ var matchToolPattern = (pattern, toolName, arg) => {
16
+ const open = pattern.indexOf("(");
17
+ if (open === -1 || !pattern.endsWith(")")) {
18
+ return pattern === toolName ? "tool" : "none";
19
+ }
20
+ if (pattern.slice(0, open) !== toolName || arg === void 0) return "none";
21
+ const inner = pattern.slice(open + 1, -1);
22
+ if (inner.endsWith(":*")) {
23
+ return arg.startsWith(inner.slice(0, -2)) ? "prefix" : "none";
24
+ }
25
+ return arg === inner ? "exact" : "none";
26
+ };
27
+ var POLICY_ARG_KEYS = {
28
+ Bash: "command",
29
+ Edit: "file_path",
30
+ Write: "file_path"
31
+ };
32
+ var extractPolicyArg = (toolName, toolInput) => {
33
+ const key = POLICY_ARG_KEYS[toolName];
34
+ if (!key) return void 0;
35
+ const value = toolInput[key];
36
+ return typeof value === "string" ? value : void 0;
37
+ };
38
+ var SAFE_SHELL_COMMANDS = /* @__PURE__ */ new Set([
39
+ "ls",
40
+ "pwd",
41
+ "cd",
42
+ "cat",
43
+ "head",
44
+ "tail",
45
+ "wc",
46
+ "echo",
47
+ "printf",
48
+ "which",
49
+ "type",
50
+ "whoami",
51
+ "id",
52
+ "uname",
53
+ "arch",
54
+ "printenv",
55
+ "locale",
56
+ "tty",
57
+ "dirname",
58
+ "basename",
59
+ "realpath",
60
+ "readlink",
61
+ "stat",
62
+ "cut",
63
+ "nl",
64
+ "tr",
65
+ "comm",
66
+ "diff",
67
+ "cmp",
68
+ "grep",
69
+ "egrep",
70
+ "fgrep",
71
+ "jq",
72
+ "cksum",
73
+ "md5sum",
74
+ "sha1sum",
75
+ "sha256sum",
76
+ "du",
77
+ "df",
78
+ "ps",
79
+ "true"
80
+ ]);
81
+ var SAFE_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set([
82
+ "status",
83
+ "log",
84
+ "diff",
85
+ "show",
86
+ "rev-parse",
87
+ "describe",
88
+ "blame",
89
+ "shortlog",
90
+ "ls-files",
91
+ "ls-tree",
92
+ "cat-file",
93
+ "whatchanged",
94
+ "rev-list",
95
+ "name-rev",
96
+ "for-each-ref",
97
+ "var",
98
+ "count-objects"
99
+ ]);
100
+ var GIT_WRITE_FLAGS = (token) => token === "--output" || token.startsWith("--output=");
101
+ var UNSAFE_SHELL_CHARS = /[;&|<>(){}`\\\n\r]/;
102
+ var basenameOf = (token) => {
103
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
104
+ return slash === -1 ? token : token.slice(slash + 1);
105
+ };
106
+ var tokenizeShellCommand = (command) => {
107
+ const tokens = [];
108
+ let current = "";
109
+ let quote = null;
110
+ let started = false;
111
+ for (const ch of command) {
112
+ if (quote) {
113
+ if (ch === quote) quote = null;
114
+ else current += ch;
115
+ started = true;
116
+ continue;
117
+ }
118
+ if (ch === '"' || ch === "'") {
119
+ quote = ch;
120
+ started = true;
121
+ continue;
122
+ }
123
+ if (ch === " " || ch === " ") {
124
+ if (started) {
125
+ tokens.push(current);
126
+ current = "";
127
+ started = false;
128
+ }
129
+ continue;
130
+ }
131
+ current += ch;
132
+ started = true;
133
+ }
134
+ if (quote) return null;
135
+ if (started) tokens.push(current);
136
+ return tokens;
137
+ };
138
+ var isSafeReadOnlyCommand = (command) => {
139
+ const trimmed = command.trim();
140
+ if (!trimmed || trimmed.length > 2e3) return false;
141
+ if (UNSAFE_SHELL_CHARS.test(trimmed)) return false;
142
+ const tokens = tokenizeShellCommand(trimmed);
143
+ if (!tokens || tokens.length === 0) return false;
144
+ const first = tokens[0];
145
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(first) || first.includes("$")) return false;
146
+ const exe = basenameOf(first);
147
+ if (exe === "git") {
148
+ const sub = tokens[1];
149
+ if (!sub || sub.startsWith("-")) return false;
150
+ if (!SAFE_GIT_SUBCOMMANDS.has(sub)) return false;
151
+ return !tokens.some(GIT_WRITE_FLAGS);
152
+ }
153
+ return SAFE_SHELL_COMMANDS.has(exe);
154
+ };
155
+ var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
156
+ var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
157
+ var ACTION_BODY_MAX = 4e3;
158
+ var DECISION_LINE_MAX = 500;
159
+
19
160
  // src/validate.ts
20
161
  var isPolicyConfig = (data) => {
21
162
  if (!data || typeof data !== "object") return false;
@@ -132,6 +273,15 @@ var autoApprove = (tool) => ({
132
273
  mode: "terminal_only",
133
274
  pushFirstSeconds: 0
134
275
  });
276
+ var resolveAutoResolveOrigin = (config, toolName, toolInput) => {
277
+ const arg = toolInput ? extractPolicyArg(toolName, toolInput) : void 0;
278
+ const match = findBestMatch(config.policies, toolName, arg);
279
+ const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
280
+ if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
281
+ return "safe_readonly";
282
+ }
283
+ return "policy_timeout";
284
+ };
135
285
  var resolvePolicy = (config, toolName, modeOverride, toolInput) => {
136
286
  const arg = toolInput ? extractPolicyArg(toolName, toolInput) : void 0;
137
287
  const match = findBestMatch(config.policies, toolName, arg);
@@ -305,6 +455,12 @@ var deriveBlocker = (mode, reason) => {
305
455
  return text.slice(0, DECISION_LINE_MAX);
306
456
  };
307
457
 
458
+ // src/identity.ts
459
+ import { createHash as createHash2 } from "crypto";
460
+ import { hostname } from "os";
461
+ var deriveMachineId = (host) => createHash2("sha256").update(host).digest("hex").slice(0, 8);
462
+ var getMachineId = () => deriveMachineId(hostname());
463
+
308
464
  // src/pending.ts
309
465
  import { join as join2 } from "path";
310
466
  import { tmpdir as tmpdir2 } from "os";
@@ -351,12 +507,6 @@ var removePendingSession = (sessionId) => {
351
507
  }
352
508
  };
353
509
 
354
- // src/identity.ts
355
- import { createHash as createHash2 } from "crypto";
356
- import { hostname } from "os";
357
- var deriveMachineId = (host) => createHash2("sha256").update(host).digest("hex").slice(0, 8);
358
- var getMachineId = () => deriveMachineId(hostname());
359
-
360
510
  // src/usage.ts
361
511
  import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, readSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
362
512
  import { join as join3 } from "path";
@@ -606,29 +756,48 @@ var permissionTimeoutDecision = (timeoutAction) => {
606
756
  };
607
757
  var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
608
758
 
609
- // src/events.ts
610
- import { basename, join as join4 } from "path";
611
- import { createHash as createHash3 } from "crypto";
612
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
759
+ // src/throttle.ts
760
+ import { join as join4 } from "path";
613
761
  import { tmpdir as tmpdir4 } from "os";
614
- var INTENT_DIR = join4(tmpdir4(), "pushary-intent");
762
+ import { createHash as createHash3 } from "crypto";
763
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
764
+ var THROTTLE_DIR = join4(tmpdir4(), "pushary-throttle");
765
+ var markerPath = (key) => join4(THROTTLE_DIR, createHash3("sha256").update(key).digest("hex").slice(0, 16));
766
+ var throttlePass = (key, windowMs) => {
767
+ try {
768
+ const path = markerPath(key);
769
+ if (existsSync3(path) && Date.now() - statSync3(path).mtimeMs < windowMs) return false;
770
+ mkdirSync3(THROTTLE_DIR, { recursive: true });
771
+ writeFileSync4(path, "", "utf-8");
772
+ return true;
773
+ } catch {
774
+ return false;
775
+ }
776
+ };
777
+
778
+ // src/events.ts
779
+ import { basename, join as join5 } from "path";
780
+ import { createHash as createHash4 } from "crypto";
781
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
782
+ import { tmpdir as tmpdir5 } from "os";
783
+ var INTENT_DIR = join5(tmpdir5(), "pushary-intent");
615
784
  var INTENT_MAX_BYTES = 2048;
616
785
  var INTENT_GRACE_MS = 10 * 60 * 1e3;
617
786
  var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
618
- var intentFile = (sessionId) => join4(INTENT_DIR, sanitizeSession(sessionId));
787
+ var intentFile = (sessionId) => join5(INTENT_DIR, sanitizeSession(sessionId));
619
788
  var saveLastPrompt = (sessionId, prompt) => {
620
789
  try {
621
790
  const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
622
791
  if (!trimmed) return;
623
- mkdirSync3(INTENT_DIR, { recursive: true });
624
- writeFileSync4(intentFile(sessionId), trimmed, "utf-8");
792
+ mkdirSync4(INTENT_DIR, { recursive: true });
793
+ writeFileSync5(intentFile(sessionId), trimmed, "utf-8");
625
794
  } catch {
626
795
  }
627
796
  };
628
797
  var readLastPrompt = (sessionId) => {
629
798
  try {
630
799
  const path = intentFile(sessionId);
631
- if (isDefaultSession(sessionId) && statSync3(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
800
+ if (isDefaultSession(sessionId) && statSync4(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
632
801
  const value = readFileSync3(path, "utf-8").trim();
633
802
  return value.length > 0 ? value : void 0;
634
803
  } catch {
@@ -653,9 +822,9 @@ var cleanupPendingQuestions = async (sessionId) => {
653
822
  var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
654
823
  var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
655
824
  var readFreshCachedPolicy = (apiKey) => {
656
- const hash = createHash3("sha256").update(apiKey).digest("hex").slice(0, 12);
657
- const path = join4(tmpdir4(), `pushary-policy-${hash}.json`);
658
- if (!existsSync3(path)) return null;
825
+ const hash = createHash4("sha256").update(apiKey).digest("hex").slice(0, 12);
826
+ const path = join5(tmpdir5(), `pushary-policy-${hash}.json`);
827
+ if (!existsSync4(path)) return null;
659
828
  const cached = JSON.parse(readFileSync3(path, "utf-8"));
660
829
  if (!isPolicyConfig(cached)) return null;
661
830
  if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
@@ -674,6 +843,24 @@ var deriveDecisionSource = (toolName, toolInput, liveMode) => {
674
843
  return void 0;
675
844
  }
676
845
  };
846
+ var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode) => {
847
+ try {
848
+ if (liveMode.kill) return void 0;
849
+ const policy = readFreshCachedPolicy(getApiKey());
850
+ if (!policy) return void 0;
851
+ const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput);
852
+ if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
853
+ return {
854
+ decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput),
855
+ toolName,
856
+ toolTarget: deriveToolTarget(toolName, toolInput)
857
+ };
858
+ } catch {
859
+ return void 0;
860
+ }
861
+ };
862
+ var AUTO_DECISION_WINDOW_MS = 15 * 60 * 1e3;
863
+ var HOOK_ACTIVE_WINDOW_MS = 30 * 60 * 1e3;
677
864
  var deriveUsage = (transcriptPath, sessionId) => {
678
865
  if (!transcriptPath || process.env.PUSHARY_COST_TRACKING === "off") return void 0;
679
866
  try {
@@ -682,6 +869,7 @@ var deriveUsage = (transcriptPath, sessionId) => {
682
869
  return void 0;
683
870
  }
684
871
  };
872
+ var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
685
873
  var reportEvent = async (event, options = {}) => {
686
874
  const apiKey = getApiKey();
687
875
  const baseUrl = getBaseUrl();
@@ -694,7 +882,8 @@ var reportEvent = async (event, options = {}) => {
694
882
  },
695
883
  body: JSON.stringify({
696
884
  ...event,
697
- machineId: event.machineId ?? getMachineId()
885
+ machineId: event.machineId ?? getMachineId(),
886
+ installMode: detectInstallMode()
698
887
  }),
699
888
  signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
700
889
  });
@@ -712,9 +901,12 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
712
901
  const lookup = toPolicyLookup(input.tool_name, input.tool_input);
713
902
  const isError = isToolResultError(input.tool_result);
714
903
  const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
904
+ const sessionKey = input.session_id || DEFAULT_SESSION;
715
905
  const liveMode = await fetchModeState(getApiKey(), input.session_id);
716
- await Promise.allSettled([
717
- cleanupPendingQuestions(input.session_id || DEFAULT_SESSION),
906
+ const decisionSource = deriveDecisionSource(lookup.tool, lookup.input, liveMode);
907
+ const beacon = decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode) : void 0;
908
+ const reports = [
909
+ cleanupPendingQuestions(sessionKey),
718
910
  reportEvent({
719
911
  event: isError ? "tool_error" : "tool_complete",
720
912
  agentType: agent.type,
@@ -722,11 +914,26 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
722
914
  action,
723
915
  sessionId: input.session_id,
724
916
  error: isError ? String(input.tool_result?.error ?? input.tool_result?.stderr ?? "").slice(0, 500) : void 0,
725
- decisionSource: deriveDecisionSource(lookup.tool, lookup.input, liveMode),
917
+ decisionSource,
918
+ ...beacon ? { decisionOrigin: beacon.decisionOrigin, toolName: beacon.toolName, toolTarget: beacon.toolTarget } : {},
726
919
  meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, input.tool_result, input.cwd ?? process.cwd()) : void 0,
727
920
  usage: deriveUsage(input.transcript_path, input.session_id)
728
921
  })
729
- ]);
922
+ ];
923
+ if (throttlePass(`hookactive:${sessionKey}`, HOOK_ACTIVE_WINDOW_MS)) {
924
+ reports.push(
925
+ reportEvent(
926
+ {
927
+ event: "agent_hook_active",
928
+ agentType: agent.type,
929
+ agentName: `${agent.label} - ${projectName}`,
930
+ sessionId: input.session_id
931
+ },
932
+ { maxAttempts: 1, timeoutMs: 800 }
933
+ )
934
+ );
935
+ }
936
+ await Promise.allSettled(reports);
730
937
  } catch {
731
938
  }
732
939
  };
@@ -789,6 +996,8 @@ var handleNotification = async (input) => {
789
996
  };
790
997
 
791
998
  export {
999
+ isValidApiKey,
1000
+ DECISION_LINE_MAX,
792
1001
  askUser,
793
1002
  waitForAnswer,
794
1003
  cancelQuestion,
@@ -802,9 +1011,9 @@ export {
802
1011
  deriveActionBody,
803
1012
  deriveAction,
804
1013
  deriveBlocker,
1014
+ getMachineId,
805
1015
  DEFAULT_SESSION,
806
1016
  savePendingQuestion,
807
- getMachineId,
808
1017
  readLastUserPrompt,
809
1018
  CODEX_AGENT,
810
1019
  codexAllow,
@@ -19,7 +19,7 @@ import {
19
19
  savePendingQuestion,
20
20
  sendNotification,
21
21
  waitForAnswer
22
- } from "./chunk-5LTQNC7R.js";
22
+ } from "./chunk-OQZH5PVA.js";
23
23
  import {
24
24
  getApiKey
25
25
  } from "./chunk-NKXSILEW.js";