@pushary/agent-hooks 0.63.1 → 0.64.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.64.0
4
+
5
+ ### A typo on `--dry-run` ran a real clean
6
+
7
+ `pushary clean --yes --dryrun`, one missing hyphen, silently dropped the flag it
8
+ did not recognise and performed a full destructive clean: the stored key, the
9
+ Cursor plugin, every agent config, and the global package itself. It printed
10
+ "Clean complete." with the past tense throughout and exited `0`. The typo most
11
+ worth catching was the one on the flag that means "change nothing", sitting next
12
+ to the flag that means "do not ask me".
13
+
14
+ `clean` now rejects a flag it does not know and exits `2` before removing
15
+ anything. `--dry-run`, `--yes` and `-y` are unchanged.
16
+
17
+ ### Setup reuses the key this machine already has
18
+
19
+ `setup` read `PUSHARY_API_KEY` and never `~/.pushary/config.json`, so the key
20
+ `pushary login` had just written was invisible to it. `login` followed by `setup`
21
+ in the same shell ran a second browser login and minted a second key, and a
22
+ machine with no terminal that had been set up by `login` exited "no API key" with
23
+ a working key sitting on disk. The shell export made it work in a *new* shell,
24
+ which is what hid it.
25
+
26
+ Setup now reads the stored key, verifies it, and uses it. A key the server no
27
+ longer recognises falls through to a fresh sign-in rather than dead-ending, and a
28
+ server it cannot reach keeps the key rather than discarding it.
29
+
30
+ ### The Pushary app is the default connect path
31
+
32
+ An unflagged `setup` now pairs the app instead of sending you to the browser
33
+ subscribe page. `--connect browser` is the fallback for a machine with no app,
34
+ `--connect web` is the legacy page and keeps working exactly as before, and
35
+ `--connect none` skips the phone step like `--skip-phone`. `auto` and `pwa` are
36
+ accepted as input spellings.
37
+
38
+ Pairing only happens when there is a terminal to show the QR to and no usable key
39
+ already on the machine, so re-running setup to add an agent no longer asks you to
40
+ scan anything, and no longer mints a key you did not ask for. Typing
41
+ `--connect app` explicitly still pairs every time.
42
+
43
+ When pairing does not complete, setup now names the two ways to finish without
44
+ the app rather than only inviting you to try the QR again.
45
+
46
+
47
+ ## 0.63.2
48
+
49
+ ### Setup reports what happened, not only that it worked
50
+
51
+ The setup event fired only when at least one agent had been configured, so a run
52
+ that configured nothing, failed, or stopped partway reported nothing at all. The
53
+ only visible runs were the ones that already worked.
54
+
55
+ It now fires once per run whatever the outcome, and carries the connect mode and
56
+ its result, how many agents failed, which were skipped, whether the run was
57
+ non-interactive, and where a run stopped if it did.
58
+
59
+ This is the same event that already powers your activity feed, sent with your own
60
+ API key to your own workspace. No repo path, hostname or agent content is
61
+ included, and nothing is sent before you have a key.
62
+
63
+
3
64
  ## 0.63.1
4
65
 
5
66
  ### `pushary status --bogus` printed a stack trace
@@ -29,15 +29,21 @@ import {
29
29
  import {
30
30
  removeClaudeAlias
31
31
  } from "../chunk-BC3VCZ3E.js";
32
+ import {
33
+ rejectUnknownFlags
34
+ } from "../chunk-GMXKITVA.js";
32
35
  import {
33
36
  execNpm
34
37
  } from "../chunk-MUEW424A.js";
35
38
  import {
36
39
  exitOnHelpFlag
37
- } from "../chunk-VYD27DKZ.js";
38
- import "../chunk-PPTC2QDB.js";
40
+ } from "../chunk-ATP6UQPH.js";
41
+ import "../chunk-JGH37QRB.js";
42
+ import "../chunk-7QLSKOSU.js";
39
43
  import "../chunk-DQAN3JQP.js";
