@pushary/agent-hooks 0.83.2 → 0.84.1

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 (64) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/bin/pushary-bell-hook.js +5 -5
  3. package/dist/bin/pushary-bell.js +15 -14
  4. package/dist/bin/pushary-claude.js +93 -143
  5. package/dist/bin/pushary-clean.js +13 -12
  6. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  7. package/dist/bin/pushary-codex-bridge.js +349 -0
  8. package/dist/bin/pushary-codex-hook.js +18 -14
  9. package/dist/bin/pushary-codex.js +7 -7
  10. package/dist/bin/pushary-connect.js +13 -12
  11. package/dist/bin/pushary-daemon.js +331 -41
  12. package/dist/bin/pushary-doctor.js +48 -25
  13. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  14. package/dist/bin/pushary-gemini-bridge.js +299 -0
  15. package/dist/bin/pushary-gemini-hook.js +28 -21
  16. package/dist/bin/pushary-hook.js +14 -13
  17. package/dist/bin/pushary-login.js +15 -14
  18. package/dist/bin/pushary-logout.js +12 -11
  19. package/dist/bin/pushary-mode.js +10 -9
  20. package/dist/bin/pushary-notification-hook.js +5 -5
  21. package/dist/bin/pushary-permission-denied-hook.js +9 -9
  22. package/dist/bin/pushary-permission-hook.js +9 -9
  23. package/dist/bin/pushary-post-hook.js +5 -5
  24. package/dist/bin/pushary-prompt-hook.js +5 -5
  25. package/dist/bin/pushary-session-end-hook.js +5 -5
  26. package/dist/bin/pushary-session-start-hook.js +5 -5
  27. package/dist/bin/pushary-setup.js +42 -36
  28. package/dist/bin/pushary-stats.js +6 -5
  29. package/dist/bin/pushary-status.js +17 -16
  30. package/dist/bin/pushary-stop-hook.js +5 -5
  31. package/dist/bin/pushary-stopfailure-hook.js +5 -5
  32. package/dist/bin/pushary-suggestions.js +7 -6
  33. package/dist/bin/pushary-upgrade.js +18 -17
  34. package/dist/bin/pushary-wait.js +10 -9
  35. package/dist/bin/pushary.js +8 -6
  36. package/dist/{chunk-SAA3VBDK.js → chunk-437T77TY.js} +3 -3
  37. package/dist/{chunk-U27BZWFT.js → chunk-6LC4WSCL.js} +1 -1
  38. package/dist/{chunk-7WWZPII5.js → chunk-7PZPJSWI.js} +1 -1
  39. package/dist/{chunk-4QQOKEOS.js → chunk-BLDDXHHA.js} +6 -6
  40. package/dist/{chunk-7PO2VEA4.js → chunk-CCCJ6BPW.js} +40 -34
  41. package/dist/chunk-CCH775Y6.js +343 -0
  42. package/dist/chunk-DINDL2CF.js +45 -0
  43. package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
  44. package/dist/chunk-ERYKGH4J.js +140 -0
  45. package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
  46. package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
  47. package/dist/chunk-GCKAWHFP.js +124 -0
  48. package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
  49. package/dist/chunk-ILB6UOAF.js +192 -0
  50. package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
  51. package/dist/{chunk-WDRRULBH.js → chunk-K7CSRGKZ.js} +4 -4
  52. package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
  53. package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
  54. package/dist/{chunk-YWRJGYUH.js → chunk-MQMPG5X4.js} +28 -2
  55. package/dist/{chunk-3AGM3TDM.js → chunk-NJ7JT26G.js} +1 -1
  56. package/dist/chunk-NRGRUOAR.js +25 -0
  57. package/dist/{chunk-EHLTWCDZ.js → chunk-SEAHGHCE.js} +1 -1
  58. package/dist/{chunk-NDC3TBHI.js → chunk-UKSQWTBH.js} +1 -1
  59. package/dist/{chunk-UVAC2KHH.js → chunk-UXWEE3QI.js} +1 -1
  60. package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
  61. package/dist/{chunk-DHAZ6SOT.js → chunk-YR5TSPXB.js} +81 -25
  62. package/dist/src/index.d.ts +6 -3
  63. package/dist/src/index.js +9 -9
  64. package/package.json +3 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HOOK_BUDGETS
