@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-G72IJS7A.js";
4
+ } from "./chunk-J45QGUDO.js";
5
5
 
6
6
  // src/node-check.ts
7
7
  var MIN_NODE_MAJOR = 20;
@@ -80,7 +80,7 @@ function renderRootHelp(version, commands) {
80
80
  const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
81
81
  const featured = commands.filter((c) => c.gettingStarted !== void 0);
82
82
  return [
83
- `birdybeep ${version} \u2014 stream coding-agent lifecycle events to BirdyBeep.`,
83
+ `birdybeep ${version}: phone alerts for coding agents.`,
84
84
  "",
85
85
  "Usage:",
86
86
  " birdybeep <command> [options]",
@@ -112,7 +112,7 @@ function commandFlagTokens(...commands) {
112
112
  }
113
113
  function renderCommandHelp(path, command) {
114
114
  const lines = [
115
- `birdybeep ${path} \u2014 ${command.summary}`,
115
+ `birdybeep ${path}: ${command.summary}`,
116
116
  "",
117
117
  "Usage:",
118
118
  ` ${command.usage ?? `birdybeep ${path} [options]`}`
@@ -227,7 +227,7 @@ async function dispatch(argv, deps) {
227
227
  }
228
228
 
229
229
  // src/version.ts
230
- var CLI_VERSION = "0.8.0".length > 0 ? "0.8.0" : "0.0.0";
230
+ var CLI_VERSION = "0.8.1".length > 0 ? "0.8.1" : "0.0.0";
231
231
 
232
232
  // src/commands/agent.ts
233
233
  import { claudeCodeAdapter } from "@birdybeep/claude-code";
@@ -333,7 +333,7 @@ function filteredActivity() {
333
333
  function describeFilteredActivity(activity) {
334
334
  const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
335
335
  const since = new Date(activity.firstAt).toISOString();
336
- return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""} \u2014 hooks are firing; these types never beep, so they are not sent.`;
336
+ return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}. Hooks are firing; these types never produce notifications, so they are not sent.`;
337
337
  }
338
338
  function machineIdentity() {
339
339
  return getMachineIdentity();
@@ -421,18 +421,18 @@ function describeSurface(state) {
421
421
  function describeSurfaceCoverage(state, group) {
422
422
  if (state.coverage === "active") {
423
423
  const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
424
- return `covered \u2014 ${state.events} event(s) from this build${last}`;
424
+ return `covered: ${state.events} event(s) from this build${last}`;
425
425
  }
426
426
  if (state.coverage === "wired") {
427
- 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`;
427
+ 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`;
428
428
  }
429
429
  if (!CONFIGURED_STATUSES.has(group.status)) {
430
- return `not covered \u2014 ${group.displayName} carries no BirdyBeep hooks, so this build cannot beep`;
430
+ return `not covered: ${group.displayName} carries no BirdyBeep hooks, so this build cannot produce notifications`;
431
431
  }
432
432
  const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
433
433
  const delivering = active.join(", ");
434
434
  const verb = active.length === 1 ? "is" : "are";
435
- return `not covered \u2014 nothing has ever fired from this build, while ${delivering} ${verb} delivering through the same config`;
435
+ return `not covered: nothing has fired from this build, while ${delivering} ${verb} delivering through the same config`;
436
436
  }
437
437
  function installTarget(harness) {
438
438
  return harness === "claude_code" ? "claude" : harness;
@@ -441,7 +441,7 @@ function surfaceRemedy(state, group) {
441
441
  if (state.coverage !== "uncovered") return void 0;
442
442
  if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
443
443
  const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
444
- 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.`;
444
+ 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.`;
445
445
  }
446
446
 
447
447
  // src/commands/agent.ts
@@ -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;
@@ -722,15 +722,15 @@ function createDoctorCommand(deps = {}) {
722
722
  name: "Events lost while unpaired",
723
723
  ok: false,
724
724
  detail: describeUnpairedActivity(unpaired),
725
- remedy: "Run `birdybeep pair`. Events that fired before pairing are gone \u2014 a first pairing does not replay them."
725
+ remedy: "Run `birdybeep pair`. Events that fired before pairing were not saved and will not be replayed."
726
726
  });
727
727
  }
728
728
  if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
729
729
  checks.push({
730
730
  name: "Approval beeps from Cursor",
731
731
  ok: false,
732
- 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.",
733
- 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."
732
+ 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.",
733
+ 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."
734
734
  });
735
735
  }
736
736
  const filtered = filteredActivity();
@@ -779,7 +779,7 @@ function createDoctorCommand(deps = {}) {
779
779
  name: "Backend reachable",
780
780
  ok: false,
781
781
  detail: `Could not reach ${apiUrl}.`,
782
- remedy: "Check your network; queued events will retry automatically."
782
+ remedy: "Check your network. Queued events retry automatically."
783
783
  }
784
784
  );
785
785
  const ok = checks.every((c) => c.ok);
@@ -804,10 +804,10 @@ function createDoctorCommand(deps = {}) {
804
804
  });
805
805
  } else {
806
806
  for (const c of checks) {
807
- ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
807
+ ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
808
808
  if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
809
809
  }
810
- ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed \u2014 see fixes above.");
810
+ ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
811
811
  }
812
812
  return ok ? EXIT.OK : EXIT.ERROR;
813
813
  }
