pushary 1.9.57 → 1.9.59

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 (119) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/{api-RQAY3A5V.js → api-UVJGNZIF.js} +10 -10
  3. package/dist/bin/pushary-bell-hook.js +27 -27
  4. package/dist/bin/pushary-bell.js +10 -10
  5. package/dist/bin/pushary-claude.js +26 -26
  6. package/dist/bin/pushary-clean.js +49 -46
  7. package/dist/bin/pushary-codex-bridge.js +21 -21
  8. package/dist/bin/pushary-codex-hook.js +32 -32
  9. package/dist/bin/pushary-codex-session.js +10 -8
  10. package/dist/bin/pushary-codex.js +14 -14
  11. package/dist/bin/pushary-connect.js +27 -26
  12. package/dist/bin/pushary-cowork.js +5 -5
  13. package/dist/bin/pushary-daemon.js +37 -37
  14. package/dist/bin/pushary-disconnect.js +26 -26
  15. package/dist/bin/pushary-doctor.js +41 -39
  16. package/dist/bin/pushary-elicitation-hook.js +27 -27
  17. package/dist/bin/pushary-gemini-bridge.js +15 -15
  18. package/dist/bin/pushary-gemini-hook.js +29 -29
  19. package/dist/bin/pushary-hook.js +31 -31
  20. package/dist/bin/pushary-login.js +13 -13
  21. package/dist/bin/pushary-logout.js +26 -26
  22. package/dist/bin/pushary-mcp.js +11 -11
  23. package/dist/bin/pushary-mode.js +13 -13
  24. package/dist/bin/pushary-notification-hook.js +26 -26
  25. package/dist/bin/pushary-opencode-hook.js +28 -28
  26. package/dist/bin/pushary-permission-denied-hook.js +31 -31
  27. package/dist/bin/pushary-permission-hook.js +30 -30
  28. package/dist/bin/pushary-post-hook.js +26 -26
  29. package/dist/bin/pushary-prompt-hook.js +26 -26
  30. package/dist/bin/pushary-session-end-hook.js +26 -26
  31. package/dist/bin/pushary-session-start-hook.js +33 -33
  32. package/dist/bin/pushary-setup.js +65 -268
  33. package/dist/bin/pushary-stats.js +5 -5
  34. package/dist/bin/pushary-status.js +32 -30
  35. package/dist/bin/pushary-stop-hook.js +26 -26
  36. package/dist/bin/pushary-stopfailure-hook.js +26 -26
  37. package/dist/bin/pushary-suggestions.js +13 -13
  38. package/dist/bin/pushary-transcript-register.js +18 -18
  39. package/dist/bin/pushary-transcripts.js +7 -7
  40. package/dist/bin/pushary-upgrade.js +107 -58
  41. package/dist/bin/pushary-wait.js +13 -13
  42. package/dist/bin/pushary.js +7 -7
  43. package/dist/chunk-2FVP7WRP.js +378 -0
  44. package/dist/{chunk-W6363YYZ.js → chunk-2WMZDKPM.js} +8 -8
  45. package/dist/{chunk-N3NSWWNU.js → chunk-3N2ZTHZZ.js} +1 -1
  46. package/dist/{chunk-JCMSISCC.js → chunk-3NROUNIS.js} +2 -2
  47. package/dist/{chunk-7RZO6VWD.js → chunk-3RKIJM26.js} +2 -2
  48. package/dist/{chunk-Z7Q36I4K.js → chunk-4QF2YLYK.js} +11 -7
  49. package/dist/{chunk-LVDC3RUE.js → chunk-4XXZUFWN.js} +1 -1
  50. package/dist/{chunk-BAJ3A3BU.js → chunk-5BNXJTOZ.js} +5 -2
  51. package/dist/{chunk-A54W5A74.js → chunk-5EFNBL6V.js} +3 -3
  52. package/dist/{chunk-4YTFYILS.js → chunk-5JWCWALO.js} +9 -9
  53. package/dist/{chunk-3IBUZQKM.js → chunk-5O6D4J2K.js} +2 -2
  54. package/dist/{chunk-DDQRYX2L.js → chunk-5PP4455M.js} +3 -3
  55. package/dist/{chunk-NQDEMRAS.js → chunk-6ONSTCDJ.js} +5 -5
  56. package/dist/{chunk-ECJJKDP7.js → chunk-75UYYIFJ.js} +1 -1
  57. package/dist/{chunk-GZL3KEPC.js → chunk-762CDAJJ.js} +140 -136
  58. package/dist/{chunk-YKYVDHIY.js → chunk-AS4Z6KS4.js} +1 -1
  59. package/dist/{chunk-4YNNAF53.js → chunk-AY32RSUC.js} +2 -2
  60. package/dist/{chunk-I43PY5G3.js → chunk-BKVX53QR.js} +1 -1
  61. package/dist/{chunk-5SFM6XB4.js → chunk-BMPZ5HVJ.js} +2 -2
  62. package/dist/{chunk-7OQ4AJC7.js → chunk-BRUHVTFB.js} +2 -7
  63. package/dist/{chunk-OZSP4RIQ.js → chunk-CPRA7IEK.js} +174 -41
  64. package/dist/{chunk-C3IWX6KU.js → chunk-D6CXQIXO.js} +1 -1
  65. package/dist/{chunk-T45PAPOG.js → chunk-DK35M4TH.js} +5 -5
  66. package/dist/{chunk-K45CFSYV.js → chunk-DS7X36Z3.js} +4 -4
  67. package/dist/{chunk-IBSRULIY.js → chunk-EBQTWH5O.js} +10 -10
  68. package/dist/{chunk-OLNBYAPI.js → chunk-EHCIQA5D.js} +4 -4
  69. package/dist/{chunk-2NIRYGTK.js → chunk-IBXHVYN2.js} +2 -2
  70. package/dist/{chunk-HCIB4YNB.js → chunk-JAKBCKEB.js} +3 -3
  71. package/dist/{chunk-PZDMZ7LM.js → chunk-JBCMXSBO.js} +5 -5
  72. package/dist/{chunk-IRZ5FVWG.js → chunk-KAJDKYUM.js} +3 -3
  73. package/dist/{chunk-AN6TVB2H.js → chunk-KHDD74MK.js} +5 -5
  74. package/dist/{chunk-ZZB4J6CX.js → chunk-KQHY6L3X.js} +4 -4
  75. package/dist/{chunk-UPFFO4F6.js → chunk-KQWN7C5C.js} +4 -4
  76. package/dist/{chunk-D2EETYAL.js → chunk-KW3DED2A.js} +14 -369
  77. package/dist/{chunk-TWLWDIFB.js → chunk-KZCYHWDF.js} +4 -2
  78. package/dist/{chunk-RFAF3TOX.js → chunk-L3NVHPBU.js} +94 -15
  79. package/dist/{chunk-LVZYIUSR.js → chunk-LAEW5QQD.js} +1 -1
  80. package/dist/{chunk-SQ5G6A6K.js → chunk-M46WDK64.js} +1 -1
  81. package/dist/{chunk-LVWXRXT7.js → chunk-M6MOQBR5.js} +1 -1
  82. package/dist/{chunk-FBKVCODG.js → chunk-MA37WNVS.js} +3 -3
  83. package/dist/{chunk-XTZHUIGJ.js → chunk-MMLIQDTG.js} +1 -1
  84. package/dist/{chunk-CB565EVO.js → chunk-MSU3VKGJ.js} +2 -2
  85. package/dist/{chunk-ZU6L34DQ.js → chunk-MYU5D5VH.js} +4 -4
  86. package/dist/{chunk-WXZN5CS3.js → chunk-OZIXBN3O.js} +2 -2
  87. package/dist/{chunk-JSQOHMNJ.js → chunk-PVANKDWM.js} +1 -1
  88. package/dist/{chunk-JYXIARGW.js → chunk-RAMQBSVZ.js} +1 -1
  89. package/dist/{chunk-S5LX25XE.js → chunk-RQDP2SJY.js} +1 -1
  90. package/dist/{chunk-DS6OR5OS.js → chunk-SIU6OWA6.js} +6 -6
  91. package/dist/{chunk-QLDTBZBP.js → chunk-T65RIQK2.js} +6 -6
  92. package/dist/{chunk-2PJENKRJ.js → chunk-TCH5UCPR.js} +77 -30
  93. package/dist/{chunk-QQYUWTYS.js → chunk-TDOKDCFH.js} +3 -3
  94. package/dist/chunk-UFJS4RR7.js +266 -0
  95. package/dist/{chunk-AEXACKDR.js → chunk-US3IT6ZC.js} +3 -3
  96. package/dist/{chunk-4QD7VE4E.js → chunk-UTJ5465F.js} +2 -2
  97. package/dist/{chunk-M7NX25ZA.js → chunk-UVZK5LIK.js} +1 -1
  98. package/dist/{chunk-IUOUNRLH.js → chunk-V3PDUDPB.js} +2 -2
  99. package/dist/{chunk-K5SDLOFS.js → chunk-VAJHDTJX.js} +11 -11
  100. package/dist/{chunk-QTCFEKLJ.js → chunk-W47WL6JY.js} +1 -1
  101. package/dist/{chunk-6QM54CSJ.js → chunk-WW24SG27.js} +5 -5
  102. package/dist/{chunk-3TJLPABQ.js → chunk-WWAVHLGS.js} +3 -3
  103. package/dist/{chunk-CUEUANIU.js → chunk-X4ES7QBT.js} +5 -5
  104. package/dist/{chunk-XXNT4FGA.js → chunk-XGP5VSQG.js} +1 -1
  105. package/dist/{chunk-LW7IWGSV.js → chunk-YQVKG5MC.js} +5 -5
  106. package/dist/{codex-app-cards-switch-SVGTZ2UN.js → codex-app-cards-switch-N77RWKCO.js} +21 -19
  107. package/dist/{codex-desktop-launch-SH7OUEEC.js → codex-desktop-launch-KIX7OMTL.js} +3 -1
  108. package/dist/{codex-session-launch-IQ57OL3N.js → codex-session-launch-XV7SIIWP.js} +18 -18
  109. package/dist/{config-SYLUYZQE.js → config-HH4DYYNX.js} +3 -3
  110. package/dist/global-upgrade-YWIVJIUR.js +34 -0
  111. package/dist/{identity-6KRUO2YO.js → identity-4J7Y2MW3.js} +4 -4
  112. package/dist/{live-capabilities-RKUHAWT6.js → live-capabilities-LHZBCI5U.js} +4 -4
  113. package/dist/{paths-MUPQZNLG.js → paths-ES3V7BWQ.js} +5 -3
  114. package/dist/{reapply-YZXZCWCI.js → reapply-KA5SIYWX.js} +26 -26
  115. package/dist/src/index.js +21 -21
  116. package/dist/{upgrade-lock-JXP27UKI.js → upgrade-lock-TJPKU5HL.js} +4 -6
  117. package/dist/{usage-OUV76AIY.js → usage-7ALXBJVJ.js} +3 -3
  118. package/package.json +1 -1
  119. package/dist/global-upgrade-SRCWQE7N.js +0 -26
