@pushary/agent-hooks 0.87.2 → 0.87.4

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 (63) hide show
  1. package/data/cursor-plugin/hooks/hooks.json +7 -0
  2. package/data/cursor-plugin/scripts/pushary-gate.mjs +94 -28
  3. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +81 -1
  4. package/dist/bin/pushary-bell-hook.js +4 -4
  5. package/dist/bin/pushary-bell.js +7 -6
  6. package/dist/bin/pushary-claude.js +14 -11
  7. package/dist/bin/pushary-clean.js +36 -20
  8. package/dist/bin/pushary-codex-bridge.js +6 -5
  9. package/dist/bin/pushary-codex-hook.js +129 -52
  10. package/dist/bin/pushary-codex.js +4 -3
  11. package/dist/bin/pushary-connect.js +6 -6
  12. package/dist/bin/pushary-daemon.js +5 -4
  13. package/dist/bin/pushary-disconnect.js +65 -8
  14. package/dist/bin/pushary-doctor.js +79 -18
  15. package/dist/bin/pushary-elicitation-hook.js +1 -1
  16. package/dist/bin/pushary-gemini-bridge.js +6 -5
  17. package/dist/bin/pushary-gemini-hook.js +12 -10
  18. package/dist/bin/pushary-hook.js +6 -5
  19. package/dist/bin/pushary-login.js +4 -4
  20. package/dist/bin/pushary-logout.js +5 -4
  21. package/dist/bin/pushary-mode.js +3 -3
  22. package/dist/bin/pushary-notification-hook.js +4 -3
  23. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  24. package/dist/bin/pushary-opencode-hook.js +329 -0
  25. package/dist/bin/pushary-permission-denied-hook.js +6 -5
  26. package/dist/bin/pushary-permission-hook.js +6 -5
  27. package/dist/bin/pushary-post-hook.js +4 -3
  28. package/dist/bin/pushary-prompt-hook.js +4 -3
  29. package/dist/bin/pushary-session-end-hook.js +4 -3
  30. package/dist/bin/pushary-session-start-hook.js +4 -3
  31. package/dist/bin/pushary-setup.js +238 -84
  32. package/dist/bin/pushary-stats.js +2 -2
  33. package/dist/bin/pushary-status.js +5 -5
  34. package/dist/bin/pushary-stop-hook.js +4 -3
  35. package/dist/bin/pushary-stopfailure-hook.js +4 -3
  36. package/dist/bin/pushary-upgrade.js +7 -6
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-5646VKR4.js → chunk-2JYQAW4B.js} +5 -53
  39. package/dist/chunk-7J67NYK4.js +287 -0
  40. package/dist/{chunk-M7RYIZX6.js → chunk-B7ZBHWOW.js} +10 -1
  41. package/dist/{chunk-QBWYNVKY.js → chunk-BFBAHOGR.js} +1 -1
  42. package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
  43. package/dist/{chunk-3LKYBFWA.js → chunk-EJL3PJU7.js} +1 -1
  44. package/dist/chunk-GIRT7677.js +11 -0
  45. package/dist/{chunk-SJVLFIYP.js → chunk-H5PIPIKQ.js} +1 -1
  46. package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
  47. package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
  48. package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
  49. package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
  50. package/dist/{chunk-LT2VLPUK.js → chunk-KULUNLRO.js} +18 -2
  51. package/dist/{chunk-B3V2H6SA.js → chunk-NAPRGHZ3.js} +54 -13
  52. package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
  53. package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
  54. package/dist/{chunk-XW5CHAQX.js → chunk-QXZ2CKRK.js} +2 -2
  55. package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
  56. package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
  57. package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
  58. package/dist/{chunk-V2XPF77V.js → chunk-VOE4SYPR.js} +42 -279
  59. package/dist/{chunk-OT4YD54Q.js → chunk-YX6ZKCC5.js} +1 -1
  60. package/dist/{chunk-2T3GX7WG.js → chunk-YYTLTEGU.js} +1 -1
  61. package/dist/chunk-ZGC3W23N.js +371 -0
  62. package/dist/src/index.js +6 -5
  63. package/package.json +2 -1
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLedgerSummary
4
- } from "../chunk-7MBDFNUC.js";
4
+ } from "../chunk-VIHRP25S.js";
5
5
  import {
6
6
  exitOnHelpFlag
7
7
  } from "../chunk-SQOFGPNX.js";