3
- } from "./chunk-YWRJGYUH.js";
3
+ } from "./chunk-MQMPG5X4.js";
4
4
 
5
5
  // src/codex-config.ts
6
6
  import { createHash } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HOOK_BUDGETS
3
- } from "./chunk-YWRJGYUH.js";
3
+ } from "./chunk-MQMPG5X4.js";
4
4
 
5
5
  // src/claude-config.ts
6
6
  import { join } from "path";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  codexHomeFrom
3
- } from "./chunk-EHLTWCDZ.js";
3
+ } from "./chunk-SEAHGHCE.js";
4
4
 
5
5
  // src/vscode-config.ts
6
6
  import { homedir } from "os";
@@ -1,7 +1,7 @@
1
1
  // src/npm.ts
2
- import { execSync } from "child_process";
3
- import { existsSync } from "fs";
4
- import { join } from "path";
2
+ import { execFileSync, execSync } from "child_process";
3
+ import { existsSync, readFileSync, realpathSync } from "fs";
4
+ import { dirname, join } from "path";
5
5
  var cleanNpmEnv = () => {
6
6
  const env = {};
7
7
  for (const [key, value] of Object.entries(process.env)) {
@@ -33,6 +33,50 @@ var resolveGlobalBinary = (name) => {
33
33
  return void 0;
34
34
  }
35
35
  };
36
+ var resolvePathBinary = (name, platform = process.platform) => {
37
+ try {
38
+ return execFileSync(platform === "win32" ? "where" : "which", [name], {
39
+ encoding: "utf8",
40
+ stdio: "pipe",
41
+ timeout: 5e3
42
+ }).split(/\r?\n/, 1)[0]?.trim() || void 0;
43
+ } catch {
44
+ return void 0;
45
+ }
46
+ };
47
+ var resolveBinary = (name) => resolveGlobalBinary(name) ?? resolvePathBinary(name);
48
+ var resolvePackageVersionFromBinary = (binary, packageName) => {
49
+ try {
50
+ let dir = dirname(realpathSync(binary));
51
+ const packagePath = packageName.split("/");
52
+ for (let depth = 0; depth < 8; depth++) {
53
+ for (const manifest of [
54
+ join(dir, "package.json"),
55
+ join(dir, "node_modules", ...packagePath, "package.json")
56
+ ]) {
57
+ try {
58
+ const value = JSON.parse(readFileSync(manifest, "utf8"));
59
+ if (value.name === packageName && typeof value.version === "string") return value.version;
60
+ } catch {
61
+ }
62
+ }
63
+ const parent = dirname(dir);
64
+ if (parent === dir) break;
65
+ dir = parent;
66
+ }
67
+ } catch {
68
+ }
69
+ return void 0;
70
+ };
71
+ var resolveGlobalPackageVersion = (name) => {
72
+ try {
73
+ const result = JSON.parse(execNpm("list -g --depth=0 --json --no-workspaces", { timeout: 5e3 }).toString());
74
+ const value = result.dependencies?.[name]?.version;
75
+ return typeof value === "string" ? value : void 0;
76
+ } catch {
77
+ return void 0;
78
+ }
79
+ };
36
80
  var resolveGlobalBinDir = (probe) => {
37
81
  if (process.platform === "win32") return void 0;
38
82
  const resolved = resolveGlobalBinary(probe);
@@ -61,6 +105,9 @@ export {
61
105
  npmErrorMessage,
62
106
  execNpm,
63
107
  resolveGlobalBinary,
108
+ resolveBinary,
109
+ resolvePackageVersionFromBinary,
110
+ resolveGlobalPackageVersion,
64
111
  resolveGlobalBinDir,
65
112
  quoteIfNeeded,
66
113
  installGlobally
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  PRETOOLUSE_HANDLED_TOOLS
3
- } from "./chunk-NDC3TBHI.js";
3
+ } from "./chunk-UKSQWTBH.js";
4
4
  import {
5
5
  isGatingMoment,
6
6
  recordKeylessMoment
7
- } from "./chunk-3AGM3TDM.js";
7
+ } from "./chunk-NJ7JT26G.js";
8
8
  import {
9
9
  denyReasonFrom,
10
10
  isDeferAnswer,
11
11
  resolveGate
12
- } from "./chunk-4QQOKEOS.js";
12
+ } from "./chunk-BLDDXHHA.js";
13
13
  import {
14
14
  DEFAULT_SESSION,
15
15
  askUser,
@@ -29,13 +29,7 @@ import {
29
29
  sendNotification,
30
30
  throttlePass,
31
31
  waitForAnswer
32
- } from "./chunk-7PO2VEA4.js";
33
- import {
34
- buildDecisionEpisodeFeatures,
35
- effectiveWaitSeconds,
36
- hookWaitClamped,
37
- hookWaitDeadline
38
- } from "./chunk-YWRJGYUH.js";
32
+ } from "./chunk-CCCJ6BPW.js";
39
33
  import {
40
34
  getMachineId
41
35
  } from "./chunk-RN3NOEJF.js";
