@birdybeep/cli 0.8.0 → 0.8.1

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/dist/bin.cjs CHANGED
@@ -110,7 +110,7 @@ function filteredActivity() {
110
110
  function describeFilteredActivity(activity) {
111
111
  const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
112
112
  const since = new Date(activity.firstAt).toISOString();
113
- return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""} \u2014 hooks are firing; these types never beep, so they are not sent.`;
113
+ return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}. Hooks are firing; these types never produce notifications, so they are not sent.`;
114
114
  }
115
115
  function machineIdentity() {
116
116
  return (0, import_agent_core.getMachineIdentity)();
@@ -198,18 +198,18 @@ function describeSurface(state) {
198
198
  function describeSurfaceCoverage(state, group) {
199
199
  if (state.coverage === "active") {
200
200
  const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
201
- return `covered \u2014 ${state.events} event(s) from this build${last}`;
201
+ return `covered: ${state.events} event(s) from this build${last}`;
202
202
  }
203
203
  if (state.coverage === "wired") {
204
- return state.surface.shadowed === true ? `${group.displayName}'s hooks are installed and this build shares them, but another install comes first on PATH \u2014 it only runs if that order changes` : `${group.displayName}'s hooks are installed and this build shares them; nothing has fired from it yet`;
204
+ return state.surface.shadowed === true ? `${group.displayName}'s hooks are installed and this build shares them, but another install comes first on PATH. This build runs only if that order changes.` : `${group.displayName}'s hooks are installed and this build shares them; nothing has fired from it yet`;
205
205
  }
206
206
  if (!CONFIGURED_STATUSES.has(group.status)) {
207
- return `not covered \u2014 ${group.displayName} carries no BirdyBeep hooks, so this build cannot beep`;
207
+ return `not covered: ${group.displayName} carries no BirdyBeep hooks, so this build cannot produce notifications`;
208
208
  }
209
209
  const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
210
210
  const delivering = active.join(", ");
211
211
  const verb = active.length === 1 ? "is" : "are";
212
- return `not covered \u2014 nothing has ever fired from this build, while ${delivering} ${verb} delivering through the same config`;
212
+ return `not covered: nothing has fired from this build, while ${delivering} ${verb} delivering through the same config`;
213
213
  }
214
214
  function installTarget(harness) {
215
215
  return harness === "claude_code" ? "claude" : harness;
@@ -218,7 +218,7 @@ function surfaceRemedy(state, group) {
218
218
  if (state.coverage !== "uncovered") return void 0;
219
219
  if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
220
220
  const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
221
- return state.surface.kind === "desktop" ? `Run a turn in ${state.surface.label}. If it stays uncovered, that build cannot run the hook command: a desktop app spawns its engine with your LOGIN shell's PATH, not an interactive shell's, so a bare command is invisible to it. Re-run ${install} from a shell where \`birdybeep\` resolves \u2014 it rewrites the entry with absolute paths that need no PATH at all.` : `Run a turn in ${state.surface.label}. If it stays uncovered, re-run ${install} from a shell where \`birdybeep\` resolves, then check that ${state.surface.enginePath} is the build you are actually running.`;
221
+ return state.surface.kind === "desktop" ? `Run a turn in ${state.surface.label}. If it stays uncovered, that build cannot run the hook command: a desktop app spawns its engine with your LOGIN shell's PATH, not an interactive shell's, so a bare command is invisible to it. Re-run ${install} from a shell where \`birdybeep\` resolves. This rewrites the entry with absolute paths that do not depend on PATH.` : `Run a turn in ${state.surface.label}. If it stays uncovered, re-run ${install} from a shell where \`birdybeep\` resolves, then check that ${state.surface.enginePath} is the build you are actually running.`;
222
222
  }
223
223
 
224
224
  // src/framework.ts
@@ -298,7 +298,7 @@ function renderRootHelp(version, commands) {
298
298
  const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
299
299
  const featured = commands.filter((c) => c.gettingStarted !== void 0);
300
300
  return [
301
- `birdybeep ${version} \u2014 stream coding-agent lifecycle events to BirdyBeep.`,
301
+ `birdybeep ${version}: phone alerts for coding agents.`,
302
302
  "",
303
303
  "Usage:",
304
304
  " birdybeep <command> [options]",
@@ -330,7 +330,7 @@ function commandFlagTokens(...commands) {
330
330
  }
331
331
  function renderCommandHelp(path, command) {
332
332
  const lines = [
333
- `birdybeep ${path} \u2014 ${command.summary}`,
333
+ `birdybeep ${path}: ${command.summary}`,
334
334
  "",
335
335
  "Usage:",
336
336
  ` ${command.usage ?? `birdybeep ${path} [options]`}`
@@ -509,12 +509,12 @@ async function installSelected(adapters, ctx, tokenOptions) {
509
509
  return EXIT.OK;
510
510
  }
511
511
  if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {
512
- ctx.io.line("No supported harnesses detected \u2014 nothing to install.");
512
+ ctx.io.line("No supported harnesses detected. Nothing was installed.");
513
513
  }
514
514
  for (const o of outcomes) {
515
515
  if (!o.detected) {
516
516
  ctx.io.line(
517
- `\u2013 ${o.displayName}: not detected (skipped) \u2014 install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\``
517
+ `\u2013 ${o.displayName}: not detected (skipped). Install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\`.`
518
518
  );
519
519
  continue;
520
520
  }
@@ -524,7 +524,7 @@ async function installSelected(adapters, ctx, tokenOptions) {
524
524
  }
525
525
  if (!paired) {
526
526
  ctx.io.line(
527
- "\u26A0 This machine is not paired, so nothing these hooks produce can reach you. Run `birdybeep setup` \u2014 it pairs, wires up every agent, and sends a test Beep."
527
+ "\u26A0 This machine is not paired. Run `birdybeep setup` before expecting notifications."
528
528
  );
529
529
  }
530
530
  return EXIT.OK;
@@ -715,15 +715,15 @@ function createDoctorCommand(deps = {}) {
715
715
  name: "Events lost while unpaired",
716
716
  ok: false,
717
717
  detail: describeUnpairedActivity(unpaired),
718
- remedy: "Run `birdybeep pair`. Events that fired before pairing are gone \u2014 a first pairing does not replay them."
718
+ remedy: "Run `birdybeep pair`. Events that fired before pairing were not saved and will not be replayed."
719
719
  });
720
720
  }
721
721
  if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
722
722
  checks.push({
723
723
  name: "Approval beeps from Cursor",
724
724
  ok: false,
725
- detail: "Cursor is running your agent through the Claude Code hooks \u2014 that is why Cursor events arrive without a Cursor install. Its bridge drops Notification and PermissionRequest, so approvals never reach you.",
726
- remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe \u2014 duplicate events are collapsed."
725
+ detail: "Cursor is running your agent through the Claude Code hooks. This is why Cursor events arrive without a Cursor install. Its bridge drops Notification and PermissionRequest, so approvals never reach you.",
726
+ remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe; duplicate events are collapsed."
727
727
  });
728
728
  }
729
729
  const filtered = filteredActivity();
@@ -772,7 +772,7 @@ function createDoctorCommand(deps = {}) {
772
772
  name: "Backend reachable",
773
773
  ok: false,
774
774
  detail: `Could not reach ${apiUrl}.`,
775
- remedy: "Check your network; queued events will retry automatically."
775
+ remedy: "Check your network. Queued events retry automatically."
776
776
  }
777
777
  );
778
778
  const ok = checks.every((c) => c.ok);
@@ -797,10 +797,10 @@ function createDoctorCommand(deps = {}) {
797
797
  });
798
798
  } else {
799
799
  for (const c of checks) {
800
- ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
800
+ ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
801
801
  if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
802
802
  }
803
- ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed \u2014 see fixes above.");
803
+ ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
804
804
  }
805
805
  return ok ? EXIT.OK : EXIT.ERROR;
806
806
  }
@@ -975,7 +975,7 @@ function createHookCommand(deps = {}) {
975
975
  const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
976
976
  if (harness === "copilot" && copilotEventName === void 0) {
977
977
  ctx.io.errline(
978
- `birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")} \u2014 nothing was sent.`
978
+ `birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
979
979
  );
980
980
  return EXIT.USAGE;
981
981
  }
@@ -993,7 +993,7 @@ function createHookCommand(deps = {}) {
993
993
  }
994
994
  const drop = (reason, detail) => {
995
995
  ctx.io.result({ harness, outcome: "skipped", reason });
996
- ctx.io.errline(`birdybeep hook ${harness}: ${detail} \u2014 nothing was sent.`);
996
+ ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
997
997
  return EXIT.ERROR;
998
998
  };
999
999
  if (read === null) {
@@ -1023,7 +1023,7 @@ function createHookCommand(deps = {}) {
1023
1023
  });
1024
1024
  const article = handler === "opencode" ? "an" : "a";
1025
1025
  ctx.io.errline(
1026
- `birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
1026
+ `birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
1027
1027
  );
1028
1028
  return EXIT.ERROR;
1029
1029
  }
@@ -1037,17 +1037,23 @@ function createHookCommand(deps = {}) {
1037
1037
  eventType: result.eventType,
1038
1038
  ...result.send?.decision ? { decision: result.send.decision } : {},
1039
1039
  ...result.send?.status !== void 0 ? { status: result.send.status } : {},
1040
+ ...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
1040
1041
  ...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
1041
1042
  });
1042
1043
  if (result.outcome === "unpaired") {
1043
1044
  ctx.io.errline(
1044
- "birdybeep: this machine is not paired \u2014 the event was not sent and was not queued. Run `birdybeep pair` (or `birdybeep doctor` to see how many events this has cost)."
1045
+ "birdybeep: this machine is not paired. The event was not sent or queued. Run `birdybeep pair`, or run `birdybeep doctor` to see how many events were missed."
1045
1046
  );
1046
1047
  }
1047
- const unavailable = result.send?.tokenStoreUnavailable;
1048
+ if (result.outcome === "failed") {
1049
+ ctx.io.errline(
1050
+ "birdybeep: the event could not be sent or saved locally. It will not retry. Check that BirdyBeep can write to its user data directory, then run `birdybeep doctor`."
1051
+ );
1052
+ }
1053
+ const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
1048
1054
  if (unavailable !== void 0) {
1049
1055
  ctx.io.errline(
1050
- `birdybeep: could not read the machine token (${unavailable.reason}) \u2014 the event was QUEUED, not sent. It will deliver once the token store is readable (unlock your keychain); \`birdybeep doctor\` drains the queue.`
1056
+ `birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
1051
1057
  );
1052
1058
  }
1053
1059
  return EXIT.OK;
@@ -1065,7 +1071,7 @@ function createLogoutCommand(deps = {}) {
1065
1071
  usage: "birdybeep logout",
1066
1072
  run: async (ctx) => {
1067
1073
  await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
1068
- ctx.io.emit("Logged out \u2014 the machine token was removed.", { loggedOut: true });
1074
+ ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
1069
1075
  return EXIT.OK;
1070
1076
  }
1071
1077
  };
@@ -1092,14 +1098,14 @@ function createUnpairCommand(deps = {}) {
1092
1098
  const timeoutMs = deps.timeoutMs ?? 1e4;
1093
1099
  return {
1094
1100
  name: "unpair",
1095
- summary: "Unpair this machine \u2014 revoke it server-side and remove the local token",
1101
+ summary: "Revoke this machine and remove its local token",
1096
1102
  usage: "birdybeep unpair",
1097
1103
  run: async (ctx) => {
1098
1104
  const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
1099
1105
  const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
1100
1106
  await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
1101
1107
  const serverRevoked = outcome === "revoked";
1102
- const human = outcome === "revoked" ? "Unpaired \u2014 the machine was revoked and removed from your account." : outcome === "no_token" ? "Already unpaired \u2014 there was no local token to remove." : outcome === "unreachable" ? "Unpaired locally, but the server was unreachable \u2014 the machine may still show in the app. Open BirdyBeep and revoke it there to fully remove it." : "Unpaired locally, but the server didn't confirm removal \u2014 if the machine still shows in the app, revoke it there.";
1108
+ const human = outcome === "revoked" ? "Unpaired. The machine was revoked and removed from your account." : outcome === "no_token" ? "Already unpaired. There was no local token to remove." : outcome === "unreachable" ? "Unpaired locally, but the server was unreachable. The machine may still appear in the app. Open BirdyBeep and revoke it there." : "Unpaired locally, but the server did not confirm removal. If the machine still appears in the app, revoke it there.";
1103
1109
  ctx.io.emit(human, { unpaired: true, serverRevoked });
1104
1110
  return EXIT.OK;
1105
1111
  }
@@ -1182,7 +1188,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
1182
1188
  }
1183
1189
 
1184
1190
  // src/version.ts
1185
- var CLI_VERSION = "0.8.0".length > 0 ? "0.8.0" : "0.0.0";
1191
+ var CLI_VERSION = "0.8.1".length > 0 ? "0.8.1" : "0.0.0";
1186
1192
 
1187
1193
  // src/commands/setup.ts
1188
1194
  var import_claude_code5 = require("@birdybeep/claude-code");
@@ -1207,8 +1213,8 @@ function buildTestEvent(opts = {}) {
1207
1213
  source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
1208
1214
  machine: { label: machine.label, os: machine.os },
1209
1215
  workspace: { cwd: process.cwd() },
1210
- title: "BirdyBeep test event",
1211
- body: "If you can see this, your machine is wired up correctly.",
1216
+ title: "BirdyBeep test",
1217
+ body: "Notifications from this machine are working.",
1212
1218
  metadata: { test: true }
1213
1219
  },
1214
1220
  opts
@@ -1243,43 +1249,43 @@ function createTestCommand(deps = {}) {
1243
1249
  });
1244
1250
  if (reach.state === "ok" && reach.data.active_device_count === 0) {
1245
1251
  ctx.io.line(
1246
- "\u26A0 The backend accepted the test event, but NO device on this account can receive it \u2014 nothing will arrive. Open the BirdyBeep app on your phone to register it (if it says the device limit is full, free a slot in Settings \u203A devices)."
1252
+ "\u26A0 No active device can receive a Beep. Open BirdyBeep on your phone and sign in to register it. If the device limit is full, free a slot in Settings \u203A devices."
1247
1253
  );
1248
1254
  } else if (reach.state === "ok") {
1249
1255
  ctx.io.line(
1250
- `\u2713 Test event accepted and queued for ${String(reach.data.active_device_count)} registered device(s) \u2014 check your phone for a test Beep.`
1256
+ `\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
1251
1257
  );
1252
1258
  } else {
1253
- ctx.io.line("\u2713 Test event accepted by the backend \u2014 check your phone for a test Beep.");
1259
+ ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
1254
1260
  }
1255
1261
  } else if (result.decision === "suppressed") {
1256
1262
  ctx.io.line(
1257
- "\u26A0 The backend accepted the test event but suppressed the push \u2014 this machine or integration is probably muted. Check mutes in the app, or run `birdybeep doctor`."
1263
+ "\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
1258
1264
  );
1259
1265
  } else if (result.decision === "deduped") {
1260
- ctx.io.line(
1261
- "\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
1262
- );
1266
+ ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
1263
1267
  } else {
1264
1268
  ctx.io.line(
1265
- `\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
1269
+ `\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
1266
1270
  );
1267
1271
  }
1268
1272
  } else if (result.outcome === "unpaired") {
1269
1273
  ctx.io.line(
1270
- "\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
1274
+ "\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
1271
1275
  );
1272
- } else if (result.tokenStoreUnavailable !== void 0) {
1276
+ } else if (result.outcome === "failed") {
1273
1277
  ctx.io.line(
1274
- `\u2022 Could not read the machine token (${result.tokenStoreUnavailable.reason}) \u2014 the test event was queued and delivers once the token store is readable. If your keychain is locked, unlock it and run \`birdybeep test\` again.`
1278
+ "\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
1275
1279
  );
1276
- } else if (result.outcome === "queued" && result.queueCause === "backend") {
1277
- const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
1280
+ } else if (result.tokenStoreUnavailable !== void 0) {
1278
1281
  ctx.io.line(
1279
- result.code === "rate_limited" || result.status === 429 ? `\u2022 Throttled by the backend${status} \u2014 test event queued; it retries on its own. Not your network: the request got through.` : `\u2022 The backend is having trouble${status} \u2014 test event queued; it retries on its own. Not your network: the request got through.`
1282
+ `\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
1280
1283
  );
1284
+ } else if (result.outcome === "queued" && result.queueCause === "backend") {
1285
+ const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
1286
+ ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
1281
1287
  } else if (result.outcome === "queued") {
1282
- ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
1288
+ ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
1283
1289
  } else if (result.code === "quota_exceeded") {
1284
1290
  const reach = await (0, import_agent_core8.fetchPushReachability)({
1285
1291
  baseUrl,
@@ -1287,18 +1293,18 @@ function createTestCommand(deps = {}) {
1287
1293
  ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
1288
1294
  });
1289
1295
  const quota = reach.state === "ok" ? reach.data.quota : void 0;
1290
- const exhausted = quota ? (0, import_agent_core8.describeExhaustedQuota)(quota) : void 0;
1296
+ const exhausted = quota !== void 0 && quota.beeps_limit !== null ? (0, import_agent_core8.describeExhaustedQuota)({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
1291
1297
  ctx.io.line(
1292
- quota && exhausted ? `\u2717 Test event REJECTED \u2014 this account's monthly beep quota is used up (${String(quota.beeps_accepted)}/${String(quota.beeps_limit)} beeps on the ${quota.plan} plan, period ${exhausted.window}). Every notifiable event is being rejected. ${exhausted.remedy}` : (
1298
+ quota?.beeps_limit === null ? "\u2717 Test event rejected. The backend rejected this event for quota, but this account now reports unlimited beeps on Plus. The plan changed between those requests; run `birdybeep test` again. If it repeats, run `birdybeep doctor`." : quota && exhausted ? `\u2717 Test event rejected. This account's monthly beep quota is used up (${String(quota.beeps_accepted)}/${String(quota.beeps_limit)} beeps on the ${quota.plan} plan, period ${exhausted.window}). Every notifiable event is being rejected. ${exhausted.remedy}` : (
1293
1299
  // An older backend reports no quota, so there is no period and no reset date to
1294
1300
  // give — and `doctor` cannot supply them either, since it reads this same response.
1295
- "\u2717 Test event REJECTED \u2014 this account's monthly beep quota is used up, so no beep can be sent. This server does not report the period or the reset date; check your usage in the BirdyBeep app."
1301
+ "\u2717 Test event rejected. This account's monthly beep quota is used up, so no Beep can be sent. This server does not report the period or the reset date; check your usage in the BirdyBeep app."
1296
1302
  )
1297
1303
  );
1298
1304
  } else {
1299
1305
  ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
1300
1306
  }