40
- import "../chunk-KZERVKTD.js";
44
+ import {
45
+ EXIT
46
+ } from "../chunk-KZERVKTD.js";
41
47
 
42
48
  // bin/pushary-clean.ts
43
49
  import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
@@ -149,6 +155,16 @@ var main = async () => {
149
155
  console.log(` ${bold("Pushary Clean")}`);
150
156
  console.log(` ${dim("Removes all Pushary configuration")}`);
151
157
  console.log();
158
+ try {
159
+ rejectUnknownFlags(
160
+ process.argv.slice(2).filter((a) => a !== "clean"),
161
+ ["--dry-run", "--yes", "-y", "--help", "-h"]
162
+ );
163
+ } catch (err) {
164
+ process.stderr.write(` ! ${err instanceof Error ? err.message : String(err)}
165
+ `);
166
+ process.exit(EXIT.USAGE);
167
+ }
152
168
  dryRun = process.argv.includes("--dry-run");
153
169
  if (dryRun) {
154
170
  console.log(` ${bold("Dry run")} ${dim("- nothing will be changed")}`);
@@ -10,7 +10,7 @@ import {
10
10
  import "../chunk-3EGEA4KH.js";
11
11
  import {
12
12
  readKeySource
13
- } from "../chunk-COC6NPWG.js";
13
+ } from "../chunk-NHKWU7MN.js";
14
14
  import "../chunk-6MTNS63X.js";
15
15
  import {
16
16
  checkApiKey
@@ -18,14 +18,14 @@ import {
18
18
  import {
19
19
  createIo
20
20
  } from "../chunk-TX7KBKT7.js";
21
+ import "../chunk-BC3VCZ3E.js";
21
22
  import {
22
23
  parseFlags
23
24
  } from "../chunk-GMXKITVA.js";
24
- import "../chunk-BC3VCZ3E.js";
25
25
  import {
26
26
  exitOnHelpFlag
27
- } from "../chunk-VYD27DKZ.js";
28
- import "../chunk-PPTC2QDB.js";
27
+ } from "../chunk-ATP6UQPH.js";
28
+ import "../chunk-JGH37QRB.js";
29
29
  import "../chunk-BSZYIAZL.js";
30
30
  import "../chunk-SAF6HGAA.js";
31
31
  import {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HEARTBEAT_INTERVAL_MS,
4
4
  sendHeartbeat
5
- } from "../chunk-R22IAMSS.js";
5
+ } from "../chunk-YZT2CBC3.js";
6
6
  import {
7
7
  spawnClaude
8
8
  } from "../chunk-XHKBHWLX.js";
@@ -11,8 +11,8 @@ import {
11
11
  } from "../chunk-MUEW424A.js";
12
12
  import {
13
13
  exitOnHelpFlag
14
- } from "../chunk-VYD27DKZ.js";
15
- import "../chunk-PPTC2QDB.js";
14
+ } from "../chunk-ATP6UQPH.js";
15
+ import "../chunk-JGH37QRB.js";
16
16
  import {
17
17
  getMachineId
18
18
  } from "../chunk-RN3NOEJF.js";
@@ -39,7 +39,7 @@ import "../chunk-3EGEA4KH.js";
39
39
  import {
40
40
  keyPrefix,
41
41
  readKeySource
42
- } from "../chunk-COC6NPWG.js";
42
+ } from "../chunk-NHKWU7MN.js";
43
43
  import "../chunk-6MTNS63X.js";
44
44
  import {
45
45
  checkApiKey,
@@ -48,19 +48,19 @@ import {
48
48
  import {
49
49
  createIo
50
50
  } from "../chunk-TX7KBKT7.js";
51
+ import "../chunk-BC3VCZ3E.js";
51
52
  import {
52
53
  parseFlags
53
54
  } from "../chunk-GMXKITVA.js";
54
- import "../chunk-BC3VCZ3E.js";
55
55
  import {
56
56
  execNpm
57
57
  } from "../chunk-MUEW424A.js";
58
58
  import {
59
59
  exitOnHelpFlag
60
- } from "../chunk-VYD27DKZ.js";
60
+ } from "../chunk-ATP6UQPH.js";
61
61
  import {
62
62
  getPackageVersion
63
- } from "../chunk-PPTC2QDB.js";
63
+ } from "../chunk-JGH37QRB.js";
64
64
  import {
65
65
  readLedgerSummary
66
66
  } from "../chunk-WLGEY4UX.js";