@@ -989,7 +989,7 @@ function createHookCommand(deps = {}) {
989
989
  const copilotEventName = harness === "copilot" && isCopilotHookEventName(ctx.args[1]) ? ctx.args[1] : void 0;
990
990
  if (harness === "copilot" && copilotEventName === void 0) {
991
991
  ctx.io.errline(
992
- `birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")} \u2014 nothing was sent.`
992
+ `birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
993
993
  );
994
994
  return EXIT.USAGE;
995
995
  }
@@ -1007,7 +1007,7 @@ function createHookCommand(deps = {}) {
1007
1007
  }
1008
1008
  const drop = (reason, detail) => {
1009
1009
  ctx.io.result({ harness, outcome: "skipped", reason });
1010
- ctx.io.errline(`birdybeep hook ${harness}: ${detail} \u2014 nothing was sent.`);
1010
+ ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
1011
1011
  return EXIT.ERROR;
1012
1012
  };
1013
1013
  if (read === null) {
@@ -1037,7 +1037,7 @@ function createHookCommand(deps = {}) {
1037
1037
  });
1038
1038
  const article = handler === "opencode" ? "an" : "a";
1039
1039
  ctx.io.errline(
1040
- `birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
1040
+ `birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
1041
1041
  );
1042
1042
  return EXIT.ERROR;
1043
1043
  }
@@ -1051,17 +1051,23 @@ function createHookCommand(deps = {}) {
1051
1051
  eventType: result.eventType,
1052
1052
  ...result.send?.decision ? { decision: result.send.decision } : {},
1053
1053
  ...result.send?.status !== void 0 ? { status: result.send.status } : {},
1054
+ ...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
1054
1055
  ...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
1055
1056
  });
1056
1057
  if (result.outcome === "unpaired") {
1057
1058
  ctx.io.errline(
1058
- "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)."
1059
+ "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."
1059
1060
  );
1060
1061
  }
1061
- const unavailable = result.send?.tokenStoreUnavailable;
1062
+ if (result.outcome === "failed") {
1063
+ ctx.io.errline(
1064
+ "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`."
1065
+ );
1066
+ }
1067
+ const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
1062
1068
  if (unavailable !== void 0) {
1063
1069
  ctx.io.errline(
1064
- `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.`
1070
+ `birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
1065
1071
  );
1066
1072
  }
1067
1073
  return EXIT.OK;
@@ -1079,7 +1085,7 @@ function createLogoutCommand(deps = {}) {
1079
1085
  usage: "birdybeep logout",
1080
1086
  run: async (ctx) => {
1081
1087
  await clearToken(deps.tokenOptions ?? {});
1082
- ctx.io.emit("Logged out \u2014 the machine token was removed.", { loggedOut: true });
1088
+ ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
1083
1089
  return EXIT.OK;
1084
1090
  }
1085
1091
  };
@@ -1106,14 +1112,14 @@ function createUnpairCommand(deps = {}) {
1106
1112
  const timeoutMs = deps.timeoutMs ?? 1e4;
1107
1113
  return {
1108
1114
  name: "unpair",
1109
- summary: "Unpair this machine \u2014 revoke it server-side and remove the local token",
1115
+ summary: "Revoke this machine and remove its local token",
1110
1116
  usage: "birdybeep unpair",
1111
1117
  run: async (ctx) => {
1112
1118
  const token = await getToken2(deps.tokenOptions ?? {});
1113
1119
  const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
1114
1120
  await clearToken(deps.tokenOptions ?? {});
1115
1121
  const serverRevoked = outcome === "revoked";
1116
- 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.";
1122
+ 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.";
1117
1123
  ctx.io.emit(human, { unpaired: true, serverRevoked });
1118
1124
  return EXIT.OK;
1119
1125
  }
@@ -1236,8 +1242,8 @@ function buildTestEvent(opts = {}) {
1236
1242
  source_session_id: `birdybeep-cli-test-${randomUUID()}`,
1237
1243
  machine: { label: machine.label, os: machine.os },
1238
1244
  workspace: { cwd: process.cwd() },
1239
- title: "BirdyBeep test event",
1240
- body: "If you can see this, your machine is wired up correctly.",
1245
+ title: "BirdyBeep test",
1246
+ body: "Notifications from this machine are working.",
1241
1247
  metadata: { test: true }
1242
1248
  },
1243
1249
  opts
@@ -1272,43 +1278,43 @@ function createTestCommand(deps = {}) {
1272
1278
  });
1273
1279
  if (reach.state === "ok" && reach.data.active_device_count === 0) {
1274
1280
  ctx.io.line(
1275
- "\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)."
1281
+ "\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."
1276
1282
  );
1277
1283
  } else if (reach.state === "ok") {
1278
1284
  ctx.io.line(
1279
- `\u2713 Test event accepted and queued for ${String(reach.data.active_device_count)} registered device(s) \u2014 check your phone for a test Beep.`
1285
+ `\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
1280
1286
  );
1281
1287
  } else {
1282
- ctx.io.line("\u2713 Test event accepted by the backend \u2014 check your phone for a test Beep.");
1288
+ ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
1283
1289
  }
1284
1290
  } else if (result.decision === "suppressed") {
1285
1291
  ctx.io.line(
1286
- "\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`."
1292
+ "\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
1287
1293
  );
1288
1294
  } else if (result.decision === "deduped") {
1289
- ctx.io.line(
1290
- "\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
1291
- );
1295
+ ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
1292
1296
  } else {
1293
1297
  ctx.io.line(
1294
- `\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
1298
+ `\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
1295
1299
  );
1296
1300
  }
