@pushary/agent-hooks 1.6.3 → 1.7.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 (100) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +3 -2
  3. package/dist/bin/pushary-bell-hook.js +24 -22
  4. package/dist/bin/pushary-bell.js +11 -12
  5. package/dist/bin/pushary-claude.js +29 -28
  6. package/dist/bin/pushary-clean.js +99 -40
  7. package/dist/bin/pushary-codex-bridge.js +25 -25
  8. package/dist/bin/pushary-codex-hook.js +39 -34
  9. package/dist/bin/pushary-codex.js +13 -12
  10. package/dist/bin/pushary-connect.js +13 -12
  11. package/dist/bin/pushary-cowork.js +5 -6
  12. package/dist/bin/pushary-daemon.js +31 -32
  13. package/dist/bin/pushary-disconnect.js +22 -21
  14. package/dist/bin/pushary-doctor.js +98 -61
  15. package/dist/bin/pushary-elicitation-hook.js +26 -25
  16. package/dist/bin/pushary-gemini-bridge.js +12 -13
  17. package/dist/bin/pushary-gemini-hook.js +35 -30
  18. package/dist/bin/pushary-hook.js +38 -33
  19. package/dist/bin/pushary-login.js +13 -15
  20. package/dist/bin/pushary-logout.js +12 -13
  21. package/dist/bin/pushary-mcp.d.ts +1 -0
  22. package/dist/bin/pushary-mcp.js +56 -0
  23. package/dist/bin/pushary-mode.js +10 -8
  24. package/dist/bin/pushary-notification-hook.js +28 -27
  25. package/dist/bin/pushary-opencode-hook.js +34 -29
  26. package/dist/bin/pushary-permission-denied-hook.js +32 -31
  27. package/dist/bin/pushary-permission-hook.js +31 -30
  28. package/dist/bin/pushary-post-hook.js +28 -27
  29. package/dist/bin/pushary-prompt-hook.js +28 -27
  30. package/dist/bin/pushary-session-end-hook.js +28 -27
  31. package/dist/bin/pushary-session-start-hook.js +32 -31
  32. package/dist/bin/pushary-setup.js +222 -110
  33. package/dist/bin/pushary-stats.js +5 -6
  34. package/dist/bin/pushary-status.js +15 -16
  35. package/dist/bin/pushary-stop-hook.js +28 -27
  36. package/dist/bin/pushary-stopfailure-hook.js +28 -27
  37. package/dist/bin/pushary-suggestions.js +4 -4
  38. package/dist/bin/pushary-transcript-register.js +19 -20
  39. package/dist/bin/pushary-transcripts.js +7 -8
  40. package/dist/bin/pushary-upgrade.js +28 -28
  41. package/dist/bin/pushary-wait.js +10 -8
  42. package/dist/bin/pushary.js +9 -5
  43. package/dist/{chunk-4KSTLHWY.js → chunk-2C7CCWVX.js} +1 -1
  44. package/dist/{chunk-P4BNYJ5Q.js → chunk-2PS6QWDW.js} +2 -2
  45. package/dist/{chunk-ILGL4L3J.js → chunk-4F46VIEE.js} +8 -8
  46. package/dist/{chunk-UAV5MNKY.js → chunk-52NVBNJT.js} +3 -3
  47. package/dist/{chunk-GQPCPEG2.js → chunk-5HLTWFJ6.js} +1 -1
  48. package/dist/{chunk-EJ7ADMNM.js → chunk-6ADZQFVW.js} +1 -1
  49. package/dist/{chunk-QKOVVL7I.js → chunk-73T6MSU5.js} +7 -7
  50. package/dist/{chunk-YOCXPNQ5.js → chunk-7OS4IHGI.js} +2 -2
  51. package/dist/{chunk-BY2CRWBO.js → chunk-B234346F.js} +1 -1
  52. package/dist/{chunk-EA3IELHP.js → chunk-B3UXMSMW.js} +6 -2
  53. package/dist/{chunk-STOMNX4S.js → chunk-B3YQANFN.js} +1 -1
  54. package/dist/{chunk-JBU7XKY2.js → chunk-B76WVEDX.js} +1 -1
  55. package/dist/{chunk-WA7SQX6S.js → chunk-BBKNJJVX.js} +6 -6
  56. package/dist/{chunk-KNIIFNNQ.js → chunk-BH2SKSSK.js} +1 -1
  57. package/dist/{chunk-CTOK3AYA.js → chunk-BWYJ6AAF.js} +6 -7
  58. package/dist/{chunk-HCVAMXHT.js → chunk-CVALVGA7.js} +1 -1
  59. package/dist/{chunk-DX5FOQSO.js → chunk-D6BULEJW.js} +2 -2
  60. package/dist/{chunk-RJUHSFYE.js → chunk-DANOZVZK.js} +1 -1
  61. package/dist/{chunk-3BJQYXTL.js → chunk-DNJUSFBL.js} +13 -5
  62. package/dist/{chunk-57YDFAPP.js → chunk-DVQDPI3T.js} +8 -8
  63. package/dist/{chunk-BEHGVU4W.js → chunk-DZURML2D.js} +1 -1
  64. package/dist/{chunk-LDS4ERW4.js → chunk-EAQMWJPG.js} +10 -10
  65. package/dist/{chunk-LVHGSD5E.js → chunk-EGKXSQCY.js} +13 -7
  66. package/dist/{chunk-HHYVBHHC.js → chunk-HDRNWNVP.js} +6 -6
  67. package/dist/{chunk-JBXUKGZS.js → chunk-IJ6ECPA6.js} +15 -9
  68. package/dist/{chunk-NMRH3VX4.js → chunk-J2355HR7.js} +1 -1
  69. package/dist/{chunk-HETOKNIL.js → chunk-JNXVOU6Y.js} +10 -6
  70. package/dist/{chunk-COGHCL6K.js → chunk-JXUAXO74.js} +2 -2
  71. package/dist/chunk-KW2RRR7D.js +124 -0
  72. package/dist/{chunk-66H2QQAK.js → chunk-KXMZMZD3.js} +1 -1
  73. package/dist/{chunk-H3FEVIQD.js → chunk-KYQXUGH7.js} +3 -3
  74. package/dist/{chunk-P35IUFFR.js → chunk-L4H2Q2S4.js} +276 -19
  75. package/dist/{chunk-NKR4YDRP.js → chunk-LEZFOTMI.js} +1 -1
  76. package/dist/{chunk-WD6W7QDI.js → chunk-O2LEPIU6.js} +1 -1
  77. package/dist/chunk-POFBE6ZY.js +12 -0
  78. package/dist/{chunk-SND3C2AV.js → chunk-PQ4F64M2.js} +3 -3
  79. package/dist/{chunk-3UOC3GRO.js → chunk-PUDBG3GM.js} +1 -1
  80. package/dist/{chunk-YQMFGWBD.js → chunk-QXAK5RFU.js} +1 -1
  81. package/dist/{chunk-REYWOCQJ.js → chunk-R2XCLT6I.js} +4 -4
  82. package/dist/{chunk-NPGOQQCV.js → chunk-ROQK7IN3.js} +2 -2
  83. package/dist/{chunk-GU3EJCVV.js → chunk-S3UD4V6W.js} +39 -0
  84. package/dist/{chunk-ICGAIM2R.js → chunk-SQRWHALT.js} +2 -2
  85. package/dist/{chunk-JX67DVMY.js → chunk-UC65FJJT.js} +5 -6
  86. package/dist/{chunk-CWIARY6F.js → chunk-UOAQ6QMP.js} +2 -2
  87. package/dist/{chunk-OSJEMXSR.js → chunk-V4AQBAVG.js} +1 -1
  88. package/dist/chunk-V6QRSU2G.js +142 -0
  89. package/dist/{chunk-VDMX5EMU.js → chunk-VKVIAZQO.js} +1 -1
  90. package/dist/{chunk-BG6OCZ4N.js → chunk-WUNZAE7I.js} +5 -5
  91. package/dist/{chunk-TZ4KUNZG.js → chunk-X6KDKOV5.js} +3 -3
  92. package/dist/{chunk-LM2IOEDK.js → chunk-XNCMT7YY.js} +3 -1
  93. package/dist/{chunk-GHOB245Q.js → chunk-YROGUPOZ.js} +132 -30
  94. package/dist/chunk-YYINL576.js +120 -0
  95. package/dist/{chunk-57DDP4TB.js → chunk-ZQ7IDOKY.js} +3 -3
  96. package/dist/{reapply-IB7YE7BU.js → reapply-6PH3FMPX.js} +9 -10
  97. package/dist/src/index.js +20 -19
  98. package/package.json +2 -1
  99. package/dist/chunk-HWJW3CPO.js +0 -65
  100. package/dist/chunk-SAF6HGAA.js +0 -57