@@ -11,7 +11,7 @@ import "../chunk-KZERVKTD.js";
11
11
  import {
12
12
  getApiKey
13
13
  } from "../chunk-2UMNXADU.js";
14
- import "../chunk-7YJS2VTG.js";
14
+ import "../chunk-DMW54VWK.js";
15
15
 
16
16
  // bin/pushary-stats.ts
17
17
  exitOnHelpFlag("stats");
@@ -8,18 +8,18 @@ import {
8
8
  describeReach,
9
9
  fetchChannels,
10
10
  reachVerdict
11
- } from "../chunk-3LKYBFWA.js";
11
+ } from "../chunk-EJL3PJU7.js";
12
12
  import "../chunk-3EGEA4KH.js";
13
13
  import {
14
14
  checkApiKey,
15
15
  describeKeyCheck
16
- } from "../chunk-A4I5JGCW.js";
16
+ } from "../chunk-KFAH6KRE.js";
17
17
  import {
18
18
  createIo
19
19
  } from "../chunk-5RUIFDTP.js";
20
20
  import {
21
21
  readKeySource
22
- } from "../chunk-OT4YD54Q.js";
22
+ } from "../chunk-YX6ZKCC5.js";
23
23
  import {
24
24
  rejectUnknownFlags
25
25
  } from "../chunk-GMXKITVA.js";
@@ -32,7 +32,7 @@ import "../chunk-DINDL2CF.js";
32
32
  import "../chunk-6MTNS63X.js";
33
33
  import "../chunk-BSZYIAZL.js";
34
34
  import "../chunk-SAF6HGAA.js";
35
- import "../chunk-Y3UTTFTZ.js";
35
+ import "../chunk-KBS53WOE.js";
36
36
  import {
37
37
  UsageError
38
38
  } from "../chunk-7QLSKOSU.js";
@@ -40,7 +40,7 @@ import {
40
40
  EXIT
41
41
  } from "../chunk-KZERVKTD.js";
42
42
  import "../chunk-2UMNXADU.js";
43
- import "../chunk-7YJS2VTG.js";
43
+ import "../chunk-DMW54VWK.js";
44
44
 
45
45
  // src/status.ts
46
46
  var keySourceLine = (source) => {
@@ -4,16 +4,17 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleStop
7
- } from "../chunk-V2XPF77V.js";
7
+ } from "../chunk-VOE4SYPR.js";
8
8
  import "../chunk-J3YDT4HM.js";
9
+ import "../chunk-7J67NYK4.js";
9
10
  import "../chunk-RN3NOEJF.js";
10
11
  import "../chunk-BSZYIAZL.js";
11
12
  import "../chunk-SAF6HGAA.js";
12
- import "../chunk-Y3UTTFTZ.js";
13
+ import "../chunk-KBS53WOE.js";
13
14
  import "../chunk-7QLSKOSU.js";
14
15
  import "../chunk-KZERVKTD.js";
15
16
  import "../chunk-2UMNXADU.js";
16
- import "../chunk-7YJS2VTG.js";
17
+ import "../chunk-DMW54VWK.js";
17
18
 
18
19
  // bin/pushary-stop-hook.ts
19
20
  var main = async () => {
@@ -4,16 +4,17 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleStopFailure
7
- } from "../chunk-V2XPF77V.js";
7
+ } from "../chunk-VOE4SYPR.js";
8
8
  import "../chunk-J3YDT4HM.js";
9
+ import "../chunk-7J67NYK4.js";
9
10
  import "../chunk-RN3NOEJF.js";
10
11
  import "../chunk-BSZYIAZL.js";
11
12
  import "../chunk-SAF6HGAA.js";
12
- import "../chunk-Y3UTTFTZ.js";
13
+ import "../chunk-KBS53WOE.js";
13
14
  import "../chunk-7QLSKOSU.js";
14
15
  import "../chunk-KZERVKTD.js";
15
16
  import "../chunk-2UMNXADU.js";
