@pushary/agent-hooks 0.14.3 → 0.14.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.
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  execNpm,
8
8
  removeCodexHooks
9
- } from "../chunk-2HMNOZPY.js";
9
+ } from "../chunk-VC6U3QGF.js";
10
10
 
11
11
  // bin/pushary-clean.ts
12
12
  import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
@@ -12,7 +12,7 @@ import {
12
12
  reportEvent,
13
13
  toCodexWire,
14
14
  toPolicyLookup
15
- } from "../chunk-YCL4GUFR.js";
15
+ } from "../chunk-3QZESA66.js";
16
16
  import {
17
17
  DEFAULT_SESSION,
18
18
  askUser,
@@ -25,12 +25,12 @@ import {
25
25
  savePendingQuestion,
26
26
  sendNotification,
27
27
  waitForAnswer
28
- } from "../chunk-QEPRH7JB.js";
28
+ } from "../chunk-HRQEECB6.js";
29
29
  import "../chunk-22CV7V7A.js";
30
- import "../chunk-3MIR7ODJ.js";
30
+ import "../chunk-DWED7BS3.js";
31
31
  import {
32
32
  getApiKey
33
- } from "../chunk-VUNL35KE.js";
33
+ } from "../chunk-NKXSILEW.js";
34
34
 
35
35
  // bin/pushary-codex-hook.ts
36
36
  import { basename, join } from "path";
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-YCL4GUFR.js";
4
+ } from "../chunk-3QZESA66.js";
5
5
  import {
6
6
  askUser,
7
7
  getMachineId,
8
8
  waitForAnswer
9
- } from "../chunk-QEPRH7JB.js";
9
+ } from "../chunk-HRQEECB6.js";
10
10
  import "../chunk-22CV7V7A.js";
11
- import "../chunk-3MIR7ODJ.js";
11
+ import "../chunk-DWED7BS3.js";
12
12
  import {
13
13
  getApiKey
14
- } from "../chunk-VUNL35KE.js";
14
+ } from "../chunk-NKXSILEW.js";
15
15
 
16
16
  // bin/pushary-codex.ts
17
17
  import { basename } from "path";
@@ -3,12 +3,12 @@ import {
3
3
  execNpm,
4
4
  hasCodexHooks,
5
5
  missingCodexHookEvents
6
- } from "../chunk-2HMNOZPY.js";
6
+ } from "../chunk-VC6U3QGF.js";
7
7
  import {
8
8
  callMcpTool,
9
9
  sendMcpRequest
10
- } from "../chunk-3MIR7ODJ.js";
11
- import "../chunk-VUNL35KE.js";
10
+ } from "../chunk-DWED7BS3.js";
11
+ import "../chunk-NKXSILEW.js";
12
12
 
13
13
  // bin/pushary-doctor.ts
14
14
  import { existsSync, readFileSync } from "fs";