@@ -43,6 +37,13 @@ import {
43
37
  getApiKey,
44
38
  getBaseUrl
45
39
  } from "./chunk-2UMNXADU.js";
40
+ import {
41
+ buildDecisionEpisodeFeatures,
42
+ effectiveWaitSeconds,
43
+ hookWaitClamped,
44
+ hookWaitDeadline,
45
+ parseAgentQuestionAnswers
46
+ } from "./chunk-MQMPG5X4.js";
46
47
 
47
48
  // src/decision-episode.ts
48
49
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -271,6 +272,7 @@ var dispatchModeHandler = async (apiKey, input, toolPolicy, machineId, trainingC
271
272
  action: deriveAction(input.tool_name, input.tool_input),
272
273
  blocker: scopeReason ?? deriveBlocker(toolPolicy.mode),
273
274
  actionBody: deriveActionBody(input.tool_name, input.tool_input),
275
+ repoKey: repoKeyFor(input.cwd),
274
276
  // Only when the breach is what caused the question, so an ordinary approval
275
277
  // can never silently widen a scope.
276
278
  scopePath: scopeReason ? scopePath : void 0,
@@ -352,10 +354,25 @@ var shouldDeferToNativeMode = (permissionMode, toolName) => {
352
354
  var MAX_PHONE_QUESTIONS = 4;
353
355
  var parseQuestion = (raw) => {
354
356
  const q = raw;
355
- if (!q || typeof q.question !== "string" || q.multiSelect === true) return void 0;
356
- const labels = Array.isArray(q.options) ? q.options.map((o) => o && typeof o === "object" ? o.label : void 0).filter((l) => typeof l === "string" && l.length > 0) : [];
357
+ if (!q || typeof q.question !== "string") return void 0;
358
+ const options = Array.isArray(q.options) ? q.options.flatMap((option) => {
359
+ if (!option || typeof option !== "object") return [];
360
+ const value = option;
361
+ if (typeof value.label !== "string" || value.label.length === 0) return [];
362
+ return [{
363
+ label: value.label,
364
+ ...typeof value.description === "string" ? { description: value.description } : {}
365
+ }];
366
+ }) : [];
367
+ const labels = options.map((option) => option.label);
357
368
  if (labels.length < 2) return void 0;
358
- return { question: q.question, header: typeof q.header === "string" ? q.header : void 0, labels };
369
+ return {
370
+ question: q.question,
371
+ header: typeof q.header === "string" ? q.header : void 0,
372
+ multiSelect: q.multiSelect === true,
373
+ options,
374
+ labels
375
+ };
359
376
  };
360
377
  var parseQuestions = (toolInput) => {
361
378
  const questions = toolInput.questions;
@@ -364,20 +381,49 @@ var parseQuestions = (toolInput) => {
364
381
  const parsed = [];
365
382
  for (const raw of questions) {
366
383
  const one = parseQuestion(raw);
367
- if (!one) return void 0;
384
+ if (!one || parsed.some((question) => question.question === one.question)) return void 0;
368
385
  parsed.push(one);
369
386
  }
370
387
  return parsed;
371
388
  };
372
- var questionContext = (q, index, total, project) => {
373
- const subject = q.header ? `${q.header}: your agent is asking in ${project}` : `Your agent is asking in ${project}`;
389
+ var questionContext = (question, index, total, project) => {
390
+ const subject = question.header ? `${question.header}: your agent is asking in ${project}` : `Your agent is asking in ${project}`;
374
391
  return total > 1 ? `${subject} (${index + 1} of ${total})` : subject;
375
392
  };
376
- var handleAskUserQuestion = async (apiKey, input) => {
377
- const parsed = parseQuestions(input.tool_input ?? {});
378
- if (!parsed) return void 0;
379
- const projectName = basename(input.cwd ?? process.cwd());
380
- const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
393
+ var encodeAnswer = (question, value) => question.multiSelect ? JSON.stringify([value]) : value;
394
+ var askAsOneCard = async (apiKey, input, parsed, projectName, deadline) => {
395
+ const only = parsed[0];
396
+ let result;
397
+ try {
398
+ result = await askUser(apiKey, {
399
+ question: only.question,
400
+ type: "select",
401
+ options: [...only.labels],
402
+ questions: parsed.map(({ labels: _, ...question }) => question),
403
+ context: questionContext(only, 0, 1, projectName),
404
+ agentName: `Claude Code - ${projectName}`,
405
+ sessionId: input.session_id,
406
+ machineId: getMachineId(),
407
+ toolName: "AskUserQuestion",
408
+ toolUseId: input.tool_use_id
409
+ });
410
+ } catch {
411
+ return void 0;
412
+ }
413
+ if (result.suppressed || result.noDevices) {
414
+ await cancelQuestion(apiKey, result.correlationId).catch(() => {
415
+ });
416
+ return void 0;
417
+ }
418
+ const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
419
+ const values = answer.answered && answer.value ? parseAgentQuestionAnswers(parsed, answer.value) : null;
420
+ if (!values || isDeferAnswer(answer.value)) {
421
+ savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
422
+ return void 0;
423
+ }
424
+ return values;
425
+ };
426
+ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline) => {
381
427
  const answers = {};
382
428
  for (const [index, question] of parsed.entries()) {
383
429
  let result;
@@ -410,8 +456,17 @@ var handleAskUserQuestion = async (apiKey, input) => {
410
456
  savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
411
457
  return void 0;
412
458
  }
413
- answers[question.question] = value;
459
+ answers[question.question] = encodeAnswer(question, value);
414
460
  }
461
+ return answers;
462
+ };
463
+ var handleAskUserQuestion = async (apiKey, input) => {
464
+ const parsed = parseQuestions(input.tool_input ?? {});
465
+ if (!parsed) return void 0;
466
+ const projectName = basename(input.cwd ?? process.cwd());
467
+ const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
468
+ const answers = parsed.length === 1 ? await askAsOneCard(apiKey, input, parsed, projectName, deadline) : await askQuestionByQuestion(apiKey, input, parsed, projectName, deadline);
469
+ if (!answers) return void 0;
415
470
  return allowWithInput({ ...input.tool_input ?? {}, answers });
416
471
  };
417
472
  var handleKeyless = (input) => {
@@ -438,7 +493,8 @@ var claudeGateInput = (modeState, config, input) => {
438
493
  toolInputs: [input.tool_input ?? {}],
439
494
  scopePaths: scopePath ? [scopePath] : [],
440
495
  cwd: input.cwd,
441
- repoKey: repoKeyFor(input.cwd)
496
+ repoKey: repoKeyFor(input.cwd),
497
+ sessionId: input.session_id
442
498
  };
443
499
  };
444
500
  var handlePreToolUse = async (input) => {
@@ -451,7 +507,7 @@ var handlePreToolUse = async (input) => {
451
507
  }
452
508
  try {
453
509
  const modeState = await fetchModeState(apiKey, input.session_id);
454
- const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
510
+ const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true, input.session_id);
455
511
  const verdict = resolveGate(claudeGateInput(modeState, policy, input));
456
512
  if (verdict.kind === "kill") return deny(verdict.reason);
457
513
  if (input.tool_name === "AskUserQuestion") {
@@ -509,7 +565,7 @@ var handlePermissionRequest = async (input) => {
509
565
  }
510
566
  try {
511
567
  const modeState = await fetchModeState(apiKey, input.session_id);
512
- const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
568
+ const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true, input.session_id);
513
569
  const verdict = resolveGate(claudeGateInput(modeState, policy, input));
514
570
  switch (verdict.kind) {
515
571
  case "kill":
@@ -1,4 +1,4 @@
1
- import { PolicyConfig, ApprovalMode, ToolPolicy, ScopeContract } from '@pushary/contracts';
1
+ import { PolicyConfig, ApprovalMode, ToolPolicy, ScopeContract, AgentQuestion } from '@pushary/contracts';
2
2
  export { ApprovalMode, PolicyConfig, ToolPolicy } from '@pushary/contracts';
3
3
 
4
4
  interface ToolInput {
@@ -27,9 +27,9 @@ interface ReceiptMeta {
27
27
  ok: boolean;
28
28
  }
29
29
 
30
- declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean) => Promise<PolicyConfig>;
30
+ declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean, sessionId?: string) => Promise<PolicyConfig>;
31
31
  type AutoResolveOrigin = 'safe_readonly' | 'policy_timeout';
32
- declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown>, cwd?: string, repoKey?: string) => ToolPolicy;
32
+ declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown>, cwd?: string, repoKey?: string, sessionId?: string) => ToolPolicy;
33
33
  interface ModeState {
34
34
  readonly mode: ApprovalMode | null;
35
35
  readonly kill: boolean;
@@ -76,6 +76,7 @@ interface AgentEvent {
76
76
  interface ReportEventOptions {
77
77
  maxAttempts?: number;
78
78
  timeoutMs?: number;
79
+ requireOk?: boolean;
79
80
  /**
80
81
  * Authenticate with this key instead of the ambient one.
81
82
  *
@@ -127,12 +128,14 @@ interface AskUserParams {
127
128
  question: string;
128
129
  type?: 'confirm' | 'select' | 'input';
129
130
  options?: string[];
131
+ questions?: AgentQuestion[];
130
132
  context?: string;
131
133
  agentName?: string;
132
134
  sessionId?: string;
133
135
  machineId?: string;
134
136
  toolName?: string;
135
137
  toolTarget?: string;
138
+ repoKey?: string;
136
139
  intent?: string;
137
140
  action?: string;
138
141
  blocker?: string;
package/dist/src/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-DHAZ6SOT.js";
4
- import "../chunk-NDC3TBHI.js";
5
- import "../chunk-3AGM3TDM.js";
6
- import "../chunk-4QQOKEOS.js";
3
+ } from "../chunk-YR5TSPXB.js";
4
+ import "../chunk-UKSQWTBH.js";
5
+ import "../chunk-NJ7JT26G.js";
6
+ import "../chunk-BLDDXHHA.js";
7
7
  import {
8
8
  askUser,
9
9
  cancelQuestion,
@@ -16,18 +16,18 @@ import {
16
16
  reportEvent,
17
17
  resolvePolicy,
18
18
  waitForAnswer
19
- } from "../chunk-7PO2VEA4.js";
19
+ } from "../chunk-CCCJ6BPW.js";
20
+ import "../chunk-RN3NOEJF.js";
20
21
  import "../chunk-BSZYIAZL.js";
21
22
  import "../chunk-SAF6HGAA.js";
22
- import "../chunk-SAA3VBDK.js";
23
+ import "../chunk-437T77TY.js";
23
24
  import "../chunk-7QLSKOSU.js";
24
- import "../chunk-YWRJGYUH.js";
25
- import "../chunk-RN3NOEJF.js";
25
+ import "../chunk-KZERVKTD.js";
26
26
  import {
27
27
  getApiKey,
28
28
  getBaseUrl
29
29
  } from "../chunk-2UMNXADU.js";
30
- import "../chunk-KZERVKTD.js";
30
+ import "../chunk-MQMPG5X4.js";
31
31
  export {
32
32
  askUser,
33
33
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.83.2",
3
+ "version": "0.84.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -51,8 +51,10 @@
51
51
  "pushary-claude": "./dist/bin/pushary-claude.js",
52
52
  "pushary-daemon": "./dist/bin/pushary-daemon.js",
53
53
  "pushary-codex": "./dist/bin/pushary-codex.js",
54
+ "pushary-codex-bridge": "./dist/bin/pushary-codex-bridge.js",
54
55
  "pushary-codex-hook": "./dist/bin/pushary-codex-hook.js",
55
56
  "pushary-gemini-hook": "./dist/bin/pushary-gemini-hook.js",
57
+ "pushary-gemini-bridge": "./dist/bin/pushary-gemini-bridge.js",
56
58
  "pushary-setup": "./dist/bin/pushary-setup.js",
57
59
  "pushary-clean": "./dist/bin/pushary-clean.js",
58
60
  "pushary-status": "./dist/bin/pushary-status.js",