@pushary/agent-hooks 0.85.2 → 0.86.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 (55) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/bin/pushary-bell-hook.js +4 -4
  3. package/dist/bin/pushary-bell.js +5 -5
  4. package/dist/bin/pushary-claude.js +7 -7
  5. package/dist/bin/pushary-clean.js +9 -8
  6. package/dist/bin/pushary-codex-bridge.js +5 -5
  7. package/dist/bin/pushary-codex-hook.js +4 -4
  8. package/dist/bin/pushary-codex.js +3 -3
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-daemon.js +4 -4
  11. package/dist/bin/pushary-disconnect.js +7 -6
  12. package/dist/bin/pushary-doctor.js +11 -11
  13. package/dist/bin/pushary-gemini-bridge.js +5 -5
  14. package/dist/bin/pushary-gemini-hook.js +4 -4
  15. package/dist/bin/pushary-hook.js +6 -8
  16. package/dist/bin/pushary-login.js +4 -4
  17. package/dist/bin/pushary-logout.js +4 -4
  18. package/dist/bin/pushary-mode.js +3 -3
  19. package/dist/bin/pushary-notification-hook.js +3 -3
  20. package/dist/bin/pushary-permission-denied-hook.js +6 -8
  21. package/dist/bin/pushary-permission-hook.js +6 -8
  22. package/dist/bin/pushary-post-hook.js +3 -3
  23. package/dist/bin/pushary-prompt-hook.js +3 -3
  24. package/dist/bin/pushary-session-end-hook.js +3 -3
  25. package/dist/bin/pushary-session-start-hook.js +3 -3
  26. package/dist/bin/pushary-setup.js +20 -19
  27. package/dist/bin/pushary-stats.js +2 -2
  28. package/dist/bin/pushary-status.js +5 -5
  29. package/dist/bin/pushary-stop-hook.js +3 -3
  30. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  31. package/dist/bin/pushary-upgrade.js +10 -9
  32. package/dist/bin/pushary-wait.js +3 -3
  33. package/dist/{chunk-Q4URN3E7.js → chunk-2255DZ74.js} +1 -1
  34. package/dist/{chunk-GY3I353G.js → chunk-2C6B6V2U.js} +7 -3
  35. package/dist/{chunk-LSB6PFLN.js → chunk-2TZCZ7CL.js} +1 -1
  36. package/dist/{chunk-DPDUB4XF.js → chunk-33T5OOKP.js} +11 -5
  37. package/dist/{chunk-GM74DUMS.js → chunk-5EUZYWID.js} +1 -1
  38. package/dist/{chunk-CWV4CMB6.js → chunk-7C5CJC3H.js} +1 -1
  39. package/dist/{chunk-KZIGJLOX.js → chunk-7G5VO76N.js} +1 -1
  40. package/dist/chunk-ATBKJNWS.js +19 -0
  41. package/dist/{chunk-K337LIBD.js → chunk-CY4TZK5O.js} +5 -0
  42. package/dist/{chunk-JBLRZGTT.js → chunk-FOY5FISQ.js} +1 -1
  43. package/dist/{chunk-XJDL3MUE.js → chunk-GK4CED6Z.js} +12 -18
  44. package/dist/{chunk-PKUNB3SN.js → chunk-MNQI6ZZU.js} +1 -1
  45. package/dist/{chunk-4YMWLMAP.js → chunk-MTKR4YLW.js} +1 -1
  46. package/dist/{chunk-NTIUQB3E.js → chunk-N52CIK47.js} +1 -1
  47. package/dist/{chunk-LCA2EQTK.js → chunk-PZR4XMFU.js} +1 -1
  48. package/dist/{chunk-JT5NQQCE.js → chunk-Q7N3XGWM.js} +2 -2
  49. package/dist/{chunk-4U2YRHNF.js → chunk-QW27ZFA4.js} +2 -2
  50. package/dist/{chunk-Y7E2MC3G.js → chunk-S3UD7O35.js} +51 -6
  51. package/dist/{chunk-KJW6NKF7.js → chunk-VPJAAGD4.js} +7 -3
  52. package/dist/{chunk-LXWISFF3.js → chunk-VZNDR3Z7.js} +1 -1
  53. package/dist/{chunk-T54OT7IY.js → chunk-XPAGN3SC.js} +1 -1
  54. package/dist/src/index.js +6 -8
  55. package/package.json +1 -1