@@ -71,7 +71,18 @@ var main = async () => {
71
71
  console.log();
72
72
  console.log(` ${dim("Configuration")}`);
73
73
  let apiKey = process.env.PUSHARY_API_KEY;
74
- let keyFromProfile = false;
74
+ let keySource = apiKey ? "env" : null;
75
+ const PUSHARY_CONFIG_FILE = join(homedir(), ".pushary", "config.json");
76
+ if (!apiKey) {
77
+ try {
78
+ const parsed = JSON.parse(readFileSync(PUSHARY_CONFIG_FILE, "utf-8"));
79
+ if (typeof parsed.apiKey === "string" && parsed.apiKey) {
80
+ apiKey = parsed.apiKey;
81
+ keySource = "config-file";
82
+ }
83
+ } catch {
84
+ }
85
+ }
75
86
  if (!apiKey) {
76
87
  for (const f of SHELL_FILES) {
77
88
  try {
@@ -79,18 +90,21 @@ var main = async () => {
79
90
  const match = content.match(/export\s+PUSHARY_API_KEY=['"](pk_[a-f0-9]+\.[a-f0-9]+)['"]/);
80
91
  if (match) {
81
92
  apiKey = match[1];
82
- keyFromProfile = true;
93
+ keySource = "profile";
83
94
  break;
84
95
  }
85
96
  } catch {
86
97
  }
87
98
  }
88
99
  }
89
- if (keyFromProfile) {
90
- console.log(` ${pass} API key in shell profile ${dim(`(pk_${apiKey.split(".")[0]?.slice(3, 7)}...)`)}`);
91
- console.log(` ${warn} Not loaded in this shell \u2014 run ${cyan("source ~/.zshrc")} or open a new terminal`);
100
+ const maskKey = (k) => `pk_${k.split(".")[0]?.slice(3, 7)}...`;
101
+ if (keySource === "config-file") {
102
+ console.log(` ${pass} API key in ${dim("~/.pushary/config.json")} ${dim(`(${maskKey(apiKey)})`)}`);
103
+ } else if (keySource === "profile") {
104
+ console.log(` ${pass} API key in shell profile ${dim(`(${maskKey(apiKey)})`)}`);
105
+ console.log(` ${warn} Re-run ${cyan("npx @pushary/agent-hooks setup")} so hooks read it from ${dim("~/.pushary/config.json")} without a shell reload`);
92
106
  } else {
93
- check(!!apiKey, "API key in environment", apiKey ? `pk_${apiKey.split(".")[0]?.slice(3, 7)}...` : "PUSHARY_API_KEY not set");
107
+ check(!!apiKey, "API key in environment", apiKey ? maskKey(apiKey) : "PUSHARY_API_KEY not set");
94
108
  }
95
109
  const CLAUDE_JSON = join(homedir(), ".claude.json");
96
110
  const claudeJson = readJson(CLAUDE_JSON);
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-5QHQVXWN.js";
5
- import "../chunk-QEPRH7JB.js";
4
+ } from "../chunk-VWBNI4SC.js";
5
+ import "../chunk-HRQEECB6.js";
6
6
  import "../chunk-22CV7V7A.js";
7
- import "../chunk-3MIR7ODJ.js";
8
- import "../chunk-VUNL35KE.js";
7
+ import "../chunk-DWED7BS3.js";
8
+ import "../chunk-NKXSILEW.js";
9
9
 
10
10
  // bin/pushary-hook.ts
11
11
  var main = async () => {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getApiKey,
4
4
  getBaseUrl
5
- } from "../chunk-VUNL35KE.js";
5
+ } from "../chunk-NKXSILEW.js";
6
6
 
7
7
  // bin/pushary-mode.ts
8
8
  var VALID_MODES = ["push_only", "push_first", "terminal_only", "notify_only"];
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-YCL4GUFR.js";
5
- import "../chunk-QEPRH7JB.js";
4
+ } from "../chunk-3QZESA66.js";
5
+ import "../chunk-HRQEECB6.js";
6
6
  import "../chunk-22CV7V7A.js";
7
- import "../chunk-3MIR7ODJ.js";
8
- import "../chunk-VUNL35KE.js";
7
+ import "../chunk-DWED7BS3.js";
8
+ import "../chunk-NKXSILEW.js";
9
9
 
10
10
  // bin/pushary-post-hook.ts
11
11
  var main = async () => {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-YCL4GUFR.js";
5
- import "../chunk-QEPRH7JB.js";
4
+ } from "../chunk-3QZESA66.js";
5
+ import "../chunk-HRQEECB6.js";
6
6
  import "../chunk-22CV7V7A.js";
7
- import "../chunk-3MIR7ODJ.js";
8
- import "../chunk-VUNL35KE.js";
7
+ import "../chunk-DWED7BS3.js";
8
+ import "../chunk-NKXSILEW.js";
9
9
 
10
10
  // bin/pushary-prompt-hook.ts
11
11
  var main = async () => {
@@ -5,16 +5,17 @@ import {
5
5
  addPusharyToolPermissions
6
6
  } from "../chunk-5MA3CPZB.js";
7
7
  import {
8
+ addCodexHookTrust,
8
9
  addCodexHooks,
9
10
  execNpm,
10
11
  npmErrorMessage
11
- } from "../chunk-2HMNOZPY.js";
12
+ } from "../chunk-VC6U3QGF.js";
12
13
  import {
13
14
  isValidApiKey
14
15
  } from "../chunk-22CV7V7A.js";
15
16
 
16
17
  // bin/pushary-setup.ts
17
- import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync } from "fs";
18
+ import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, cpSync, rmSync, chmodSync } from "fs";
18
19
  import { join, dirname, basename } from "path";
