@pushary/agent-hooks 0.89.12 → 0.90.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 (82) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +1 -0
  3. package/data/SKILL.md +1 -1
  4. package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
  5. package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
  6. package/dist/bin/pushary-bell-hook.js +4 -4
  7. package/dist/bin/pushary-bell.js +8 -8
  8. package/dist/bin/pushary-claude.js +194 -38
  9. package/dist/bin/pushary-clean.js +15 -32
  10. package/dist/bin/pushary-codex-bridge.js +37 -12
  11. package/dist/bin/pushary-codex-hook.js +45 -12
  12. package/dist/bin/pushary-codex.js +7 -7
  13. package/dist/bin/pushary-connect.js +11 -10
  14. package/dist/bin/pushary-daemon.js +186 -62
  15. package/dist/bin/pushary-disconnect.js +8 -8
  16. package/dist/bin/pushary-doctor.js +108 -25
  17. package/dist/bin/pushary-elicitation-hook.js +4 -4
  18. package/dist/bin/pushary-gemini-bridge.js +33 -11
  19. package/dist/bin/pushary-gemini-hook.js +21 -10
  20. package/dist/bin/pushary-hook.js +13 -13
  21. package/dist/bin/pushary-login.js +18 -7
  22. package/dist/bin/pushary-logout.js +6 -6
  23. package/dist/bin/pushary-mode.js +28 -21
  24. package/dist/bin/pushary-notification-hook.js +7 -7
  25. package/dist/bin/pushary-opencode-hook.js +8 -8
  26. package/dist/bin/pushary-permission-denied-hook.js +11 -11
  27. package/dist/bin/pushary-permission-hook.js +11 -11
  28. package/dist/bin/pushary-post-hook.js +14 -8
  29. package/dist/bin/pushary-prompt-hook.js +7 -7
  30. package/dist/bin/pushary-session-end-hook.js +7 -7
  31. package/dist/bin/pushary-session-start-hook.js +7 -7
  32. package/dist/bin/pushary-setup.js +168 -45
  33. package/dist/bin/pushary-stats.js +4 -4
  34. package/dist/bin/pushary-status.js +11 -10
  35. package/dist/bin/pushary-stop-hook.js +14 -8
  36. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  37. package/dist/bin/pushary-suggestions.js +2 -2
  38. package/dist/bin/pushary-transcripts.d.ts +1 -0
  39. package/dist/bin/pushary-transcripts.js +79 -0
  40. package/dist/bin/pushary-upgrade.js +15 -15
  41. package/dist/bin/pushary-wait.js +5 -5
  42. package/dist/bin/pushary.js +3 -2
  43. package/dist/{chunk-KVHP5ZME.js → chunk-2LAHXHBR.js} +1 -1
  44. package/dist/{chunk-RLQWNEPM.js → chunk-43GMYSS4.js} +15 -0
  45. package/dist/{chunk-5NDB3YOE.js → chunk-54ELC7D7.js} +1 -1
  46. package/dist/{chunk-VPLQFXCH.js → chunk-5NAZ7SFS.js} +1 -1
  47. package/dist/chunk-7FE2KABY.js +156 -0
  48. package/dist/{chunk-DHR4F2CR.js → chunk-7TPQEWUZ.js} +1 -1
  49. package/dist/{chunk-Y4WVZIEI.js → chunk-AJYV3VGU.js} +1 -1
  50. package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
  51. package/dist/{chunk-OCMOTJTW.js → chunk-B7UZHEBL.js} +29 -22
  52. package/dist/{chunk-7HYJPOGG.js → chunk-BQBEH6TV.js} +5 -5
  53. package/dist/{chunk-OTPJ4FUR.js → chunk-BVBEURWJ.js} +1 -1
  54. package/dist/{chunk-7DK3ZH3N.js → chunk-CELX75MW.js} +1 -1
  55. package/dist/{chunk-6VXNA5JB.js → chunk-CNPAT3HL.js} +3 -2
  56. package/dist/{chunk-327YMKP5.js → chunk-DYP6SMOR.js} +1 -1
  57. package/dist/chunk-DZDI62LY.js +145 -0
  58. package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
  59. package/dist/{chunk-PS7DQ2SU.js → chunk-F2ICEWZL.js} +101 -19
  60. package/dist/{chunk-3XSP5SML.js → chunk-FQL5E6NG.js} +2 -2
  61. package/dist/{chunk-JHJHCMAF.js → chunk-FXEVRLHC.js} +20 -2
  62. package/dist/{chunk-6V7ZDH3A.js → chunk-GL3FFM7A.js} +7 -4
  63. package/dist/{chunk-NBRM3FMD.js → chunk-H2QZRQR2.js} +1 -1
  64. package/dist/{chunk-GKPURU7P.js → chunk-I463AEG3.js} +1 -1
  65. package/dist/{chunk-XREW7RQ3.js → chunk-JW4KO5AF.js} +1 -1
  66. package/dist/{chunk-OVEGEQSB.js → chunk-KUZWXPUS.js} +3 -2
  67. package/dist/{chunk-JVZX5YRJ.js → chunk-L4E2BIJ2.js} +80 -7
  68. package/dist/{chunk-RNKPLUWC.js → chunk-OSALSIUF.js} +1 -1
  69. package/dist/{chunk-MFNQ5IMW.js → chunk-PYA4FLKI.js} +32 -21
  70. package/dist/chunk-QCGNGEGE.js +15 -0
  71. package/dist/chunk-S6LYP7QA.js +127 -0
  72. package/dist/{chunk-OADGBOP7.js → chunk-TDZFPMSZ.js} +70 -11
  73. package/dist/{chunk-JJV5Q4KG.js → chunk-UZNKVRLE.js} +1 -1
  74. package/dist/{chunk-FCUUVHTS.js → chunk-VHWCVD42.js} +1 -1
  75. package/dist/{chunk-3MCEES6Z.js → chunk-VNMKIJZB.js} +15 -2
  76. package/dist/{chunk-6GU55AQQ.js → chunk-Y6BWPERV.js} +90 -7
  77. package/dist/chunk-YKLCWWEI.js +76 -0
  78. package/dist/src/index.d.ts +3 -0
  79. package/dist/src/index.js +11 -11
  80. package/package.json +3 -2
  81. package/dist/chunk-NESKLV6R.js +0 -72
  82. package/dist/chunk-WDVQ6O4C.js +0 -84
