@pushary/agent-hooks 0.69.0 → 0.70.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 (42) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +3 -2
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +34 -5
  4. package/data/vscode-plugin/scripts/pushary-gate.mjs +27 -2
  5. package/dist/bin/pushary-claude.js +2 -2
  6. package/dist/bin/pushary-clean.js +2 -2
  7. package/dist/bin/pushary-codex-hook.js +2 -2
  8. package/dist/bin/pushary-codex.js +1 -1
  9. package/dist/bin/pushary-connect.js +6 -4
  10. package/dist/bin/pushary-daemon.js +3 -3
  11. package/dist/bin/pushary-doctor.js +8 -29
  12. package/dist/bin/pushary-gemini-hook.js +2 -2
  13. package/dist/bin/pushary-hook.js +5 -5
  14. package/dist/bin/pushary-login.js +2 -2
  15. package/dist/bin/pushary-logout.js +2 -2
  16. package/dist/bin/pushary-mode.js +2 -2
  17. package/dist/bin/pushary-notification-hook.js +1 -1
  18. package/dist/bin/pushary-permission-denied-hook.js +3 -3
  19. package/dist/bin/pushary-permission-hook.js +3 -3
  20. package/dist/bin/pushary-post-hook.js +1 -1
  21. package/dist/bin/pushary-prompt-hook.js +1 -1
  22. package/dist/bin/pushary-session-end-hook.js +1 -1
  23. package/dist/bin/pushary-session-start-hook.js +1 -1
  24. package/dist/bin/pushary-setup.js +76 -9
  25. package/dist/bin/pushary-stats.js +2 -2
  26. package/dist/bin/pushary-status.js +4 -4
  27. package/dist/bin/pushary-stop-hook.js +1 -1
  28. package/dist/bin/pushary-stopfailure-hook.js +1 -1
  29. package/dist/bin/pushary-suggestions.js +2 -2
  30. package/dist/bin/pushary-upgrade.js +2 -2
  31. package/dist/bin/pushary-wait.js +2 -2
  32. package/dist/bin/pushary.js +1 -1
  33. package/dist/{chunk-JGH37QRB.js → chunk-7OYGFJYZ.js} +1 -1
  34. package/dist/chunk-B7R4YSAB.js +38 -0
  35. package/dist/{chunk-ATP6UQPH.js → chunk-BBK3TTU2.js} +1 -1
  36. package/dist/{chunk-H46LSQRT.js → chunk-FZIQSDTQ.js} +27 -12
  37. package/dist/{chunk-3USMXNVB.js → chunk-GD275GEX.js} +5 -3
  38. package/dist/{chunk-C5I2RAMT.js → chunk-HU43EZ5Q.js} +2 -2
  39. package/dist/{chunk-P7VBAYQO.js → chunk-MGFZXUUR.js} +32 -4
  40. package/dist/{chunk-YZT2CBC3.js → chunk-NQTRA7H5.js} +1 -1
  41. package/dist/src/index.js +3 -3
  42. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.70.0
4
+
5
+ ### A key you name is the key you get
6
+
7
+ Passing `--key` with a malformed value did not fail. Setup read it as no key at
8
+ all, went looking elsewhere, and used whichever key it found: one already stored
9
+ on the machine, one in your environment, or a brand new one it created by pairing.
10
+ You named a credential and quietly got a different one. It now stops and says the
11
+ key is not valid.
12
+
13
+ ### setup --json no longer stops to ask a question nobody can see
14
+
15
+ Asking for machine-readable output says plainly that no human is watching, but
16
+ setup could still reach an agent-selection prompt. Piped into a script it waited
17
+ for a keypress that was never coming, and the prompt drew itself onto the same
18
+ output a program was trying to read as JSON. It now selects the agents it detects,
19
+ which is exactly what pressing Enter on that prompt would have chosen.
20
+
21
+ `--json` also promises a single object of output, and every failing exit used to
22
+ print nothing at all. A refused key, a cancelled pairing and a crash were
23
+ indistinguishable to anything reading the output. Each now says which it was.
24
+
25
+ ### Setup checks its own work
26
+
27
+ An agent installer that finished without an error was reported as configured,
28
+ even if nothing had been written. Doctor would then contradict setup minutes
29
+ later. Setup now confirms the configuration is really there, using the same check
30
+ doctor uses, so the two cannot disagree.
31
+
32
+ ### connect points at the fix that matches your situation
33
+
34
+ `pushary connect --app` gave one answer for every way a phone can be out of
35
+ reach: open the app and allow notifications. That is right when the app is signed
36
+ in and the permission was declined, and wrong otherwise. A terminal that was
37
+ never paired needs setup. A phone signed in to a colleague's account needs your
38
+ account, not a permission toggle. It now says which applies, and when it cannot
39
+ tell the two apart it says both rather than sounding certain about the wrong one.
40
+
41
+ ### The editor gates say when a proxy is in the way
42
+
43
+ The Cursor and VS Code gates cannot route through a corporate proxy. They still
44
+ hand the decision safely to the editor's own prompt, so nothing is blocked, but
45
+ every approval quietly stopped reaching your phone with no explanation. When a
46
+ proxy is configured they now name it as the likely cause, and on Node 24 or newer
47
+ they name the setting that fixes it.
48
+
49
+ VS Code is also now listed among the supported agents, which it has been since
50
+ its plugin shipped.
51
+
3
52
  ## 0.69.0
4
53
 