@@ -3,18 +3,18 @@ import {
3
3
  addGeminiMcpServer,
4
4
  removeGeminiMcpServer,
5
5
  supportedClaudeEvents
6
- } from "./chunk-QQYUWTYS.js";
6
+ } from "./chunk-TDOKDCFH.js";
7
7
  import {
8
8
  CLAUDE_CODE_AGENT,
9
9
  compatibleToolLookup
10
- } from "./chunk-4YTFYILS.js";
10
+ } from "./chunk-5JWCWALO.js";
11
11
  import {
12
12
  guardBinary,
13
13
  guardBinaryRefusing,
14
14
  holdBudgetOf,
15
15
  isProtectedByScope,
16
16
  registerHookEntry
17
- } from "./chunk-TWLWDIFB.js";
17
+ } from "./chunk-KZCYHWDF.js";
18
18
  import {
19
19
  HOOK_COMPATIBLE_PROFILES,
20
20
  HOOK_LOCATOR_BINARY,
@@ -26,7 +26,7 @@ import {
26
26
  pusharyMissingReason,
27
27
  refusalOutput,
28
28
  withHookHoldBudget
29
- } from "./chunk-GZL3KEPC.js";
29
+ } from "./chunk-762CDAJJ.js";
30
30
 
31
31
  // src/claude-config.ts
