@pushary/agent-hooks 0.64.1 → 0.64.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.64.3
4
+
5
+ ### A stale shell export hid the working key on disk
6
+
7
+ `PUSHARY_API_KEY` wins over `~/.pushary/config.json`, which is right and
8
+ unchanged. But the environment used to win on the key's *shape* alone, so a
9
+ well-formed key the server had since revoked shadowed a working one in the config
10
+ file and setup exited "This key was rejected by pushary.com" with a perfectly
11
+ good key sitting on the machine.
12
+
13
+ That is the state any shell left open across a key rotation is in: the export is
14
+ the old key, the file is the new one, and the terminal you happen to be typing in
15
+ decides whether setup works.
16
+
17
+ The environment key is checked with the server before it is chosen now, and a
18
+ rejected one falls through to the stored key rather than ending the run. It costs
19
+ no extra call: the verdict is handed to the verification step that was already
20
+ making it.
21
+
22
+ ### `--connect app` on a machine with no terminal named the wrong problem
23
+
24
+ A headless run that asked to pair said "No API key, and no terminal to sign in
25
+ from" even with a key in the config file, because asking to pair deliberately
26
+ takes the stored key off the table. It now says the flag needs a terminal for the
27
+ QR, and that dropping it uses the key already there.
28
+
29
+ ## 0.64.2
30
+
31
+ ### A run that gave up reported nothing, and said it stopped at the start
32
+
33
+ `agent_setup_completed` was wired to one of setup's fifteen exits: the last line
34
+ of a run that finished. Every other way out returned before it. A key the server
35
+ refused, a cancelled pairing, no terminal to sign in from, an installer that
36
+ threw, a Ctrl-C at the agent checkbox: all silent. The beacon was added so failed
37
+ and abandoned runs would stop being invisible, and the runs it could see were
38
+ still only the ones that already worked.
39
+
40
+ Every early exit reports now, and each says why it stopped: `key-refused`,
41
+ `pairing-cancelled`, `no-key`, `login-rejected`, `no-agents-detected`,
42
+ `invalid-key`, `interrupted`, `crashed`.
43
+
44
+ Argv-time exits stay silent, along with `--help` and `--dry-run`. A flag typo is
45
+ not an abandoned run: nothing was attempted and nothing was written. It is also
46
+ the fastest-failing path there is, and waiting on a beacon took `setup --bogus`
47
+ from 60ms to two seconds against an unreachable server. A dry run stays silent
48
+ for the other reason: counting it as a completed setup would be its own lie.
49
+
50
+ `abandonedAt` said where. It was always `start`, because `markStep` had no call
51
+ site outside its own test, so the field that was supposed to locate a failure was
52
+ a constant. It now moves through the run, per agent inside the configure loop, so
53
+ an installer that hangs is named rather than grouped.
54
+
55
+ Ctrl-C at the pairing QR reports before the process leaves. The cancel and the
56
+ beacon run concurrently and the exit waits for both, so this adds nothing to the
57
+ worst case a cancel already had.
58
+
59
+ The beacon authenticates with the key the run resolved rather than the one
60
+ already installed. Most of these exits happen before the key is written to disk,
61
+ so without that they posted as nobody, which meant the window holding the
62
+ interesting failures was exactly the window that could not report.
63
+
3
64
  ## 0.64.1
4
65
 
5
66
  ### Ctrl-C at the pairing QR printed nothing
@@ -17,7 +17,7 @@ import {
17
17
  reportEvent,
18
18
  resolvePolicy,
19
19
  waitForAnswer
20
- } from "../chunk-5ZDYDU2Q.js";
20
+ } from "../chunk-XOUYM27W.js";
21
21
  import "../chunk-BSZYIAZL.js";
22
22
  import "../chunk-SAF6HGAA.js";
23
23
  import "../chunk-7QLSKOSU.js";