5
54
  ### An approval is never granted on a state Pushary could not confirm
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@pushary/agent-hooks.svg)](https://www.npmjs.com/package/@pushary/agent-hooks)
5
5
  [![license](https://img.shields.io/npm/l/@pushary/agent-hooks.svg)](./LICENSE)
6
6
 
7
- Push notifications, questions, and permission approvals for AI coding agents. Get a push when your agent finishes work, answer its questions from your phone, and approve risky commands before they run. Works with Claude Code, Codex, Cursor, Gemini CLI, and Hermes.
7
+ Push notifications, questions, and permission approvals for AI coding agents. Get a push when your agent finishes work, answer its questions from your phone, and approve risky commands before they run. Works with Claude Code, Codex, Cursor, VS Code, Gemini CLI, and Hermes.
8
8
 
9
9
  Your agent froze waiting for a yes. This is how it reaches you.
10
10
 
@@ -53,6 +53,7 @@ npx @pushary/agent-hooks@latest doctor
53
53
  | Cursor | yes | yes | yes |
54
54
  | Gemini CLI | yes | yes | yes |
55
55
  | Hermes | yes | yes | yes |
56
+ | VS Code | yes | yes | yes |
56
57
 
57
58
  Any other MCP client can use notify and ask by pointing at the Pushary MCP endpoint directly.
58
59
 
@@ -62,7 +63,7 @@ Any other MCP client can use notify and ask by pointing at the Pushary MCP endpo
62
63
  asserts this list matches it. Do not edit one without the other. -->
63
64
 
64
65
  ```
65
- setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
66
+ setup Configure Claude Code, Codex, Gemini CLI, Hermes, Cursor, or VS Code with Pushary
66
67
  claude Run Claude Code through Pushary, reachable from your phone even when idle
67
68
  daemon Keep this machine reachable so your phone can START a new session
68
69
  status One screen: which key is in force, whether the server accepts it, what can receive an approval
@@ -271,6 +271,31 @@ const capActionBody = (text) =>
271
271
  text.length <= ACTION_BODY_MAX ? text : `${text.slice(0, ACTION_BODY_MAX - ACTION_BODY_TRUNCATION_MARKER.length)}${ACTION_BODY_TRUNCATION_MARKER}`
272
272
  const deriveActionBody = (command) => capActionBody(redactSecretsDeep(command))
273
273
 
274
+ // ── network diagnosis ────────────────────────────────────────────────────────
275
+ //
276
+ // This gate is a dependency-free .mjs, so unlike the CLI it cannot install
277
+ // undici's EnvHttpProxyAgent and its fetch ignores HTTP_PROXY entirely. Node
278
+ // gained a native equivalent in 24 (NODE_USE_ENV_PROXY), but that is read at
279
+ // startup, so a script cannot switch it on for itself.
280
+ //
281
+ // The gate already fails safe: any error here hands the decision to the editor's
282
+ // own prompt. The cost is therefore not a blocked command, it is SILENCE. On a
283
+ // corporate network every approval quietly stops reaching the phone and the only
284
+ // trace is one stderr line nobody reads. So when a proxy is configured and the
285
+ // network call fails, say which of those two it is.
286
+ export const PROXY_VARS = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy']
287
+
288
+ export const proxyConfigured = (env = process.env) =>
289
+ PROXY_VARS.some(name => (env[name] ?? '').trim() !== '')
290
+
291
+ export const describeNetworkFailure = (error, env = process.env) => {
292
+ const detail = error?.message ?? String(error)
293
+ if (!proxyConfigured(env)) return detail
294
+ const enabled = (env.NODE_USE_ENV_PROXY ?? '').trim() !== ''
295
+ if (enabled) return detail
296
+ return `${detail} (a proxy is set in HTTP_PROXY/HTTPS_PROXY and this hook cannot use it; on Node 24+ set NODE_USE_ENV_PROXY=1 in the editor's environment)`
297
+ }
298
+
274
299
  // ── ask / wait ───────────────────────────────────────────────────────────────
275
300
  // Redacted, not raw. This is the text a human reads on a lock screen and in
276
301
  // Slack, and it used to be the command verbatim: `curl -H "Authorization:
@@ -447,12 +472,16 @@ const main = async () => {
447
472
  return respond(await handlePushFirst(apiKey, command, project, ident, tool.pushFirstSeconds))
448
473
  }
449
474
  } catch (error) {
450
- process.stderr.write(`[pushary-gate] ${error?.message ?? error}\n`)
475
+ process.stderr.write(`[pushary-gate] ${describeNetworkFailure(error)}\n`)
451
476
  return respond(ask())
452
477
  }
453
478
  }
454
479
 
455
- main().catch((error) => {
456
- process.stderr.write(`[pushary-gate] fatal: ${error?.message ?? error}\n`)
457
- respond(ask())
458
- })
480
+ // Guarded like the VS Code gate: importing this file to unit-test its pure
481
+ // helpers must not start a hook run that reads stdin and writes a decision.
482
+ if (!process.env.PUSHARY_GATE_IMPORT) {
483
+ main().catch((error) => {
484
+ process.stderr.write(`[pushary-gate] fatal: ${describeNetworkFailure(error)}\n`)
485
+ respond(ask())
486
+ })
487
+ }
@@ -345,6 +345,31 @@ export const shouldGate = (toolName, toolInput) => {
345
345
  return command
346
346
  }
347
347
 
348
+ // ── network diagnosis ────────────────────────────────────────────────────────
349
+ //
350
+ // This gate is a dependency-free .mjs, so unlike the CLI it cannot install
351
+ // undici's EnvHttpProxyAgent and its fetch ignores HTTP_PROXY entirely. Node
352
+ // gained a native equivalent in 24 (NODE_USE_ENV_PROXY), but that is read at
353
+ // startup, so a script cannot switch it on for itself.
354
+ //
355
+ // The gate already fails safe: any error here hands the decision to the editor's
356
+ // own prompt. The cost is therefore not a blocked command, it is SILENCE. On a
357
+ // corporate network every approval quietly stops reaching the phone and the only
358
+ // trace is one stderr line nobody reads. So when a proxy is configured and the
359
+ // network call fails, say which of those two it is.
360
+ export const PROXY_VARS = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy']
361
+
362
+ export const proxyConfigured = (env = process.env) =>
363
+ PROXY_VARS.some(name => (env[name] ?? '').trim() !== '')
364
+
365
+ export const describeNetworkFailure = (error, env = process.env) => {
366
+ const detail = error?.message ?? String(error)
367
+ if (!proxyConfigured(env)) return detail
368
+ const enabled = (env.NODE_USE_ENV_PROXY ?? '').trim() !== ''
369
+ if (enabled) return detail
370
+ return `${detail} (a proxy is set in HTTP_PROXY/HTTPS_PROXY and this hook cannot use it; on Node 24+ set NODE_USE_ENV_PROXY=1 in the editor's environment)`
371
+ }
372
+
348
373
  // ── ask / wait ────────────────────────────────────────────────────────────────
349
374
  // Redacted, not raw. This is the text a human reads on a lock screen and in
350
375
  // Slack, and it used to be the command verbatim: `curl -H "Authorization:
@@ -517,7 +542,7 @@ const main = async () => {
517
542
  return respond(await handlePushFirst(apiKey, command, project, ident, tool.pushFirstSeconds))
518
543
  }
519
544
  } catch (error) {
520
- diag(String(error?.message ?? error))
545
+ diag(describeNetworkFailure(error))
521
546
  return respond(ask())
522
547
  }