@@ -0,0 +1,142 @@
1
+ import {
2
+ CLIENT_REPORT_DISABLE_ENV,
3
+ isDisabledByEnv
4
+ } from "./chunk-POFBE6ZY.js";
5
+ import {
6
+ isNetworkFailure
7
+ } from "./chunk-KW2RRR7D.js";
8
+ import {
9
+ clientReportClaims
10
+ } from "./chunk-DNJUSFBL.js";
11
+ import {
12
+ getApiKey,
13
+ getBaseUrl
14
+ } from "./chunk-V4AQBAVG.js";
15
+ import {
16
+ CLIENT_REPORT_PATH,
17
+ degradedLine
18
+ } from "./chunk-YROGUPOZ.js";
19
+
20
+ // src/client-report-claim.ts
21
+ import { createHash } from "crypto";
22
+ import { closeSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
23
+ import { join } from "path";
24
+ var CLIENT_REPORT_WINDOW_MS = 6e4;
25
+ var CLAIM_DIRECTORY_MODE = 448;
26
+ var CLAIM_FILE_MODE = 384;
27
+ var CLAIM_KEY_HASH_LENGTH = 16;
28
+ var CLAIM_FILE_PATTERN = /^[0-9a-f]{16}-(\d+)$/;
29
+ var EXCLUSIVE_CREATE = "wx";
30
+ var ALREADY_EXISTS = "EEXIST";
31
+ var WINDOWS_KEPT_BEHIND = 1;
32
+ var reportWindowOf = (now) => Math.floor(now / CLIENT_REPORT_WINDOW_MS);
33
+ var claimFileName = (key, window) => `${createHash("sha256").update(key).digest("hex").slice(0, CLAIM_KEY_HASH_LENGTH)}-${window}`;
34
+ var errorCodeOf = (error) => typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
35
+ var claimWindowOf = (fileName) => {
36
+ const match = CLAIM_FILE_PATTERN.exec(fileName);
37
+ return match ? Number(match[1]) : null;
38
+ };
39
+ var isStaleClaim = (fileName, window) => {
40
+ const claimedWindow = claimWindowOf(fileName);
41
+ return claimedWindow !== null && claimedWindow < window - WINDOWS_KEPT_BEHIND;
42
+ };
43
+ var removeQuietly = (filePath) => {
44
+ try {
45
+ unlinkSync(filePath);
46
+ } catch {
47
+ return;
48
+ }
49
+ };
50
+ var removeStaleClaims = (claimsDir, window) => {
51
+ try {
52
+ for (const fileName of readdirSync(claimsDir)) {
53
+ if (isStaleClaim(fileName, window)) removeQuietly(join(claimsDir, fileName));
54
+ }
55
+ } catch {
56
+ return;
57
+ }
58
+ };
59
+ var claimReportWindow = (claimsDir, key, now) => {
60
+ const window = reportWindowOf(now);
61
+ try {
62
+ mkdirSync(claimsDir, { recursive: true, mode: CLAIM_DIRECTORY_MODE });
63
+ closeSync(openSync(join(claimsDir, claimFileName(key, window)), EXCLUSIVE_CREATE, CLAIM_FILE_MODE));
64
+ } catch (error) {
65
+ return errorCodeOf(error) === ALREADY_EXISTS ? "already_claimed" : "unavailable";
66
+ }
67
+ removeStaleClaims(claimsDir, window);
68
+ return "claimed";
69
+ };
70
+
71
+ // src/client-report.ts
72
+ var CLIENT_REPORT_TIMEOUT_MS = 1e3;
73
+ var readApiKey = () => {
74
+ try {
75
+ return getApiKey();
76
+ } catch {
77
+ return null;
78
+ }
79
+ };
80
+ var defaultDeps = () => ({
81
+ disabled: () => isDisabledByEnv(process.env, CLIENT_REPORT_DISABLE_ENV),
82
+ fetchImpl: fetch,
83
+ apiKey: readApiKey,
84
+ baseUrl: getBaseUrl,
85
+ now: Date.now,
86
+ claimsDir: clientReportClaims()
87
+ });
88
+ var reportKey = (event) => `${event.kind}:${event.code}`;
89
+ var createClientErrorReporter = (deps) => {
90
+ const windowClaimedInProcess = /* @__PURE__ */ new Map();
91
+ const claimInProcess = (key, now) => {
92
+ if (windowClaimedInProcess.get(key) === reportWindowOf(now)) return false;
93
+ windowClaimedInProcess.set(key, reportWindowOf(now));
94
+ return true;
95
+ };
96
+ const claimReport = (key, now) => {
97
+ const claim = claimReportWindow(deps.claimsDir, key, now);
98
+ return claim === "unavailable" ? claimInProcess(key, now) : claim === "claimed";
99
+ };
100
+ return async (event) => {
101
+ try {
102
+ if (deps.disabled()) return;
103
+ const apiKey = deps.apiKey();
104
+ const now = deps.now();
105
+ if (!apiKey || !claimReport(reportKey(event), now)) return;
106
+ await deps.fetchImpl(`${deps.baseUrl()}${CLIENT_REPORT_PATH}`, {
107
+ method: "POST",
108
+ headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
109
+ body: JSON.stringify({ ...event, occurredAt: new Date(now).toISOString() }),
110
+ signal: AbortSignal.timeout(CLIENT_REPORT_TIMEOUT_MS)
111
+ });
112
+ } catch {
113
+ return;
114
+ }
115
+ };
116
+ };
117
+ var processReporter = null;
118
+ var inFlightReports = /* @__PURE__ */ new Set();
119
+ var reportClientError = (event) => {
120
+ processReporter ??= createClientErrorReporter(defaultDeps());
121
+ const report = processReporter(event);
122
+ inFlightReports.add(report);
123
+ void report.finally(() => inFlightReports.delete(report));
124
+ return report;
125
+ };
126
+ var exitAfterClientReports = async (code) => {
127
+ await Promise.all(inFlightReports);
128
+ return process.exit(code);
129
+ };
130
+ var clientErrorKindOf = (error) => isNetworkFailure(error) ? "network" : "unexpected";
131
+ var writeDegraded = (subsystem, report) => {
132
+ process.stderr.write(`${degradedLine(subsystem, report)}
133
+ `);
134
+ void reportClientError({ kind: "degraded", code: `${subsystem}:${report.reason}` });
135
+ };
136
+
137
+ export {
138
+ reportClientError,
139
+ exitAfterClientReports,
140
+ clientErrorKindOf,
141
+ writeDegraded
142
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  guardBinary
3
- } from "./chunk-3BJQYXTL.js";
3
+ } from "./chunk-DNJUSFBL.js";
4
4
 