16
- import "../chunk-7YJS2VTG.js";
17
+ import "../chunk-DMW54VWK.js";
17
18
 
18
19
  // bin/pushary-stopfailure-hook.ts
19
20
  var main = async () => {
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectClaudeVersion
4
- } from "../chunk-HAUTLCMA.js";
4
+ } from "../chunk-ICERLAE2.js";
5
5
  import {
6
6
  addClaudeMcpServer,
7
7
  addPusharyHooks,
8
8
  addPusharyToolPermissions
9
- } from "../chunk-5646VKR4.js";
9
+ } from "../chunk-2JYQAW4B.js";
10
10
  import {
11
11
  GEMINI_HOOK_BINARY,
12
12
  addGeminiHooks,
13
13
  addGeminiMcpServer,
14
14
  hasGeminiHooks
15
- } from "../chunk-ZZE6FN7T.js";
15
+ } from "../chunk-K42RXJPG.js";
16
16
  import "../chunk-ATBKJNWS.js";
17
17
  import {
18
18
  guardBinary
19
- } from "../chunk-M7RYIZX6.js";
19
+ } from "../chunk-B7ZBHWOW.js";
20
20
  import {
21
21
  execNpm,
22
22
  npmErrorMessage,
@@ -37,7 +37,8 @@ import {
37
37
  hasCodexHooks,
38
38
  vscodePluginDir,
39
39
  vscodePluginMcp
40
- } from "../chunk-B3V2H6SA.js";
40
+ } from "../chunk-NAPRGHZ3.js";
41
+ import "../chunk-GIRT7677.js";
41
42
  import {
42
43
  exitOnHelpFlag
43
44
  } from "../chunk-SQOFGPNX.js";
@@ -52,7 +53,7 @@ import "../chunk-KZERVKTD.js";
52
53
  import {
53
54
  getApiKey
54
55
  } from "../chunk-2UMNXADU.js";
55
- import "../chunk-7YJS2VTG.js";
56
+ import "../chunk-DMW54VWK.js";
56
57
 
57
58
  // bin/pushary-upgrade.ts
58
59
  import { existsSync as existsSync2 } from "fs";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-IIENZE2J.js";
5
5
  import {
6
6
  exitOnFailedResponse
7
- } from "../chunk-JAICP7BA.js";
8
- import "../chunk-A4I5JGCW.js";
7
+ } from "../chunk-TD3M232T.js";
8
+ import "../chunk-KFAH6KRE.js";
9
9
  import {
10
10
  createIo
11
11
  } from "../chunk-5RUIFDTP.js";
@@ -28,7 +28,7 @@ import {
28
28
  getApiKey,
29
29
  getBaseUrl
30
30
  } from "../chunk-2UMNXADU.js";
31
- import "../chunk-7YJS2VTG.js";
31
+ import "../chunk-DMW54VWK.js";
32
32
 
33
33
  // bin/pushary-wait.ts
34
34
  exitOnHelpFlag("wait");
@@ -1,62 +1,16 @@
1
1
  import {
2
- PRETOOLUSE_GATED_TOOLS,
3
- PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
- } from "./chunk-ATBKJNWS.js";
2
+ CLAUDE_HOOK_EVENTS,
3
+ supportedClaudeEvents
4
+ } from "./chunk-K42RXJPG.js";
5
5
  import {
6
6
  guardBinary
7
- } from "./chunk-M7RYIZX6.js";
7
+ } from "./chunk-B7ZBHWOW.js";
8
8
  import {
9
- HOOK_BUDGETS,
10
9
  HOOK_LOCATOR_BINARY
11
- } from "./chunk-7YJS2VTG.js";
10
+ } from "./chunk-DMW54VWK.js";
12
11
 
13
12
  // src/claude-config.ts
14
13
  import { join } from "path";