@@ -31,7 +31,7 @@ import {
31
31
  toCodexWire,
32
32
  toPolicyLookup,
33
33
  waitForAnswer
34
- } from "../chunk-5ZDYDU2Q.js";
34
+ } from "../chunk-XOUYM27W.js";
35
35
  import {
36
36
  isGatingMoment,
37
37
  recordKeylessMoment
@@ -3,7 +3,7 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-5ZDYDU2Q.js";
6
+ } from "../chunk-XOUYM27W.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-RASBS2CC.js";
9
+ } from "../chunk-GFHQ26ST.js";
10
10
  import "../chunk-3EGEA4KH.js";
11
11
  import {
12
12
  readKeySource
@@ -34,7 +34,7 @@ import {
34
34
  describeReach,
35
35
  fetchChannels,
36
36
  reachVerdict
37
- } from "../chunk-RASBS2CC.js";
37
+ } from "../chunk-GFHQ26ST.js";
38
38
  import "../chunk-3EGEA4KH.js";
39
39
  import {
40
40
  keyPrefix,
@@ -23,7 +23,7 @@ import {
23
23
  savePendingQuestion,
24
24
  sendNotification,
25
25
  waitForAnswer
26
- } from "../chunk-5ZDYDU2Q.js";
26
+ } from "../chunk-XOUYM27W.js";
27
27
  import {
28
28
  isGatingMoment,
29
29
  recordKeylessMoment
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-SDXF5I46.js";
4
+ } from "../chunk-SONXGCZY.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
6
  import {
7
7
  exitOnHelpFlag
8
8
  } from "../chunk-ATP6UQPH.js";
9
9
  import "../chunk-JGH37QRB.js";
10
10
  import "../chunk-YHG74UFF.js";
11
- import "../chunk-5ZDYDU2Q.js";
11
+ import "../chunk-XOUYM27W.js";
12
12
  import "../chunk-WLGEY4UX.js";
13
13
  import "../chunk-BSZYIAZL.js";
14
14
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.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-SDXF5I46.js";
4
+ } from "../chunk-SONXGCZY.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
6
  import "../chunk-YHG74UFF.js";
7
- import "../chunk-5ZDYDU2Q.js";
7
+ import "../chunk-XOUYM27W.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-SDXF5I46.js";
4
+ } from "../chunk-SONXGCZY.js";
5
5
  import "../chunk-CAJZAFVS.js";
6
6
  import "../chunk-YHG74UFF.js";
7
- import "../chunk-5ZDYDU2Q.js";
7
+ import "../chunk-XOUYM27W.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-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.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-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.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-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.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-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -45,7 +45,7 @@ import {
45
45
  connectViaAppPairing,
46
46
  printConnectInstructions,
47
47
  setHumanStream
48
- } from "../chunk-RASBS2CC.js";
48
+ } from "../chunk-GFHQ26ST.js";
49
49
  import "../chunk-3EGEA4KH.js";
50
50
  import {
51
51
  KEY_FILE_MODE,
@@ -86,7 +86,7 @@ import {
86
86
  } from "../chunk-JGH37QRB.js";
87
87
  import {
88
88
  reportEvent
89
- } from "../chunk-5ZDYDU2Q.js";
89
+ } from "../chunk-XOUYM27W.js";
90
90
  import "../chunk-BSZYIAZL.js";
91
91
  import "../chunk-SAF6HGAA.js";