19
20
  import { homedir } from "os";
20
21
  import { execSync } from "child_process";
@@ -179,7 +180,10 @@ var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
179
180
  var CLAUDE_JSON = join(homedir(), ".claude.json");
180
181
  var CURSOR_PLUGIN_DIR = join(homedir(), ".cursor", "plugins", "local", "pushary");
181
182
  var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
182
- var CODEX_SKILL_DIR = join(homedir(), ".codex", "skills", "pushary");
183
+ var CODEX_HOME = process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
184
+ var CODEX_SKILL_DIR = join(CODEX_HOME, "skills", "pushary");
185
+ var PUSHARY_CONFIG_DIR = join(homedir(), ".pushary");
186
+ var PUSHARY_CONFIG_FILE = join(PUSHARY_CONFIG_DIR, "config.json");
183
187
  var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
184
188
  var dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
185
189
  var bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
@@ -397,27 +401,29 @@ var setupHermes = async (_apiKey) => {
397
401
  console.log(` ${dim2("\u2022")} Permission gating: set ${bold2("PUSHARY_GATE_TOOLS")} to require lock-screen approval for risky tools`);
398
402
  console.log(` ${dim2("To re-enable terminal prompts:")} remove ${bold2("clarify")} from ${dim2("agent.disabled_toolsets")} in ~/.hermes/config.yaml`);
399
403
  };
400
- var CODEX_HOOKS_JSON = join(homedir(), ".codex", "hooks.json");
404
+ var CODEX_HOOKS_JSON = join(CODEX_HOME, "hooks.json");
401
405
  var CODEX_HOOKS_MIN_VERSION = [0, 122, 0];
406
+ var CODEX_TRUST_VERIFIED_MAX = [0, 138, 0];
402
407
  var parseCodexVersion = (raw) => {
403
408
  const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
404
409
  if (!match) return null;
405
410
  return [Number(match[1]), Number(match[2]), Number(match[3])];
406
411
  };