5
5
  // src/bell/install.ts
6
6
  import { join } from "path";
@@ -3,28 +3,28 @@ import {
3
3
  addPusharyToolPermissions,
4
4
  isPusharyHook,
5
5
  removeClaudeMcpServers
6
- } from "./chunk-H3FEVIQD.js";
6
+ } from "./chunk-KYQXUGH7.js";
7
7
  import {
8
8
  CLAUDE_HOOK_EVENTS,
9
9
  addGeminiMcpServer,
10
10
  removeGeminiMcpServer
11
- } from "./chunk-57DDP4TB.js";
11
+ } from "./chunk-ZQ7IDOKY.js";
12
12
  import {
13
13
  CLAUDE_CODE_AGENT,
14
14
  parseApplyPatchFiles
15
- } from "./chunk-JBXUKGZS.js";
15
+ } from "./chunk-IJ6ECPA6.js";
16
16
  import {
17
17
  guardBinary,
18
18
  holdBudgetOf,
19
19
  registerHookEntry
20
- } from "./chunk-3BJQYXTL.js";
20
+ } from "./chunk-DNJUSFBL.js";
21
21
  import {
22
22
  PHONE_ANSWERED_TOOLS,
23
23
  hookCompatibleProfile,
24
24
  hookCompatibleProfileBySource,
25
25
  toToolLookup,
26
26
  withHookHoldBudget
27
- } from "./chunk-GHOB245Q.js";
27
+ } from "./chunk-YROGUPOZ.js";
28
28
 