32
32
  import { join } from "path";
@@ -1,3 +1,13 @@
1
+ import {
2
+ describeAppRepair,
3
+ describeJustPairedRepair,
4
+ describeReach,
5
+ describeVerifyOutcome,
6
+ ownAppPlatforms,
7
+ reachVerdict,
8
+ reachesYou,
9
+ verifyDelivery
10
+ } from "./chunk-2FVP7WRP.js";
1
11
  import {
2
12
  generateKeypair,
3
13
  openSealedKey,
@@ -12,371 +22,16 @@ import {
12
22
  terminalColumns,
13
23
  useColor
14
24
  } from "./chunk-FO3MDD26.js";
15
- import {
16
- callMcpTool
17
- } from "./chunk-A54W5A74.js";
18
25
  import {
19
26
  reportedInstallSource
20
- } from "./chunk-7RZO6VWD.js";
27
+ } from "./chunk-3RKIJM26.js";
21
28
  import {
22
29
  NPX_LATEST_COMMAND
23
- } from "./chunk-5SFM6XB4.js";
30
+ } from "./chunk-BMPZ5HVJ.js";
24
31
  import {
25
32
  NO_PAIRING_COMPUTER,
26
- PAIR_TTL_SECONDS,
27
- classifyConfirmResponse,
28
- isAnswerSource
29
- } from "./chunk-GZL3KEPC.js";
30
-
31
- // src/reach.ts
32
- var reachVerdict = (channels) => {
33
- const { yours, site } = channels;
34
- if (yours === null || site === null) {
35
- return channels.phoneReachable || channels.webActive > 0 ? { kind: "yours" } : { kind: "nobody" };
36
- }
37
- if (yours.phoneReachable) return { kind: "yours" };
38
- const solo = site.otherMembers === 0;
39
- if (solo) {
40
- return site.phoneReachable || site.webActive > 0 ? { kind: "yours" } : { kind: "nobody" };
41
- }
42
- if (site.appDevices === 0 && site.webActive === 0) return { kind: "nobody" };
43
- if (site.webActive > 0 && !yours.webAttributable) {
44
- return { kind: "unattributable", otherMembers: site.otherMembers };
45
- }
46
- return { kind: "others", otherMembers: site.otherMembers };
47
- };
48
- var reachesYou = (channels) => reachVerdict(channels).kind === "yours";
49
- var plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
50
- var describeReach = (channels) => {
51
- const verdict = reachVerdict(channels);
52
- if (verdict.kind === "nobody") return "nothing can receive an approval";
53
- const parts = [];
54
- const own = channels.yours;
55
- if (verdict.kind === "yours" && own !== null && own.appDevices > 0) {
56
- const platforms = own.appPlatforms.length > 0 ? ` (${own.appPlatforms.join(", ")})` : "";
57
- parts.push(`${plural(own.appDevices, "app device")}${platforms}`);
58
- } else if (channels.appDevices > 0) {
59
- const platforms = channels.appPlatforms.length > 0 ? ` (${channels.appPlatforms.join(", ")})` : "";
60
- parts.push(`${plural(channels.appDevices, "app device")}${platforms}`);
61
- }
62
- if (channels.webPhone > 0) parts.push(`${plural(channels.webPhone, "browser")} on a phone`);
63
- if (channels.webDesktop > 0) parts.push(`${plural(channels.webDesktop, "browser")} on a desktop`);
64
- const summary = parts.length > 0 ? parts.join(", ") : "a device";
65
- if (verdict.kind === "others") return `${summary}, none of them yours`;
66
- if (verdict.kind === "unattributable") return `${summary}, and no way to tell whose`;
67
- return summary;
68
- };
69
- var describeAppRepair = (verdict) => {
70
- switch (verdict.kind) {
71
- case "yours":
72
- return null;
73
- case "nobody":
74
- return [
75
- "Open the Pushary app, sign in, and allow notifications.",
76
- `If this terminal was never paired with your phone, run: ${NPX_LATEST_COMMAND} setup --connect app`
77
- ];
78
- case "others":
79
- return [
80
- `Reachable devices belong to other members of this workspace (${verdict.otherMembers}).`,
81
- "Sign in on the Pushary app with your own account so approvals can reach you."
82
- ];
83
- case "unattributable":
84
- return [
85
- "Something is reachable here, but not identifiably yours.",
86
- "Sign in on the Pushary app so your approvals are routed to your device."
87
- ];
88
- }
89
- };
90
- var describeJustPairedRepair = (verdict) => {
91
- if (verdict.kind === "nobody") {
92
- return [
93
- "The app asks for the notification permission after it grants the pairing.",
94
- "Open the Pushary app and allow notifications. That is the whole remaining step."
95
- ];
96
- }
97
- return describeAppRepair(verdict) ?? [];
98
- };
99
-
100
- // src/setup/verify.ts
101
- var plural2 = (count, one) => `${count} ${one}${count === 1 ? "" : "s"}`;
102
- var SETUP_AGENT_NAME = "Pushary setup";
103
- var ownAppPlatforms = (channels) => channels.yours?.appPlatforms ?? channels.appPlatforms;
104
- var describeRecipients = (push, channels) => {
105
- const parts = [];
106
- if (push.app > 0) {
107
- const platforms = ownAppPlatforms(channels);
108
- const suffix = platforms.length > 0 ? ` (${platforms.join(", ")})` : "";
109
- parts.push(`${plural2(push.app, "app device")}${suffix}`);
110
- }
111
- if (push.web > 0) parts.push(plural2(push.web, "browser"));
112
- return parts.length > 0 ? parts.join(" and ") : plural2(push.total, "device");
113
- };
114
- var classifyDelivery = (channels, push) => {
115
- if (push.total <= 0) return { kind: "no-recipients" };
116
- const verdict = reachVerdict(channels);
117
- if (verdict.kind === "yours" && (push.app > 0 || channels.yours === null || channels.site?.otherMembers === 0)) {
118
- return { kind: "delivered", recipients: push.total, deviceLabel: describeRecipients(push, channels) };
119
- }
120
- if (verdict.kind === "unattributable") {
121
- return { kind: "unattributable", recipients: push.total, otherMembers: verdict.otherMembers };
122
- }
123
- if (verdict.kind === "others") {
124
- return { kind: "others-only", recipients: push.total, otherMembers: verdict.otherMembers };
125
- }
126
- if (verdict.kind === "yours") {
127
- const otherMembers = channels.site?.otherMembers ?? 0;
128
- return { kind: "unattributable", recipients: push.total, otherMembers };
129
- }
130
- return { kind: "delivered", recipients: push.total, deviceLabel: describeRecipients(push, channels) };
131
- };
132
- var sendTestNotification = async (apiKey) => {
133
- const result = await callMcpTool(apiKey, "send_notification", {
134
- title: "Pushary is connected",
135
- body: "This setup notification reached you. Agent hooks still need a real-agent check.",
136
- agentName: SETUP_AGENT_NAME
137
- }, { timeoutMs: 2e4 });
138
- const toCount = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
139
- const app = toCount(result.delivery?.mobile?.recipients);
140
- const web = toCount(result.delivery?.web?.recipients);
141
- return { app, web, total: typeof result.sent === "number" ? result.sent : app + web };
142
- };
143
- var verifyDelivery = async (apiKey, channels, deps = {}) => {
144
- const send = deps.send ?? sendTestNotification;
145
- try {
146
- return classifyDelivery(channels, await send(apiKey));
147
- } catch (err) {
148
- return { kind: "error", detail: err instanceof Error ? err.message : "send failed" };
149
- }
150
- };
151
- var describeVerifyOutcome = (outcome) => {
152
- switch (outcome.kind) {
153
- case "delivered":
154
- return { ok: true, message: `Delivered to ${outcome.deviceLabel}. Push works.`, next: [] };
155
- case "no-recipients":
156
- return {
157
- ok: false,
158
- message: "Nothing to deliver to. Agents are configured; approvals will fall back to the terminal.",
159
- next: [
160
- "Run pushary pair to pair your phone.",
161
- "Then pushary doctor --roundtrip to test a synthetic question and answer."
162
- ]
163
- };
164
- case "others-only":
165
- return {
166
- ok: false,
167
- message: `Delivered to ${plural2(outcome.recipients, "device")} on this site, none of them yours.`,
168
- next: [
169
- `Approvals for your agent would wake one of your ${plural2(outcome.otherMembers, "teammate")}.`,
170
- "Run pushary pair to pair your own phone."
171
- ]
172
- };
173
- case "unattributable":
174
- return {
175
- ok: false,
176
- message: `Delivered to ${plural2(outcome.recipients, "device")} on this site, and no way to tell whose.`,
177
- next: [
178
- "Browser subscriptions carry no owner, and this site has other members.",
179
- "Install the Pushary app and pair it with pushary pair, or run pushary doctor --roundtrip to prove it reaches you."
180
- ]
181
- };
182
- case "error":
183
- return {
184
- ok: false,
185
- message: `Could not send the test notification (${outcome.detail}).`,
186
- next: ["Agents are configured. Run pushary doctor once the connection is back."]
187
- };
188
- }
189
- };
190
- var ROUND_TRIP_QUESTION = "Pushary delivery test. Answer either way to test returning an answer. This does not test your agent hooks.";
191
- var ROUND_TRIP_TIMEOUT_MS = 55e3;
192
- var reportNoInterrupt = async () => {
193
- };
194
- var defaultAsk = (apiKey, agentName) => (
195
- // wait:false so the poll is ours: the blocking form returns on the SITE's
196
- // policy timeout, which a user may have set to seconds, and a setup check
197
- // reporting "unanswered" after three seconds would be a lie about the product.
198
- callMcpTool(apiKey, "ask_user", {
199
- question: ROUND_TRIP_QUESTION,
200
- type: "confirm",
201
- agentName,
202
- wait: false
203
- }, { timeoutMs: 15e3 })
204
- );
205
- var defaultWait = (apiKey, correlationId) => callMcpTool(apiKey, "wait_for_answer", {
206
- correlationId,
207
- timeoutMs: ROUND_TRIP_TIMEOUT_MS
208
- }, { timeoutMs: ROUND_TRIP_TIMEOUT_MS + 5e3 });
209
- var defaultCancel = async (apiKey, correlationId) => {
210
- await callMcpTool(apiKey, "cancel_question", { correlationId }, { timeoutMs: 1e4 }).catch(() => {
211
- });
212
- };
213
- var answeredRoundTrip = (answer, correlationId, latencyMs) => {
214
- if (answer.value === void 0 || classifyConfirmResponse(answer.value) === null) {
215
- return { kind: "error", detail: "the server reported an answer without a valid Yes, No or defer value" };
216
- }
217
- return {
218
- kind: "answered",
219
- value: answer.value,
220
- latencyMs,
221
- correlationId,
222
- ...isAnswerSource(answer.answerSource) ? { answerSource: answer.answerSource } : {}
223
- };
224
- };
225
- var verifyRoundTrip = async (apiKey, agentName = SETUP_AGENT_NAME, deps = {}) => {
226
- const ask = deps.ask ?? defaultAsk;
227
- const wait = deps.wait ?? defaultWait;
228
- const cancel = deps.cancel ?? defaultCancel;
229
- const interrupted = deps.interrupted ?? reportNoInterrupt;
230
- const now = deps.now ?? Date.now;
231
- const started = now();
232
- let asked;
233
- try {
234
- asked = await ask(apiKey, agentName);
235
- } catch (err) {
236
- return { kind: "error", detail: err instanceof Error ? err.message : "ask failed" };
237
- }
238
- let shutdown;
239
- const stop = (code) => {
240
- if (shutdown) return;
241
- shutdown = (async () => {
242
- let timer;
243
- try {
244
- await Promise.race([
245
- Promise.all([
246
- cancel(apiKey, asked.correlationId).catch(() => {
247
- }),
248
- interrupted(code).catch(() => {
249
- })
250
- ]),
251
- new Promise((resolve) => {
252
- timer = setTimeout(resolve, 4e3);
253
- })
254
- ]);
255
- } finally {
256
- clearTimeout(timer);
257
- process.stderr.write("Delivery test interrupted. Cleanup was attempted; an unresolved question expires automatically.\n");
258
- process.exit(code);
259
- }
260
- })();
261
- };
262
- const onInterrupt = () => stop(130);
263
- const onTerminate = () => stop(143);
264
- if (asked.correlationId) {
265
- process.on("SIGINT", onInterrupt);
266
- process.on("SIGTERM", onTerminate);
267
- }
268
- let answerObserved = false;
269
- try {
270
- if (asked.noDevices || asked.warning) return { kind: "no-recipients" };
271
- if (asked.mode === "notify_only" || asked.status === "notified") return { kind: "notify-only" };
272
- if (!asked.correlationId) return { kind: "error", detail: "no correlationId returned" };
273
- const answer = asked.answered === true ? asked : await wait(apiKey, asked.correlationId);
274
- if (answer.answered === true) {
275
- const outcome = answeredRoundTrip(answer, asked.correlationId, now() - started);
276
- answerObserved = outcome.kind === "answered";
277
- return outcome;
278
- }
279
- return { kind: "unanswered" };
280
- } catch (err) {
281
- return { kind: "error", detail: err instanceof Error ? err.message : "wait failed" };
282
- } finally {
283
- if (shutdown) await shutdown;
284
- process.off("SIGINT", onInterrupt);
285
- process.off("SIGTERM", onTerminate);
286
- if (!answerObserved && asked.correlationId) await cancel(apiKey, asked.correlationId).catch(() => {
287
- });
288
- }
289
- };
290
- var describeAnswerSource = (source) => {
291
- switch (source) {
292
- case "mobile_app":
293
- case "mobile_background":
294
- return "Answered on your phone.";
295
- case "mac_app":
296
- return "Answered on your Mac.";
297
- case "sandbox":
298
- return "This was a simulated sandbox answer.";
299
- default:
300
- return source ? `Answer source: ${source}. This was a synthetic test.` : "The answering surface was not reported. This was a synthetic test.";
301
- }
302
- };
303
- var NOTIFICATION_ANSWER_STEPS = /* @__PURE__ */ new Map([
304
- ["ios", "Press and hold the notification, then tap Approve."],
305
- ["android", "Expand the notification, then tap Approve."]
306
- ]);
307
- var EITHER_PHONE_ANSWER_STEP = "Press and hold the notification on iPhone, or expand it on Android, then tap Approve.";
308
- var notificationAnswerStep = (appPlatforms) => {
309
- const platforms = [...new Set(appPlatforms)];
310
- const [onlyPlatform] = platforms;
311
- if (onlyPlatform === void 0) return null;
312
- if (platforms.length > 1) return EITHER_PHONE_ANSWER_STEP;
313
- return NOTIFICATION_ANSWER_STEPS.get(onlyPlatform) ?? EITHER_PHONE_ANSWER_STEP;
314
- };
315
- var WAITING_FOR_TAP = "Waiting for you to tap the notification";
316
- var WAITING_FOR_ANSWER = "Waiting for your answer";
317
- var describeAnswerTestPrompt = (appPlatforms) => {
318
- const step = notificationAnswerStep(appPlatforms);
319
- return { step, waiting: step === null ? WAITING_FOR_TAP : WAITING_FOR_ANSWER };
320
- };
321
- var CHECK_THE_NOTIFICATION_OPENED = "Check the notification actually opened something you could tap, then run pushary doctor --roundtrip.";
322
- var unansweredNextStep = (appPlatforms) => {
323
- const step = notificationAnswerStep(appPlatforms);
324
- return step === null ? CHECK_THE_NOTIFICATION_OPENED : `To send another one, run pushary doctor --roundtrip. ${step}`;
325
- };
326
- var describeRoundTripOutcome = (outcome, appPlatforms = []) => {
327
- switch (outcome.kind) {
328
- case "answered":
329
- return {
330
- ok: true,
331
- message: `Answered "${outcome.value}" in ${(outcome.latencyMs / 1e3).toFixed(1)}s. ${describeAnswerSource(outcome.answerSource)}`,
332
- next: []
333
- };
334
- case "unanswered":
335
- return {
336
- ok: false,
337
- message: "The question was sent and nothing came back.",
338
- next: [
339
- "A push that arrives and cannot be answered is the most common way this breaks, so this is worth resolving now.",
340
- unansweredNextStep(appPlatforms)
341
- ]
342
- };
343
- case "notify-only":
344
- return {
345
- ok: true,
346
- message: "Sent. This site is in notify-only mode, so questions never wait for an answer.",
347
- next: ["Switch with pushary mode push_first if you want approvals to hold for your phone."]
348
- };
349
- case "no-recipients":
350
- return {
351
- ok: false,
352
- message: "Nothing to ask. No phone or browser is connected to this site.",
353
- next: [
354
- "Run pushary pair to pair your phone.",
355
- "Or pushary pair --app if you already have the Pushary app."
356
- ]
357
- };
358
- case "error":
359
- return {
360
- ok: false,
361
- message: `Could not complete the round trip (${outcome.detail}).`,
362
- next: ["Agents are configured. Run pushary doctor --roundtrip once the connection is back."]
363
- };
364
- }
365
- };
366
- var describeRoundTrip = (outcome, appPlatforms = []) => {
367
- const report = describeRoundTripOutcome(outcome, appPlatforms);
368
- return { ...report, next: [...report.next, "Agent hook activation remains unverified. Restart your agent and check one real approval."] };
369
- };
370
- var roundTripEvidence = (outcome) => ({
371
- kind: outcome?.kind ?? "skipped",
372
- ...outcome?.kind === "answered" ? {
373
- correlationId: outcome.correlationId ?? null,
374
- answerSource: outcome.answerSource ?? null,
375
- value: outcome.value,
376
- latencyMs: outcome.latencyMs
377
- } : {},
378
- hookActivation: "unverified"
379
- });
33
+ PAIR_TTL_SECONDS
34
+ } from "./chunk-762CDAJJ.js";
380
35
 