92
92
  import {
@@ -206,32 +206,60 @@ var installSkillViaSkillsCli = (agent) => {
206
206
 
207
207
  // src/setup/telemetry.ts
208
208
  var currentStep = "start";
209
+ var markStep = (step) => {
210
+ currentStep = step;
211
+ };
212
+ var recordedOptions = null;
213
+ var recordSetupOptions = (options) => {
214
+ recordedOptions = options;
215
+ };
216
+ var recordedKey = null;
217
+ var recordSetupKey = (apiKey) => {
218
+ recordedKey = apiKey;
219
+ };
209
220
  var emitted = false;
210
- var reportSetupOutcome = async (outcome) => {
221
+ var defaultDeps = { report: reportEvent };
222
+ var emit = async (deps, props) => {
211
223
  if (emitted) return;
212
224
  emitted = true;
213
225
  try {
214
- await reportEvent(
215
- {
216
- event: "agent_setup_completed",
217
- agentType: "setup",
218
- agents: outcome.completed,
219
- connectMode: outcome.options.connectMode,
220
- connectOutcome: outcome.connectOutcome,
221
- failedAgents: outcome.failed.length,
222
- skippedAgents: outcome.skipped,
223
- nonInteractive: outcome.options.nonInteractive,
224
- keyCheck: outcome.keyCheck,
225
- exitCode: outcome.exitCode,
226
- // Where the run stopped. On a completed run this is the closing step, so
227
- // the interesting values are the ones that are not.
228
- abandonedAt: outcome.exitCode === 0 ? null : currentStep
229
- },
230
- { maxAttempts: 1, timeoutMs: 2e3 }
226
+ await deps.report(
227
+ { event: "agent_setup_completed", agentType: "setup", ...props },
228
+ { maxAttempts: 1, timeoutMs: 2e3, ...recordedKey ? { apiKey: recordedKey } : {} }
231
229
  );
232
230
  } catch {
233
231
  }
234
232
  };
233
+ var reportSetupOutcomeWith = async (deps, outcome) => emit(deps, {
234
+ agents: outcome.completed,
235
+ connectMode: outcome.options.connectMode,
236
+ connectOutcome: outcome.connectOutcome,
237
+ failedAgents: outcome.failed.length,
238
+ skippedAgents: outcome.skipped,
239
+ nonInteractive: outcome.options.nonInteractive,
240
+ keyCheck: outcome.keyCheck,
241
+ exitCode: outcome.exitCode,
242
+ // Where the run stopped. On a completed run this is the closing step, so the
243
+ // interesting values are the ones that are not.
244
+ abandonedAt: outcome.exitCode === 0 ? null : currentStep,
245
+ // Always present, so a query can separate "ended" from "gave up" on the
246
+ // property rather than on the absence of one.
247
+ abortReason: null
248
+ });
249
+ var reportSetupOutcome = (outcome) => reportSetupOutcomeWith(defaultDeps, outcome);
250
+ var reportSetupAbortWith = async (deps, reason, exitCode, keyCheck = "unknown") => emit(deps, {
251
+ agents: [],
252
+ connectMode: recordedOptions?.connectMode ?? null,
253
+ connectOutcome: "skipped",
254
+ failedAgents: 0,
255
+ skippedAgents: [],
256
+ nonInteractive: recordedOptions?.nonInteractive ?? null,
257
+ keyCheck,
258
+ exitCode,
259
+ abandonedAt: currentStep,
260
+ abortReason: reason
261
+ });
262
+ var reportSetupAbort = (reason, exitCode, keyCheck) => reportSetupAbortWith(defaultDeps, reason, exitCode, keyCheck);
235
263
 
236
264
  // src/setup/options.ts
237
265
  var AGENT_CHOICES = ["claude_code", "codex", "gemini_cli", "hermes", "cursor", "custom"];
@@ -333,6 +361,10 @@ var green = (s) => `\x1B[32m${s}\x1B[0m`;
333
361
  var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
334
362
  var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
335
363
  var check = green("\u2713");
364
+ var abortWith = async (reason, code, keyCheck) => {
365
+ await reportSetupAbort(reason, code, keyCheck);
366
+ return code;
367
+ };
336
368
  var readKeyFromStdin = async (options) => {
337
369
  if (!options.keyStdin) return void 0;
338
370
  const chunks = [];
@@ -341,14 +373,14 @@ var readKeyFromStdin = async (options) => {
341
373
  if (!first) {
342
374
  console.log(`
343
375
  ${yellow("!")} --key-stdin was passed but stdin was empty.`);
344
- process.exit(2);
376
+ process.exit(EXIT.USAGE);
345
377
  }
346
378
  if (!isValidApiKey(first)) {
347
379
  console.log(`
348
380
  ${yellow("!")} The key on stdin is not a valid Pushary key. Expected: ${dim("pk_xxx.xxx")}`);
349
381
  console.log(` ${dim("Copy your key from")} ${cyan("https://pushary.com/dashboard/agent/settings")}
350
382
  `);
351
- process.exit(2);
383
+ process.exit(EXIT.USAGE);
352
384
  }
353
385
  return first;
354
386
  };
@@ -971,7 +1003,7 @@ var verifyKeyOrExit = async (apiKey, options, precomputed) => {
971
1003
  console.log(` ${yellow("!")} ${report.message}`);
972
1004
  for (const line of report.next) console.log(` ${dim(line)}`);
973
1005
  console.log();
974
- process.exit(report.exitCode);
1006
+ process.exit(await abortWith("key-refused", report.exitCode, result.kind));
975
1007
  }
976
1008
  if (result.kind === "ok") {
977
1009
  const { siteSlug, siteName, plan } = result.identity;
@@ -1054,7 +1086,7 @@ var resolveAgents = async (options) => {
1054
1086
  console.log(` ${yellow("!")} No agents detected, so nothing was configured. Your key is saved.`);
1055
1087
  console.log(` ${dim("Install an agent and re-run, name one with --agents <id>, or pass --agents none to accept this.")}`);
1056
1088
  console.log();
1057
- process.exit(EXIT.PROBLEMS_FOUND);
1089
+ process.exit(await abortWith("no-agents-detected", EXIT.PROBLEMS_FOUND));
1058
1090
  }
1059
1091
  console.log(` ${check} Selected automatically: ${picked.map((a) => AGENT_NAMES[a]).join(", ")} ${dim("(--agents to choose)")}`);
1060
1092
  return picked;
@@ -1125,6 +1157,7 @@ var main = async () => {
1125
1157
  `);
1126
1158
  process.exit(EXIT.OK);
1127
1159
  }
1160
+ recordSetupOptions(options);
1128
1161
  if (options.json) {
1129
1162
  humanStream = process.stderr;
1130
1163
  setHumanStream(process.stderr);
@@ -1139,6 +1172,7 @@ var main = async () => {
1139
1172
  console.log(` ${dim("Push notifications for AI agents")}`);
1140
1173
  console.log();
1141
1174
  await checkForUpdates(version);
1175
+ markStep("resolve-key");
1142
1176
  const connectMode = options.connectMode;
1143
1177
  const flagKey = await readKeyFromStdin(options) ?? options.key ?? void 0;
1144
1178
  const envKey = process.env.PUSHARY_API_KEY?.trim();
@@ -1146,7 +1180,10 @@ var main = async () => {
1146
1180
  let keyFromPairing = false;
1147
1181
  const storedKey = readConfigFileKey();
1148
1182
  const pairingRequested = connectMode === "app" && options.connectExplicit && !options.skipPhone;
1149
- const storedCheck = !(flagKey && isValidApiKey(flagKey)) && !pairingRequested && !(envKey && isValidApiKey(envKey)) && storedKey !== null && isValidApiKey(storedKey) ? await usableStoredKey(storedKey) : null;
1183
+ const ambientEligible = !(flagKey && isValidApiKey(flagKey)) && !pairingRequested;
1184
+ const envCheck = ambientEligible && envKey && isValidApiKey(envKey) ? await usableStoredKey(envKey) : null;
1185
+ const storedCheck = ambientEligible && envCheck === null && storedKey !== null && isValidApiKey(storedKey) && // login writes both, so without this the same key is probed twice.
1186
+ storedKey !== envKey ? await usableStoredKey(storedKey) : null;
1150
1187
  let storedKeyCheck = null;
1151
1188
  if (flagKey && isValidApiKey(flagKey)) {
1152
1189
  const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
@@ -1160,13 +1197,14 @@ var main = async () => {
1160
1197
  console.log();
1161
1198
  trimmedKey = storedKey;
1162
1199
  storedKeyCheck = storedCheck;
1163
- } else if (!pairingRequested && envKey && isValidApiKey(envKey)) {
1200
+ } else if (envKey && envCheck !== null) {
1164
1201
  const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
1165
1202
  console.log(` ${check} Found API key in environment: ${dim(masked)}`);
1166
1203
  console.log();
1167
1204
  const useExisting = options.nonInteractive ? true : await confirm({ message: "Use this key?", default: true });
1168
1205
  if (useExisting) {
1169
1206
  trimmedKey = envKey;
1207
+ storedKeyCheck = envCheck;
1170
1208
  } else {
1171
1209
  const apiKey = await input({ message: "API key:" });
1172
1210
  trimmedKey = apiKey.trim();
@@ -1174,11 +1212,17 @@ var main = async () => {
1174
1212
  } else if (connectMode === "app" && canRunBrowserLogin() && !options.skipPhone) {
1175
1213
  console.log(` ${dim("Pairing with your Pushary app.")}`);
1176
1214
  console.log(` ${dim("No app? Ctrl-C, then:")} ${cyan("npx @pushary/agent-hooks setup --connect browser")}`);
1215
+ markStep("pair");
1177
1216
  const previous = readKeySource();
1178
- const paired = await connectViaAppPairing({ onCancelled: printPairingEscape });
1217
+ const paired = await connectViaAppPairing({
1218
+ onCancelled: async () => {
1219
+ printPairingEscape();
1220
+ await reportSetupAbort("pairing-cancelled", 130);
1221
+ }
1222
+ });
1179
1223
  if (!paired) {
1180
1224
  printPairingEscape();
1181
- process.exit(EXIT.NOT_CONFIGURED);
1225
+ process.exit(await abortWith("pairing-cancelled", EXIT.NOT_CONFIGURED));
1182
1226
  }
1183
1227
  trimmedKey = paired.apiKey;
1184
1228
  keyFromPairing = true;
@@ -1191,12 +1235,18 @@ var main = async () => {
1191
1235
  }
1192
1236
  console.log();
1193
1237
  } else if (!canRunBrowserLogin()) {
1194
- console.log(` ${yellow("!")} No API key, and no terminal to sign in from.`);
1195
- console.log(` ${dim("Pass --key <pk_xxx.xxx>, or --key-stdin, or export PUSHARY_API_KEY.")}`);
1196
- console.log(` ${dim("On a machine with a terminal, run")} ${cyan("pushary login")} ${dim("once and re-run this.")}`);
1238
+ if (pairingRequested) {
1239
+ console.log(` ${yellow("!")} ${cyan("--connect app")} needs a terminal to show the pairing QR in.`);
1240
+ console.log(` ${dim("Drop the flag to use the key this machine already has, or pass --key <pk_xxx.xxx>.")}`);
1241
+ } else {
1242
+ console.log(` ${yellow("!")} No API key, and no terminal to sign in from.`);
1243
+ console.log(` ${dim("Pass --key <pk_xxx.xxx>, or --key-stdin, or export PUSHARY_API_KEY.")}`);
1244
+ console.log(` ${dim("On a machine with a terminal, run")} ${cyan("pushary login")} ${dim("once and re-run this.")}`);
1245
+ }
1197
1246
  console.log();
1198
- process.exit(EXIT.NOT_CONFIGURED);
1247
+ process.exit(await abortWith("no-key", EXIT.NOT_CONFIGURED));
1199
1248
  } else {
1249
+ markStep("browser-login");
1200
1250
  const outcome = await runBrowserLogin(
1201
1251
  { say: (line) => console.log(line), bold, dim, cyan, yellow },
1202
1252
  { cliVersion: version }
@@ -1210,7 +1260,7 @@ var main = async () => {
1210
1260
  console.log();
1211
1261
  console.log(` ${yellow("!")} That request was rejected in the browser. Nothing was written.`);
1212
1262
  console.log();
1213
- process.exit(EXIT.NOT_CONFIGURED);
1263
+ process.exit(await abortWith("login-rejected", EXIT.NOT_CONFIGURED));
1214
1264
  } else {
1215
1265
  console.log();
1216
1266
  if (outcome.kind === "unavailable") {
@@ -1231,15 +1281,19 @@ var main = async () => {
1231
1281
  console.log(` ${dim("Copy your key from")} ${cyan("https://pushary.com/dashboard/agent/settings")}`);
1232
1282
  console.log(` ${dim("Or sign up at")} ${cyan("https://pushary.com/sign-up?from=ai-coding")}
1233
1283
  `);
1234
- process.exit(EXIT.USAGE);
1284
+ process.exit(await abortWith("invalid-key", EXIT.USAGE));
1235
1285
  }
1286
+ recordSetupKey(trimmedKey);
1287
+ markStep("verify-key");
1236
1288
  const keyCheck = keyFromPairing ? null : await verifyKeyOrExit(trimmedKey, options, storedKeyCheck ?? void 0);
1289
+ markStep("resolve-agents");
1237
1290
  const agents = await resolveAgents(options);
1238
1291
  if (options.dryRun) {
1239
1292
  reportDryRun(trimmedKey, agents, keyCheck);
1240
1293
  emitJson(options, { ok: true, dryRun: true, agents, keyCheck: keyCheck?.kind ?? "skipped" });
1241
1294
  process.exit(EXIT.OK);
1242
1295
  }
1296
+ markStep("save-key");
1243
1297
  const rc = await saveApiKey(trimmedKey);
1244
1298
  const context = { rc };
1245
1299
  const failed = [];
@@ -1251,6 +1305,7 @@ var main = async () => {
1251
1305
  console.log(` ${dim("Run setup again with --agents <id> when you want one wired up.")}`);
1252
1306
  } else {
1253
1307
  for (const agent of agents) {
1308
+ markStep(`configure:${agent}`);
1254
1309
  try {
1255
1310
  const outcome = await AGENT_SETUP[agent](trimmedKey, context);
1256
1311
  if (outcome === "skipped") skipped.push(agent);
@@ -1269,11 +1324,13 @@ var main = async () => {
1269
1324
  console.log();
1270
1325
  console.log(` ${yellow("!")} Failed: ${failed.join(", ")} ${dim("(others completed successfully)")}`);
1271
1326
  }
1327
+ markStep("project-instructions");
1272
1328
  await offerProjectInstructions(agents, options);
1273
1329
  if (completed.includes("claude_code")) {
1274
1330
  await offerClaudeAlias(options);
1275
1331
  }
1276
1332
  }
1333
+ markStep("connect-phone");
1277
1334
  const skipPhone = options.skipPhone;
1278
1335
  let phone = null;
1279
1336
  if (keyCheck?.kind === "locked") {
@@ -1311,6 +1368,15 @@ var main = async () => {
1311
1368
  rc: rc.kind,
1312
1369
  exitCode: report.exitCode
1313
1370
  });
1371
+ await reportSetupOutcome({
1372
+ options,
1373
+ completed,
1374
+ skipped,
1375
+ failed,
1376
+ connectOutcome: "skipped",
1377
+ keyCheck: "locked",
1378
+ exitCode: report.exitCode
1379
+ });
1314
1380
  process.exit(report.exitCode);
1315
1381
  }
1316
1382
  printManualSteps();
@@ -1318,6 +1384,7 @@ var main = async () => {
1318
1384
  const agentsOk = failed.length === 0 && !configuredNothing;
1319
1385
  const reachChecked = phone !== null;
1320
1386
  const reachesYou = phone?.reachesYou ?? true;
1387
+ markStep("closing");
1321
1388
  const closing = describeClosing({
1322
1389
  agentsOk,
1323
1390
  configuredNothing,
@@ -1372,7 +1439,7 @@ var main = async () => {
1372
1439
  process.exit(exitCode);
1373
1440
  }
1374
1441
  };
1375
- main().catch((err) => {
1442
+ main().catch(async (err) => {
1376
1443
  if (err instanceof UsageError) {
1377
1444
  console.log();
1378
1445
  console.log(` ${yellow("!")} ${err.message}`);
@@ -1384,11 +1451,11 @@ main().catch((err) => {
1384
1451
  console.log();
1385
1452
  console.log(` ${dim("Cancelled. Nothing further was changed.")}`);
1386
1453
  console.log();
1387
- process.exit(130);
1454
+ process.exit(await abortWith("interrupted", 130));
1388
1455
  }
1389
1456
  console.log();
1390
1457
  console.log(` ${yellow("!")} Setup failed: ${formatError(err)}`);
1391
1458
  console.log(` ${dim("Run")} ${cyan("npx @pushary/agent-hooks doctor")} ${dim("after fixing the issue, then rerun setup.")}`);
1392
1459
  console.log();
1393
- process.exit(1);
1460
+ process.exit(await abortWith("crashed", EXIT.FAILED));
1394
1461
  });
@@ -7,7 +7,7 @@ import {
7
7
  describeReach,
8
8
  fetchChannels,
9
9
  reachVerdict
10
- } from "../chunk-RASBS2CC.js";
10
+ } from "../chunk-GFHQ26ST.js";
11
11
  import "../chunk-3EGEA4KH.js";
12
12
  import {
13
13
  readKeySource
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.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-5ZDYDU2Q.js";
4
+ } from "../chunk-XOUYM27W.js";
5
5
  import "../chunk-BSZYIAZL.js";
6
6
  import "../chunk-SAF6HGAA.js";
7
7
  import "../chunk-7QLSKOSU.js";
@@ -520,8 +520,8 @@ var connectViaAppPairing = async (options = {}) => {
520
520
  const onInterrupt = () => {
521
521
  cancelled = true;
522
522
  stop(yellow("!"), "Cancelled");
523
- options.onCancelled?.();
524
- void cancelPairing(pairId).finally(() => process.exit(EXIT_ABORTED));
523
+ const flushed = options.onCancelled?.();
524
+ void Promise.allSettled([Promise.resolve(flushed), cancelPairing(pairId)]).finally(() => process.exit(EXIT_ABORTED));
525
525
  };
526
526
  process.once("SIGINT", onInterrupt);
527
527
  try {
@@ -25,7 +25,7 @@ import {
25
25
  sendNotification,
26
26
  throttlePass,
27
27
  waitForAnswer
28
- } from "./chunk-5ZDYDU2Q.js";
28
+ } from "./chunk-XOUYM27W.js";
29
29
  import {
30
30
  isGatingMoment,
31
31
  recordKeylessMoment
@@ -928,7 +928,7 @@ ${injected}` : injected;
928
928
  };
929
929
  var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
930
930
  var reportEvent = async (event, options = {}) => {
931
- const apiKey = getApiKey();
931
+ const apiKey = options.apiKey ?? getApiKey();
932
932
  const baseUrl = getBaseUrl();
933
933
  return withRetry(async () => {
934
934
  const res = await fetch(`${baseUrl}/api/agent/event`, {
@@ -73,6 +73,16 @@ interface AgentEvent {
73
73
  interface ReportEventOptions {
74
74
  maxAttempts?: number;
75
75
  timeoutMs?: number;
76
+ /**
77
+ * Authenticate with this key instead of the ambient one.
78
+ *
79
+ * getApiKey() reads the environment and ~/.pushary/config.json, so it can only
80
+ * ever speak for a key that is already installed. setup resolves a key minutes
81
+ * before it writes one, and its abort beacons run inside that window: without
82
+ * an override they post as nobody, so the runs that fail before saveApiKey are
83
+ * exactly the runs that cannot report failing.
84
+ */
85
+ apiKey?: string;
76
86
  }
77
87
  interface EventResponse {
78
88
  readonly ok?: boolean;
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-SDXF5I46.js";
3
+ } from "../chunk-SONXGCZY.js";
4
4
  import "../chunk-CAJZAFVS.js";
5
5
  import "../chunk-YHG74UFF.js";
6
6
  import {
@@ -15,7 +15,7 @@ import {
15
15
  reportEvent,
16
16
  resolvePolicy,
17
17
  waitForAnswer
18
- } from "../chunk-5ZDYDU2Q.js";
18
+ } from "../chunk-XOUYM27W.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.64.1",
3
+ "version": "0.64.3",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",