@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.
- package/data/cursor-plugin/hooks/hooks.json +7 -0
- package/data/cursor-plugin/scripts/pushary-gate.mjs +94 -28
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +81 -1
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +7 -6
- package/dist/bin/pushary-claude.js +14 -11
- package/dist/bin/pushary-clean.js +36 -20
- package/dist/bin/pushary-codex-bridge.js +6 -5
- package/dist/bin/pushary-codex-hook.js +129 -52
- package/dist/bin/pushary-codex.js +4 -3
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +5 -4
- package/dist/bin/pushary-disconnect.js +65 -8
- package/dist/bin/pushary-doctor.js +79 -18
- package/dist/bin/pushary-elicitation-hook.js +1 -1
- package/dist/bin/pushary-gemini-bridge.js +6 -5
- package/dist/bin/pushary-gemini-hook.js +12 -10
- package/dist/bin/pushary-hook.js +6 -5
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +5 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +4 -3
- package/dist/bin/pushary-opencode-hook.d.ts +1 -0
- package/dist/bin/pushary-opencode-hook.js +329 -0
- package/dist/bin/pushary-permission-denied-hook.js +6 -5
- package/dist/bin/pushary-permission-hook.js +6 -5
- package/dist/bin/pushary-post-hook.js +4 -3
- package/dist/bin/pushary-prompt-hook.js +4 -3
- package/dist/bin/pushary-session-end-hook.js +4 -3
- package/dist/bin/pushary-session-start-hook.js +4 -3
- package/dist/bin/pushary-setup.js +238 -84
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +4 -3
- package/dist/bin/pushary-stopfailure-hook.js +4 -3
- package/dist/bin/pushary-upgrade.js +7 -6
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-5646VKR4.js → chunk-2JYQAW4B.js} +5 -53
- package/dist/chunk-7J67NYK4.js +287 -0
- package/dist/{chunk-M7RYIZX6.js → chunk-B7ZBHWOW.js} +10 -1
- package/dist/{chunk-QBWYNVKY.js → chunk-BFBAHOGR.js} +1 -1
- package/dist/{chunk-7YJS2VTG.js → chunk-DMW54VWK.js} +12 -1
- package/dist/{chunk-3LKYBFWA.js → chunk-EJL3PJU7.js} +1 -1
- package/dist/chunk-GIRT7677.js +11 -0
- package/dist/{chunk-SJVLFIYP.js → chunk-H5PIPIKQ.js} +1 -1
- package/dist/{chunk-HAUTLCMA.js → chunk-ICERLAE2.js} +1 -1
- package/dist/{chunk-ZZE6FN7T.js → chunk-K42RXJPG.js} +52 -1
- package/dist/{chunk-Y3UTTFTZ.js → chunk-KBS53WOE.js} +1 -1
- package/dist/{chunk-A4I5JGCW.js → chunk-KFAH6KRE.js} +1 -1
- package/dist/{chunk-LT2VLPUK.js → chunk-KULUNLRO.js} +18 -2
- package/dist/{chunk-B3V2H6SA.js → chunk-NAPRGHZ3.js} +54 -13
- package/dist/{chunk-HJ44HIUW.js → chunk-OMA2OZRE.js} +11 -9
- package/dist/{chunk-ECEUMWBH.js → chunk-QLMX5LLV.js} +1 -1
- package/dist/{chunk-XW5CHAQX.js → chunk-QXZ2CKRK.js} +2 -2
- package/dist/{chunk-JAICP7BA.js → chunk-TD3M232T.js} +1 -1
- package/dist/{chunk-WLLBFDVY.js → chunk-TV3UFGT7.js} +1 -1
- package/dist/{chunk-7MBDFNUC.js → chunk-VIHRP25S.js} +1 -1
- package/dist/{chunk-V2XPF77V.js → chunk-VOE4SYPR.js} +42 -279
- package/dist/{chunk-OT4YD54Q.js → chunk-YX6ZKCC5.js} +1 -1
- package/dist/{chunk-2T3GX7WG.js → chunk-YYTLTEGU.js} +1 -1
- package/dist/chunk-ZGC3W23N.js +371 -0
- package/dist/src/index.js +6 -5
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readLedgerSummary
|
|
4
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
11
|
+
} from "../chunk-EJL3PJU7.js";
|
|
12
12
|
import "../chunk-3EGEA4KH.js";
|
|
13
13
|
import {
|
|
14
14
|
checkApiKey,
|
|
15
15
|
describeKeyCheck
|
|
16
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
4
|
+
} from "../chunk-ICERLAE2.js";
|
|
5
5
|
import {
|
|
6
6
|
addClaudeMcpServer,
|
|
7
7
|
addPusharyHooks,
|
|
8
8
|
addPusharyToolPermissions
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
15
|
+
} from "../chunk-K42RXJPG.js";
|
|
16
16
|
import "../chunk-ATBKJNWS.js";
|
|
17
17
|
import {
|
|
18
18
|
guardBinary
|
|
19
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
56
|
+
import "../chunk-DMW54VWK.js";
|
|
56
57
|
|
|
57
58
|
// bin/pushary-upgrade.ts
|
|
58
59
|
import { existsSync as existsSync2 } from "fs";
|
package/dist/bin/pushary-wait.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from "../chunk-IIENZE2J.js";
|
|
5
5
|
import {
|
|
6
6
|
exitOnFailedResponse
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
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-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
2
|
+
CLAUDE_HOOK_EVENTS,
|
|
3
|
+
supportedClaudeEvents
|
|
4
|
+
} from "./chunk-K42RXJPG.js";
|
|
5
5
|
import {
|
|
6
6
|
guardBinary
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B7ZBHWOW.js";
|
|
8
8
|
import {
|
|
9
|
-
HOOK_BUDGETS,
|
|
10
9
|
HOOK_LOCATOR_BINARY
|
|
11
|
-
} from "./chunk-
|
|
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
|
};
|
|
@@ -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,
|
|
@@ -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
|
+
};
|