@@ -5,8 +5,8 @@ import {
5
5
  import "../chunk-CAJZAFVS.js";
6
6
  import {
7
7
  exitOnHelpFlag
8
- } from "../chunk-VYD27DKZ.js";
9
- import "../chunk-PPTC2QDB.js";
8
+ } from "../chunk-ATP6UQPH.js";
9
+ import "../chunk-JGH37QRB.js";
10
10
  import "../chunk-YHG74UFF.js";
11
11
  import "../chunk-5ZDYDU2Q.js";
12
12
  import "../chunk-WLGEY4UX.js";
@@ -6,7 +6,7 @@ import "../chunk-3EGEA4KH.js";
6
6
  import {
7
7
  readKeySource,
8
8
  writeKey
9
- } from "../chunk-COC6NPWG.js";
9
+ } from "../chunk-NHKWU7MN.js";
10
10
  import "../chunk-6MTNS63X.js";
11
11
  import {
12
12
  checkApiKey,
@@ -15,16 +15,16 @@ import {
15
15
  import {
16
16
  createIo
17
17
  } from "../chunk-TX7KBKT7.js";
18
+ import "../chunk-BC3VCZ3E.js";
18
19
  import {
19
20
  parseFlags
20
21
  } from "../chunk-GMXKITVA.js";
21
- import "../chunk-BC3VCZ3E.js";
22
22
  import {
23
23
  exitOnHelpFlag
24
- } from "../chunk-VYD27DKZ.js";
24
+ } from "../chunk-ATP6UQPH.js";
25
25
  import {
26
26
  getPackageVersion
27
- } from "../chunk-PPTC2QDB.js";
27
+ } from "../chunk-JGH37QRB.js";
28
28
  import "../chunk-SAF6HGAA.js";
29
29
  import {
30
30
  UsageError
@@ -8,19 +8,19 @@ import {
8
8
  import {
9
9
  clearKey,
10
10
  readKeySource
11
- } from "../chunk-COC6NPWG.js";
11
+ } from "../chunk-NHKWU7MN.js";
12
12
  import "../chunk-6MTNS63X.js";
13
13
  import {
14
14
  createIo
15
15
  } from "../chunk-TX7KBKT7.js";
16
+ import "../chunk-BC3VCZ3E.js";
16
17
  import {
17
18
  parseFlags
18
19
  } from "../chunk-GMXKITVA.js";
19
- import "../chunk-BC3VCZ3E.js";
20
20
  import {
21
21
  exitOnHelpFlag
22
- } from "../chunk-VYD27DKZ.js";
23
- import "../chunk-PPTC2QDB.js";
22
+ } from "../chunk-ATP6UQPH.js";
23
+ import "../chunk-JGH37QRB.js";
24
24
  import {
25
25
  UsageError
26
26
  } from "../chunk-7QLSKOSU.js";
@@ -11,8 +11,8 @@ import {
11
11
  } from "../chunk-GMXKITVA.js";
12
12
  import {
13
13
  exitOnHelpFlag
14
- } from "../chunk-VYD27DKZ.js";
15
- import "../chunk-PPTC2QDB.js";
14
+ } from "../chunk-ATP6UQPH.js";
15
+ import "../chunk-JGH37QRB.js";
16
16
  import "../chunk-SAF6HGAA.js";