@@ -1,9 +1,14 @@
1
+ import {
2
+ PRETOOLUSE_GATED_TOOLS,
3
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
+ } from "./chunk-ATBKJNWS.js";
1
5
  import {
2
6
  guardBinary
3
7
  } from "./chunk-HSGEEBBL.js";
4
8
  import {
5
- HOOK_BUDGETS
6
- } from "./chunk-K337LIBD.js";
9
+ HOOK_BUDGETS,
10
+ HOOK_LOCATOR_BINARY
11
+ } from "./chunk-CY4TZK5O.js";
7
12
 
8
13
  // src/claude-config.ts
9
14
  import { join } from "path";
@@ -21,21 +26,8 @@ var compareVersion = (a, b) => {
21
26
  };
22
27
  var atLeast = (version, floor) => compareVersion(version, floor) >= 0;
23
28
 
24
- // src/claude-tools.ts
25
- var PRETOOLUSE_GATED_TOOLS = [
26
- "Bash",
27
- "Write",
28
- "Edit",
29
- "MultiEdit",
30
- "NotebookEdit",
31
- "PowerShell",
32
- "Monitor"
33
- ];
34
- var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"];
35
- var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"]);
36
-
37
29
  // src/claude-events.ts
38
- var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "AskUserQuestion"].join("|");
30
+ var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS].join("|");
39
31
  var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
40
32
  var ESTABLISHED = [1, 0, 0];
41
33
  var OBSERVED = [2, 0, 0];
@@ -72,7 +64,10 @@ var ensureRecord = (target, key) => {
72
64
  return created;
73
65
  };
74
66
  var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
75
- var HOOK_BINARIES = new Set(CLAUDE_HOOK_EVENTS.map((hook) => hook.binary));
67
+ var HOOK_BINARIES = /* @__PURE__ */ new Set([
68
+ ...CLAUDE_HOOK_EVENTS.map((hook) => hook.binary),
69
+ HOOK_LOCATOR_BINARY
70
+ ]);
76
71
  var namesPusharyBinary = (command) => command.split(/[\s/\\"']+/).some((segment) => HOOK_BINARIES.has(segment.replace(/\.(cmd|exe)$/i, "")));
77
72
  var isPusharyHook = (entry2) => {
78
73
  const hooks = asRecord(entry2)?.hooks;
@@ -178,7 +173,6 @@ var removePusharySettings = (settings) => {
178
173
  export {
179
174
  parseVersion,
180
175
  compareVersion,
181
- PRETOOLUSE_HANDLED_TOOLS,
182
176
  addClaudeMcpServer,
183
177
  removeClaudeMcpServers,
184
178
  addPusharyToolPermissions,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-K337LIBD.js";
3
+ } from "./chunk-CY4TZK5O.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-KJW6NKF7.js";
10
+ } from "./chunk-VPJAAGD4.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-LSB6PFLN.js";
4
+ } from "./chunk-2TZCZ7CL.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-BSZYIAZL.js";
12
12
  import {
13
13
  reportedInstallSource
14
- } from "./chunk-CWV4CMB6.js";
14
+ } from "./chunk-7C5CJC3H.js";
15
15
 
16
16
  // src/reach.ts
17
17
  var reachVerdict = (channels) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hasGeminiHooks
3
- } from "./chunk-GY3I353G.js";
3
+ } from "./chunk-2C6B6V2U.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-KJW6NKF7.js";
6
+ } from "./chunk-VPJAAGD4.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  codexHomeFrom
3
- } from "./chunk-KJW6NKF7.js";
3
+ } from "./chunk-VPJAAGD4.js";
4
4
 
5
5
  // src/vscode-config.ts
6
6
  import { homedir } from "os";
@@ -253,7 +253,7 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
253
253
 