@@ -2,9 +2,17 @@
2
2
  import {
3
3
  describeWaitLadder
4
4
  } from "../chunk-IIENZE2J.js";
5
+ import {
6
+ describeBridgeResolution,
7
+ isBridgeLocator,
8
+ liveBridgeProbe,
9
+ readServiceExecPath,
10
+ resolveBridgeStub,
11
+ versionManagerOf
12
+ } from "../chunk-S6LYP7QA.js";
5
13
  import {
6
14
  readMachineReadiness
7
- } from "../chunk-DHR4F2CR.js";
15
+ } from "../chunk-7TPQEWUZ.js";
8
16
  import {
9
17
  CURSOR_GATE_SCRIPT,
10
18
  agentProbes,
@@ -14,34 +22,44 @@ import {
14
22
  isDetected,
15
23
  isPluginRegistered,
16
24
  vscodeSettingsTargets
17
- } from "../chunk-3XSP5SML.js";
25
+ } from "../chunk-FQL5E6NG.js";
18
26
  import {
27
+ claudeOtherHolders,
19
28
  claudeWired,
29
+ codexOtherHolders,
20
30
  codexWired,
21
31
  cursorMcpAuthorization,
32
+ cursorOtherHolders,
22
33
  cursorWired,
34
+ describeHoldSentence,
35
+ describeOtherHolder,
36
+ geminiOtherHolders,
23
37
  geminiWired,
38
+ holdsLong,
24
39
  pusharyHookExecutable
25
- } from "../chunk-NESKLV6R.js";
40
+ } from "../chunk-DZDI62LY.js";
26
41
  import {
27
42
  OPENCODE_PLUGIN_PLACEHOLDER,
28
43
  openCodePluginDir,
29
44
  openCodePluginFile,
30
45
  openCodeWired
31
- } from "../chunk-7DK3ZH3N.js";
46
+ } from "../chunk-CELX75MW.js";
32
47
  import {
33
48
  GEMINI_HOOK_BINARY,
34
49
  hasGeminiHooks,
35
50
  missingGeminiHookEvents
36
- } from "../chunk-6V7ZDH3A.js";
37
- import "../chunk-KVHP5ZME.js";
38
- import "../chunk-VF3RTVWY.js";
51
+ } from "../chunk-GL3FFM7A.js";
52
+ import "../chunk-2LAHXHBR.js";
53
+ import "../chunk-EZIEXHYM.js";
39
54
  import {
40
55
  daemonHasRemoteProof,
41
56
  daemonServiceManagerAvailable,
57
+ describeDaemonLoginBlock,
42
58
  hasDaemonServiceDefinition,
43
- readDaemonStatus
44
- } from "../chunk-PS7DQ2SU.js";
59
+ readDaemonNeedsLogin,
60
+ readDaemonStatus,
61
+ readInstalledDaemonService
62
+ } from "../chunk-F2ICEWZL.js";
45
63
  import {
46
64
  detectProviderLiveCapabilities,
47
65
  hasSpawnProvider,
@@ -53,11 +71,11 @@ import {
53
71
  } from "../chunk-L4ZCFMWW.js";
54
72
  import {
55
73
  readLedgerSummary
56
- } from "../chunk-FCUUVHTS.js";
57
- import "../chunk-JVZX5YRJ.js";
74
+ } from "../chunk-VHWCVD42.js";
75
+ import "../chunk-L4E2BIJ2.js";
58
76
  import {
59
77
  getMachineId
60
- } from "../chunk-5NDB3YOE.js";
78
+ } from "../chunk-54ELC7D7.js";
61
79
  import {
62
80
  claudeJson,
63
81
  claudeSettings,
@@ -78,19 +96,20 @@ import {
78
96
  readCodexMcpAuth,
79
97
  untrustedCodexHookEvents,
80
98
  vscodePluginDir
81
- } from "../chunk-OVEGEQSB.js";
99
+ } from "../chunk-KUZWXPUS.js";
82
100
  import {
83
101
  describeReach,
84
102
  describeRoundTrip,
85
103
  fetchChannels,
86
104
  reachVerdict,
87
105
  verifyRoundTrip
88
- } from "../chunk-MFNQ5IMW.js";
106
+ } from "../chunk-PYA4FLKI.js";
89
107
  import "../chunk-3EGEA4KH.js";