381
36
  // src/onboarding.ts
382
37
  import qrcodeTerminal from "qrcode-terminal";
@@ -1058,16 +713,6 @@ var waitForYourPhone = async (apiKey, options = {}) => {
1058
713
  };
1059
714
 
1060
715
  export {
1061
- reachVerdict,
1062
- describeReach,
1063
- SETUP_AGENT_NAME,
1064
- ownAppPlatforms,
1065
- verifyRoundTrip,
1066
- notificationAnswerStep,
1067
- describeAnswerTestPrompt,
1068
- describeRoundTripOutcome,
1069
- describeRoundTrip,
1070
- roundTripEvidence,
1071
716
  fetchChannels,
1072
717
  connectDevice,
1073
718
  confirmAppConnection,
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  quoteIfNeeded
3
- } from "./chunk-AEXACKDR.js";
3
+ } from "./chunk-US3IT6ZC.js";
4
4
  import {
5
5
  CODEX_QUESTION_TOOLS,
6
6
  HOOK_BUDGETS,
7
7
  HOOK_LOCATOR_BINARY,
8
8
  withHookHoldBudget
9
- } from "./chunk-GZL3KEPC.js";
9
+ } from "./chunk-762CDAJJ.js";
10
10
 
11
11
  // src/setup/paths.ts