254
254
  - When you need the user to make a decision or answer a clarifying question, call \`ask_user\` instead of guessing or stalling. Use type "confirm" for yes/no, "select" for a fixed set of options, and "input" for free text. \`ask_user\` blocks until the user replies or it times out, so you do not need a separate wait step.
255
255
  - Before any risky or irreversible step (deleting files, force pushing, spending money, sending external messages), call \`ask_user\` with type "confirm" and wait for approval.
256
- - Before starting a multi-step run that will change several files, call \`propose_scope\` once with the paths you expect to change, anything you promise not to touch, and what done means. The user agrees to the boundary in one tap, and after that only work outside it needs approving. Skip it for a single quick edit, and do not propose a new scope mid-run just to widen one: if you need to go outside the agreed scope, do the work and let the approval that follows widen it.
256
+ - Before starting a multi-step run that will change several files, call \`propose_scope\` once with the paths you expect to change, anything you promise not to touch, and what done means. The user agrees to the boundary in one tap. A ratified boundary NARROWS what may proceed without asking, it does not pre-approve work inside it, so an action that would normally need approval still needs it. Skip it for a single quick edit, and do not propose a new scope mid-run just to widen one: if you need to go outside the agreed scope, do the work and let the approval that follows widen it.
257
257
  - When you are blocked, stuck, or hit an error you cannot resolve on your own, call \`send_notification\` so the user knows, and call \`ask_user\` if you need a decision to continue.
258
258
  - When a task that took several steps finishes, call \`send_notification\` so the user knows it is done. Always pass \`context\` with a \`type\` of "task_complete", "error" or "info": it is what tells Pushary this is a task update, so the user's own setting for where updates land can route it.
259
259
  - Use \`cancel_question\` to retract a pending question once it is no longer needed.
@@ -1,10 +1,11 @@
1
1
  import {
2
- PRETOOLUSE_HANDLED_TOOLS
3
- } from "./chunk-XJDL3MUE.js";
2
+ PRETOOLUSE_HANDLED_TOOLS,
3
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
+ } from "./chunk-ATBKJNWS.js";
4
5
  import {
5
6
  isGatingMoment,
6
7
  recordKeylessMoment
7
- } from "./chunk-PKUNB3SN.js";
8
+ } from "./chunk-MNQI6ZZU.js";
8
9
  import {
9
10
  denyReasonFrom,
10
11
  isDeferAnswer
@@ -28,7 +29,7 @@ import {
28
29
  sendNotification,
29
30
  throttlePass,
30
31
  waitForAnswer
31
- } from "./chunk-DPDUB4XF.js";
32
+ } from "./chunk-33T5OOKP.js";
32
33
  import {
33
34
  getMachineId
34
35
  } from "./chunk-RN3NOEJF.js";
@@ -45,7 +46,7 @@ import {
45
46
  hookWaitDeadline,
46
47
  parseAgentQuestionAnswers,
47
48
  resolveGate
48
- } from "./chunk-K337LIBD.js";
49
+ } from "./chunk-CY4TZK5O.js";
49
50
 
50
51
  // src/decision-episode.ts
51
52
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -448,6 +449,47 @@ var handleAskUserQuestion = async (apiKey, input) => {
448
449
  if (!answers) return void 0;
449
450
  return allowWithInput({ ...input.tool_input ?? {}, answers });
450
451
  };
452
+ var PLAN_APPROVE = "Approve, start coding";
453
+ var PLAN_KEEP_PLANNING = "Keep planning";
454
+ var handleExitPlanMode = async (apiKey, input) => {
455
+ const toolInput = input.tool_input ?? {};
456
+ const projectName = basename(input.cwd ?? process.cwd());
457
+ let result;
458
+ try {
459
+ result = await askUser(apiKey, {
460
+ question: `Approve the plan and start coding in ${projectName}?`,
461
+ type: "select",
462
+ options: [PLAN_APPROVE, PLAN_KEEP_PLANNING],
463
+ context: `Your agent finished planning in ${projectName} and is waiting to start.`,
464
+ agentName: `Claude Code - ${projectName}`,
465
+ sessionId: input.session_id,
466
+ machineId: getMachineId(),
467
+ toolName: "ExitPlanMode",
468
+ action: deriveAction("ExitPlanMode", toolInput),
469
+ // The plan markdown itself, through the same redaction and cap as any diff,
470
+ // so approving from a lock screen is a real decision and not a blind yes.
471
+ actionBody: deriveActionBody("ExitPlanMode", toolInput),
472
+ intent: readLastPrompt(input.session_id ?? DEFAULT_SESSION) ?? (input.transcript_path ? readLastUserPrompt(input.transcript_path) : void 0),
473
+ blocker: "Waiting for you to approve the plan"
474
+ });
475
+ } catch {
476
+ return void 0;
477
+ }
478
+ if (result.suppressed || result.noDevices) {
479
+ await cancelQuestion(apiKey, result.correlationId).catch(() => {
480
+ });
481
+ return void 0;
482
+ }
483
+ const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
484
+ const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
485
+ const value = answer.answered ? (answer.value ?? "").trim() : "";
486
+ if (!value || isDeferAnswer(answer.value)) {
487
+ savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
488
+ return void 0;
489
+ }
490
+ if (value === PLAN_APPROVE) return allow();
491
+ return deny("Plan declined from the phone. Ask what to change before coding.");
492
+ };
451
493
  var handleKeyless = (input) => {
452
494
  try {
453
495
  if (isGatingMoment(input.tool_name, input.tool_input ?? {})) {
@@ -493,6 +535,9 @@ var handlePreToolUse = async (input) => {
493
535
  return handleAskUserQuestion(apiKey, input);
494
536
  }
495
537
  if (verdict.kind === "deny") return deny(verdict.reason);
538
+ if (input.tool_name === "ExitPlanMode") {
539
+ return handleExitPlanMode(apiKey, input);
540
+ }
496
541
  if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
497
542
  return void 0;
498
543
  }
@@ -524,7 +569,7 @@ var toPermissionRequestOutput = (hookOutput) => {
524
569
  };
525
570
  var handlePermissionRequest = async (input) => {
526
571
  if (PRETOOLUSE_HANDLED_TOOLS.has(input.tool_name)) {
527
- const reclaimDeferredExec = input.tool_name !== "AskUserQuestion" && shouldDeferToNativeMode(input.permission_mode, input.tool_name);
572
+ const reclaimDeferredExec = !PRETOOLUSE_PHONE_ANSWERED_TOOLS.includes(input.tool_name) && shouldDeferToNativeMode(input.permission_mode, input.tool_name);
528
573
  if (!reclaimDeferredExec) return void 0;
529
574
  }
530
575
  if (input.tool_name.startsWith("mcp__pushary__")) return void 0;
@@ -1,6 +1,7 @@
1
1
  import {
2
- HOOK_BUDGETS
3
- } from "./chunk-K337LIBD.js";
2
+ HOOK_BUDGETS,
3
+ HOOK_LOCATOR_BINARY
4
+ } from "./chunk-CY4TZK5O.js";
4
5
 
5
6
  // src/codex-config.ts
6
7
  import { createHash } from "crypto";
@@ -63,7 +64,10 @@ var readCodexMcpAuth = (config) => {
63
64
  var isPusharyCodexHook = (entry) => {
64
65
  const hooks = asRecord(entry)?.hooks;
65
66
  if (!Array.isArray(hooks)) return false;
66
- return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(CODEX_HOOK_BINARY));
67
+ return hooks.some((hook) => {
68
+ const command = String(asRecord(hook)?.command ?? "");
69
+ return command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
70
+ });
67
71
  };
68
72
  var addCodexHooks = (config, command) => {
69
73
  const hooks = ensureRecord(config, "hooks");
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-KJW6NKF7.js";
3
+ } from "./chunk-VPJAAGD4.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  redactSecrets
6
- } from "./chunk-K337LIBD.js";
6
+ } from "./chunk-CY4TZK5O.js";
7
7
 
8
8
  // src/crypto.ts
9
9
  import nacl from "tweetnacl";
package/dist/src/index.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-Y7E2MC3G.js";
4
- import "../chunk-XJDL3MUE.js";
5
- import "../chunk-HSGEEBBL.js";
6
- import "../chunk-VT4IVERX.js";
7
- import "../chunk-PKUNB3SN.js";
3
+ } from "../chunk-S3UD7O35.js";
4
+ import "../chunk-ATBKJNWS.js";
5
+ import "../chunk-MNQI6ZZU.js";
8
6
  import "../chunk-YHG74UFF.js";
9
7
  import {
10
8
  askUser,
@@ -17,11 +15,11 @@ import {
17
15
  handleStop,
18
16
  reportEvent,
19
17
  waitForAnswer
20
- } from "../chunk-DPDUB4XF.js";
18
+ } from "../chunk-33T5OOKP.js";
21
19
  import "../chunk-RN3NOEJF.js";
22
20
  import "../chunk-BSZYIAZL.js";
23
21
  import "../chunk-SAF6HGAA.js";
24
- import "../chunk-CWV4CMB6.js";
22
+ import "../chunk-7C5CJC3H.js";
25
23
  import "../chunk-7QLSKOSU.js";
26
24
  import "../chunk-KZERVKTD.js";
27
25
  import {
@@ -30,7 +28,7 @@ import {
30
28
  } from "../chunk-2UMNXADU.js";
31
29
  import {
32
30
  resolvePolicy
33
- } from "../chunk-K337LIBD.js";
31
+ } from "../chunk-CY4TZK5O.js";
34
32
  export {
35
33
  askUser,
36
34
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.85.2",
3
+ "version": "0.86.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",