90
108
  import {
91
109
  checkApiKey,
92
110
  describeKeyCheck
93
- } from "../chunk-Y4WVZIEI.js";
111
+ } from "../chunk-AJYV3VGU.js";
112
+ import "../chunk-QCGNGEGE.js";
94
113
  import {
95
114
  createIo
96
115
  } from "../chunk-5RUIFDTP.js";
@@ -99,8 +118,8 @@ import {
99
118
  } from "../chunk-GMXKITVA.js";
100
119
  import {
101
120
  exitOnHelpFlag
102
- } from "../chunk-OTPJ4FUR.js";
103
- import "../chunk-RLQWNEPM.js";
121
+ } from "../chunk-BVBEURWJ.js";
122
+ import "../chunk-43GMYSS4.js";
104
123
  import {
105
124
  getPackageVersion
106
125
  } from "../chunk-DINDL2CF.js";
@@ -108,7 +127,7 @@ import {
108
127
  keyPrefix,
109
128
  readControlMode,
110
129
  readKeySource
111
- } from "../chunk-RNKPLUWC.js";
130
+ } from "../chunk-OSALSIUF.js";
112
131
  import "../chunk-BC3VCZ3E.js";
113
132
  import {
114
133
  callMcpTool,
@@ -121,7 +140,7 @@ import {
121
140
  import {
122
141
  EXIT
123
142
  } from "../chunk-KZERVKTD.js";
124
- import "../chunk-XREW7RQ3.js";
143
+ import "../chunk-JW4KO5AF.js";
125
144
  import "../chunk-4LL6LG5X.js";
126
145
  import {
127
146
  getBaseUrl
@@ -129,7 +148,7 @@ import {
129
148
  import {
130
149
  CURSOR_GATE_EVENTS,
131
150
  HOOK_LOCATOR_BINARY
132
- } from "../chunk-3MCEES6Z.js";
151
+ } from "../chunk-VNMKIJZB.js";
133
152
 
134
153
  // bin/pushary-doctor.ts
135
154
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -192,6 +211,15 @@ var resolveDoctorExit = ({ keyExit, failures }) => {
192
211
  return EXIT.PROBLEMS_FOUND;
193
212
  };
194
213
 
214
+ // src/diagnostics/roundtrip-plan.ts
215
+ var planRoundTrip = (context) => {
216
+ if (context.roundtrip) return { kind: "always" };
217
+ if (context.noPush) return { kind: "never", why: "--no-push" };
218
+ if (context.json) return { kind: "never", why: "--json" };
219
+ if (!context.stdinIsTty) return { kind: "never", why: "not a terminal" };
220
+ return { kind: "ask", defaultAnswer: true };
221
+ };
222
+
195
223
  // src/setup/agent-exec.ts
196
224
  var firstToken = (command) => {
197
225
  const trimmed = command.trim();
@@ -295,6 +323,21 @@ var check = (passed, label, detail, kind = "config") => {
295
323
  var probes = agentProbes();
296
324
  var installed = (agent) => isDetected(detectAgent(probes[agent]));
297
325
  var agentsChecked = [];
326
+ var otherHoldersByAgent = {};
327
+ var bridgeStub = null;
328
+ var noteHookTarget = (command) => {
329
+ const executable = command ? pusharyHookExecutable(command) : null;
330
+ if (isBridgeLocator(executable)) bridgeStub = executable;
331
+ };
332
+ var reportOtherHolders = (agentLabel, holders) => {
333
+ if (holders.length === 0) return;
334
+ otherHoldersByAgent[agentLabel] = holders;
335
+ for (const holder of holders) {
336
+ const icon = holdsLong(holder) ? warn : dim("\u2013");
337
+ console.log(` ${icon} ${agentLabel}: ${describeOtherHolder(holder)}`);
338
+ }
339
+ console.log(` ${dim(describeHoldSentence(agentLabel))}`);
340
+ };
298
341
  var skipped = (label, why) => {
299
342
  console.log(` ${dim("\u2013")} ${label} ${dim(`(${why})`)}`);
300
343
  };
@@ -418,6 +461,8 @@ var main = async () => {
418
461
  const resolves = commandResolves(preHookCommand);
419
462
  check(resolves, "Claude Code: hook command resolves", resolves ? preHookCommand : `not on PATH \u2014 ${preHookCommand}`);
420
463
  }
464
+ noteHookTarget(preHookCommand);
465
+ reportOtherHolders("Claude Code", claudeOtherHolders(settings));
421
466
  const permissions = settings.permissions;
422
467
  const hasWildcard = permissions?.allow?.some((r) => r === "mcp__pushary__*" || r === "MCP(pushary:*)") ?? false;
423
468
  check(hasWildcard, "Claude Code: Pushary tools auto-allowed", hasWildcard ? "mcp__pushary__*" : "missing");
@@ -485,6 +530,8 @@ var main = async () => {
485
530
  const resolves = commandResolves(hookCommand);
486
531
  check(resolves, "Codex: hook command resolves", resolves ? hookCommand : `not on PATH: ${hookCommand}`);
487
532
  }
533
+ noteHookTarget(hookCommand);
534
+ reportOtherHolders("Codex", codexOtherHolders(codexHooksConfig));
488
535
  const features = codexParsed.features;
489
536
  const hooksFeatureDisabled = !!features && typeof features === "object" && features.hooks === false;
490
537
  check(!hooksFeatureDisabled, "Codex: hooks feature enabled", hooksFeatureDisabled ? "[features].hooks = false in config.toml" : void 0);
@@ -547,11 +594,13 @@ var main = async () => {
547
594
  const resolves = commandResolves(hookCommand);
548
595
  check(resolves, "Gemini CLI: hook command resolves", resolves ? hookCommand : `not on PATH: ${hookCommand}`);
549
596
  }
597
+ noteHookTarget(hookCommand);
550
598
  const geminiTimeout = extractHookTimeout(geminiHooks?.BeforeTool, GEMINI_HOOK_BINARY) ?? extractHookTimeout(geminiHooks?.BeforeTool, HOOK_LOCATOR_BINARY);
551
599
  if (typeof geminiTimeout === "number") {
552
600
  const ok = geminiTimeout >= 3e4;
553
601
  check(ok, "Gemini CLI: hook timeout", ok ? `${geminiTimeout}ms` : `${geminiTimeout}ms too short (Gemini timeouts are milliseconds) \u2014 re-run setup`);
554
602
  }
603
+ reportOtherHolders("Gemini CLI", geminiOtherHolders(geminiSettings2));
555
604
  } else {
556
605
  check(false, "Gemini CLI: native hooks installed", "no Pushary hooks in ~/.gemini/settings.json \u2014 re-run setup");
557
606
  }
@@ -570,6 +619,7 @@ var main = async () => {
570
619
  const controlSupported = hasSpawnProvider(controlCapabilities);
571
620
  const controlMode = readControlMode();
572
621
  const daemon = readDaemonStatus();
622
+ const loginBlock = readDaemonNeedsLogin();
573
623
  const serviceDefined = hasDaemonServiceDefinition();
574
624
  const managerAvailable = daemonServiceManagerAvailable();
575
625
  const controlPolicy = resolveControlPolicy({
@@ -581,8 +631,9 @@ var main = async () => {
581
631
  const controlRequired = controlPolicy === "enabled";
582
632
  const controlUnsupported = controlMode === "on" && controlPolicy !== "enabled";
583
633
  const daemonOk = controlUnsupported ? false : controlPolicy === "disabled" ? daemon.kind === "absent" && !serviceDefined : controlPolicy === "enabled" ? daemonHasRemoteProof(daemon) : controlPolicy === "legacy" ? false : daemon.kind === "absent" && !serviceDefined;
584
- const daemonFailure = daemonOk ? null : controlUnsupported ? "control is required but no usable provider/service-manager pair is available" : controlPolicy === "enabled" && daemon.kind === "running" ? "the daemon has not completed an authenticated heartbeat and control poll" : controlPolicy === "disabled" ? `control is disabled but ${serviceDefined ? "the service definition remains" : `local state is ${daemon.kind}`}` : controlPolicy === "legacy" ? "a legacy service exists without a usable provider/service-manager pair" : `local state is ${daemon.kind}`;
585
- const daemonDetail = daemonFailure ? controlUnsupported ? `${daemonFailure}; re-run setup with \`--control off\`, or restore both requirements` : controlPolicy === "disabled" ? `${daemonFailure}; re-run setup with \`--control off\`` : controlPolicy === "legacy" ? `${daemonFailure}; run setup with \`--control off\`, or restore both requirements` : `${daemonFailure}; run \`pushary daemon install\` to install or repair the background service` : controlPolicy === "disabled" ? "disabled by setup" : controlPolicy === "preserve-absent" ? "legacy absence preserved" : !controlRequired && daemon.kind === "absent" ? "managed control is unsupported on this provider or operating-system session" : null;
634
+ const needsLogin = loginBlock.kind === "blocked" ? `needs login: ${describeDaemonLoginBlock(loginBlock.record)}` : null;
635
+ const daemonFailure = daemonOk ? null : needsLogin ?? (controlUnsupported ? "control is required but no usable provider/service-manager pair is available" : controlPolicy === "enabled" && daemon.kind === "running" ? "the daemon has not completed an authenticated heartbeat and control poll" : controlPolicy === "disabled" ? `control is disabled but ${serviceDefined ? "the service definition remains" : `local state is ${daemon.kind}`}` : controlPolicy === "legacy" ? "a legacy service exists without a usable provider/service-manager pair" : `local state is ${daemon.kind}`);
636
+ const daemonDetail = daemonFailure ? needsLogin ? daemonFailure : controlUnsupported ? `${daemonFailure}; re-run setup with \`--control off\`, or restore both requirements` : controlPolicy === "disabled" ? `${daemonFailure}; re-run setup with \`--control off\`` : controlPolicy === "legacy" ? `${daemonFailure}; run setup with \`--control off\`, or restore both requirements` : `${daemonFailure}; run \`pushary daemon install\` to install or repair the background service` : controlPolicy === "disabled" ? "disabled by setup" : controlPolicy === "preserve-absent" ? "legacy absence preserved" : !controlRequired && daemon.kind === "absent" ? "managed control is unsupported on this provider or operating-system session" : null;
586
637
  check(
587
638
  daemonOk,
588
639
  "Remote-control daemon",
@@ -594,9 +645,23 @@ var main = async () => {
594
645
  `installed ${daemon.installedVersion}`,
595
646
  `machine ${daemon.machineId ?? "none"}`,
596
647
  `heartbeat ${daemon.lastSuccessfulHeartbeatAt ?? "none yet"}`,
597
- `control ${daemon.lastSuccessfulControlAt ?? "none yet"}`
648
+ `control ${daemon.lastSuccessfulControlAt ?? "none yet"}`,
649
+ daemon.lastError ? `last error ${daemon.lastError.message} at ${daemon.lastError.at}` : null
598
650
  ].filter((value) => value !== null).join(" \xB7 ")
599
651
  );
652
+ const serviceFile = serviceDefined ? readInstalledDaemonService() : null;
653
+ const serviceNode = serviceFile ? readServiceExecPath(serviceFile.contents, process.platform) : null;
654
+ if (serviceFile && serviceNode) {
655
+ const present = existsSync(serviceNode);
656
+ const manager = versionManagerOf(serviceNode);
657
+ check(
658
+ present,
659
+ "Remote-control service runtime",
660
+ present ? `${serviceNode}${manager ? ` (managed by ${manager}; a version switch strands the service)` : ""}` : `${serviceNode} is gone${manager ? ` (${manager} switched Node since setup)` : ""}; run \`pushary daemon install\` to repin the service`
661
+ );
662
+ } else if (serviceFile) {
663
+ console.log(` ${warn} Remote-control service runtime ${dim(`(could not read the node path out of ${serviceFile.path})`)}`);
664
+ }
600
665
  if (controlPolicy === "enabled" && apiKey && daemon.machineId) {
601
666
  const serverProof = await readMachineReadiness(apiKey, ["spawn", ...controlCapabilities], {
602
667
  machineId: daemon.machineId
@@ -639,7 +704,9 @@ var main = async () => {
639
704
  "Cursor: permission gate registered",
640
705
  broken.length === 0 ? `~/.cursor/hooks.json \u2192 ${paths_[0]} (${gateEntries.length} events)` : `gate script not found: ${broken[0]} \u2014 re-run setup`
641
706
  );
707
+ noteHookTarget(String(gateEntries[0].entry.command));
642
708
  }
709
+ reportOtherHolders("Cursor", cursorOtherHolders(cursorUserHooksJson));
643
710
  const { authorization: cursorAuth, source: cursorMcpSource } = cursorMcpAuthorization(
644
711
  readJson(join(cursorPluginDir2, "mcp.json")),
645
712
  cursorUserMcpJson
@@ -679,6 +746,7 @@ var main = async () => {
679
746
  "VS Code: permission gate present",
680
747
  resolves ? scriptPath : `gate script not found: ${scriptPath || gateCommand} \u2014 re-run setup`
681
748
  );
749
+ noteHookTarget(gateCommand);
682
750
  }
683
751
  const vscodeMcp = readJson(join(vscodePluginDir2, ".mcp.json"));
684
752
  const vscodeServers = vscodeMcp?.mcpServers ?? {};
@@ -737,6 +805,10 @@ var main = async () => {
737
805
  } else {
738
806
  skipped("OpenCode", installed("opencode") ? "installed, but Pushary is not set up for it" : "not installed");
739
807
  }
808
+ if (bridgeStub) {
809
+ const resolution = resolveBridgeStub(bridgeStub, liveBridgeProbe());
810
+ check(resolution.kind === "bundle", "Mac app behind pushary-bridge", describeBridgeResolution(resolution));
811
+ }
740
812
  check(
741
813
  agentsChecked.length > 0,
742
814
  "Wired to at least one agent",
@@ -886,7 +958,16 @@ var main = async () => {
886
958
  const msg = err instanceof Error ? err.message : "network error";
887
959
  check(false, "Push notification sent", msg);
888
960
  }
889
- const testQuestion = options.roundtrip ? true : options.noPush || options.json || !process.stdin.isTTY ? false : await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false });
961
+ const roundTripPlan = planRoundTrip({
962
+ roundtrip: options.roundtrip,
963
+ noPush: options.noPush,
964
+ json: options.json,
965
+ stdinIsTty: process.stdin.isTTY === true
966
+ });
967
+ const testQuestion = roundTripPlan.kind === "always" ? true : roundTripPlan.kind === "never" ? false : await confirm({
968
+ message: "Test question roundtrip? (sends a push notification; the only check that proves you can answer one)",
969
+ default: roundTripPlan.defaultAnswer
970
+ });
890
971
  if (testQuestion) {
891
972
  console.log();
892
973
  console.log(` ${dim("Question Roundtrip")}`);
@@ -964,9 +1045,11 @@ var main = async () => {
964
1045
  exitCode,
965
1046
  keyCheck: keyVerdict?.kind ?? null,
966
1047
  daemon,
1048
+ daemonNeedsLogin: loginBlock.kind === "blocked" ? loginBlock.record : null,
967
1049
  passed: results.filter((r) => r.passed).length,
968
1050
  failed: failed.length,
969
- checks: results.map((r) => ({ label: r.label, passed: r.passed, detail: r.detail ?? null }))
1051
+ checks: results.map((r) => ({ label: r.label, passed: r.passed, detail: r.detail ?? null })),
1052
+ otherHolders: otherHoldersByAgent
970
1053
  }, null, 2)}
971
1054
  `);
972
1055
  }
@@ -11,11 +11,11 @@ import {
11
11
  cancelQuestion,
12
12
  pollForAnswer,
13
13
  savePendingQuestion
14
- } from "../chunk-JHJHCMAF.js";
14
+ } from "../chunk-FXEVRLHC.js";
15
15
  import {
16
16
  getMachineId
17
- } from "../chunk-5NDB3YOE.js";
18
- import "../chunk-OVEGEQSB.js";
17
+ } from "../chunk-54ELC7D7.js";
18
+ import "../chunk-KUZWXPUS.js";
19
19
  import "../chunk-BSZYIAZL.js";
20
20
  import "../chunk-SAF6HGAA.js";
21
21
  import "../chunk-7QLSKOSU.js";
@@ -34,7 +34,7 @@ import {
34
34
  planElicitationAsk,
35
35
  readElicitationFields,
36
36
  renderElicitationOutput
37
- } from "../chunk-3MCEES6Z.js";
37
+ } from "../chunk-VNMKIJZB.js";
38
38
 
39
39
  // bin/pushary-elicitation-hook.ts
40
40
  import { basename } from "path";
@@ -5,16 +5,19 @@ import {
5
5
  postLiveSession,
6
6
  postLiveSessionUntilAccepted,
7
7
  startLiveSessionHeartbeat
8
- } from "../chunk-6VXNA5JB.js";
9
- import "../chunk-WDVQ6O4C.js";
8
+ } from "../chunk-CNPAT3HL.js";
9
+ import "../chunk-7FE2KABY.js";
10
10
  import "../chunk-QZCFVWA7.js";
11
11
  import {
12
12
  transcriptsEnabled
13
- } from "../chunk-JJV5Q4KG.js";
13
+ } from "../chunk-UZNKVRLE.js";
14
14
  import {
15
+ SESSION_IDLE_TIMEOUT_MS,
16
+ describeIdleWindow,
17
+ idleDeadlinePassed,
15
18
  startProcessOwnership,
16
19
  withSpawnProcessOwnership
17
- } from "../chunk-6GU55AQQ.js";
20
+ } from "../chunk-Y6BWPERV.js";
18
21
  import {
19
22
  spawnClaude
20
23
  } from "../chunk-XHKBHWLX.js";
@@ -23,17 +26,17 @@ import {
23
26
  } from "../chunk-L4ZCFMWW.js";
24
27
  import {
25
28
  getMachineId
26
- } from "../chunk-5NDB3YOE.js";
27
- import "../chunk-OVEGEQSB.js";
28
- import "../chunk-RNKPLUWC.js";
29
+ } from "../chunk-54ELC7D7.js";
30
+ import "../chunk-KUZWXPUS.js";
31
+ import "../chunk-OSALSIUF.js";
29
32
  import "../chunk-BC3VCZ3E.js";
30
- import "../chunk-XREW7RQ3.js";
33
+ import "../chunk-JW4KO5AF.js";
31
34
  import "../chunk-4LL6LG5X.js";
32
35
  import {
33
36
  getApiKey,
34
37
  getBaseUrl
35
38
  } from "../chunk-2UMNXADU.js";
36
- import "../chunk-3MCEES6Z.js";
39
+ import "../chunk-VNMKIJZB.js";
37
40
 
38
41
  // bin/pushary-gemini-bridge.ts
39
42
  import { existsSync } from "fs";
@@ -158,11 +161,15 @@ var runGeminiTurn = (options) => new Promise((resolve, reject) => {
158
161
  });
159
162
  var runGeminiBridge = async (options) => {
160
163
  const sessionId = options.resumeSessionId ?? randomUUID();
164
+ const now = options.now ?? Date.now;
165
+ const idleTimeoutMs = options.idleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
161
166
  const ownership = startProcessOwnership({ sessionId, pid: process.pid, spawnId: options.spawnId });
162
167
  let linked = false;
163
168
  let stopHeartbeat = () => {
164
169
  };
165
170
  let firstTurn = !options.resumeSessionId;
171
+ let idleSince = now();
172
+ let idledOut = false;
166
173
  const relay = options.transcripts === false ? null : new LiveTranscriptRelay(options, sessionId);
167
174
  const reportBody = (event, action) => ({
168
175
  event,
@@ -185,7 +192,12 @@ var runGeminiBridge = async (options) => {
185
192
  reportBody("session_start", options.resumeSessionId ? "Session resumed" : "Session started")
186
193
  );
187
194
  linked = true;
188
- stopHeartbeat = startLiveSessionHeartbeat(options, reportBody("heartbeat", "Session online"));
195
+ stopHeartbeat = startLiveSessionHeartbeat(
196
+ options,
197
+ reportBody("heartbeat", "Session online"),
198
+ void 0,
199
+ () => ownership.touch()
200
+ );
189
201
  };
190
202
  const turn = (text, commandId) => {
191
203
  const result = runGeminiTurn({
@@ -210,9 +222,14 @@ var runGeminiBridge = async (options) => {
210
222
  if (!linked) throw error;
211
223
  await report("turn_error", "Gemini turn failed").catch(() => null);
212
224
  }
225
+ idleSince = now();
213
226
  }
214
227
  let pendingAck = null;
215
228
  while (!options.signal.aborted) {
229
+ if (idleDeadlinePassed(idleSince, now(), idleTimeoutMs)) {
230
+ idledOut = true;
231
+ break;
232
+ }
216
233
  try {
217
234
  if (pendingAck) {
218
235
  const acknowledged = await postLiveSession(options, "/api/agent/command/ack", pendingAck);
@@ -232,8 +249,10 @@ var runGeminiBridge = async (options) => {
232
249
  const commandId = string(command?.id);
233
250
  const text = string(command?.text);
234
251
  if (commandId && text) {
252
+ idleSince = now();
235
253
  try {
236
254
  const outcome2 = await turn(text, commandId);
255
+ idleSince = now();
237
256
  if (outcome2 === "aborted") break;
238
257
  void report("stop", "Turn completed").catch(() => null);
239
258
  pendingAck = {
@@ -260,7 +279,10 @@ var runGeminiBridge = async (options) => {
260
279
  await liveSessionSleep(options.pollIntervalMs ?? 2e3, options.signal);
261
280
  }
262
281
  await relay?.flush();
263
- await report("session_closed", "Session stopped").catch(() => null);
282
+ await report(
283
+ "session_closed",
284
+ idledOut ? `Session ended after ${describeIdleWindow(idleTimeoutMs)} idle` : "Session stopped"
285
+ ).catch(() => null);
264
286
  return "stopped";
265
287
  } catch (error) {
266
288
  if (!linked && options.claimId) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  isGatingMoment,
4
4
  recordKeylessMoment
5
- } from "../chunk-FCUUVHTS.js";
5
+ } from "../chunk-VHWCVD42.js";
6
6
  import {
7
7
  readHookInput
8
8
  } from "../chunk-6CUUA7HO.js";
@@ -14,6 +14,7 @@ import {
14
14
  fetchModeState,
15
15
  getPolicy,
16
16
  handlePostToolUse,
17
+ handleSessionEnd,
17
18
  handleStop,
18
19
  handleUserPrompt,
19
20
  policyRequestFor,
@@ -21,7 +22,7 @@ import {
21
22
  readLastUserPrompt,
22
23
  repoKeyFor,
23
24
  reportEvent
24
- } from "../chunk-OADGBOP7.js";
25
+ } from "../chunk-TDZFPMSZ.js";
25
26
  import {
26
27
  DEFAULT_SESSION,
27
28
  askUser,
@@ -29,7 +30,7 @@ import {
29
30
  savePendingQuestion,
30
31
  sendNotification,
31
32
  waitForAnswer
32
- } from "../chunk-JHJHCMAF.js";
33
+ } from "../chunk-FXEVRLHC.js";
33
34
  import {
34
35
  UNNAMED_ACTION,
35
36
  deriveActionBody,
@@ -37,16 +38,16 @@ import {
37
38
  deriveToolTarget,
38
39
  describeToolCall,
39
40
  scopePathFor
40
- } from "../chunk-JVZX5YRJ.js";
41
+ } from "../chunk-L4E2BIJ2.js";
41
42
  import {
42
43
  getMachineId
43
- } from "../chunk-5NDB3YOE.js";
44
- import "../chunk-OVEGEQSB.js";
44
+ } from "../chunk-54ELC7D7.js";
45
+ import "../chunk-KUZWXPUS.js";
45
46
  import "../chunk-BSZYIAZL.js";
46
47
  import "../chunk-SAF6HGAA.js";
47
48
  import "../chunk-7QLSKOSU.js";
48
49
  import "../chunk-KZERVKTD.js";
49
- import "../chunk-XREW7RQ3.js";
50
+ import "../chunk-JW4KO5AF.js";
50
51
  import "../chunk-4LL6LG5X.js";
51
52
  import {
52
53
  getApiKey
@@ -59,7 +60,7 @@ import {
59
60
  hookWaitClamped,
60
61
  hookWaitDeadline,
61
62
  resolveGate
62
- } from "../chunk-3MCEES6Z.js";
63
+ } from "../chunk-VNMKIJZB.js";
63
64
 
64
65
  // bin/pushary-gemini-hook.ts
65
66
  import { basename } from "path";
@@ -327,12 +328,22 @@ var main = async () => {
327
328
  session_id: input.session_id
328
329
  }, GEMINI_AGENT));
329
330
  break;
330
- case "SessionEnd":
331
+ case "AfterAgent":
331
332
  if (geminiOwnsLifecycle(process.env.PUSHARY_GEMINI_BRIDGE)) {
332
333
  await handleStop({
333
334
  cwd: input.cwd,
334
335
  session_id: input.session_id,
335
- transcript_path: input.transcript_path
336
+ transcript_path: input.transcript_path,
337
+ last_assistant_message: input.prompt_response
338
+ }, GEMINI_AGENT);
339
+ }
340
+ break;
341
+ case "SessionEnd":
342
+ if (geminiOwnsLifecycle(process.env.PUSHARY_GEMINI_BRIDGE)) {
343
+ await handleSessionEnd({
344
+ cwd: input.cwd,
345
+ session_id: input.session_id,
346
+ reason: input.reason
336
347
  }, GEMINI_AGENT);
337
348
  }
338
349
  break;
@@ -1,32 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-OCMOTJTW.js";
5
- import "../chunk-KVHP5ZME.js";
6
- import "../chunk-GKPURU7P.js";
7
- import "../chunk-FCUUVHTS.js";
4
+ } from "../chunk-B7UZHEBL.js";
5
+ import "../chunk-2LAHXHBR.js";
6
+ import "../chunk-I463AEG3.js";
7
+ import "../chunk-VHWCVD42.js";
8
8
  import {
9
9
  readHookInput
10
10
  } from "../chunk-6CUUA7HO.js";
11
11
  import "../chunk-YHG74UFF.js";
12
- import "../chunk-OADGBOP7.js";
13
- import "../chunk-JHJHCMAF.js";
14
- import "../chunk-JVZX5YRJ.js";
15
- import "../chunk-5NDB3YOE.js";
16
- import "../chunk-OVEGEQSB.js";
12
+ import "../chunk-TDZFPMSZ.js";
13
+ import "../chunk-FXEVRLHC.js";
14
+ import "../chunk-L4E2BIJ2.js";
15
+ import "../chunk-54ELC7D7.js";
16
+ import "../chunk-KUZWXPUS.js";
17
17
  import {
18
18
  exitOnHelpFlag
19
- } from "../chunk-OTPJ4FUR.js";
20
- import "../chunk-RLQWNEPM.js";
19
+ } from "../chunk-BVBEURWJ.js";
20
+ import "../chunk-43GMYSS4.js";
21
21
  import "../chunk-DINDL2CF.js";
22
22
  import "../chunk-BSZYIAZL.js";
23
23
  import "../chunk-SAF6HGAA.js";
24
24
  import "../chunk-7QLSKOSU.js";
25
25
  import "../chunk-KZERVKTD.js";
26
- import "../chunk-XREW7RQ3.js";
26
+ import "../chunk-JW4KO5AF.js";
27
27
  import "../chunk-4LL6LG5X.js";
28
28
  import "../chunk-2UMNXADU.js";
29
- import "../chunk-3MCEES6Z.js";
29
+ import "../chunk-VNMKIJZB.js";
30
30
 
31
31
  // bin/pushary-hook.ts
32
32
  exitOnHelpFlag("hook");
@@ -2,11 +2,18 @@
2
2
  import {
3
3
  runBrowserLogin
4
4
  } from "../chunk-Z3Z4VK5F.js";
5
+ import {
6
+ recoverDaemonAfterLogin
7
+ } from "../chunk-F2ICEWZL.js";
8
+ import "../chunk-XHKBHWLX.js";
9
+ import "../chunk-L4ZCFMWW.js";
10
+ import "../chunk-54ELC7D7.js";
11
+ import "../chunk-KUZWXPUS.js";
5
12
  import "../chunk-3EGEA4KH.js";
6
13
  import {
7
14
  checkApiKey,
8
15
  describeKeyCheck
9
- } from "../chunk-Y4WVZIEI.js";
16
+ } from "../chunk-AJYV3VGU.js";
10
17
  import {
11
18
  createIo
12
19
  } from "../chunk-5RUIFDTP.js";
@@ -15,15 +22,15 @@ import {
15
22
  } from "../chunk-GMXKITVA.js";
16
23
  import {
17
24
  exitOnHelpFlag
18
- } from "../chunk-OTPJ4FUR.js";
19
- import "../chunk-RLQWNEPM.js";
25
+ } from "../chunk-BVBEURWJ.js";
26
+ import "../chunk-43GMYSS4.js";
20
27
  import {
21
28
  getPackageVersion
22
29
  } from "../chunk-DINDL2CF.js";
23
30
  import {
24
31
  readKeySource,
25
32
  writeKey
26
- } from "../chunk-RNKPLUWC.js";
33
+ } from "../chunk-OSALSIUF.js";
27
34
  import "../chunk-BC3VCZ3E.js";
28
35
  import "../chunk-SAF6HGAA.js";
29
36
  import {
@@ -32,12 +39,12 @@ import {
32
39
  import {
33
40
  EXIT
34
41
  } from "../chunk-KZERVKTD.js";
35
- import "../chunk-XREW7RQ3.js";
42
+ import "../chunk-JW4KO5AF.js";
36
43
  import "../chunk-4LL6LG5X.js";
37
44
  import "../chunk-2UMNXADU.js";
38
45
  import {
39
46
  isValidApiKey
40
- } from "../chunk-3MCEES6Z.js";
47
+ } from "../chunk-VNMKIJZB.js";
41
48
 
42
49
  // bin/pushary-login.ts
43
50
  exitOnHelpFlag("login");
@@ -89,7 +96,11 @@ var store = (apiKey, siteSlug, keyName) => {
89
96
  } else if (result.rc.kind === "declined") {
90
97
  say(` ${yellow("!")} ${dim(`Left ${result.rc.path} alone: line ${result.rc.declinedLine} already sets PUSHARY_API_KEY.`)}`);
91
98
  }
92
- emit({ ok: true, siteSlug, keyName, configPath: result.configPath, rc: result.rc.kind });
99
+ const daemon = recoverDaemonAfterLogin();
100
+ if (daemon.cleared) {
101
+ say(daemon.restarted ? ` ${check} ${dim("Background daemon restarted with the new key.")}` : ` ${yellow("!")} ${dim(`Background daemon may start again${daemon.detail ? ` (${daemon.detail})` : ""}`)}`);
102
+ }
103
+ emit({ ok: true, siteSlug, keyName, configPath: result.configPath, rc: result.rc.kind, daemon });
93
104
  };
94
105
  var loginWithToken = async () => {
95
106
  const token = await readStdin();