1297
1301
  } else if (result.outcome === "unpaired") {
1298
1302
  ctx.io.line(
1299
- "\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
1303
+ "\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
1300
1304
  );
1301
- } else if (result.tokenStoreUnavailable !== void 0) {
1305
+ } else if (result.outcome === "failed") {
1302
1306
  ctx.io.line(
1303
- `\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.`
1307
+ "\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
1304
1308
  );
1305
- } else if (result.outcome === "queued" && result.queueCause === "backend") {
1306
- const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
1309
+ } else if (result.tokenStoreUnavailable !== void 0) {
1307
1310
  ctx.io.line(
1308
- 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.`
1311
+ `\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
1309
1312
  );
1313
+ } else if (result.outcome === "queued" && result.queueCause === "backend") {
1314
+ const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
1315
+ ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
1310
1316
  } else if (result.outcome === "queued") {
1311
- ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
1317
+ ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
1312
1318
  } else if (result.code === "quota_exceeded") {
1313
1319
  const reach = await fetchPushReachability2({
1314
1320
  baseUrl,
@@ -1316,18 +1322,18 @@ function createTestCommand(deps = {}) {
1316
1322
  ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
1317
1323
  });
1318
1324
  const quota = reach.state === "ok" ? reach.data.quota : void 0;
1319
- const exhausted = quota ? describeExhaustedQuota(quota) : void 0;
1325
+ const exhausted = quota !== void 0 && quota.beeps_limit !== null ? describeExhaustedQuota({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
1320
1326
  ctx.io.line(
1321
- 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}` : (
1327
+ 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}` : (
1322
1328
  // An older backend reports no quota, so there is no period and no reset date to
1323
1329
  // give — and `doctor` cannot supply them either, since it reads this same response.
1324
- "\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."
1330
+ "\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."
1325
1331
  )
1326
1332
  );
1327
1333
  } else {
1328
1334
  ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
1329
1335
  }
1330
- return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
1336
+ return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
1331
1337
  }
1332
1338
  };