15
-
16
- // src/agent-version.ts
17
- var parseVersion = (raw) => {
18
- const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
19
- return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
20
- };
21
- var compareVersion = (a, b) => {
22
- for (let index = 0; index < 3; index++) {
23
- if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
24
- }
25
- return 0;
26
- };
27
- var atLeast = (version, floor) => compareVersion(version, floor) >= 0;
28
-
29
- // src/claude-events.ts
30
- var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS].join("|");
31
- var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
32
- var ESTABLISHED = [1, 0, 0];
33
- var OBSERVED = [2, 0, 0];
34
- var CLAUDE_HOOK_EVENTS = [
35
- { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
36
- { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
37
- { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
38
- { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
39
- { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
40
- { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
41
- { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
42
- { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
43
- { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
44
- { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, delivery: "telemetry", since: ESTABLISHED },
45
- // Observability only. Each carries session shape the island cannot get any other
46
- // way, and none of them decides anything.
47
- { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
48
- { event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
49
- { event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
50
- { event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
51
- { event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
52
- // An MCP server asking the person for a value, answered from the phone rather
53
- // than the terminal. No matcher: Claude Code matches this event on the MCP
54
- // server's name, and we have no business preferring one server over another.
55
- { event: "Elicitation", binary: "pushary-elicitation-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: OBSERVED, cliOnly: true }
56
- ];
57
- var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
58
-
59
- // src/claude-config.ts
60
14
  var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
61
15
  var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
62
16
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -175,8 +129,6 @@ var removePusharySettings = (settings) => {
175
129
  };
176
130
 
177
131
  export {
178
- parseVersion,
179
- compareVersion,
180
132
  addClaudeMcpServer,
181
133
  removeClaudeMcpServers,
182
134
  addPusharyToolPermissions,
@@ -0,0 +1,287 @@
1
+ import {
2
+ ACTION_BODY_MAX,
3
+ AGENT_LABELS_BY_TYPE,
4
+ DECISION_LINE_MAX,
5
+ FILE_TARGET_TOOLS,
6
+ redactSecrets,
7
+ redactSecretsDeep
8
+ } from "./chunk-DMW54VWK.js";
9
+
10
+ // ../hook-mapping/src/describe.ts
11
+ import { isAbsolute, relative, resolve } from "path";
12
+ var hookPrefixes = {
13
+ Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
14
+ PowerShell: (input) => `powershell: ${input.command ?? "(no command)"}`,
15
+ Monitor: (input) => `monitor: ${input.command ?? "(no command)"}`,
16
+ Write: (input) => `write file: ${input.file_path ?? "(unknown path)"}`,
17
+ Edit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
18
+ MultiEdit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
19
+ Read: (input) => `read file: ${input.file_path ?? "(unknown path)"}`,
20
+ // Without an entry here the fallback stringifies the whole tool input, and for
21
+ // ExitPlanMode that input IS the plan: the action line became a truncated dump of
22
+ // the plan markdown. The plan belongs in the body, not the one-line summary.
23
+ ExitPlanMode: () => "finished planning and wants to start coding"
24
+ };
25
+ var describeTodoProgress = (todos) => {
26
+ if (!Array.isArray(todos)) return "updated the plan";
27
+ const items = todos.filter((t) => !!t && typeof t === "object");
28
+ const total = items.length;
29
+ if (total === 0) return "updated the plan";
30
+ const done = items.filter((t) => t.status === "completed").length;
31
+ const current = items.find((t) => t.status === "in_progress");
32
+ const label = current ? [current.activeForm, current.content].find((v) => typeof v === "string" && v.trim().length > 0) : void 0;
33
+ if (done >= total) return `finished all ${total} tasks`;
34
+ if (label) return `${label.trim()} (${done}/${total} done)`;
35
+ return `${done}/${total} tasks done`;
36
+ };
37
+ var eventPrefixes = {
38
+ Bash: (input) => `ran: ${String(input.command ?? "")}`,
39
+ PowerShell: (input) => `ran: ${String(input.command ?? "")}`,
40
+ Monitor: (input) => `ran: ${String(input.command ?? "")}`,
41
+ Write: (input) => `wrote: ${input.file_path ?? "unknown"}`,
42
+ Edit: (input) => `edited: ${input.file_path ?? "unknown"}`,
43
+ MultiEdit: (input) => `edited: ${input.file_path ?? "unknown"}`,
44
+ Read: (input) => `read: ${input.file_path ?? "unknown"}`,
45
+ TodoWrite: (input) => describeTodoProgress(input.todos)
46
+ };
47
+ var EVENT_ACTION_MAX = 120;
48
+ var HOOK_FALLBACK_MAX = 200;
49
+ var describeToolCall = (toolName, toolInput, format = "hook") => {
50
+ const prefixes = format === "hook" ? hookPrefixes : eventPrefixes;
51
+ const builder = prefixes[toolName];
52
+ const raw = builder ? builder(toolInput) : format === "hook" ? `${toolName}: ${JSON.stringify(toolInput)}` : `${toolName}: done`;
53
+ const redacted = redactSecrets(raw);
54
+ if (format === "event") return redacted.slice(0, EVENT_ACTION_MAX);
55
+ return builder ? redacted : redacted.slice(0, HOOK_FALLBACK_MAX);
56
+ };
57
+ var TOOL_TARGET_MAX_LENGTH = 80;
58
+ var deriveCommandHead = (command) => {
59
+ if (typeof command !== "string") return void 0;
60
+ const head = command.trim().split(/\s+/).slice(0, 2).join(" ");
61
+ return head ? redactSecrets(head).slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
62
+ };
63
+ var deriveToolTarget = (toolName, toolInput) => {
64
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
65
+ return deriveCommandHead(toolInput.command);
66
+ }
67
+ if (FILE_TARGET_TOOLS.has(toolName)) {
68
+ const filePath = toolInput.file_path;
69
+ if (typeof filePath !== "string") return void 0;
70
+ const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
71
+ const base = filePath.slice(separator + 1);
72
+ const dot = base.lastIndexOf(".");
73
+ if (dot <= 0) return void 0;
74
+ return base.slice(dot).slice(0, TOOL_TARGET_MAX_LENGTH);
75
+ }
76
+ return void 0;
77
+ };
78
+ var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "npx", "deno", "rake", "make", "just", "task", "turbo"]);
79
+ var RUN_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "run-script", "exec", "task"]);
80
+ var deriveReceiptCommandHead = (command) => {
81
+ if (typeof command !== "string") return void 0;
82
+ const tokens = command.trim().split(/\s+/);
83
+ const [first, second] = tokens;
84
+ const keep = first && second && SCRIPT_RUNNERS.has(first.toLowerCase()) && RUN_SUBCOMMANDS.has(second.toLowerCase()) ? 3 : 2;
85
+ const head = tokens.slice(0, keep).join(" ");
86
+ return head ? redactSecrets(head).slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
87
+ };
88
+ var RECEIPT_TARGET_MAX_LENGTH = 256;
89
+ var parseApplyPatchOps = (command) => {
90
+ if (typeof command !== "string") return [];
91
+ const ops = [];
92
+ for (const line of command.split("\n")) {
93
+ const file = line.match(/^\*\*\*\s+(Add|Update|Delete) File:\s+(.+?)\s*$/);
94
+ if (file) {
95
+ ops.push({ op: file[1].toLowerCase(), path: file[2] });
96
+ continue;
97
+ }
98
+ const move = line.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
99
+ if (move) ops.push({ op: "move", path: move[1] });
100
+ }
101
+ return ops;
102
+ };
103
+ var parseApplyPatchFiles = (command) => [...new Set(parseApplyPatchOps(command).map((entry) => entry.path))];
104
+ var OPENCODE_TOOL_NAMES = {
105
+ bash: "Bash",
106
+ // A shell command reaching outside the worktree asks a second time under its
107
+ // own key. It is still the same command, and still Bash.
108
+ external_directory: "Bash",
109
+ edit: "Edit",
110
+ patch: "Edit",
111
+ apply_patch: "Edit",
112
+ write: "Write",
113
+ read: "Read",
114
+ glob: "Glob",
115
+ grep: "Grep",
116
+ webfetch: "WebFetch",
117
+ websearch: "WebSearch",
118
+ task: "Task"
119
+ };
120
+ var EXIT_CODE_FAILURE = /^Error: Exit code \d+/;
121
+ var isToolResultError = (toolResult) => {
122
+ try {
123
+ if (typeof toolResult === "string") return EXIT_CODE_FAILURE.test(toolResult);
124
+ if (!toolResult || typeof toolResult !== "object") return false;
125
+ return "error" in toolResult || "is_error" in toolResult;
126
+ } catch {
127
+ return false;
128
+ }
129
+ };
130
+ var toolResultErrorText = (toolResult) => {
131
+ try {
132
+ const raw = typeof toolResult === "string" ? toolResult : toolResult && typeof toolResult === "object" ? String(
133
+ toolResult.error ?? toolResult.stderr ?? ""
134
+ ) : "";
135
+ return redactSecrets(raw);
136
+ } catch {
137
+ return "";
138
+ }
139
+ };
140
+ var relativizeReceiptPath = (filePath, cwd) => {
141
+ if (!cwd || !isAbsolute(filePath)) return filePath;
142
+ return relative(cwd, filePath);
143
+ };
144
+ var deriveReceiptMeta = (toolName, toolInput, toolResult, cwd) => {
145
+ try {
146
+ const ok = !isToolResultError(toolResult);
147
+ if (toolName === "apply_patch") {
148
+ const files = parseApplyPatchFiles(toolInput.command);
149
+ const target = files.length === 1 ? files[0] : toolInput.command;
150
+ if (typeof target !== "string" || !target) return void 0;
151
+ return { kind: "edit", target: redactSecrets(target).slice(0, RECEIPT_TARGET_MAX_LENGTH), ok };
152
+ }
153
+ const canonicalTool = {
154
+ run_shell_command: "Bash",
155
+ write_file: "Write",
156
+ replace: "Edit"
157
+ }[toolName] ?? toolName;
158
+ const canonicalInput = canonicalTool === toolName ? toolInput : {
159
+ ...toolInput,
160
+ file_path: toolInput.file_path ?? toolInput.absolute_path ?? toolInput.path
161
+ };
162
+ if (FILE_TARGET_TOOLS.has(canonicalTool)) {
163
+ const filePath = canonicalInput.file_path;
164
+ if (typeof filePath !== "string" || !filePath) return void 0;
165
+ return {
166
+ kind: canonicalTool === "Write" ? "write" : "edit",
167
+ // A path is an unlikely place for a credential but not an impossible one
168
+ // (a token in a temp directory name), and this field is persisted.
169
+ target: redactSecrets(relativizeReceiptPath(filePath, cwd)).slice(0, RECEIPT_TARGET_MAX_LENGTH),
170
+ ok
171
+ };
172
+ }
173
+ if (canonicalTool === "Bash" || canonicalTool === "PowerShell" || canonicalTool === "Monitor") {
174
+ const head = deriveReceiptCommandHead(canonicalInput.command);
175
+ if (!head) return void 0;
176
+ return {
177
+ kind: head === "git commit" ? "commit" : "bash",
178
+ target: head,
179
+ ok
180
+ };
181
+ }
182
+ return void 0;
183
+ } catch {
184
+ return void 0;
185
+ }
186
+ };
187
+ var scopePathFor = (toolName, toolInput, cwd) => {
188
+ const meta = deriveReceiptMeta(toolName, toolInput, void 0, cwd);
189
+ if (!meta) return void 0;
190
+ return meta.kind === "edit" || meta.kind === "write" ? meta.target : void 0;
191
+ };
192
+ var firstString = (...values) => values.find((v) => typeof v === "string" && v.length > 0);
193
+ var ACTION_BODY_TRUNCATION_MARKER = "\n\u2026 [truncated]";
194
+ var capActionBody = (text) => text.length <= ACTION_BODY_MAX ? text : `${text.slice(0, ACTION_BODY_MAX - ACTION_BODY_TRUNCATION_MARKER.length)}${ACTION_BODY_TRUNCATION_MARKER}`;
195
+ var rawActionBody = (toolName, toolInput) => {
196
+ if (toolName === "Edit" || toolName === "replace") {
197
+ const before = firstString(toolInput.old_string);
198
+ const after = firstString(toolInput.new_string);
199
+ if (before === void 0 && after === void 0) return firstString(toolInput.diff);
200
+ return `- ${before ?? ""}
201
+ + ${after ?? ""}`;
202
+ }
203
+ if (toolName === "Write" || toolName === "write_file") {
204
+ return firstString(toolInput.content) ?? firstString(toolInput.diff);
205
+ }
206
+ if (toolName === "ExitPlanMode") return firstString(toolInput.plan);
207
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
208
+ return firstString(toolInput.command);
209
+ }
210
+ return void 0;
211
+ };
212
+ var deriveActionBody = (toolName, toolInput) => {
213
+ try {
214
+ const raw = rawActionBody(toolName, toolInput);
215
+ if (!raw) return void 0;
216
+ return capActionBody(redactSecretsDeep(raw));
217
+ } catch {
218
+ return void 0;
219
+ }
220
+ };
221
+ var deriveAction = (toolName, toolInput) => describeToolCall(toolName, toolInput, "hook").slice(0, DECISION_LINE_MAX);
222
+ var BLOCKER_BY_MODE = {
223
+ push_only: "Approval required before this runs",
224
+ push_first: "Paused for your approval before continuing",
225
+ escalate: "Escalated to you after the approval timeout"
226
+ };
227
+ var deriveBlocker = (mode, reason) => {
228
+ const text = reason?.replace(/\s+/g, " ").trim() || BLOCKER_BY_MODE[mode] || "Waiting for your approval";
229
+ return text.slice(0, DECISION_LINE_MAX);
230
+ };
231
+
232
+ // ../hook-mapping/src/map.ts
233
+ import { basename } from "path";
234
+ var AGENT_IDENTITIES = {
235
+ claude: { type: "claude_code", label: AGENT_LABELS_BY_TYPE.claude_code },
236
+ codex: { type: "codex", label: AGENT_LABELS_BY_TYPE.codex },
237
+ gemini: { type: "gemini_cli", label: AGENT_LABELS_BY_TYPE.gemini_cli },
238
+ cursor: { type: "cursor", label: AGENT_LABELS_BY_TYPE.cursor },
239
+ vscode: { type: "vscode", label: AGENT_LABELS_BY_TYPE.vscode },
240
+ opencode: { type: "opencode", label: AGENT_LABELS_BY_TYPE.opencode }
241
+ };
242
+ var TASK_TITLE_MAX_LENGTH = 120;
243
+ var STOP_SUMMARY_MAX_LENGTH = 200;
244
+ var SESSION_END_ACTIONS = {
245
+ clear: "Session cleared",
246
+ logout: "Logged out",
247
+ resume: "Session suspended",
248
+ prompt_input_exit: "Session ended",
249
+ bypass_permissions_disabled: "Session ended",
250
+ other: "Session ended"
251
+ };
252
+ var countArray = (value) => Array.isArray(value) ? value.length : 0;
253
+ var summarizeFinalMessage = (message) => {
254
+ if (typeof message !== "string") return "Session ended";
255
+ const collapsed = message.replace(/\s+/g, " ").trim();
256
+ if (!collapsed) return "Session ended";
257
+ return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
258
+ };
259
+ var summarizeStop = (input) => {
260
+ const running = countArray(input.background_tasks);
261
+ if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
262
+ const crons = countArray(input.session_crons);
263
+ if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
264
+ return summarizeFinalMessage(input.last_assistant_message);
265
+ };
266
+ var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "Session started";
267
+ var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
268
+ var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
269
+
270
+ export {
271
+ describeToolCall,
272
+ deriveToolTarget,
273
+ parseApplyPatchOps,
274
+ parseApplyPatchFiles,
275
+ OPENCODE_TOOL_NAMES,
276
+ isToolResultError,
277
+ toolResultErrorText,
278
+ deriveReceiptMeta,
279
+ scopePathFor,
280
+ deriveActionBody,
281
+ deriveAction,
282
+ deriveBlocker,
283
+ summarizeStop,
284
+ sessionStartAction,
285
+ sessionEndAction,
286
+ taskTitleFrom
287
+ };
@@ -30,8 +30,17 @@ var guardHookCommand = (target, platform = process.platform) => {
30
30
  };
31
31
  var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
32
32
  var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
33
+ var guardedArgv = (target, args = [], platform = process.platform) => {
34
+ const path = target.kind === "node" ? target.script : target.path;
35
+ if (platform === "win32") {
36
+ return target.kind === "node" ? ["node", path, ...args] : [path, ...args];
37
+ }
38
+ const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
39
+ return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
40
+ };
33
41
 
34
42
  export {
35
43
  guardBinary,
36
- guardNodeScript
44
+ guardNodeScript,
45
+ guardedArgv
37
46
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-B3V2H6SA.js";
3
+ } from "./chunk-NAPRGHZ3.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -54,7 +54,13 @@ var HOOK_BUDGETS = {
54
54
  // The Cursor gate is a dependency-free .mjs that cannot import this module; it
55
55
  // mirrors these as MAX_BLOCK_MS (45s) inside its 60s failClosed budget.
56
56
  cursor: { budgetSeconds: 60, guardSeconds: 15 },
57
- vscode: { budgetSeconds: 60, guardSeconds: 15 }
57
+ vscode: { budgetSeconds: 60, guardSeconds: 15 },
58
+ // OpenCode kills nothing and waits forever: `Permission.ask` blocks on a
59
+ // `Deferred` with no deadline of its own, so this budget is ours to choose
60
+ // rather than one we are told. Chosen to match Claude Code, because what it
61
+ // bounds is how long the person's tool call sits unanswered before Pushary
62
+ // stops asking and hands it back to the terminal.
63
+ opencode: { budgetSeconds: 120, guardSeconds: 10 }
58
64
  };
59
65
  var hookMaxWaitSeconds = (agent) => Math.max(HOOK_BUDGETS[agent].budgetSeconds - HOOK_BUDGETS[agent].guardSeconds, 1);
60
66
  var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
@@ -75,9 +81,11 @@ var AGENT_LABELS_BY_TYPE = {
75
81
  hermes: "Hermes",
76
82
  cursor: "Cursor",
77
83
  vscode: "VS Code",
84
+ opencode: "OpenCode",
78
85
  windsurf: "Windsurf",
79
86
  unknown: "Agent"
80
87
  };
88
+ var OPENCODE_APPROVAL_EVENT = "permission.ask";
81
89
  var HOOK_LOCATOR_BINARY = "pushary-bridge";
82
90
  var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
83
91
  var REPO_KEY_MAX_LENGTH = 200;
@@ -1093,6 +1101,8 @@ var gateOutputFor = (source, event, decision, reason) => {
1093
1101
  }
1094
1102
  } else if (source === "gemini" && event === "BeforeTool" && decision !== "ask") {
1095
1103
  output = decision === "allow" ? { decision: "allow" } : { decision: "deny", reason: reason ?? "Denied from Pushary" };
1104
+ } else if (source === "opencode" && event === OPENCODE_APPROVAL_EVENT) {
1105
+ output = decision === "deny" ? { status: "deny", reason: reason ?? "Denied from Pushary" } : { status: decision };
1096
1106
  } else if (source === "cursor" && event === "beforeShellExecution") {
1097
1107
  output = decision === "allow" ? { permission: "allow" } : decision === "ask" ? { permission: "ask" } : {
1098
1108
  permission: "deny",
@@ -1122,6 +1132,7 @@ export {
1122
1132
  hookWaitClamped,
1123
1133
  effectiveWaitSeconds,
1124
1134
  AGENT_LABELS_BY_TYPE,
1135
+ OPENCODE_APPROVAL_EVENT,
1125
1136
  HOOK_LOCATOR_BINARY,
1126
1137
  normalizeRepoRemote,
1127
1138
  localRepoKey,
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-BSZYIAZL.js";
12
12
  import {
13
13
  reportedInstallSource
14
- } from "./chunk-Y3UTTFTZ.js";
14
+ } from "./chunk-KBS53WOE.js";
15
15
 
16
16
  // src/reach.ts
17
17
  var reachVerdict = (channels) => {
@@ -0,0 +1,11 @@
1
+ // src/canonical-json.ts
2
+ var canonicalJson = (value) => {
3
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
4
+ if (Array.isArray(value)) return "[" + value.map(canonicalJson).join(",") + "]";
5
+ const record = value;
6
+ return "{" + Object.keys(record).sort().map((key) => JSON.stringify(key) + ":" + canonicalJson(record[key])).join(",") + "}";
7
+ };
8
+
9
+ export {
10
+ canonicalJson
11
+ };
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-B3V2H6SA.js";
10
+ } from "./chunk-NAPRGHZ3.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-5646VKR4.js";
3
+ } from "./chunk-K42RXJPG.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";