12
12
  import { homedir as homedir2 } from "os";
@@ -463,6 +463,7 @@ var claudeMcpReceipt = () => join2(pusharyDir(), "setup", "claude-mcp.json");
463
463
  var bridgeAgentKey = () => join2(pusharyDir(), "bridge-auth", "agent-key.json");
464
464
  var autoUpdateState = () => join2(pusharyDir(), "auto-update.json");
465
465
  var autoUpdateLock = () => join2(pusharyDir(), "auto-update.lock");
466
+ var serviceOwedBackMark = () => join2(pusharyDir(), "service-owed-back");
466
467
  var autoUpdateLog = () => join2(pusharyDir(), "logs", "auto-update.log");
467
468
  var clientReportClaims = () => join2(pusharyDir(), "client-reports");
468
469
  var claudeRegistrationReceipt = () => join2(pusharyDir(), "setup", "claude-registration.json");
@@ -537,6 +538,7 @@ export {
537
538
  bridgeAgentKey,
538
539
  autoUpdateState,
539
540
  autoUpdateLock,
541
+ serviceOwedBackMark,
540
542
  autoUpdateLog,
541
543
  clientReportClaims,
542
544
  claudeRegistrationReceipt,
@@ -1,20 +1,24 @@
1
1
  import {
2
2
  claimUpgradeLock,
3
- finishWithUpgradeLock,
4
3
  forkFinishWithUpgradeLock,
5
4
  installWithUpgradeLock,
6
5
  releaseAutoUpdateLock
7
- } from "./chunk-7OQ4AJC7.js";
6
+ } from "./chunk-BRUHVTFB.js";
7
+ import {
8
+ phoneStartIsRequired
9
+ } from "./chunk-5PP4455M.js";
8
10
  import {
9
11
  describeAutoUpdateCheck,
10
12
  globalInstallLocation,
11
13
  hasRetryableUpgradeFinish,
12
14
  hasSettledUpgradeFinish,
15
+ holdsUnfinishedFinish,
13
16
  nextFinishAttempt,
17
+ pendingAutoUpdateAnnouncement,
14
18
  readAutoUpdateState,
15
19
  updateAutoUpdateState,
16
20
  upgradeEntryFor
17
- } from "./chunk-BAJ3A3BU.js";
21
+ } from "./chunk-5BNXJTOZ.js";
18
22
  import {
19
23
  EXIT
20
24
  } from "./chunk-KZERVKTD.js";