1333
1339
  }
@@ -1427,7 +1433,7 @@ function buildHarnessReports(installs, groups) {
1427
1433
  }
1428
1434
  ] : surfaces.map((state) => {
1429
1435
  const graded = rowState(state, group, status);
1430
- 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);
1436
+ 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);
1431
1437
  return {
1432
1438
  harness: adapter.id,
1433
1439
  displayName: adapter.displayName,
@@ -1473,12 +1479,12 @@ function describeMissing(reports) {
1473
1479
  const names = missing.map((r) => r.displayName);
1474
1480
  if (missing.length === reports.length) {
1475
1481
  return [
1476
- "No supported coding agent is installed on this machine, so there was nothing to wire up.",
1477
- `Install one of ${names.join(", ")}, then run \`birdybeep setup\` again \u2014 pairing is already done, so it picks up from here.`
1482
+ "No supported coding agent is installed on this machine.",
1483
+ `Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
1478
1484
  ];
1479
1485
  }
1480
1486
  return [
1481
- `Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again to wire it up.`
1487
+ `Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
1482
1488
  ];
1483
1489
  }
1484
1490
  async function runHarnessSetup(ctx, options, deps = {}) {
@@ -1701,7 +1707,7 @@ function createPairingCommand(verb, deps = {}) {
1701
1707
  },
1702
1708
  {
1703
1709
  flag: "--no-install",
1704
- summary: "Stop after pairing \u2014 don't detect or wire up any coding agent"
1710
+ summary: "Stop after pairing; do not install coding-agent hooks"
1705
1711
  },
1706
1712
  {
1707
1713
  flag: "--no-test",
@@ -1721,7 +1727,7 @@ function createPairingCommand(verb, deps = {}) {
1721
1727
  };
1722
1728
  if (verb.skipWhenPaired && await getToken3(deps.tokenOptions ?? {}) !== null) {
1723
1729
  ctx.io.line(
1724
- chain !== void 0 ? "\u2713 Already paired \u2014 checking which coding agents are wired up." : "\u2713 Already paired. Nothing else to do with --no-install."
1730
+ chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
1725
1731
  );
1726
1732
  const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1727
1733
  ctx.io.result({
@@ -1757,7 +1763,7 @@ function createPairingCommand(verb, deps = {}) {
1757
1763
  ctx.io.line(
1758
1764
  ` Session code (display only; cannot approve by itself): ${start.user_code}`
1759
1765
  );
1760
- ctx.io.line("Waiting for you to approve this machine in the app\u2026");
1766
+ ctx.io.line("Waiting for approval in the BirdyBeep app.");
1761
1767
  }
1762
1768
  const deadline = Date.parse(start.expires_at);
1763
1769
  const startedAt = clock();
@@ -1786,7 +1792,7 @@ function createPairingCommand(verb, deps = {}) {
1786
1792
  }
1787
1793
  if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1788
1794
  ctx.io.line(
1789
- 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"
1795
+ poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1790
1796
  );
1791
1797
  lastBeat = nowMs;
1792
1798
  }
@@ -1826,7 +1832,7 @@ function createPairingCommand(verb, deps = {}) {
1826
1832
  if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
1827
1833
  ctx.io.result({ paired: false, reason: "declined" });
1828
1834
  ctx.io.errline(
1829
- "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."
1835
+ "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."
1830
1836
  );
1831
1837
  return EXIT.ERROR;
1832
1838
  }
@@ -1835,8 +1841,8 @@ function createPairingCommand(verb, deps = {}) {
1835
1841
  const discarded = new LocalEventQueue2().discardBefore(clock());
1836
1842
  clearUnpairedNotice();
1837
1843
  const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
1838
- const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing \u2014 you won't be beeped about them.` : "";
1839
- const nextStep = chain === void 0 ? " Run `birdybeep setup` to wire up your coding agents." : "";
1844
+ const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
1845
+ const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
1840
1846
  ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
1841
1847
  const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1842
1848
  ctx.io.result({
@@ -1854,7 +1860,7 @@ function createPairCommand(deps = {}) {
1854
1860
  return createPairingCommand(
1855
1861
  {
1856
1862
  name: "pair",
1857
- summary: "Pair this machine and wire up every coding agent on it",
1863
+ summary: "Pair this machine and install detected coding-agent hooks",
1858
1864
  usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
1859
1865
  skipWhenPaired: false
1860
1866
  },
@@ -1865,9 +1871,9 @@ function createSetupCommand(deps = {}) {
1865
1871
  return createPairingCommand(
1866
1872
  {
1867
1873
  name: "setup",
1868
- summary: "Set up BirdyBeep here: pair, wire up every coding agent, test",
1874
+ summary: "Set up BirdyBeep on this machine",
1869
1875
  usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
1870
- gettingStarted: "Pair this machine, wire up every coding agent it finds, and send a test Beep.",
1876
+ gettingStarted: "Connect this machine and install hooks for detected coding agents.",
1871
1877
  skipWhenPaired: true
1872
1878
  },
1873
1879
  deps
@@ -1944,7 +1950,7 @@ function createReportStatusCommand(deps = {}) {
1944
1950
  run: async (ctx) => {
1945
1951
  const token = await getToken4(deps.tokenOptions ?? {});
1946
1952
  if (token === null) {
1947
- ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
1953
+ ctx.io.errline("No machine token. Run `birdybeep pair` first.");
1948
1954
  return EXIT.ERROR;
1949
1955
  }
1950
1956
  const items = await gatherItems(adapters);
@@ -1989,12 +1995,12 @@ function createReportStatusCommand(deps = {}) {
1989
1995
  });
1990
1996
  } else if (outcome === "terminal") {
1991
1997
  ctx.io.errline(
1992
- `Report rejected (${errorCode ?? "auth"}) \u2014 your token may be revoked. Re-run \`birdybeep pair\`.`
1998
+ `Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
1993
1999
  );
1994
2000
  } else {
1995
2001
  for (const e of effective) {
1996
2002
  ctx.io.line(
1997
- outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred \u2014 backend unreachable)`
2003
+ outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
1998
2004
  );
1999
2005
  }
2000
2006
  }
@@ -2056,7 +2062,7 @@ function createStatusCommand(deps = {}) {
2056
2062
  } else {
2057
2063
  ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
2058
2064
  ctx.io.line(
2059
- pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no \u2014 run `birdybeep pair`" : `Paired: unknown \u2014 ${describeTokenStoreUnavailable(pairing)}`
2065
+ pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
2060
2066
  );
2061
2067
  ctx.io.line("Integrations:");
2062
2068
  for (const i of integrations) {
@@ -2064,7 +2070,7 @@ function createStatusCommand(deps = {}) {
2064
2070
  const group = surfaces.find((g) => g.harness === i.harness);
2065
2071
  for (const state of group?.surfaces ?? []) {
2066
2072
  const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
2067
- ctx.io.line(` ${mark} ${describeSurface(state)} \u2014 ${state.coverage}`);
2073
+ ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
2068
2074
  }
2069
2075
  }
2070
2076
  ctx.io.line(
@@ -2256,4 +2262,4 @@ export {
2256
2262
  buildCommands,
2257
2263
  runCli
2258
2264
  };
2259
- //# sourceMappingURL=chunk-G72IJS7A.js.map
2265
+ //# sourceMappingURL=chunk-J45QGUDO.js.map