17
17
  import {
18
18
  UsageError
@@ -51,9 +51,10 @@ import {
51
51
  KEY_FILE_MODE,
52
52
  exportLine,
53
53
  keyPrefix,
54
+ readConfigFileKey,
54
55
  readKeySource,
55
56
  writeKey
56
- } from "../chunk-COC6NPWG.js";
57
+ } from "../chunk-NHKWU7MN.js";
57
58
  import {
58
59
  backupFile,
59
60
  readJsonSafe,
@@ -64,13 +65,13 @@ import {
64
65
  checkApiKey,
65
66
  describeKeyCheck
66
67
  } from "../chunk-G4TARJAY.js";
67
- import {
68
- parseFlags
69
- } from "../chunk-GMXKITVA.js";
70
68
  import {
71
69
  installClaudeAlias,
72
70
  resolveShellRc
73
71
  } from "../chunk-BC3VCZ3E.js";
72
+ import {
73
+ parseFlags
74
+ } from "../chunk-GMXKITVA.js";
74
75
  import {
75
76
  installGlobally,
76
77
  npmErrorMessage,
@@ -82,7 +83,7 @@ import {
82
83
  getPackageVersion,
83
84
  isNewerVersion,
84
85
  renderCommandHelp
85
- } from "../chunk-PPTC2QDB.js";
86
+ } from "../chunk-JGH37QRB.js";
86
87
  import {
87
88
  reportEvent
88
89
  } from "../chunk-5ZDYDU2Q.js";
@@ -203,8 +204,45 @@ var installSkillViaSkillsCli = (agent) => {
203
204
  }
204
205
  };
205
206
 
207
+ // src/setup/telemetry.ts
208
+ var currentStep = "start";
209
+ var emitted = false;
210
+ var reportSetupOutcome = async (outcome) => {
211
+ if (emitted) return;
212
+ emitted = true;
213
+ 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 }
231
+ );
232
+ } catch {
233
+ }
234
+ };
235
+
206
236
  // src/setup/options.ts
207
237
  var AGENT_CHOICES = ["claude_code", "codex", "gemini_cli", "hermes", "cursor", "custom"];