@@ -22,17 +26,17 @@ import {
22
26
  autoUpdateLock,
23
27
  autoUpdateLog,
24
28
  autoUpdateState
25
- } from "./chunk-TWLWDIFB.js";
29
+ } from "./chunk-KZCYHWDF.js";
26
30
  import {
27
31
  execNpm,
28
32
  fetchLatestPublishedVersion,
29
33
  npmErrorMessage,
30
34
  readGlobalCliInstallAt,
31
35
  readGlobalPrefix
32
- } from "./chunk-AEXACKDR.js";
36
+ } from "./chunk-US3IT6ZC.js";
33
37
  import {
34
38
  NPX_LATEST_COMMAND
35
- } from "./chunk-5SFM6XB4.js";
39
+ } from "./chunk-BMPZ5HVJ.js";
36
40
  import {
37
41
  commandsPackage,
38
42
  globalUpgradeEntry,
@@ -75,8 +79,29 @@ var instructionsLeftNoteOf = (agent) => {
75
79
  var RETRYABLE_UPGRADE_FINISH = { kind: "retryable" };
76
80
  var SETTLED_UPGRADE_FINISH = { kind: "settled" };
77
81
  var controlWaitsOnThePerson = (readings) => readings.controlPolicy === "legacy" || readings.controlMode === "on" && readings.controlPolicy === "unsupported";
82
+ var serviceStartFellShort = (start) => start === "did_not_start" || start === "started_unverified";
83
+ var aRunningServiceDidNotComeBack = (readings) => readings.serviceWasRunning && readings.serviceStart === "did_not_start";
84
+ var phoneStartFellShortWithoutFailing = (readings) => serviceStartFellShort(readings.serviceStart) && !aRunningServiceDidNotComeBack(readings) && !phoneStartIsRequired({ mode: readings.controlMode, policy: readings.controlPolicy, lifecycleFailed: false });
85
+ var serviceCameBack = (start) => start === "verified" || start === "started_unverified";
86
+ var serviceStaysOwedBack = (readings, owedBefore) => readings.controlPolicy !== "disabled" && !serviceCameBack(readings.serviceStart) && (owedBefore || aRunningServiceDidNotComeBack(readings));
87
+ var phoneStartServiceAfter = (start) => start === "started_unverified" ? "running_unverified" : "not_running";
88
+ var DISABLED_SERVICE_NOT_REMOVED = "Upgrade installed, but the disabled background service could not be fully removed. Re-run setup with --control off.";
89
+ var LEGACY_SERVICE_NOT_ADOPTED = "Upgrade installed and the legacy background service was stopped, but it cannot be safely adopted. Run setup with --control off, or restore its provider and native service manager.";
90
+ var NO_KEY_FOR_PHONE_START = "Upgrade installed, but phone-start could not be reconciled because no API key was readable. Re-run setup.";
91
+ var PHONE_START_CANNOT_RUN_HERE = "Upgrade installed, but phone-start is required on this computer and cannot run here yet. Run pushary doctor to see what it needs.";
92
+ var SERVICE_STARTED_UNVERIFIED = "Upgrade installed, and the background service started, but it was not verified in time. Check it with: pushary doctor";
93
+ var SERVICE_DID_NOT_START = "Upgrade installed, but the background service did not start. Run: pushary daemon install";
94
+ var describeLegacyService = (stopFailure) => stopFailure ? `Upgrade installed, but the legacy background service could not be stopped: ${stopFailure}. Run setup with --control off.` : LEGACY_SERVICE_NOT_ADOPTED;
95
+ var describeUnreadyService = (service) => {
96
+ if (service.controlPolicy === "disabled") return DISABLED_SERVICE_NOT_REMOVED;
97
+ if (service.controlPolicy === "legacy") return describeLegacyService(service.legacyStopFailure);
98
+ if (service.controlPolicy === "enabled" && !service.keyReadable) return NO_KEY_FOR_PHONE_START;
99
+ if (service.controlPolicy === "unsupported") return PHONE_START_CANNOT_RUN_HERE;
100
+ return service.serviceStart === "started_unverified" ? SERVICE_STARTED_UNVERIFIED : SERVICE_DID_NOT_START;
101
+ };
102
+ var serviceFailedTheFinish = (readings) => !readings.daemonReady && !phoneStartFellShortWithoutFailing(readings);
78
103
  var upgradeFinishOutcome = (readings) => {
79
- if (!readings.daemonReady) return controlWaitsOnThePerson(readings) ? SETTLED_UPGRADE_FINISH : RETRYABLE_UPGRADE_FINISH;
104
+ if (serviceFailedTheFinish(readings)) return controlWaitsOnThePerson(readings) ? SETTLED_UPGRADE_FINISH : RETRYABLE_UPGRADE_FINISH;
80
105
  return readings.failedRefreshCount > 0 ? SETTLED_UPGRADE_FINISH : FINISHED_UPGRADE;
81
106
  };
82
107
  var UPGRADE_FINISH_EXIT_CODES = {
@@ -244,15 +269,19 @@ var readGlobalUpgradeSubject = (platform = process.platform) => {
244
269
  const prefix = readGlobalPrefix();
245
270
  return globalUpgradeSubject(prefix, prefix === void 0 ? { kind: "unavailable" } : readGlobalCliInstallAt(prefix, platform));
246
271
  };
247
- var finishWithThisNode = (entry) => finishWithUpgradeLock({ entry, args: [FINISH_UPGRADE_FLAG] });
272
+ var finishWithThisNode = async (entry) => (await forkFinishWithUpgradeLock({ entry, args: [FINISH_UPGRADE_FLAG] })).code;
273
+ var NEVER_RAN = { kind: "failed", waitsOnThePerson: false };
274
+ var finishOutcomeOfExit = (code) => {
275
+ const outcome = upgradeFinishOutcomeOfExit(code);
276
+ return outcome.kind === "finished" ? { kind: "finished" } : { kind: "failed", waitsOnThePerson: outcome.kind === "settled" };
277
+ };
248
278
  var finishGlobalUpgrade = async (subject, platform = process.platform, runFinish = finishWithThisNode) => {
249
279
  const entry = globalUpgradeEntry(subject.prefix, subject.name, platform);
250
280
  if (!existsSync(entry)) return { kind: "entry_missing", entry };
251
281
  try {
252
- await runFinish(entry);
253
- return { kind: "finished" };
282
+ return finishOutcomeOfExit(await runFinish(entry));
254
283
  } catch {
255
- return { kind: "failed" };
284
+ return NEVER_RAN;
256
285
  }
257
286
  };
258
287
  var describeHeldUpgradeLock = (holderPid) => `Another Pushary upgrade is already running${holderPid === void 0 ? "" : ` (pid ${holderPid})`}. Try again when it has finished.`;
@@ -267,21 +296,62 @@ var withUpgradeLock = async (run, request = {}) => {
267
296
  }
268
297
  return { kind: "ran" };
269
298
  };
299
+ var upgradeOnRecordIsStillThisOne = (earlier, installedVersion) => earlier.to === installedVersion && (holdsUnfinishedFinish(earlier) || pendingAutoUpdateAnnouncement(earlier) !== null);
300
+ var startOfTheUpgradeOnRecord = (statePath, installedVersion) => {
301
+ const earlier = readAutoUpdateState(statePath);
302
+ return upgradeOnRecordIsStillThisOne(earlier, installedVersion) ? earlier.from : void 0;
303
+ };
270
304
  var finishUpgradeWithLock = async (run, request) => {
271
305
  const outcome = await withUpgradeLock(run, request);
272
306
  if (outcome.kind === "refused") {
273
- updateAutoUpdateState(request.statePath, { lastResult: "finish_pending", to: request.installedVersion, detail: outcome.message });
307
+ updateAutoUpdateState(request.statePath, {
308
+ lastResult: "finish_pending",
309
+ from: startOfTheUpgradeOnRecord(request.statePath, request.installedVersion),
310
+ to: request.installedVersion,
311
+ detail: outcome.message
312
+ });
274
313
  }
275
314
  return outcome;
276
315
  };
316
+ var FINISH_DID_NOT_FINISH_DETAIL = "installed, but refreshing the agent configs did not finish; run pushary upgrade again";
317
+ var FINISH_WAITS_ON_THE_PERSON_DETAIL = "installed, but refreshing the agent configs needs a change only you can make; make it, then run pushary upgrade again";
318
+ var waitsOnThePerson = (unfinished) => unfinished.kind === "failed" && unfinished.waitsOnThePerson;
319
+ var recordUnfinishedUpgrade = (record, unfinished) => {
320
+ if (record.installed.kind !== "installed") return;
321
+ const settled = waitsOnThePerson(unfinished);
322
+ updateAutoUpdateState(record.statePath, {
323
+ lastCheckAt: record.at,
324
+ lastResult: settled ? "finish_blocked" : "finish_pending",
325
+ from: startOfTheUpgradeOnRecord(record.statePath, record.installed.version),
326
+ to: record.installed.version,
327
+ at: record.at,
328
+ detail: settled ? FINISH_WAITS_ON_THE_PERSON_DETAIL : FINISH_DID_NOT_FINISH_DETAIL,
329
+ finishAttempts: void 0
330
+ });
331
+ };
332
+ var forgetUnfinishedUpgrade = (record, upgradedFrom) => {
333
+ if (record.installed.kind !== "installed" || !holdsUnfinishedFinish(readAutoUpdateState(record.statePath))) return;
334
+ updateAutoUpdateState(record.statePath, {
335
+ lastResult: "updated",
336
+ from: upgradedFrom,
337
+ to: record.installed.version,
338
+ at: record.at,
339
+ detail: void 0,
340
+ announced: true,
341
+ finishAttempts: void 0
342
+ });
343
+ };
277
344
  var describeUpgradeStart = (subject, latest) => `Upgrading ${subject.name} ${subject.version} -> ${latest ?? "latest"}`;
278
345
  var upgradeInstalledPackage = async (subject, effects) => {
279
346
  const pendingFinish = effects.hasPendingFinish(subject.version);
280
347
  const latest = await effects.fetchLatest(subject.name);
281
348
  const newerPublished = latest !== void 0 && isNewerVersion(latest, subject.version);
282
349
  if (pendingFinish && !newerPublished) {
283
- const finish = await effects.finish(subject);
284
- if (finish.kind !== "finished") return { kind: "pending_finish_failed", finish };
350
+ const finish2 = await effects.finish(subject);
351
+ if (finish2.kind !== "finished") {
352
+ effects.recordUnfinished(finish2);
353
+ return { kind: "pending_finish_failed", finish: finish2 };
354
+ }
285
355
  effects.recordFinished();
286
356
  }
287
357
  if (latest !== void 0 && !newerPublished) return { kind: "current", version: subject.version };
@@ -291,13 +361,20 @@ var upgradeInstalledPackage = async (subject, effects) => {
291
361
  } catch (error) {
292
362
  return { kind: "install_failed", detail: npmErrorMessage(error) };
293
363
  }
294
- return { kind: "installed", finish: await effects.finish(subject) };
364
+ const finish = await effects.finish(subject);
365
+ if (finish.kind === "finished") effects.forgetUnfinished();
366
+ else effects.recordUnfinished(finish);
367
+ return { kind: "installed", finish };
295
368
  };
296
369
 
297
370
  export {
298
371
  refreshedPartsOf,
299
372
  wasRefreshed,
300
373
  instructionsLeftNoteOf,
374
+ phoneStartFellShortWithoutFailing,
375
+ serviceStaysOwedBack,
376
+ phoneStartServiceAfter,
377
+ describeUnreadyService,
301
378
  upgradeFinishOutcome,
302
379
  upgradeFinishExitCode,
303
380
  FINISH_UPGRADE_FLAG,
@@ -307,5 +384,7 @@ export {
307
384
  finishGlobalUpgrade,
308
385
  withUpgradeLock,
309
386
  finishUpgradeWithLock,
387
+ recordUnfinishedUpgrade,
388
+ forgetUnfinishedUpgrade,
310
389
  upgradeInstalledPackage
311
390
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_SESSION
3
- } from "./chunk-4YTFYILS.js";
3
+ } from "./chunk-5JWCWALO.js";
4
4
 
5
5
  // src/answer.ts
6
6
  var isDeferAnswer = (value) => value === "defer";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-TWLWDIFB.js";
3
+ } from "./chunk-KZCYHWDF.js";
4
4
  import {
5
5
  writeFileAtomic
6
6
  } from "./chunk-QENTGE75.js";