407
- var codexSupportsHooks = () => {
412
+ var compareCodexVersion = (a, b) => {
413
+ for (let i = 0; i < 3; i++) {
414
+ if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
415
+ }
416
+ return 0;
417
+ };
418
+ var readCodexVersion = () => {
408
419
  try {
409
- const raw = execSync("codex --version", { encoding: "utf-8", stdio: "pipe", timeout: 1e4 });
410
- const version = parseCodexVersion(raw);
411
- if (!version) return false;
412
- for (let i = 0; i < 3; i++) {
413
- if (version[i] > CODEX_HOOKS_MIN_VERSION[i]) return true;
414
- if (version[i] < CODEX_HOOKS_MIN_VERSION[i]) return false;
415
- }
416
- return true;
420
+ return parseCodexVersion(execSync("codex --version", { encoding: "utf-8", stdio: "pipe", timeout: 1e4 }));
417
421
  } catch {
418
- return false;
422
+ return null;
419
423
  }
420
424
  };
425
+ var codexSupportsHooks = (version) => version !== null && compareCodexVersion(version, CODEX_HOOKS_MIN_VERSION) >= 0;
426
+ var codexTrustAutoSupported = (version) => version !== null && codexSupportsHooks(version) && compareCodexVersion(version, CODEX_TRUST_VERIFIED_MAX) <= 0;
421
427
  var removeCodexNotifyEntry = (codexConfig) => {
422
428
  let raw = "";
423
429
  try {
@@ -489,16 +495,32 @@ var setupCodex = async (_apiKey) => {
489
495
  config.mcp_servers = mcpServers;
490
496
  writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
491
497
  });
492
- const hooksSupported = codexSupportsHooks();
498
+ const codexVersion = readCodexVersion();
499
+ const hooksSupported = codexSupportsHooks(codexVersion);
500
+ const trustAuto = codexTrustAutoSupported(codexVersion);
501
+ let trusted = false;
493
502
  if (hooksSupported) {
503
+ const globalPrefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
504
+ const hookCommand = join(globalPrefix, "bin", "pushary-codex-hook");
494
505
  await spinner("Adding native hooks (~/.codex/hooks.json)", async () => {
495
- const globalPrefix = execNpm("prefix -g --no-workspaces", { timeout: 5e3 }).toString().trim();
496
- const hookCommand = join(globalPrefix, "bin", "pushary-codex-hook");
497
506
  if (!existsSync(hookCommand)) throw new Error("pushary-codex-hook not found at " + hookCommand);
498
507
  const hooksConfig = readJson(CODEX_HOOKS_JSON);
499
508
  addCodexHooks(hooksConfig, hookCommand);
500
509
  writeJson(CODEX_HOOKS_JSON, hooksConfig);
501
510
  });
511
+ if (trustAuto) {
512
+ await spinner("Trusting the Pushary hook (skips the /hooks step)", async () => {
513
+ let raw = "";
514
+ try {
515
+ raw = readFileSync(codexConfig, "utf-8");
516
+ } catch {
517
+ }
518
+ const config = raw ? parseTOML(raw) : {};
519
+ addCodexHookTrust(config, CODEX_HOOKS_JSON, hookCommand);
520
+ writeFileSync(codexConfig, stringifyTOML(config), "utf-8");
521
+ trusted = true;
522
+ }, { optional: true });
523
+ }
502
524
  await spinner("Removing legacy notify handler", async () => {
503
525
  removeCodexNotifyEntry(codexConfig);
504
526
  });
@@ -517,17 +539,21 @@ var setupCodex = async (_apiKey) => {
517
539
  console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
518
540
  if (hooksSupported) {
519
541
  console.log(` ${dim2("\u2022")} Native hooks: phone approvals, policy enforcement, kill switch, session tracking`);
520
- console.log();
521
- console.log(` ${bold2("One step left, and it happens inside Codex")}`);
522
- console.log(` ${dim2("Codex makes you trust any hook before it runs. This is Codex's own")}`);
523
- console.log(` ${dim2("security check, not a Pushary thing, and you only do it once.")}`);
524
- console.log();
525
- console.log(` ${cyan2("1.")} Open Codex and type ${cyan2("/hooks")}`);
526
- console.log(` ${cyan2("2.")} You will see the Pushary hook commands. Trust them.`);
527
- console.log();
528
- console.log(` ${dim2("Until you do, Codex just runs as usual without the phone approvals.")}`);
529
- console.log(` ${dim2("To confirm:")} run a command that needs approval and watch your phone.`);
530
- console.log(` ${dim2("Pushary updates keep the same trust, so Codex will not ask again.")}`);
542
+ if (trusted) {
543
+ console.log(` ${dim2("\u2022")} Hook pre-trusted: no ${cyan2("/hooks")} step, phone approvals work right away`);
544
+ } else {
545
+ console.log();
546
+ console.log(` ${bold2("One step left, and it happens inside Codex")}`);
547
+ console.log(` ${dim2("Codex makes you trust any hook before it runs. This is Codex's own")}`);
548
+ console.log(` ${dim2("security check, not a Pushary thing, and you only do it once.")}`);
549
+ console.log();
550
+ console.log(` ${cyan2("1.")} Open Codex and type ${cyan2("/hooks")}`);
551
+ console.log(` ${cyan2("2.")} You will see the Pushary hook commands. Trust them.`);
552
+ console.log();
553
+ console.log(` ${dim2("Until you do, Codex just runs as usual without the phone approvals.")}`);
554
+ console.log(` ${dim2("To confirm:")} run a command that needs approval and watch your phone.`);
555
+ console.log(` ${dim2("Pushary updates keep the same trust, so Codex will not ask again.")}`);
556
+ }
531
557
  } else {
532
558
  console.log(` ${dim2("\u2022")} Notify handler (deprecated): captures turn completions and approval requests`);
533
559
  }
@@ -573,7 +599,14 @@ var setupCursor = async (apiKey) => {
573
599
  console.log(` ${dim2("\u2022")} Restart Cursor (or run Developer: Reload Window) to load it`);
574
600
  };
575
601
  var saveApiKey = async (apiKey) => {
576
- await spinner("Saving API key to shell profile", async () => {
602
+ await spinner("Saving your API key", async () => {
603
+ if (!existsSync(PUSHARY_CONFIG_DIR)) mkdirSync(PUSHARY_CONFIG_DIR, { recursive: true });
604
+ const config = readJson(PUSHARY_CONFIG_FILE);
605
+ writeFileSync(PUSHARY_CONFIG_FILE, JSON.stringify({ ...config, apiKey }, null, 2) + "\n", "utf-8");
606
+ try {
607
+ chmodSync(PUSHARY_CONFIG_FILE, 384);
608
+ } catch {
609
+ }
577
610
  const exportLine = `
578
611
  export PUSHARY_API_KEY='${apiKey}'
579
612
  `;
@@ -697,9 +730,8 @@ var main = async () => {
697
730
  console.log(` ${green2(bold2("Setup complete."))}`);
698
731
  console.log();
699
732
  console.log(` ${dim2("Next:")}`);
700
- console.log(` ${dim2("1.")} Load your API key: ${cyan2("source ~/.zshrc")} ${dim2("(or open a new terminal)")}`);
701
- console.log(` ${dim2("2.")} Restart your agent to load the new config`);
702
- console.log(` ${dim2("3.")} Run ${cyan2("npx @pushary/agent-hooks doctor")} to verify`);
733
+ console.log(` ${dim2("1.")} Restart your agent to load the new config`);
734
+ console.log(` ${dim2("2.")} Run ${cyan2("npx @pushary/agent-hooks doctor")} to verify`);
703
735
  console.log();
704
736
  };
705
737
  main().catch((err) => {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-YCL4GUFR.js";
5
- import "../chunk-QEPRH7JB.js";
4
+ } from "../chunk-3QZESA66.js";
5
+ import "../chunk-HRQEECB6.js";
6
6
  import "../chunk-22CV7V7A.js";
7
- import "../chunk-3MIR7ODJ.js";
8
- import "../chunk-VUNL35KE.js";
7
+ import "../chunk-DWED7BS3.js";
8
+ import "../chunk-NKXSILEW.js";
9
9
 
10
10
  // bin/pushary-stop-hook.ts
11
11
  var main = async () => {
@@ -0,0 +1,222 @@
1
+ import {
2
+ DEFAULT_SESSION,
3
+ cancelQuestion,
4
+ deriveReceiptMeta,
5
+ describeToolCall,
6
+ fetchModeState,
7
+ getMachineId,
8
+ isDefaultSession,
9
+ isPolicyConfig,
10
+ isToolResultError,
11
+ listPendingQuestions,
12
+ removePendingQuestion,
13
+ removePendingSession,
14
+ resolvePolicy
15
+ } from "./chunk-HRQEECB6.js";
16
+ import {
17
+ withRetry
18
+ } from "./chunk-DWED7BS3.js";
19
+ import {
20
+ getApiKey,
21
+ getBaseUrl
22
+ } from "./chunk-NKXSILEW.js";
23
+
24
+ // src/codex-adapter.ts
25
+ var CODEX_AGENT = { type: "codex", label: "Codex" };
26
+ var codexAllow = () => ({ kind: "allow" });
27
+ var codexDeny = (reason) => ({ kind: "deny", reason });
28
+ var codexPass = () => ({ kind: "pass" });
29
+ var toCodexWire = (event, decision) => {
30
+ if (event === "PermissionRequest") {
31
+ if (decision.kind === "allow") {
32
+ return {
33
+ hookSpecificOutput: {
34
+ hookEventName: "PermissionRequest",
35
+ decision: { behavior: "allow" }
36
+ }
37
+ };
38
+ }
39
+ if (decision.kind === "deny") {
40
+ return {
41
+ hookSpecificOutput: {
42
+ hookEventName: "PermissionRequest",
43
+ decision: { behavior: "deny", message: decision.reason }
44
+ }
45
+ };
46
+ }
47
+ return null;
48
+ }
49
+ if (event === "PreToolUse" && decision.kind === "deny") {
50
+ return {
51
+ hookSpecificOutput: {
52
+ hookEventName: "PreToolUse",
53
+ permissionDecision: "deny",
54
+ permissionDecisionReason: decision.reason
55
+ }
56
+ };
57
+ }
58
+ return null;
59
+ };
60
+ var toPolicyLookup = (toolName, toolInput) => {
61
+ if (toolName !== "apply_patch") return { tool: toolName, input: toolInput };
62
+ const command = toolInput.command;
63
+ return {
64
+ tool: "Edit",
65
+ input: typeof command === "string" ? { file_path: command } : {}
66
+ };
67
+ };
68
+ var permissionTimeoutDecision = (timeoutAction) => {
69
+ if (timeoutAction === "approve") return codexAllow();
70
+ if (timeoutAction === "deny") return codexDeny("No response within timeout");
71
+ return codexPass();
72
+ };
73
+ var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
74
+
75
+ // src/events.ts
76
+ import { basename, join } from "path";
77
+ import { createHash } from "crypto";
78
+ import { existsSync, readFileSync } from "fs";
79
+ import { tmpdir } from "os";
80
+ var cleanupPendingQuestions = async (sessionId) => {
81
+ try {
82
+ const files = listPendingQuestions(sessionId);
83
+ const apiKey = getApiKey();
84
+ for (const correlationId of files) {
85
+ try {
86
+ await cancelQuestion(apiKey, correlationId);
87
+ } catch {
88
+ }
89
+ removePendingQuestion(sessionId, correlationId);
90
+ }
91
+ if (!isDefaultSession(sessionId)) removePendingSession(sessionId);
92
+ } catch {
93
+ }
94
+ };
95
+ var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
96
+ var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
97
+ var readFreshCachedPolicy = (apiKey) => {
98
+ const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
99
+ const path = join(tmpdir(), `pushary-policy-${hash}.json`);
100
+ if (!existsSync(path)) return null;
101
+ const cached = JSON.parse(readFileSync(path, "utf-8"));
102
+ if (!isPolicyConfig(cached)) return null;
103
+ if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
104
+ return cached;
105
+ };
106
+ var deriveDecisionSource = (toolName, toolInput, liveMode) => {
107
+ try {
108
+ if (liveMode.kill) return "terminal";
109
+ const policy = readFreshCachedPolicy(getApiKey());
110
+ if (!policy) return void 0;
111
+ const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput);
112
+ if (resolved.timeoutSeconds === 0 && resolved.timeoutAction === "approve") return "policy_auto";
113
+ if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
114
+ return "terminal";
115
+ } catch {
116
+ return void 0;
117
+ }
118
+ };
119
+ var reportEvent = async (event, options = {}) => {
120
+ const apiKey = getApiKey();
121
+ const baseUrl = getBaseUrl();
122
+ await withRetry(async () => {
123
+ await fetch(`${baseUrl}/api/agent/event`, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ "Authorization": `Bearer ${apiKey}`
128
+ },
129
+ body: JSON.stringify({
130
+ ...event,
131
+ machineId: event.machineId ?? getMachineId()
132
+ }),
133
+ signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
134
+ });
135
+ }, { maxAttempts: options.maxAttempts ?? 2, baseDelayMs: 300 });
136
+ };
137
+ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
138
+ try {
139
+ const projectName = basename(input.cwd ?? process.cwd());
140
+ const action = describeToolCall(input.tool_name, input.tool_input, "event");
141
+ const lookup = toPolicyLookup(input.tool_name, input.tool_input);
142
+ const isError = isToolResultError(input.tool_result);
143
+ const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
144
+ const liveMode = await fetchModeState(getApiKey(), input.session_id);
145
+ await Promise.allSettled([
146
+ cleanupPendingQuestions(input.session_id || DEFAULT_SESSION),
147
+ reportEvent({
148
+ event: isError ? "tool_error" : "tool_complete",
149
+ agentType: agent.type,
150
+ agentName: `${agent.label} - ${projectName}`,
151
+ action,
152
+ sessionId: input.session_id,
153
+ error: isError ? String(input.tool_result?.error ?? input.tool_result?.stderr ?? "").slice(0, 500) : void 0,
154
+ decisionSource: deriveDecisionSource(lookup.tool, lookup.input, liveMode),
155
+ meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, input.tool_result, input.cwd ?? process.cwd()) : void 0
156
+ })
157
+ ]);
158
+ } catch {
159
+ }
160
+ };
161
+ var TASK_TITLE_MAX_LENGTH = 120;
162
+ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
163
+ try {
164
+ const projectName = basename(input.cwd ?? process.cwd());
165
+ const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
166
+ const taskTitle = titlesEnabled ? input.prompt?.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
167
+ await reportEvent({
168
+ event: "user_prompt",
169
+ agentType: agent.type,
170
+ agentName: `${agent.label} - ${projectName}`,
171
+ sessionId: input.session_id,
172
+ taskTitle
173
+ }, { maxAttempts: 1, timeoutMs: 800 });
174
+ } catch {
175
+ }
176
+ };
177
+ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
178
+ try {
179
+ const projectName = basename(input.cwd ?? process.cwd());
180
+ await Promise.allSettled([
181
+ cleanupPendingQuestions(input.session_id || DEFAULT_SESSION),
182
+ reportEvent({
183
+ event: "session_end",
184
+ agentType: agent.type,
185
+ agentName: `${agent.label} - ${projectName}`,
186
+ action: "Session ended",
187
+ sessionId: input.session_id
188
+ })
189
+ ]);
190
+ } catch {
191
+ }
192
+ };
193
+ var handleNotification = async (input) => {
194
+ try {
195
+ const projectName = basename(input.cwd ?? process.cwd());
196
+ await reportEvent({
197
+ event: input.type === "error" ? "error" : "notification",
198
+ agentType: "claude_code",
199
+ agentName: `Claude Code - ${projectName}`,
200
+ action: input.title ?? input.message ?? "Notification",
201
+ sessionId: input.session_id,
202
+ error: input.type === "error" ? input.message : void 0
203
+ });
204
+ } catch {
205
+ }
206
+ };
207
+
208
+ export {
209
+ CODEX_AGENT,
210
+ codexAllow,
211
+ codexDeny,
212
+ codexPass,
213
+ toCodexWire,
214
+ toPolicyLookup,
215
+ permissionTimeoutDecision,
216
+ preToolUseTimeoutDecision,
217
+ reportEvent,
218
+ handlePostToolUse,
219
+ handleUserPrompt,
220
+ handleStop,
221
+ handleNotification
222
+ };