29
29
  // src/compatible-config.ts
30
30
  import { homedir } from "os";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyFetch
3
- } from "./chunk-SAF6HGAA.js";
3
+ } from "./chunk-KW2RRR7D.js";
4
4
  import {
5
5
  NetworkError
6
6
  } from "./chunk-7QLSKOSU.js";
@@ -9,11 +9,11 @@ import {
9
9
  } from "./chunk-KZERVKTD.js";
10
10
  import {
11
11
  getBaseUrl
12
- } from "./chunk-OSJEMXSR.js";
12
+ } from "./chunk-V4AQBAVG.js";
13
13
  import {
14
14
  isValidApiKey,
15
15
  normalizeKeyScope
16
- } from "./chunk-GHOB245Q.js";
16
+ } from "./chunk-YROGUPOZ.js";
17
17
 
18
18
  // src/session.ts
19
19
  var samePusharyAccount = (left, right) => left.kind === "ok" && right.kind === "ok" && left.identity.workspaceId !== "" && left.identity.workspaceId === right.identity.workspaceId && left.identity.siteId === right.identity.siteId;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-3BJQYXTL.js";
3
+ } from "./chunk-DNJUSFBL.js";
4
4
  import {
5
5
  writeFileAtomic
6
6
  } from "./chunk-T7E75EKA.js";
@@ -18,6 +18,7 @@ var readMachineId = (filePath) => {
18
18
  return null;
19
19
  }
20
20
  };