523
548
  }
@@ -525,7 +550,7 @@ const main = async () => {
525
550
  // Importing this file for its testable helpers must not consume stdin or exit.
526
551
  if (!process.env.PUSHARY_GATE_IMPORT) {
527
552
  main().catch((error) => {
528
- diag(`fatal: ${error?.message ?? error}`)
553
+ diag(`fatal: ${describeNetworkFailure(error)}`)
529
554
  respond(ask())
530
555
  })
531
556
  }
@@ -7,7 +7,7 @@ import {
7
7
  KILL_REASON,
8
8
  isDeferAnswer,
9
9
  resolveGate
10
- } from "../chunk-3USMXNVB.js";
10
+ } from "../chunk-GD275GEX.js";
11
11
  import {
12
12
  askUser,
13
13
  cancelQuestion,
@@ -19,7 +19,7 @@ import {
19
19
  reportEvent,
20
20
  scopePathFor,
21
21
  waitForAnswer
22
- } from "../chunk-H46LSQRT.js";
22
+ } from "../chunk-FZIQSDTQ.js";
23
23
  import "../chunk-BSZYIAZL.js";
24
24
  import "../chunk-SAF6HGAA.js";
25
25
  import "../chunk-7QLSKOSU.js";
@@ -41,8 +41,8 @@ import {
41
41
  } from "../chunk-MUEW424A.js";
42
42
  import {
43
43
  exitOnHelpFlag
44
- } from "../chunk-ATP6UQPH.js";
45
- import "../chunk-JGH37QRB.js";
44
+ } from "../chunk-BBK3TTU2.js";
45
+ import "../chunk-7OYGFJYZ.js";
46
46
  import "../chunk-7QLSKOSU.js";
47
47
  import "../chunk-DQAN3JQP.js";
48
48
  import {
@@ -4,7 +4,7 @@ import {
4
4
  denyReasonFrom,
5
5
  isDeferAnswer,
6
6
  resolveGate
7
- } from "../chunk-3USMXNVB.js";
7
+ } from "../chunk-GD275GEX.js";
8
8
  import {
9
9
  CODEX_AGENT,
10
10
  DEFAULT_SESSION,
@@ -35,7 +35,7 @@ import {
35
35
  toPolicyLookup,
36
36
  toPolicyLookups,
37
37
  waitForAnswer
38
- } from "../chunk-H46LSQRT.js";
38
+ } from "../chunk-FZIQSDTQ.js";
39
39
  import {
40
40
  isGatingMoment,
41
41
  recordKeylessMoment
@@ -3,7 +3,7 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-H46LSQRT.js";
6
+ } from "../chunk-FZIQSDTQ.js";
7
7
  import "../chunk-BSZYIAZL.js";
8
8
  import "../chunk-SAF6HGAA.js";
9
9
  import "../chunk-7QLSKOSU.js";
@@ -6,7 +6,7 @@ import {
6
6
  confirmAppConnection,
7
7
  connectDevice,
8
8
  printConnectInstructions
9
- } from "../chunk-P7VBAYQO.js";
9
+ } from "../chunk-MGFZXUUR.js";
10
10
  import "../chunk-3EGEA4KH.js";
11
11
  import {
12
12
  readKeySource
@@ -24,8 +24,8 @@ import {
24
24
  } from "../chunk-GMXKITVA.js";
25
25
  import {
26
26
  exitOnHelpFlag
27
- } from "../chunk-ATP6UQPH.js";
28
- import "../chunk-JGH37QRB.js";
27
+ } from "../chunk-BBK3TTU2.js";
28
+ import "../chunk-7OYGFJYZ.js";
29
29
  import "../chunk-BSZYIAZL.js";
30
30
  import "../chunk-SAF6HGAA.js";
31
31
  import {
@@ -84,7 +84,9 @@ var main = async () => {
84
84
  console.log();
85
85
  process.exit(EXIT.OK);
86
86
  }
87
- console.log(` ${dim("No device of yours can answer yet. Run")} ${cyan("pushary connect")} ${dim("again when you are ready.")}`);
87
+ console.log(` ${dim("No device of yours can answer yet.")}`);
88
+ for (const line of result.repair ?? []) console.log(` ${dim(line)}`);
89
+ console.log(` ${dim("Then run")} ${cyan("pushary connect")} ${dim("again.")}`);
88
90
  console.log();
89
91
  process.exit(EXIT.NO_DEVICE);
90
92
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HEARTBEAT_INTERVAL_MS,
4
4
  sendHeartbeat
5
- } from "../chunk-YZT2CBC3.js";
5
+ } from "../chunk-NQTRA7H5.js";
6
6
  import {
7
7
  spawnClaude
8
8
  } from "../chunk-XHKBHWLX.js";
@@ -11,8 +11,8 @@ import {
11
11
  } from "../chunk-MUEW424A.js";
12
12
  import {
13
13
  exitOnHelpFlag
14
- } from "../chunk-ATP6UQPH.js";
15
- import "../chunk-JGH37QRB.js";
14
+ } from "../chunk-BBK3TTU2.js";
15
+ import "../chunk-7OYGFJYZ.js";
16
16
  import {
17
17
  getMachineId
18
18
  } from "../chunk-RN3NOEJF.js";
@@ -2,6 +2,11 @@
2
2
  import {
3
3
  describeWaitLadder
4
4
  } from "../chunk-IIENZE2J.js";
5
+ import {
6
+ claudeWired,
7
+ codexWired,
8
+ geminiWired
9
+ } from "../chunk-B7R4YSAB.js";
5
10
  import {
6
11
  agentProbes,
7
12
  detectAgent,
@@ -39,7 +44,7 @@ import {
39
44
  describeReach,
40
45
  fetchChannels,
41
46
  reachVerdict
42
- } from "../chunk-P7VBAYQO.js";
47
+ } from "../chunk-MGFZXUUR.js";
43
48
  import "../chunk-3EGEA4KH.js";