1301
- return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
1307
+ return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
1302
1308
  }
1303
1309
  };
1304
1310
  }
@@ -1398,7 +1404,7 @@ function buildHarnessReports(installs, groups) {
1398
1404
  }
1399
1405
  ] : surfaces.map((state) => {
1400
1406
  const graded = rowState(state, group, status);
1401
- const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks \u2014 re-run \`birdybeep agent install ${installTarget2(adapter.id)}\` from a shell where \`birdybeep\` resolves.` : void 0);
1407
+ const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks. Re-run \`birdybeep agent install ${installTarget2(adapter.id)}\` from a shell where \`birdybeep\` resolves.` : void 0);
1402
1408
  return {
1403
1409
  harness: adapter.id,
1404
1410
  displayName: adapter.displayName,
@@ -1444,12 +1450,12 @@ function describeMissing(reports) {
1444
1450
  const names = missing.map((r) => r.displayName);
1445
1451
  if (missing.length === reports.length) {
1446
1452
  return [
1447
- "No supported coding agent is installed on this machine, so there was nothing to wire up.",
1448
- `Install one of ${names.join(", ")}, then run \`birdybeep setup\` again \u2014 pairing is already done, so it picks up from here.`
1453
+ "No supported coding agent is installed on this machine.",
1454
+ `Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
1449
1455
  ];
1450
1456
  }
1451
1457
  return [
1452
- `Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again to wire it up.`
1458
+ `Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
1453
1459
  ];
1454
1460
  }
1455
1461
  async function runHarnessSetup(ctx, options, deps = {}) {
@@ -1672,7 +1678,7 @@ function createPairingCommand(verb, deps = {}) {
1672
1678
  },
1673
1679
  {
1674
1680
  flag: "--no-install",
1675
- summary: "Stop after pairing \u2014 don't detect or wire up any coding agent"
1681
+ summary: "Stop after pairing; do not install coding-agent hooks"
1676
1682
  },
1677
1683
  {
1678
1684
  flag: "--no-test",
@@ -1692,7 +1698,7 @@ function createPairingCommand(verb, deps = {}) {
1692
1698
  };
1693
1699
  if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
1694
1700
  ctx.io.line(
1695
- chain !== void 0 ? "\u2713 Already paired \u2014 checking which coding agents are wired up." : "\u2713 Already paired. Nothing else to do with --no-install."
1701
+ chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
1696
1702
  );
1697
1703
  const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1698
1704
  ctx.io.result({
@@ -1728,7 +1734,7 @@ function createPairingCommand(verb, deps = {}) {
1728
1734
  ctx.io.line(
1729
1735
  ` Session code (display only; cannot approve by itself): ${start.user_code}`
1730
1736
  );
1731
- ctx.io.line("Waiting for you to approve this machine in the app\u2026");
1737
+ ctx.io.line("Waiting for approval in the BirdyBeep app.");
1732
1738
  }
1733
1739
  const deadline = Date.parse(start.expires_at);
1734
1740
  const startedAt = clock();
@@ -1757,7 +1763,7 @@ function createPairingCommand(verb, deps = {}) {
1757
1763
  }
1758
1764
  if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1759
1765
  ctx.io.line(
1760
- poll.status === "error" ? ` still trying \u2014 the server is busy (${poll.message}). approve in the app when you can\u2026` : " still waiting \u2014 approve this machine in the BirdyBeep app\u2026"
1766
+ poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1761
1767
  );
1762
1768
  lastBeat = nowMs;
1763
1769
  }
@@ -1797,7 +1803,7 @@ function createPairingCommand(verb, deps = {}) {
1797
1803
  if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
1798
1804
  ctx.io.result({ paired: false, reason: "declined" });
1799
1805
  ctx.io.errline(
1800
- "Pairing declined \u2014 the machine token was NOT stored, and this machine will send no events. The machine may still appear in the BirdyBeep app; revoke it there if you did not intend to pair it."
1806
+ "Pairing declined. The machine token was NOT stored, and this machine will send no events. The machine may still appear in the BirdyBeep app; revoke it there if you did not intend to pair it."
1801
1807
  );
1802
1808
  return EXIT.ERROR;
1803
1809
  }
@@ -1806,8 +1812,8 @@ function createPairingCommand(verb, deps = {}) {
1806
1812
  const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
1807
1813
  (0, import_agent_core9.clearUnpairedNotice)();
1808
1814
  const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
1809
- const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing \u2014 you won't be beeped about them.` : "";
1810
- const nextStep = chain === void 0 ? " Run `birdybeep setup` to wire up your coding agents." : "";
1815
+ const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
1816
+ const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
1811
1817
  ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
1812
1818
  const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1813
1819
  ctx.io.result({
@@ -1825,7 +1831,7 @@ function createPairCommand(deps = {}) {
1825
1831
  return createPairingCommand(
1826
1832
  {
1827
1833
  name: "pair",
1828
- summary: "Pair this machine and wire up every coding agent on it",
1834
+ summary: "Pair this machine and install detected coding-agent hooks",
1829
1835
  usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
1830
1836
  skipWhenPaired: false
1831
1837
  },
@@ -1836,9 +1842,9 @@ function createSetupCommand(deps = {}) {
1836
1842
  return createPairingCommand(
1837
1843
  {
1838
1844
  name: "setup",
1839
- summary: "Set up BirdyBeep here: pair, wire up every coding agent, test",
1845
+ summary: "Set up BirdyBeep on this machine",
1840
1846
  usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
1841
- gettingStarted: "Pair this machine, wire up every coding agent it finds, and send a test Beep.",
1847
+ gettingStarted: "Connect this machine and install hooks for detected coding agents.",
1842
1848
  skipWhenPaired: true
1843
1849
  },
1844
1850
  deps
@@ -1911,7 +1917,7 @@ function createReportStatusCommand(deps = {}) {
1911
1917
  run: async (ctx) => {
1912
1918
  const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
1913
1919
  if (token === null) {
1914
- ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
1920
+ ctx.io.errline("No machine token. Run `birdybeep pair` first.");
1915
1921
  return EXIT.ERROR;
1916
1922
  }
1917
1923
  const items = await gatherItems(adapters);
@@ -1956,12 +1962,12 @@ function createReportStatusCommand(deps = {}) {
1956
1962
  });
1957
1963
  } else if (outcome === "terminal") {
1958
1964
  ctx.io.errline(
1959
- `Report rejected (${errorCode ?? "auth"}) \u2014 your token may be revoked. Re-run \`birdybeep pair\`.`
1965
+ `Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
1960
1966
  );
1961
1967
  } else {
1962
1968
  for (const e of effective) {
1963
1969
  ctx.io.line(
1964
- outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred \u2014 backend unreachable)`
1970
+ outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
1965
1971
  );
1966
1972
  }
1967
1973
  }
@@ -2021,7 +2027,7 @@ function createStatusCommand(deps = {}) {
2021
2027
  } else {
2022
2028
  ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
2023
2029
  ctx.io.line(
2024
- pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no \u2014 run `birdybeep pair`" : `Paired: unknown \u2014 ${describeTokenStoreUnavailable(pairing)}`
2030
+ pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
2025
2031
  );
2026
2032
  ctx.io.line("Integrations:");
2027
2033
  for (const i of integrations) {
@@ -2029,7 +2035,7 @@ function createStatusCommand(deps = {}) {
2029
2035
  const group = surfaces.find((g) => g.harness === i.harness);
2030
2036
  for (const state of group?.surfaces ?? []) {
2031
2037
  const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
2032
- ctx.io.line(` ${mark} ${describeSurface(state)} \u2014 ${state.coverage}`);
2038
+ ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
2033
2039
  }
2034
2040
  }
2035
2041
  ctx.io.line(