21
+ var readExistingMachineId = (options = {}) => readMachineId(options.filePath ?? join(pusharyDir(), "machine-id"));
21
22
  var getOrCreateMachineId = (options = {}) => {
22
23
  const filePath = options.filePath ?? join(pusharyDir(), "machine-id");
23
24
  const existing = readMachineId(filePath);
@@ -41,5 +42,6 @@ var getOrCreateMachineId = (options = {}) => {
41
42
  var getMachineId = (options = {}) => getOrCreateMachineId(options);
42
43
 
43
44
  export {
45
+ readExistingMachineId,
44
46
  getMachineId
45
47
  };
@@ -151,6 +151,13 @@ var AGENTS = {
151
151
  setupCommand: AGENT_HOOKS_SETUP_COMMAND,
152
152
  enforcementDetail: "Shell, writes, edits and web fetches when OpenCode itself asks for permission."
153
153
  },
154
+ fx: {
155
+ ...defaults,
156
+ label: "fx",
157
+ connectVia: "Local MCP bridge",
158
+ setupCommand: AGENT_HOOKS_SETUP_COMMAND,
159
+ capabilityNote: "fx has no hooks that other tools can register, so Pushary cannot answer fx's own approval prompts. fx's agent reaches Pushary through its MCP tools when it chooses to ask."
160
+ },
154
161
  qwen_code: {
155
162
  ...defaults,
156
163
  label: "Qwen Code",
@@ -447,6 +454,34 @@ var compatibleTargetFiles = (profile) => {
447
454
  return [...seen.values()];
448
455
  };
449
456
 
457
+ // ../contracts/src/redaction.ts
458
+ var SECRET_REDACTION_RULES = [
459
+ { pattern: /\bpk_[A-Za-z0-9_]+\.[A-Za-z0-9_]+/g, replacement: "[redacted]" },
460
+ { pattern: /-----BEGIN[A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z0-9 ]*PRIVATE KEY-----/g, replacement: "[redacted key]" },
461
+ { pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/g, replacement: "[redacted]" },
462
+ { pattern: /\b[spr]k_(?:live|test)_[A-Za-z0-9]{8,}\b/g, replacement: "[redacted]" },
463
+ { pattern: /\bwhsec_[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
464
+ { pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/g, replacement: "[redacted]" },
465
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g, replacement: "[redacted]" },
466
+ { pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, replacement: "[redacted]" },
467
+ { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, replacement: "[redacted]" },
468
+ { pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g, replacement: "[redacted]" },
469
+ { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replacement: "[redacted]" },
470
+ { pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: "[redacted]" },
471
+ { pattern: /\bxai-[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
472
+ { pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, replacement: "[redacted]" },
473
+ { pattern: /\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]+@/gi, replacement: "$1[redacted]@" },
474
+ { pattern: /\bbearer\s+[A-Za-z0-9._~+/=-]+/gi, replacement: "bearer [redacted]" },
475
+ { pattern: /\bauthorization:\s*\S+/gi, replacement: "authorization: [redacted]" },
476
+ {
477
+ pattern: /((?:secret|token|password|passwd|api[_-]?key|access[_-]?key|client[_-]?secret|private[_-]?key)["']?\s*[=:]\s*)(?:(")(?:\\[\s\S]|[^"\\])*(?:"|\\?$)|(')[^']*(?:'|$)|\S+)/gi,
478
+ replacement: "$1$2$3[redacted]$2$3"
479
+ }
480
+ ];
481
+ var HIGH_ENTROPY_RULE = { pattern: /[A-Za-z0-9+/]{40,}={0,2}/g, replacement: "[redacted]" };
482
+ var redactSecrets = (text) => SECRET_REDACTION_RULES.reduce((acc, rule) => acc.replace(rule.pattern, rule.replacement), text);
483
+ var redactSecretsDeep = (text) => redactSecrets(text).replace(HIGH_ENTROPY_RULE.pattern, HIGH_ENTROPY_RULE.replacement);
484
+
450
485
  // ../contracts/src/degraded.ts
451
486
  var DEGRADED_PREFIX = "[degraded]";
452
487
  var degradedPayload = (subsystem, report) => ({
@@ -469,12 +504,40 @@ var SETUP_AGENT_CHOICES = [
469
504
  "cursor",
470
505
  "vscode",
471
506
  "opencode",
507
+ "fx",
472
508
  ...COMPATIBLE_AGENT_TYPES,
473
509
  "custom"
474
510
  ];
475
511
  var SETUP_CONNECT_MODES = ["app", "browser", "web", "none"];
476
512
  var isSetupConnectMode = (value) => typeof value === "string" && SETUP_CONNECT_MODES.includes(value);
477
513
 
514
+ // ../contracts/src/observability.ts
515
+ var CLIENT_CONTEXT_HEADERS = {
516
+ surface: "x-pushary-surface",
517
+ clientVersion: "x-pushary-client-version",
518
+ machineId: "x-pushary-machine-id",
519
+ runId: "x-pushary-run-id",
520
+ platform: "x-pushary-platform"
521
+ };
522
+ var CLIENT_REPORT_PATH = "/api/agent/client-report";
523
+ var CLIENT_VERSION_PATTERN = /^\d+(?:\.\d+){1,3}(?:[-+][0-9A-Za-z.-]+)?$/;
524
+ var CONTEXT_ID_PATTERN = /^[A-Za-z0-9-]{8,64}$/;
525
+ var PLATFORM_PATTERN = /^[a-z0-9_.-]{1,32}$/;
526
+ var CONTEXT_VALUE_MAX_LENGTH = 64;
527
+ var isClientVersion = (value) => typeof value === "string" && value.length <= CONTEXT_VALUE_MAX_LENGTH && CLIENT_VERSION_PATTERN.test(value);
528
+ var isContextId = (value) => typeof value === "string" && CONTEXT_ID_PATTERN.test(value);
529
+ var isClientPlatform = (value) => typeof value === "string" && PLATFORM_PATTERN.test(value);
530
+ var clientContextHeaders = (source) => {
531
+ const headers = {
532
+ [CLIENT_CONTEXT_HEADERS.surface]: source.surface,
533
+ [CLIENT_CONTEXT_HEADERS.runId]: source.runId
534
+ };
535
+ if (isClientVersion(source.clientVersion)) headers[CLIENT_CONTEXT_HEADERS.clientVersion] = source.clientVersion;
536
+ if (isContextId(source.machineId)) headers[CLIENT_CONTEXT_HEADERS.machineId] = source.machineId;
537
+ if (isClientPlatform(source.platform)) headers[CLIENT_CONTEXT_HEADERS.platform] = source.platform;
538
+ return headers;
539
+ };
540
+
478
541
  // ../contracts/src/index.ts
479
542
  var KEY_SCOPES = ["full_access", "agent", "connector", "bound"];
480
543
  var normalizeKeyScope = (value) => typeof value === "string" && KEY_SCOPES.includes(value) ? value : null;
@@ -1269,8 +1332,7 @@ var DESTRUCTIVE_SUBCOMMANDS = /* @__PURE__ */ new Set([
1269
1332
  var DATA_LOSS_COMMAND_PATTERNS = [
1270
1333
  "Bash(git branch -D:*)",
1271
1334
  "Bash(git branch --delete:*)",
1272
- "Bash(git checkout --:*)",
1273
- "Bash(git checkout .:*)",
1335
+ "Bash(git checkout:*)",
1274
1336
  "Bash(git restore:*)",
1275
1337
  "Bash(git stash drop:*)",
1276
1338
  "Bash(git stash clear:*)",
@@ -1545,31 +1607,6 @@ var computeDeltaSignal = (input) => {
1545
1607
  }
1546
1608
  return { deltaSignal: "match", deltaScore: 0, remoteApprove: !destructive };
1547
1609
  };
1548
- var SECRET_REDACTION_RULES = [
1549
- { pattern: /\bpk_[A-Za-z0-9_]+\.[A-Za-z0-9_]+/g, replacement: "[redacted]" },
1550
- { pattern: /-----BEGIN[A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z0-9 ]*PRIVATE KEY-----/g, replacement: "[redacted key]" },
1551
- { pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/g, replacement: "[redacted]" },
1552
- { pattern: /\b[spr]k_(?:live|test)_[A-Za-z0-9]{8,}\b/g, replacement: "[redacted]" },
1553
- { pattern: /\bwhsec_[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
1554
- { pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/g, replacement: "[redacted]" },
1555
- { pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g, replacement: "[redacted]" },
1556
- { pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, replacement: "[redacted]" },
1557
- { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, replacement: "[redacted]" },
1558
- { pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g, replacement: "[redacted]" },
1559
- { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replacement: "[redacted]" },
1560
- { pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: "[redacted]" },
1561
- { pattern: /\bxai-[A-Za-z0-9]{16,}\b/g, replacement: "[redacted]" },
1562
- { pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, replacement: "[redacted]" },
1563
- { pattern: /\bbearer\s+[A-Za-z0-9._~+/=-]+/gi, replacement: "bearer [redacted]" },
1564
- { pattern: /\bauthorization:\s*\S+/gi, replacement: "authorization: [redacted]" },
1565
- {
1566
- pattern: /((?:secret|token|password|passwd|api[_-]?key|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)("[^"]*"|'[^']*'|\S+)/gi,
1567
- replacement: "$1[redacted]"
1568
- }
1569
- ];
1570
- var HIGH_ENTROPY_RULE = { pattern: /[A-Za-z0-9+/]{40,}={0,2}/g, replacement: "[redacted]" };
1571
- var redactSecrets = (text) => SECRET_REDACTION_RULES.reduce((acc, rule) => acc.replace(rule.pattern, rule.replacement), text);
1572
- var redactSecretsDeep = (text) => redactSecrets(text).replace(HIGH_ENTROPY_RULE.pattern, HIGH_ENTROPY_RULE.replacement);
1573
1610
  var AUTHORIZATION_CANONICAL_VERSION = "pushary.authz.v1";
1574
1611
  var AUTHORIZATION_CANONICAL_FIXTURE = [
1575
1612
  {
@@ -2468,6 +2505,63 @@ var hookActivationResult = (report) => {
2468
2505
  return report && !report.truncated && report.evidence.length === 1 ? report.evidence[0].result : "unverified";
2469
2506
  };
2470
2507
 
2508
+ // src/cli/version.ts
2509
+ import { readFileSync } from "fs";
2510
+ import { dirname, join, parse } from "path";
2511
+ import { fileURLToPath } from "url";
2512
+ var PACKAGE_NAME = "@pushary/agent-hooks";
2513
+ var UNKNOWN_VERSION = "0.0.0";
2514
+ var findOwnPackageJson = (start) => {
2515
+ const { root } = parse(start);
2516
+ let dir = start;
2517
+ while (true) {
2518
+ const candidate = join(dir, "package.json");
2519
+ try {
2520
+ const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
2521
+ if (pkg.name === PACKAGE_NAME) return candidate;
2522
+ } catch {
2523
+ }
2524
+ if (dir === root) return void 0;
2525
+ const parent = dirname(dir);
2526
+ if (parent === dir) return void 0;
2527
+ dir = parent;
2528
+ }
2529
+ };
2530
+ var getPackageVersion = () => {
2531
+ const root = ownPackageRoot();
2532
+ return (root === void 0 ? void 0 : packageVersionAt(root)) ?? UNKNOWN_VERSION;
2533
+ };
2534
+ var ownPackageRoot = () => {
2535
+ try {
2536
+ const found = findOwnPackageJson(dirname(fileURLToPath(import.meta.url)));
2537
+ return found ? dirname(found) : void 0;
2538
+ } catch {
2539
+ return void 0;
2540
+ }
2541
+ };
2542
+ var packageVersionAt = (packageRoot) => {
2543
+ try {
2544
+ const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8"));
2545
+ const version = manifest && typeof manifest === "object" && "version" in manifest ? manifest.version : void 0;
2546
+ return typeof version === "string" ? version : void 0;
2547
+ } catch {
2548
+ return void 0;
2549
+ }
2550
+ };
2551
+ var isNewerVersion = (candidate, current) => {
2552
+ const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
2553
+ const b = current.split(".").map((part) => parseInt(part, 10) || 0);
2554
+ for (let i = 0; i < 3; i++) {
2555
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
2556
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
2557
+ }
2558
+ return false;
2559
+ };
2560
+ var RELEASE_VERSION = /^\d+\.\d+\.\d+$/;
2561
+ var isReleaseVersion = (value) => RELEASE_VERSION.test(value);
2562
+ var majorOf = (version) => parseInt(version.split(".")[0] ?? "", 10) || 0;
2563
+ var isSameMajorVersion = (candidate, current) => majorOf(candidate) === majorOf(current);
2564
+
2471
2565
  export {
2472
2566
  CLAUDE_CONNECTOR_URL,
2473
2567
  CLAUDE_CONNECTOR_GUIDE,
@@ -2489,6 +2583,8 @@ export {
2489
2583
  hookCompatibleProfile,
2490
2584
  hookCompatibleProfileBySource,
2491
2585
  compatibleTargetFiles,
2586
+ redactSecrets,
2587
+ redactSecretsDeep,
2492
2588
  degradedLine,
2493
2589
  hookWritePath,
2494
2590
  isHookActivationReport,
@@ -2497,6 +2593,8 @@ export {
2497
2593
  SETUP_AGENT_CHOICES,
2498
2594
  SETUP_CONNECT_MODES,
2499
2595
  isSetupConnectMode,
2596
+ CLIENT_REPORT_PATH,
2597
+ clientContextHeaders,
2500
2598
  normalizeKeyScope,
2501
2599
  parseAgentQuestion,
2502
2600
  parseAgentQuestions,
@@ -2524,8 +2622,6 @@ export {
2524
2622
  isValidApiKey,
2525
2623
  ACTION_BODY_MAX,
2526
2624
  DECISION_LINE_MAX,
2527
- redactSecrets,
2528
- redactSecretsDeep,
2529
2625
  isAnswerSource,
2530
2626
  buildDecisionEpisodeFeatures,
2531
2627
  resolveAutoResolveOrigin,
@@ -2568,5 +2664,11 @@ export {
2568
2664
  readElicitationFields,
2569
2665
  planElicitationAsk,
2570
2666
  elicitationContent,
2571
- elicitationContentFromAnswers
2667
+ elicitationContentFromAnswers,
2668
+ getPackageVersion,
2669
+ ownPackageRoot,
2670
+ packageVersionAt,
2671
+ isNewerVersion,
2672
+ isReleaseVersion,
2673
+ isSameMajorVersion
2572
2674
  };
@@ -0,0 +1,120 @@
1
+ import {
2
+ parseSseMessages
3
+ } from "./chunk-JNXVOU6Y.js";
4
+ import {
5
+ NPX_LATEST_COMMAND
6
+ } from "./chunk-B3UXMSMW.js";
7
+
8
+ // src/mcp-stdio-bridge.ts
9
+ var MCP_BRIDGE_BINARY = "pushary-mcp";
10
+ var MCP_BRIDGE_PATH = "/api/mcp/mcp";
11
+ var MCP_BRIDGE_REQUEST_TIMEOUT_MS = 9e4;
12
+ var JSON_RPC_PARSE_ERROR = -32700;
13
+ var MCP_BRIDGE_ERROR = -32e3;
14
+ var MCP_SESSION_HEADER = "mcp-session-id";
15
+ var MCP_PROTOCOL_HEADER = "mcp-protocol-version";
16
+ var INITIALIZE_METHOD = "initialize";
17
+ var MISSING_KEY_MESSAGE = `Pushary has no API key for this machine. Run \`${NPX_LATEST_COMMAND} setup\`.`;
18
+ var REJECTED_KEY_MESSAGE = `Pushary rejected this machine's API key. Run \`${NPX_LATEST_COMMAND} setup\` to sign in again.`;
19
+ var PARSE_ERROR_MESSAGE = "Parse error";
20
+ var NO_RESPONSE_MESSAGE = "Pushary sent no response.";
21
+ var unreachableMessage = (err) => `Pushary is unreachable: ${err instanceof Error ? err.message : String(err)}`;
22
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
23
+ var isJsonRpcId = (value) => typeof value === "string" || typeof value === "number";
24
+ var messagesOf = (payload) => Array.isArray(payload) ? payload : [payload];
25
+ var requestOf = (message) => {
26
+ const record = asRecord(message);
27
+ return record && typeof record.method === "string" && isJsonRpcId(record.id) ? [{ id: record.id, method: record.method }] : [];
28
+ };
29
+ var requestsIn = (payload) => messagesOf(payload).flatMap(requestOf);
30
+ var isJsonRpcMessage = (message) => asRecord(message)?.jsonrpc === "2.0";
31
+ var responseIdOf = (message) => {
32
+ const record = asRecord(message);
33
+ return record && !("method" in record) && isJsonRpcId(record.id) ? record.id : void 0;
34
+ };
35
+ var bridgeError = (id, message, code = MCP_BRIDGE_ERROR) => ({
36
+ jsonrpc: "2.0",
37
+ id,
38
+ error: { code, message }
39
+ });
40
+ var parseBridgeLine = (line) => {
41
+ try {
42
+ return { ok: true, payload: JSON.parse(line) };
43
+ } catch {
44
+ return { ok: false };
45
+ }
46
+ };
47
+ var parseMcpMessages = (body, contentType) => {
48
+ if (!body.trim()) return [];
49
+ if (contentType?.includes("text/event-stream")) return parseSseMessages(body);
50
+ const parsed = parseBridgeLine(body);
51
+ return parsed.ok ? messagesOf(parsed.payload) : [];
52
+ };
53
+ var httpFailureMessage = (status, hasKey, body) => {
54
+ if (status === 401) return hasKey ? REJECTED_KEY_MESSAGE : MISSING_KEY_MESSAGE;
55
+ const detail = body.trim().slice(0, 300);
56
+ return detail ? `Pushary returned ${status}: ${detail}` : `Pushary returned ${status}.`;
57
+ };
58
+ var negotiatedProtocolOf = (messages, initializeIds) => {
59
+ for (const message of messages) {
60
+ const id = responseIdOf(message);
61
+ const version = asRecord(asRecord(message)?.result)?.protocolVersion;
62
+ if (id !== void 0 && initializeIds.has(id) && typeof version === "string") return version;
63
+ }
64
+ return void 0;
65
+ };
66
+ var createMcpStdioBridge = (deps) => {
67
+ let sessionId = null;
68
+ let protocolVersion = null;
69
+ const headersFor = (apiKey) => ({
70
+ "Content-Type": "application/json",
71
+ "Accept": "application/json, text/event-stream",
72
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
73
+ ...sessionId ? { [MCP_SESSION_HEADER]: sessionId } : {},
74
+ ...protocolVersion ? { [MCP_PROTOCOL_HEADER]: protocolVersion } : {}
75
+ });
76
+ const failRequests = (requests, message, answered = /* @__PURE__ */ new Set()) => {
77
+ for (const request of requests) {
78
+ if (!answered.has(request.id)) deps.emit(bridgeError(request.id, message));
79
+ }
80
+ };
81
+ const post = async (line, apiKey) => deps.fetchImpl(deps.endpoint, {
82
+ method: "POST",
83
+ headers: headersFor(apiKey),
84
+ body: line,
85
+ signal: AbortSignal.timeout(deps.timeoutMs ?? MCP_BRIDGE_REQUEST_TIMEOUT_MS)
86
+ });
87
+ const forward = async (line) => {
88
+ const parsed = parseBridgeLine(line);
89
+ if (!parsed.ok) {
90
+ deps.emit(bridgeError(null, PARSE_ERROR_MESSAGE, JSON_RPC_PARSE_ERROR));
91
+ return;
92
+ }
93
+ const requests = requestsIn(parsed.payload);
94
+ const apiKey = deps.apiKey();
95
+ let response;
96
+ let body;
97
+ try {
98
+ response = await post(line, apiKey);
99
+ body = await response.text();
100
+ } catch (err) {
101
+ failRequests(requests, unreachableMessage(err));
102
+ return;
103
+ }
104
+ sessionId = response.headers.get(MCP_SESSION_HEADER) ?? sessionId;
105
+ const messages = parseMcpMessages(body, response.headers.get("content-type")).filter(isJsonRpcMessage);
106
+ const initializeIds = new Set(requests.filter((request) => request.method === INITIALIZE_METHOD).map((request) => request.id));
107
+ protocolVersion = negotiatedProtocolOf(messages, initializeIds) ?? protocolVersion;
108
+ for (const message of messages) deps.emit(message);
109
+ const answered = new Set(messages.map(responseIdOf).filter(isJsonRpcId));
110
+ const failure = response.ok ? NO_RESPONSE_MESSAGE : httpFailureMessage(response.status, apiKey !== null, body);
111
+ failRequests(requests, failure, answered);
112
+ };
113
+ return { forward };
114
+ };
115
+
116
+ export {
117
+ MCP_BRIDGE_BINARY,
118
+ MCP_BRIDGE_PATH,
119
+ createMcpStdioBridge
120
+ };
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  NOTIFICATION_MATCHED_TYPES,
3
3
  POSTTOOLUSE_MIRRORED_TOOLS
4
- } from "./chunk-BEHGVU4W.js";
4
+ } from "./chunk-DZURML2D.js";
5
5
  import {
6
6
  isProtectedByScope,
7
7
  registerHookEntry
8
- } from "./chunk-3BJQYXTL.js";
8
+ } from "./chunk-DNJUSFBL.js";
9
9
  import {
10
10
  CLAUDE_PRETOOLUSE_MATCHER,
11
11
  HOOK_BUDGETS,
12
12
  HOOK_LOCATOR_BINARY
13
- } from "./chunk-GHOB245Q.js";
13
+ } from "./chunk-YROGUPOZ.js";
14
14
 
15
15
  // src/gemini-config.ts
16
16
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
@@ -9,18 +9,17 @@ import {
9
9
  reapplyGeminiSettings,
10
10
  reapplyVsCodePlugin,
11
11
  repairCodexHolds
12
- } from "./chunk-WA7SQX6S.js";
13
- import "./chunk-WD6W7QDI.js";
14
- import "./chunk-YOCXPNQ5.js";
15
- import "./chunk-H3FEVIQD.js";
16
- import "./chunk-SND3C2AV.js";
17
- import "./chunk-57DDP4TB.js";
12
+ } from "./chunk-BBKNJJVX.js";
13
+ import "./chunk-O2LEPIU6.js";
14
+ import "./chunk-7OS4IHGI.js";
15
+ import "./chunk-KYQXUGH7.js";
16
+ import "./chunk-PQ4F64M2.js";
17
+ import "./chunk-ZQ7IDOKY.js";
18
18
  import "./chunk-XHKBHWLX.js";
19
- import "./chunk-BEHGVU4W.js";
20
- import "./chunk-3BJQYXTL.js";
19
+ import "./chunk-DZURML2D.js";
20
+ import "./chunk-DNJUSFBL.js";
21
21
  import "./chunk-T7E75EKA.js";
22
- import "./chunk-GHOB245Q.js";
23
- import "./chunk-HWJW3CPO.js";
22
+ import "./chunk-YROGUPOZ.js";
24
23
  export {
25
24
  ensureCodexTrustAfterReapply,
26
25
  inspectConfiguredManagedProviders,