238
+ var CONNECT_MODES = ["app", "browser", "web", "none"];
239
+ var CONNECT_ALIASES = { auto: "app", pwa: "web" };
240
+ var parseConnectMode = (raw) => {
241
+ const value = raw.trim().toLowerCase();
242
+ if (Object.hasOwn(CONNECT_ALIASES, value)) return CONNECT_ALIASES[value];
243
+ if (CONNECT_MODES.includes(value)) return value;
244
+ throw new UsageError(`--connect must be one of: ${CONNECT_MODES.join(", ")} (got ${raw})`);
245
+ };
208
246
  var SETUP_FLAGS = {
209
247
  bools: ["key-stdin", "skip-phone", "yes", "y", "json", "dry-run", "help", "h"],
210
248
  values: ["key", "connect", "agents"]
@@ -243,18 +281,19 @@ var parseSetupOptions = (input2) => {
243
281
  const agentsFlag = flags.values.get("agents");
244
282
  const agents = parseAgentSelection(agentsFlag, yes);
245
283
  const connectRaw = flags.values.get("connect");
246
- if (connectRaw !== void 0 && connectRaw !== "web" && connectRaw !== "app") {
247
- throw new UsageError(`--connect must be one of: web, app (got ${connectRaw})`);
248
- }
284
+ const connectMode = connectRaw === void 0 ? "app" : parseConnectMode(connectRaw);
249
285
  const key = flags.values.get("key")?.trim() || null;
250
286
  const keyStdin = flags.bools.has("key-stdin");
251
287
  if (key && keyStdin) {
252
288
  throw new UsageError("Pass either --key or --key-stdin, not both");
253
289
  }
254
290
  return {
255
- connectMode: connectRaw === "app" ? "app" : "web",
291
+ connectMode,
256
292
  connectExplicit: connectRaw !== void 0,
257
- skipPhone: flags.bools.has("skip-phone"),
293
+ // `--connect none` and `--skip-phone` are the same instruction, so they
294
+ // resolve to the same field rather than being checked separately at the two
295
+ // call sites that would inevitably drift apart.
296
+ skipPhone: flags.bools.has("skip-phone") || connectMode === "none",
258
297
  // Anything that means "there is nobody to answer a prompt". A non-TTY stdin
259
298
  // is included because that is the case that used to hang: it is better to
260
299
  // take the defaults and say so than to wait forever for a keypress.
@@ -913,14 +952,18 @@ var offerClaudeAlias = async (options) => {
913
952
  console.log(` ${check} ${dim("The")} ${cyan("claude")} ${dim("alias is already set.")}`);
914
953
  }
915
954
  };
916
- var verifyKeyOrExit = async (apiKey, options) => {
917
- const live = process.stdout.isTTY === true && !options.json;
955
+ var verifyKeyOrExit = async (apiKey, options, precomputed) => {
956
+ const live = process.stdout.isTTY === true && !options.json && precomputed === void 0;
918
957
  if (live) writeHuman(` ${dim("...")} Verifying your key with pushary.com`);
919
958
  let result;
920
- try {
921
- result = await checkApiKey(apiKey);
922
- } catch (err) {
923
- result = { kind: "unreachable", detail: formatError(err) };
959
+ if (precomputed !== void 0) {
960
+ result = precomputed;
961
+ } else {
962
+ try {
963
+ result = await checkApiKey(apiKey);
964
+ } catch (err) {
965
+ result = { kind: "unreachable", detail: formatError(err) };
966
+ }
924
967
  }
925
968
  if (live) writeHuman("\r\x1B[2K");
926
969
  const report = describeKeyCheck(result);
@@ -939,6 +982,15 @@ var verifyKeyOrExit = async (apiKey, options) => {
939
982
  }
940
983
  return result;
941
984
  };
985
+ var usableStoredKey = async (apiKey) => {
986
+ let result;
987
+ try {
988
+ result = await checkApiKey(apiKey);
989
+ } catch (err) {
990
+ result = { kind: "unreachable", detail: formatError(err) };
991
+ }
992
+ return result.kind === "rejected" || result.kind === "malformed" ? null : result;
993
+ };
942
994
  var AGENT_NAMES = {
943
995
  claude_code: "Claude Code",
944
996
  codex: "Codex",
@@ -1079,20 +1131,47 @@ var main = async () => {
1079
1131
  const envKey = process.env.PUSHARY_API_KEY?.trim();
1080
1132
  let trimmedKey;
1081
1133
  let keyFromPairing = false;
1134
+ const storedKey = readConfigFileKey();
1135
+ const pairingRequested = connectMode === "app" && options.connectExplicit && !options.skipPhone;
1136
+ const storedCheck = !(flagKey && isValidApiKey(flagKey)) && !pairingRequested && !(envKey && isValidApiKey(envKey)) && storedKey !== null && isValidApiKey(storedKey) ? await usableStoredKey(storedKey) : null;
1137
+ let storedKeyCheck = null;
1082
1138
  if (flagKey && isValidApiKey(flagKey)) {
1083
1139
  const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
1084
1140
  console.log(` ${check} Using API key: ${dim(masked)}`);
1085
1141
  console.log();
1086
1142
  trimmedKey = flagKey;
1087
- } else if (connectMode === "app") {
1143
+ } else if (storedKey !== null && storedCheck !== null) {
1144
+ const masked = `${storedKey.slice(0, 8)}...${storedKey.slice(-4)}`;
1145
+ console.log(` ${check} Using the key already on this machine: ${dim(masked)}`);
1146
+ console.log(` ${dim(PUSHARY_CONFIG_FILE)}`);
1147
+ console.log();
1148
+ trimmedKey = storedKey;
1149
+ storedKeyCheck = storedCheck;
1150
+ } else if (!pairingRequested && envKey && isValidApiKey(envKey)) {
1151
+ const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
1152
+ console.log(` ${check} Found API key in environment: ${dim(masked)}`);
1153
+ console.log();
1154
+ const useExisting = options.nonInteractive ? true : await confirm({ message: "Use this key?", default: true });
1155
+ if (useExisting) {
1156
+ trimmedKey = envKey;
1157
+ } else {
1158
+ const apiKey = await input({ message: "API key:" });
1159
+ trimmedKey = apiKey.trim();
1160
+ }
1161
+ } else if (connectMode === "app" && canRunBrowserLogin() && !options.skipPhone) {
1088
1162
  console.log(` ${dim("Pairing with your Pushary app.")}`);
1163
+ console.log(` ${dim("No app? Press Ctrl-C for the ways to finish without one.")}`);
1089
1164
  const previous = readKeySource();
1090
1165
  const paired = await connectViaAppPairing();
1091
1166
  if (!paired) {
1092
1167
  console.log();
1093
- console.log(` ${yellow("!")} Pairing didn't complete. Re-run setup when you're ready to scan.`);
1094
- console.log(` ${dim("No app yet? Get it at")} ${cyan("https://pushary.com/download")}`);
1095
- process.exit(1);
1168
+ console.log(` ${yellow("!")} Pairing didn't complete. Two ways to finish without the app:`);
1169
+ console.log();
1170
+ console.log(` ${cyan("setup --connect browser")} ${dim("Approve in a browser tab")}`);
1171
+ console.log(` ${cyan("setup --key pk_...")} ${dim("Paste a key you already have")}`);
1172
+ console.log();
1173
+ console.log(` ${dim("Or get the app at")} ${cyan("https://pushary.com/download")} ${dim("and re-run setup.")}`);
1174
+ process.exit(EXIT.NOT_CONFIGURED);
1096
1175
  }
1097
1176
  trimmedKey = paired.apiKey;
1098
1177
  keyFromPairing = true;
@@ -1104,17 +1183,6 @@ var main = async () => {
1104
1183
  console.log(` ${dim("Revoke it at")} ${cyan("pushary.com/dashboard/agent/settings")} ${dim("if nothing else uses it.")}`);
1105
1184
  }
1106
1185
  console.log();
1107
- } else if (envKey && isValidApiKey(envKey)) {
1108
- const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
1109
- console.log(` ${check} Found API key in environment: ${dim(masked)}`);
1110
- console.log();
1111
- const useExisting = options.nonInteractive ? true : await confirm({ message: "Use this key?", default: true });
1112
- if (useExisting) {
1113
- trimmedKey = envKey;
1114
- } else {
1115
- const apiKey = await input({ message: "API key:" });
1116
- trimmedKey = apiKey.trim();
1117
- }
1118
1186
  } else if (!canRunBrowserLogin()) {
1119
1187
  console.log(` ${yellow("!")} No API key, and no terminal to sign in from.`);
1120
1188
  console.log(` ${dim("Pass --key <pk_xxx.xxx>, or --key-stdin, or export PUSHARY_API_KEY.")}`);
@@ -1158,7 +1226,7 @@ var main = async () => {
1158
1226
  `);
1159
1227
  process.exit(EXIT.USAGE);
1160
1228
  }
1161
- const keyCheck = keyFromPairing ? null : await verifyKeyOrExit(trimmedKey, options);
1229
+ const keyCheck = keyFromPairing ? null : await verifyKeyOrExit(trimmedKey, options, storedKeyCheck ?? void 0);
1162
1230
  const agents = await resolveAgents(options);
1163
1231
  if (options.dryRun) {
1164
1232
  reportDryRun(trimmedKey, agents, keyCheck);
@@ -1198,15 +1266,6 @@ var main = async () => {
1198
1266
  if (completed.includes("claude_code")) {
1199
1267
  await offerClaudeAlias(options);
1200
1268
  }
1201
- if (completed.length > 0) {
1202
- try {
1203
- await reportEvent(
1204
- { event: "agent_setup_completed", agentType: "setup", agents: completed },
1205
- { maxAttempts: 1, timeoutMs: 2e3 }
1206
- );
1207
- } catch {
1208
- }
1209
- }
1210
1269
  }
1211
1270
  const skipPhone = options.skipPhone;
1212
1271
  let phone = null;
@@ -1218,8 +1277,10 @@ var main = async () => {
1218
1277
  console.log(` ${dim("Skipping the phone check (--skip-phone).")}`);
1219
1278
  } else if (!process.stdout.isTTY) {
1220
1279
  phone = await printConnectInstructions(trimmedKey);
1221
- } else if (connectMode === "app") {
1280
+ } else if (keyFromPairing) {
1222
1281
  phone = await confirmAppConnection(trimmedKey);
1282
+ } else if (connectMode === "app") {
1283
+ phone = await printConnectInstructions(trimmedKey);
1223
1284
  } else {
1224
1285
  phone = await connectDevice(trimmedKey, { showCapabilities: !options.connectExplicit });
1225
1286
  }
@@ -1271,6 +1332,18 @@ var main = async () => {
1271
1332
  for (const line of renderCommandSurface()) console.log(` ${dim(line)}`);
1272
1333
  console.log();
1273
1334
  const exitCode = closing.exitCode;
1335
+ await reportSetupOutcome({
1336
+ options,
1337
+ completed,
1338
+ skipped,
1339
+ failed,
1340
+ // 'answered' is deliberately unreachable here: it means a human tapped, and
1341
+ // setup no longer sends a question. It stays in the type for doctor
1342
+ // --roundtrip, which does.
1343
+ connectOutcome: phone === null ? "skipped" : phone.reachesYou ? "delivered" : "unreachable",
1344
+ keyCheck: keyCheck?.kind ?? "skipped",
1345
+ exitCode
1346
+ });
1274
1347
  emitJson(options, {
1275
1348
  ok: closing.ok,
1276
1349
  configured: completed,
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  exitOnHelpFlag
4
- } from "../chunk-VYD27DKZ.js";
5
- import "../chunk-PPTC2QDB.js";
4
+ } from "../chunk-ATP6UQPH.js";
5
+ import "../chunk-JGH37QRB.js";
6
6
  import {
7
7
  readLedgerSummary
8
8
  } from "../chunk-WLGEY4UX.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  describeLastSeen,
4
4
  listMachines
5
- } from "../chunk-R22IAMSS.js";
5
+ } from "../chunk-YZT2CBC3.js";
6
6
  import {
7
7
  describeReach,
8
8
  fetchChannels,
@@ -11,7 +11,7 @@ import {
11
11
  import "../chunk-3EGEA4KH.js";
12
12
  import {
13
13
  readKeySource
14
- } from "../chunk-COC6NPWG.js";
14
+ } from "../chunk-NHKWU7MN.js";
15
15
  import "../chunk-6MTNS63X.js";
16
16
  import {
17
17
  checkApiKey,
@@ -20,14 +20,14 @@ import {
20
20
  import {
21
21
  createIo
22
22
  } from "../chunk-TX7KBKT7.js";
23
+ import "../chunk-BC3VCZ3E.js";
23
24
  import {
24
25
  rejectUnknownFlags
25
26
  } from "../chunk-GMXKITVA.js";
26
- import "../chunk-BC3VCZ3E.js";
27
27
  import {
28
28
  exitOnHelpFlag
29
- } from "../chunk-VYD27DKZ.js";
30
- import "../chunk-PPTC2QDB.js";
29
+ } from "../chunk-ATP6UQPH.js";
30
+ import "../chunk-JGH37QRB.js";
31
31
  import "../chunk-BSZYIAZL.js";
32
32
  import "../chunk-SAF6HGAA.js";
33
33
  import {
@@ -7,8 +7,8 @@ import {
7
7
  } from "../chunk-GMXKITVA.js";
8
8
  import {
9
9
  exitOnHelpFlag
10
- } from "../chunk-VYD27DKZ.js";
11
- import "../chunk-PPTC2QDB.js";
10
+ } from "../chunk-ATP6UQPH.js";
11
+ import "../chunk-JGH37QRB.js";
12
12
  import {
13
13
  UsageError
14
14
  } from "../chunk-7QLSKOSU.js";
@@ -34,10 +34,10 @@ import {
34
34
  } from "../chunk-MUEW424A.js";
35
35
  import {
36
36
  exitOnHelpFlag
37
- } from "../chunk-VYD27DKZ.js";
37
+ } from "../chunk-ATP6UQPH.js";
38
38
  import {
39
39
  isNewerVersion
40
- } from "../chunk-PPTC2QDB.js";
40
+ } from "../chunk-JGH37QRB.js";
41
41
  import "../chunk-DQAN3JQP.js";
42
42
  import {
43
43
  getApiKey
@@ -14,8 +14,8 @@ import {
14
14
  } from "../chunk-GMXKITVA.js";
15
15
  import {
16
16
  exitOnHelpFlag
17
- } from "../chunk-VYD27DKZ.js";
18
- import "../chunk-PPTC2QDB.js";
17
+ } from "../chunk-ATP6UQPH.js";
18
+ import "../chunk-JGH37QRB.js";
19
19
  import "../chunk-SAF6HGAA.js";
20
20
  import {
21
21
  UsageError
@@ -4,7 +4,7 @@ import {
4
4
  getPackageVersion,
5
5
  isCommandName,
6
6
  renderHelp
7
- } from "../chunk-PPTC2QDB.js";
7
+ } from "../chunk-JGH37QRB.js";
8
8
  import {
9
9
  installProxyDispatcher
10
10
  } from "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getPackageVersion,
3
3
  renderCommandHelp
4
- } from "./chunk-PPTC2QDB.js";
4
+ } from "./chunk-JGH37QRB.js";
5
5
  import {
6
6
  EXIT
7
7
  } from "./chunk-KZERVKTD.js";
@@ -105,8 +105,10 @@ var SETUP_OPTIONS = [
105
105
  ["--key-stdin", "Read the API key from the first line of stdin, so it never appears in argv or shell history"],
106
106
  ["--agents <list>", 'Comma-separated agent ids, or "auto" for everything detected, or "none" to save the key only'],
107
107
  ["--yes, -y", "Accept every default and ask nothing. Implies --agents auto unless --agents is given"],
108
- ["--connect app", "Pair the native Pushary app, no key to paste"],
109
- ["--connect web", "Connect your phone via the browser subscribe page (default)"],
108
+ ["--connect app", "Pair the native Pushary app, no key to paste (default)"],
109
+ ["--connect browser", "Approve in a browser tab instead. For a machine with no app"],
110
+ ["--connect web", "Legacy browser subscribe page"],
111
+ ["--connect none", "Skip the phone connect step, same as --skip-phone"],
110
112
  ["--skip-phone", "Skip the phone connect step"],
111
113
  ["--dry-run", "Print what would be written and change nothing"],
112
114
  ["--json", "Emit one machine-readable object instead of the human report"]
@@ -169,6 +169,7 @@ var readConfigKey = (configPath) => {
169
169
  const key = typeof result.value.apiKey === "string" ? result.value.apiKey.trim() : "";
170
170
  return key || null;
171
171
  };
172
+ var readConfigFileKey = () => readConfigKey(configFilePath());
172
173
  var readKeySource = () => {
173
174
  const configPath = configFilePath();
174
175
  const envKey = process.env.PUSHARY_API_KEY?.trim() || null;
@@ -185,5 +186,6 @@ export {
185
186
  keyPrefix,
186
187
  writeKey,
187
188
  clearKey,
189
+ readConfigFileKey,
188
190
  readKeySource
189
191
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getPackageVersion
3
- } from "./chunk-PPTC2QDB.js";
3
+ } from "./chunk-JGH37QRB.js";
4
4
  import {
5
5
  getMachineId
6
6
  } from "./chunk-RN3NOEJF.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.63.1",
3
+ "version": "0.64.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",