44
49
  import {
45
50
  keyPrefix,
@@ -62,10 +67,10 @@ import {
62
67
  } from "../chunk-MUEW424A.js";
63
68
  import {
64
69
  exitOnHelpFlag
65
- } from "../chunk-ATP6UQPH.js";
70
+ } from "../chunk-BBK3TTU2.js";
66
71
  import {
67
72
  getPackageVersion
68
- } from "../chunk-JGH37QRB.js";
73
+ } from "../chunk-7OYGFJYZ.js";
69
74
  import {
70
75
  readLedgerSummary
71
76
  } from "../chunk-WLGEY4UX.js";
@@ -149,32 +154,6 @@ var resolveDoctorExit = ({ keyExit, failures }) => {
149
154
  return EXIT.PROBLEMS_FOUND;
150
155
  };
151
156
 
152
- // src/diagnostics/wiring.ts
153
- var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
154
- var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
155
- var claudeWired = ({ claudeJson: claudeJson2, settings, skillExists }) => {
156
- if (record(record(claudeJson2)?.mcpServers)?.pushary) return true;
157
- const s = record(settings);
158
- if (s) {
159
- if (CLAUDE_HOOK_BINARY.test(JSON.stringify(s.hooks ?? {}))) return true;
160
- const allow = record(s.permissions)?.allow;
161
- if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
162
- }
163
- return skillExists;
164
- };
165
- var codexWired = ({ config, hooks, skillExists }) => {
166
- if (record(record(config)?.mcp_servers)?.pushary) return true;
167
- const h = record(hooks);
168
- if (h && hasCodexHooks(h)) return true;
169
- return skillExists;
170
- };
171
- var geminiWired = ({ settings }) => {
172
- const s = record(settings);
173
- if (!s) return false;
174
- if (record(s.mcpServers)?.pushary) return true;
175
- return hasGeminiHooks(s);
176
- };
177
-
178
157
  // bin/pushary-doctor.ts
179
158
  exitOnHelpFlag("doctor");
180
159
  var parseDoctorOptions = (argv) => {
@@ -4,7 +4,7 @@ import {
4
4
  denyReasonFrom,
5
5
  isDeferAnswer,
6
6
  resolveGate
7
- } from "../chunk-3USMXNVB.js";
7
+ } from "../chunk-GD275GEX.js";
8
8
  import {
9
9
  DEFAULT_SESSION,
10
10
  askUser,
@@ -26,7 +26,7 @@ import {
26
26
  scopePathFor,
27
27
  sendNotification,
28
28
  waitForAnswer
29
- } from "../chunk-H46LSQRT.js";
29
+ } from "../chunk-FZIQSDTQ.js";
30
30
  import {
31
31
  isGatingMoment,
32
32
  recordKeylessMoment
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-C5I2RAMT.js";
4
+ } from "../chunk-HU43EZ5Q.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
6
  import {
7
7
  exitOnHelpFlag
8
- } from "../chunk-ATP6UQPH.js";
9
- import "../chunk-JGH37QRB.js";
10
- import "../chunk-3USMXNVB.js";
11
- import "../chunk-H46LSQRT.js";
8
+ } from "../chunk-BBK3TTU2.js";
9
+ import "../chunk-7OYGFJYZ.js";
10
+ import "../chunk-GD275GEX.js";
11
+ import "../chunk-FZIQSDTQ.js";
12
12
  import "../chunk-WLGEY4UX.js";
13
13
  import "../chunk-BSZYIAZL.js";
14
14
  import "../chunk-SAF6HGAA.js";
@@ -21,10 +21,10 @@ import {
21
21
  } from "../chunk-GMXKITVA.js";
22
22
  import {
23
23
  exitOnHelpFlag
24
- } from "../chunk-ATP6UQPH.js";
24
+ } from "../chunk-BBK3TTU2.js";
25
25
  import {
26
26
  getPackageVersion
27
- } from "../chunk-JGH37QRB.js";
27
+ } from "../chunk-7OYGFJYZ.js";
28
28
  import "../chunk-SAF6HGAA.js";
29
29
  import {
30
30
  UsageError
@@ -19,8 +19,8 @@ import {
19
19
  } from "../chunk-GMXKITVA.js";
20
20
  import {
21
21
  exitOnHelpFlag
22
- } from "../chunk-ATP6UQPH.js";
23
- import "../chunk-JGH37QRB.js";
22
+ } from "../chunk-BBK3TTU2.js";
23
+ import "../chunk-7OYGFJYZ.js";
24
24
  import {
25
25
  UsageError
26
26
  } from "../chunk-7QLSKOSU.js";
@@ -11,8 +11,8 @@ import {
11
11
  } from "../chunk-GMXKITVA.js";
12
12
  import {
13
13
  exitOnHelpFlag
14
- } from "../chunk-ATP6UQPH.js";
15
- import "../chunk-JGH37QRB.js";
14
+ } from "../chunk-BBK3TTU2.js";
15
+ import "../chunk-7OYGFJYZ.js";
16
16
  import "../chunk-SAF6HGAA.js";
17
17
  import {
18
18
  UsageError
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionDenied
4
- } from "../chunk-C5I2RAMT.js";
4
+ } from "../chunk-HU43EZ5Q.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
- import "../chunk-3USMXNVB.js";
7
- import "../chunk-H46LSQRT.js";
6
+ import "../chunk-GD275GEX.js";
7
+ import "../chunk-FZIQSDTQ.js";
8
8
  import "../chunk-WLGEY4UX.js";
9
9
  import "../chunk-BSZYIAZL.js";
10
10
  import "../chunk-SAF6HGAA.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionRequest
4
- } from "../chunk-C5I2RAMT.js";
4
+ } from "../chunk-HU43EZ5Q.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
- import "../chunk-3USMXNVB.js";
7
- import "../chunk-H46LSQRT.js";
6
+ import "../chunk-GD275GEX.js";
7
+ import "../chunk-FZIQSDTQ.js";
8
8
  import "../chunk-WLGEY4UX.js";
9
9
  import "../chunk-BSZYIAZL.js";
10
10
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionEnd
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionStart
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -7,6 +7,11 @@ import {
7
7
  addPusharyHooks,
8
8
  addPusharyToolPermissions
9
9
  } from "../chunk-CAJZAFVS.js";
10
+ import {
11
+ claudeWired,
12
+ codexWired,
13
+ geminiWired
14
+ } from "../chunk-B7R4YSAB.js";
10
15
  import {
11
16
  describeDetection,
12
17
  detectAllAgents,
@@ -48,7 +53,7 @@ import {
48
53
  connectViaAppPairing,
49
54
  printConnectInstructions,
50
55
  setHumanStream
51
- } from "../chunk-P7VBAYQO.js";
56
+ } from "../chunk-MGFZXUUR.js";
52
57
  import "../chunk-3EGEA4KH.js";
53
58
  import {
54
59
  KEY_FILE_MODE,
@@ -86,10 +91,10 @@ import {
86
91
  getPackageVersion,
87
92
  isNewerVersion,
88
93
  renderCommandHelp
89
- } from "../chunk-JGH37QRB.js";
94
+ } from "../chunk-7OYGFJYZ.js";
90
95
  import {
91
96
  reportEvent
92
- } from "../chunk-H46LSQRT.js";
97
+ } from "../chunk-FZIQSDTQ.js";
93
98
  import "../chunk-BSZYIAZL.js";
94
99
  import "../chunk-SAF6HGAA.js";
95
100
  import {
@@ -318,6 +323,9 @@ var parseSetupOptions = (input2) => {
318
323
  if (key && keyStdin) {
319
324
  throw new UsageError("Pass either --key or --key-stdin, not both");
320
325
  }
326
+ if (key && !isValidApiKey(key)) {
327
+ throw new UsageError("--key is not a valid API key. Expected the pk_xxx.xxx shape from pushary.com/dashboard/agent/settings");
328
+ }
321
329
  return {
322
330
  connectMode,
323
331
  connectExplicit: connectRaw !== void 0,
@@ -328,7 +336,14 @@ var parseSetupOptions = (input2) => {
328
336
  // Anything that means "there is nobody to answer a prompt". A non-TTY stdin
329
337
  // is included because that is the case that used to hang: it is better to
330
338
  // take the defaults and say so than to wait forever for a keypress.
331
- nonInteractive: yes || agentsFlag !== void 0 || !input2.stdinIsTty,
339
+ //
340
+ // --json counts. Under it stdout carries exactly one object and every human
341
+ // line is redirected to stderr, so a prompt has nowhere to render: the
342
+ // question goes to stderr while the run blocks on a keypress the caller
343
+ // cannot see it is waiting for, and whatever is reading stdout waits forever
344
+ // for an object that never arrives. Asking for machine-readable output is
345
+ // itself a statement that no human is watching.
346
+ nonInteractive: yes || agentsFlag !== void 0 || flags.bools.has("json") || !input2.stdinIsTty,
332
347
  json: flags.bools.has("json"),
333
348
  dryRun: flags.bools.has("dry-run"),
334
349
  help,
@@ -359,6 +374,7 @@ var humanStream = process.stdout;
359
374
  var writeHuman = (text) => {
360
375
  humanStream.write(text);
361
376
  };
377
+ var jsonMode = false;
362
378
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
363
379
  var bold = (s) => `\x1B[1m${s}\x1B[0m`;
364
380
  var green = (s) => `\x1B[32m${s}\x1B[0m`;
@@ -367,6 +383,10 @@ var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
367
383
  var check = green("\u2713");
368
384
  var abortWith = async (reason, code, keyCheck) => {
369
385
  await reportSetupAbort(reason, code, keyCheck);
386
+ if (jsonMode) {
387
+ process.stdout.write(`${JSON.stringify({ ok: false, reason, exitCode: code, ...keyCheck ? { keyCheck } : {} }, null, 2)}
388
+ `);
389
+ }
370
390
  return code;
371
391
  };
372
392
  var readKeyFromStdin = async (options) => {
@@ -1053,6 +1073,44 @@ var setupGemini = async (apiKey) => {
1053
1073
  console.log(` ${dim("Restart Gemini CLI to load the new config.")}`);
1054
1074
  noteManual("Restart Gemini CLI to load the new config.");
1055
1075
  };
1076
+ var agentIsWired = (agent) => {
1077
+ const readJson2 = (path) => {
1078
+ try {
1079
+ return JSON.parse(readFileSync(path, "utf-8"));
1080
+ } catch {
1081
+ return null;
1082
+ }
1083
+ };
1084
+ switch (agent) {
1085
+ case "claude_code":
1086
+ return claudeWired({
1087
+ claudeJson: readJson2(CLAUDE_JSON),
1088
+ settings: readJson2(CLAUDE_SETTINGS),
1089
+ skillExists: existsSync(join(CLAUDE_SKILL_DIR, "SKILL.md"))
1090
+ });
1091
+ case "codex": {
1092
+ let config = null;
1093
+ try {
1094
+ config = parseTOML(readFileSync(join(CODEX_HOME, "config.toml"), "utf-8"));
1095
+ } catch {
1096
+ config = null;
1097
+ }
1098
+ return codexWired({
1099
+ config,
1100
+ hooks: readJson2(CODEX_HOOKS_JSON),
1101
+ skillExists: existsSync(join(CODEX_SKILL_DIR, "SKILL.md"))
1102
+ });
1103
+ }
1104
+ case "gemini_cli":
1105
+ return geminiWired({ settings: readJson2(GEMINI_SETTINGS) });
1106
+ case "cursor":
1107
+ return existsSync(join(CURSOR_PLUGIN_DIR, "mcp.json"));
1108
+ case "vscode":
1109
+ return existsSync(join(VSCODE_PLUGIN_DIR, ".mcp.json"));
1110
+ default:
1111
+ return true;
1112
+ }
1113
+ };
1056
1114
  var AGENT_SETUP = {
1057
1115
  claude_code: setupClaudeCode,
1058
1116
  codex: setupCodex,
@@ -1201,7 +1259,8 @@ var resolveAgents = async (options) => {
1201
1259
  return options.agents.agents;
1202
1260
  }
1203
1261
  const detected = detectAgents();
1204
- if (options.agents.kind === "auto") {
1262
+ const wantsAuto = options.agents.kind === "auto" || options.nonInteractive;
1263
+ if (wantsAuto) {
1205
1264
  const picked = Object.keys(detected).filter((agent) => detected[agent].found);
1206
1265
  if (picked.length === 0) {
1207
1266
  console.log(` ${yellow("!")} No agents detected, so nothing was configured. Your key is saved.`);
@@ -1283,6 +1342,7 @@ var main = async () => {
1283
1342
  }
1284
1343
  recordSetupOptions(options);
1285
1344
  if (options.json) {
1345
+ jsonMode = true;
1286
1346
  humanStream = process.stderr;
1287
1347
  setHumanStream(process.stderr);
1288
1348
  console.log = (...args) => {
@@ -1304,12 +1364,12 @@ var main = async () => {
1304
1364
  let keyFromPairing = false;
1305
1365
  const storedKey = readConfigFileKey();
1306
1366
  const pairingRequested = connectMode === "app" && options.connectExplicit && !options.skipPhone;
1307
- const ambientEligible = !(flagKey && isValidApiKey(flagKey)) && !pairingRequested;
1367
+ const ambientEligible = flagKey === void 0 && !pairingRequested;
1308
1368
  const envCheck = ambientEligible && envKey && isValidApiKey(envKey) ? await usableStoredKey(envKey) : null;
1309
1369
  const storedCheck = ambientEligible && envCheck === null && storedKey !== null && isValidApiKey(storedKey) && // login writes both, so without this the same key is probed twice.
1310
1370
  storedKey !== envKey ? await usableStoredKey(storedKey) : null;
1311
1371
  let storedKeyCheck = null;
1312
- if (flagKey && isValidApiKey(flagKey)) {
1372
+ if (flagKey) {
1313
1373
  const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
1314
1374
  console.log(` ${check} Using API key: ${dim(masked)}`);
1315
1375
  console.log();
@@ -1445,8 +1505,15 @@ var main = async () => {
1445
1505
  markStep(`configure:${agent}`);
1446
1506
  try {
1447
1507
  const outcome = await AGENT_SETUP[agent](trimmedKey, context);
1448
- if (outcome === "skipped") skipped.push(agent);
1449
- else completed.push(agent);
1508
+ if (outcome === "skipped") {
1509
+ skipped.push(agent);
1510
+ } else if (agentIsWired(agent)) {
1511
+ completed.push(agent);
1512
+ } else {
1513
+ failed.push(agent.replace("_", " "));
1514
+ console.log(` ${yellow("!")} ${agent.replace("_", " ")} reported success but its config is not there.`);
1515
+ console.log(` ${dim("Re-run setup, then")} ${cyan("npx @pushary/agent-hooks@latest doctor")} ${dim("to see what is missing.")}`);
1516
+ }
1450
1517
  } catch (err) {
1451
1518
  failed.push(agent.replace("_", " "));
1452
1519
  console.log(` ${yellow("!")} ${agent.replace("_", " ")} setup failed: ${formatError(err)}`);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  exitOnHelpFlag
4
- } from "../chunk-ATP6UQPH.js";
5
- import "../chunk-JGH37QRB.js";
4
+ } from "../chunk-BBK3TTU2.js";
5
+ import "../chunk-7OYGFJYZ.js";
6
6
  import {
7
7
  readLedgerSummary
8
8
  } from "../chunk-WLGEY4UX.js";
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  describeLastSeen,
4
4
  listMachines
5
- } from "../chunk-YZT2CBC3.js";
5
+ } from "../chunk-NQTRA7H5.js";
6
6
  import {
7
7
  describeReach,
8
8
  fetchChannels,
9
9
  reachVerdict
10
- } from "../chunk-P7VBAYQO.js";
10
+ } from "../chunk-MGFZXUUR.js";
11
11
  import "../chunk-3EGEA4KH.js";
12
12
  import {
13
13
  readKeySource
@@ -26,8 +26,8 @@ import {
26
26
  } from "../chunk-GMXKITVA.js";
27
27
  import {
28
28
  exitOnHelpFlag
29
- } from "../chunk-ATP6UQPH.js";
30
- import "../chunk-JGH37QRB.js";
29
+ } from "../chunk-BBK3TTU2.js";
30
+ import "../chunk-7OYGFJYZ.js";
31
31
  import "../chunk-BSZYIAZL.js";
32
32
  import "../chunk-SAF6HGAA.js";
33
33
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStopFailure
4
- } from "../chunk-H46LSQRT.js";
4
+ } from "../chunk-FZIQSDTQ.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -7,8 +7,8 @@ import {
7
7
  } from "../chunk-GMXKITVA.js";
8
8
  import {
9
9
  exitOnHelpFlag
10
- } from "../chunk-ATP6UQPH.js";
11
- import "../chunk-JGH37QRB.js";
10
+ } from "../chunk-BBK3TTU2.js";
11
+ import "../chunk-7OYGFJYZ.js";
12
12
  import {
13
13
  UsageError
14
14
  } from "../chunk-7QLSKOSU.js";
@@ -36,10 +36,10 @@ import {
36
36
  } from "../chunk-MUEW424A.js";
37
37
  import {
38
38
  exitOnHelpFlag
39
- } from "../chunk-ATP6UQPH.js";
39
+ } from "../chunk-BBK3TTU2.js";
40
40
  import {
41
41
  isNewerVersion
42
- } from "../chunk-JGH37QRB.js";
42
+ } from "../chunk-7OYGFJYZ.js";
43
43
  import "../chunk-DQAN3JQP.js";
44
44
  import {
45
45
  getApiKey
@@ -14,8 +14,8 @@ import {
14
14
  } from "../chunk-GMXKITVA.js";
15
15
  import {
16
16
  exitOnHelpFlag
17
- } from "../chunk-ATP6UQPH.js";
18
- import "../chunk-JGH37QRB.js";
17
+ } from "../chunk-BBK3TTU2.js";
18
+ import "../chunk-7OYGFJYZ.js";
19
19
  import "../chunk-SAF6HGAA.js";
20
20
  import {
21
21
  UsageError
@@ -4,7 +4,7 @@ import {
4
4
  getPackageVersion,
5
5
  isCommandName,
6
6
  renderHelp
7
- } from "../chunk-JGH37QRB.js";
7
+ } from "../chunk-7OYGFJYZ.js";
8
8
  import {
9
9
  installProxyDispatcher
10
10
  } from "../chunk-SAF6HGAA.js";
@@ -3,7 +3,7 @@ var COMMAND_LIST = [
3
3
  {
4
4
  name: "setup",
5
5
  module: "pushary-setup",
6
- summary: "Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary"
6
+ summary: "Configure Claude Code, Codex, Gemini CLI, Hermes, Cursor, or VS Code with Pushary"
7
7
  },
8
8
  {
9
9
  name: "claude",
@@ -0,0 +1,38 @@
1
+ import {
2
+ hasGeminiHooks
3
+ } from "./chunk-E2U35RLD.js";
4
+ import {
5
+ hasCodexHooks
6
+ } from "./chunk-7HG4WUIE.js";
7
+
8
+ // src/diagnostics/wiring.ts
9
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
10
+ var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
11
+ var claudeWired = ({ claudeJson, settings, skillExists }) => {
12
+ if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
13
+ const s = record(settings);
14
+ if (s) {
15
+ if (CLAUDE_HOOK_BINARY.test(JSON.stringify(s.hooks ?? {}))) return true;
16
+ const allow = record(s.permissions)?.allow;
17
+ if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
18
+ }
19
+ return skillExists;
20
+ };
21
+ var codexWired = ({ config, hooks, skillExists }) => {
22
+ if (record(record(config)?.mcp_servers)?.pushary) return true;
23
+ const h = record(hooks);
24
+ if (h && hasCodexHooks(h)) return true;
25
+ return skillExists;
26
+ };
27
+ var geminiWired = ({ settings }) => {
28
+ const s = record(settings);
29
+ if (!s) return false;
30
+ if (record(s.mcpServers)?.pushary) return true;
31
+ return hasGeminiHooks(s);
32
+ };
33
+
34
+ export {
35
+ claudeWired,
36
+ codexWired,
37
+ geminiWired
38
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getPackageVersion,
3
3
  renderCommandHelp
4
- } from "./chunk-JGH37QRB.js";
4
+ } from "./chunk-7OYGFJYZ.js";
5
5
  import {
6
6
  EXIT
7
7
  } from "./chunk-KZERVKTD.js";
@@ -55,6 +55,10 @@ var isWaitForAnswerResponse = (data) => {
55
55
  // src/policy.ts
56
56
  var CACHE_TTL_MS = 60 * 1e3;
57
57
  var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
58
+ var policyRequestFor = (agentType) => ({
59
+ agent: agentType,
60
+ repoAware: agentType === "claude_code"
61
+ });
58
62
  var policyCacheFile = (apiKey, agent, repoAware = false) => {
59
63
  const parts = [apiKey, agent, repoAware ? "repoAware" : void 0].filter(Boolean);
60
64
  const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
@@ -132,6 +136,8 @@ var findBestMatch = (policies, toolName, args, repoKey) => {
132
136
  }
133
137
  return best;
134
138
  };
139
+ var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
140
+ var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
135
141
  var autoApprove = (tool) => ({
136
142
  tool,
137
143
  timeoutSeconds: 0,
@@ -164,7 +170,7 @@ var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) =>
164
170
  if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
165
171
  base = autoApprove(base.tool);
166
172
  }
167
- const wouldAutoApprove = base.timeoutSeconds === 0 && base.timeoutAction === "approve";
173
+ const wouldAutoApprove = isAutoApprove(base);
168
174
  if (!governedBySpecificRule && wouldAutoApprove && toolName === "Bash" && typeof arg === "string" && classifyDecisionRisk(toolName, arg) === "destructive") {
169
175
  base = {
170
176
  tool: base.tool,
@@ -181,8 +187,8 @@ var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) =>
181
187
  return base;
182
188
  };
183
189
  var restraintRank = (policy) => {
184
- if (policy.timeoutSeconds === 0 && policy.timeoutAction === "deny") return 4;
185
- if (policy.timeoutSeconds === 0 && policy.timeoutAction === "approve") return 0;
190
+ if (isAutoDeny(policy)) return 4;
191
+ if (isAutoApprove(policy)) return 0;
186
192
  if (policy.mode === "push_only") return 3;
187
193
  return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
188
194
  };
@@ -945,31 +951,38 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
945
951
  };
946
952
  var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
947
953
  var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
948
- var readFreshCachedPolicy = (apiKey) => {
949
- const path = policyCacheFile(apiKey);
954
+ var readFreshCachedPolicy = (apiKey, agentType) => {
955
+ const request = policyRequestFor(agentType);
956
+ const path = policyCacheFile(apiKey, request.agent, request.repoAware);
950
957
  if (!existsSync5(path)) return null;
951
958
  const cached = JSON.parse(readFileSync4(path, "utf-8"));
952
959
  if (!isPolicyConfig(cached)) return null;
953
960
  if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
954
961
  return cached;
955
962
  };
956
- var deriveDecisionSource = (toolName, toolInput, liveMode, cwd) => {
963
+ var autoDecisionEnabled = () => {
964
+ const flag = process.env.PUSHARY_AUTO_DECISION_BEACON;
965
+ return flag === "1" || flag === "true";
966
+ };
967
+ var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
957
968
  try {
958
969
  if (liveMode.kill) return "terminal";
959
- const policy = readFreshCachedPolicy(getApiKey());
970
+ if (!autoDecisionEnabled()) return void 0;
971
+ const policy = readFreshCachedPolicy(getApiKey(), agentType);
960
972
  if (!policy) return void 0;
961
973
  const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
962
- if (resolved.timeoutSeconds === 0 && resolved.timeoutAction === "approve") return "policy_auto";
974
+ if (isAutoApprove(resolved)) return "policy_auto";
963
975
  if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
964
976
  return "terminal";
965
977
  } catch {
966
978
  return void 0;
967
979
  }
968
980
  };
969
- var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, cwd) => {
981
+ var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd) => {
970
982
  try {
971
983
  if (liveMode.kill) return void 0;
972
- const policy = readFreshCachedPolicy(getApiKey());
984
+ if (!autoDecisionEnabled()) return void 0;
985
+ const policy = readFreshCachedPolicy(getApiKey(), agentType);
973
986
  if (!policy) return void 0;
974
987
  const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
975
988
  if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
@@ -1055,8 +1068,8 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1055
1068
  for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
1056
1069
  }
1057
1070
  const isMirrorOnly = NON_GATED_MIRROR_TOOLS.has(input.tool_name);
1058
- const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, input.cwd);
1059
- const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, input.cwd) : void 0;
1071
+ const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd);
1072
+ const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, agent.type, input.cwd) : void 0;
1060
1073
  const toolReport = reportEvent({
1061
1074
  event: isError ? "tool_error" : "tool_complete",
1062
1075
  agentType: agent.type,
@@ -1310,6 +1323,8 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1310
1323
 
1311
1324
  export {
1312
1325
  getPolicy,
1326
+ isAutoApprove,
1327
+ isAutoDeny,
1313
1328
  resolveAutoResolveOrigin,
1314
1329
  resolvePolicy,
1315
1330
  resolvePolicyAcross,
@@ -1,8 +1,10 @@
1
1
  import {
2
+ isAutoApprove,
3
+ isAutoDeny,
2
4
  isModeStateDegraded,
3
5
  resolveAutoResolveOrigin,
4
6
  resolvePolicyAcross
5
- } from "./chunk-H46LSQRT.js";
7
+ } from "./chunk-FZIQSDTQ.js";
6
8
  import {
7
9
  evaluateScope,
8
10
  scopeChangeReason,
@@ -34,8 +36,8 @@ var resolveGate = (input) => {
34
36
  const { modeState, config, toolName, toolInputs, cwd, repoKey } = input;
35
37
  if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
36
38
  const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey);
37
- const autoApproves = policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
38
- const autoDenies = policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
39
+ const autoApproves = isAutoApprove(policy);
40
+ const autoDenies = isAutoDeny(policy);
39
41
  if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
40
42
  if (isModeStateDegraded(modeState)) {
41
43
  if (autoApproves) {
@@ -5,7 +5,7 @@ import {
5
5
  denyReasonFrom,
6
6
  isDeferAnswer,
7
7
  resolveGate
8
- } from "./chunk-3USMXNVB.js";
8
+ } from "./chunk-GD275GEX.js";
9
9
  import {
10
10
  DEFAULT_SESSION,
11
11
  askUser,
@@ -25,7 +25,7 @@ import {
25
25
  sendNotification,
26
26
  throttlePass,
27
27
  waitForAnswer
28
- } from "./chunk-H46LSQRT.js";
28
+ } from "./chunk-FZIQSDTQ.js";
29
29
  import {
30
30
  isGatingMoment,
31
31
  recordKeylessMoment
@@ -45,6 +45,27 @@ var describeReach = (channels) => {
45
45
  if (verdict.kind === "unattributable") return `${summary}, and no way to tell whose`;
46
46
  return summary;
47
47
  };
48
+ var describeAppRepair = (verdict) => {
49
+ switch (verdict.kind) {
50
+ case "yours":
51
+ return null;
52
+ case "nobody":
53
+ return [
54
+ "Open the Pushary app, sign in, and allow notifications.",
55
+ "If this terminal was never paired with your phone, run: npx @pushary/agent-hooks@latest setup --connect app"
56
+ ];
57
+ case "others":
58
+ return [
59
+ `Reachable devices belong to other members of this workspace (${verdict.otherMembers}).`,
60
+ "Sign in on the Pushary app with your own account so approvals can reach you."
61
+ ];
62
+ case "unattributable":
63
+ return [
64
+ "Something is reachable here, but not identifiably yours.",
65
+ "Sign in on the Pushary app so your approvals are routed to your device."
66
+ ];
67
+ }
68
+ };
48
69
 
49
70
  // src/cli/human.ts
50
71
  var stream = process.stdout;
@@ -324,7 +345,12 @@ var printQr = (url) => new Promise((resolve, reject) => {
324
345
  reject(err);
325
346
  }
326
347
  });
327
- var phoneFailure = (detail) => ({ reachesYou: false, outcome: null, detail });
348
+ var phoneFailure = (detail, repair) => ({
349
+ reachesYou: false,
350
+ outcome: null,
351
+ detail,
352
+ ...repair ? { repair } : {}
353
+ });
328
354
  var waitForDevice = async (apiKey, baseline) => {
329
355
  const deadline = Date.now() + CONNECT_TIMEOUT_MS;
330
356
  let consecutiveFailures = 0;
@@ -469,9 +495,11 @@ var confirmAppConnection = async (apiKey) => {
469
495
  const channels = probe.status;
470
496
  const ownedApp = channels.yours?.appDevices ?? channels.appDevices;
471
497
  if (channels.precise && ownedApp === 0) {
472
- console.log(` ${yellow("!")} Paired, but the app hasn't registered for notifications yet.`);
473
- console.log(` ${dim("Open the Pushary app and allow notifications, then run")} ${cyan("pushary doctor")}${dim(".")}`);
474
- return phoneFailure("app_not_registered");
498
+ const repair = describeAppRepair(reachVerdict(channels)) ?? [];
499
+ console.log(` ${yellow("!")} No device of yours is registered for notifications yet.`);
500
+ for (const line of repair) console.log(` ${dim(line)}`);
501
+ console.log(` ${dim("Then run")} ${cyan("pushary doctor")}${dim(".")}`);
502
+ return phoneFailure("app_not_registered", repair);
475
503
  }
476
504
  return sendAndConfirm(apiKey, channels);
477
505
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getPackageVersion
3
- } from "./chunk-JGH37QRB.js";
3
+ } from "./chunk-7OYGFJYZ.js";
4
4
  import {
5
5
  getMachineId
6
6
  } from "./chunk-RN3NOEJF.js";
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-C5I2RAMT.js";
3
+ } from "../chunk-HU43EZ5Q.js";
4
4
  import "../chunk-CAJZAFVS.js";
5
- import "../chunk-3USMXNVB.js";
5
+ import "../chunk-GD275GEX.js";
6
6
  import {
7
7
  askUser,
8
8
  cancelQuestion,
@@ -15,7 +15,7 @@ import {
15
15
  reportEvent,
16
16
  resolvePolicy,
17
17
  waitForAnswer
18
- } from "../chunk-H46LSQRT.js";
18
+ } from "../chunk-FZIQSDTQ.js";
19
19
  import "../chunk-WLGEY4UX.js";
20
20
  import "../chunk-BSZYIAZL.js";
21
21
  import "../chunk-SAF6HGAA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",