@birdybeep/cli 0.6.0 → 0.7.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/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-BY5MNQE3.js";
4
+ } from "./chunk-6RIMM4RO.js";
5
5
 
6
6
  // src/node-check.ts
7
7
  var MIN_NODE_MAJOR = 20;
@@ -227,7 +227,7 @@ async function dispatch(argv, deps) {
227
227
  }
228
228
 
229
229
  // src/version.ts
230
- var CLI_VERSION = "0.6.0".length > 0 ? "0.6.0" : "0.0.0";
230
+ var CLI_VERSION = "0.7.0".length > 0 ? "0.7.0" : "0.0.0";
231
231
 
232
232
  // src/commands/agent.ts
233
233
  import { claudeCodeAdapter } from "@birdybeep/claude-code";
@@ -245,6 +245,7 @@ import {
245
245
  LocalEventQueue,
246
246
  readFilteredActivity,
247
247
  readObservedBuilds,
248
+ readToken,
248
249
  readUnpairedNotice
249
250
  } from "@birdybeep/agent-core";
250
251
  import {
@@ -270,6 +271,21 @@ async function gatherIntegrations(adapters) {
270
271
  async function isPaired(tokenOptions = {}) {
271
272
  return await getToken(tokenOptions) !== null;
272
273
  }
274
+ async function pairingReport(tokenOptions = {}) {
275
+ const lookup = await readToken(tokenOptions);
276
+ if (lookup.state === "present") return { state: "paired" };
277
+ if (lookup.state === "absent") return { state: "unpaired" };
278
+ return { state: "unknown", reason: lookup.reason, store: lookup.store };
279
+ }
280
+ function describeTokenStoreUnavailable(report) {
281
+ return `Could not read the token store (${report.reason ?? "unknown error"}), so whether this machine is paired is unknown. Events fired now are QUEUED, not lost, and send once it is readable.`;
282
+ }
283
+ function tokenStoreRemedy(report) {
284
+ if (report.store === "file") {
285
+ return "Repair the token file \u2014 check that its directory and the file itself are readable and writable by you (`chmod 700` the directory, `chmod 600` the file), then run `birdybeep doctor` again to drain the queue.";
286
+ }
287
+ return "Unlock your login keychain (log in to the desktop session, or unlock the screen), then run `birdybeep doctor` again to drain the queue. If it stays unreadable, run `birdybeep pair`.";
288
+ }
273
289
  function localQueueDepth() {
274
290
  return new LocalEventQueue().size();
275
291
  }
@@ -574,7 +590,9 @@ function createAgentCommand(deps = {}) {
574
590
  // src/commands/doctor.ts
575
591
  import {
576
592
  createSender as defaultCreateSender,
577
- DEFAULT_QUEUE_MAX_ENTRIES as QUEUE_CAP
593
+ DEFAULT_QUEUE_MAX_ENTRIES as QUEUE_CAP,
594
+ describeReachability,
595
+ fetchPushReachability
578
596
  } from "@birdybeep/agent-core";
579
597
  import { claudeCodeAdapter as claudeCodeAdapter2 } from "@birdybeep/claude-code";
580
598
  import { codexAdapter as codexAdapter2 } from "@birdybeep/codex";
@@ -654,16 +672,36 @@ function createDoctorCommand(deps = {}) {
654
672
  usage: "birdybeep doctor [--json]",
655
673
  run: async (ctx) => {
656
674
  const checks = [];
657
- const apiUrl = resolveApiUrl();
658
- const paired = await isPaired(deps.tokenOptions ?? {});
675
+ const apiUrl = deps.baseUrl ?? resolveApiUrl();
676
+ const pairing = await pairingReport(deps.tokenOptions ?? {});
659
677
  checks.push(
660
- paired ? { name: "Machine token", ok: true } : {
678
+ pairing.state === "paired" ? { name: "Machine token", ok: true } : pairing.state === "unpaired" ? {
661
679
  name: "Machine token",
662
680
  ok: false,
663
681
  detail: "No machine token found.",
664
682
  remedy: "Run `birdybeep pair` to pair this machine."
683
+ } : {
684
+ name: "Machine token",
685
+ ok: false,
686
+ detail: describeTokenStoreUnavailable(pairing),
687
+ remedy: tokenStoreRemedy(pairing)
665
688
  }
666
689
  );
690
+ const reach = describeReachability(
691
+ await fetchPushReachability({
692
+ baseUrl: apiUrl,
693
+ ...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
694
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
695
+ })
696
+ );
697
+ if (reach !== null) {
698
+ checks.push({
699
+ name: "Push reachability",
700
+ ok: reach.ok,
701
+ detail: reach.detail,
702
+ ...reach.remedy !== void 0 ? { remedy: reach.remedy } : {}
703
+ });
704
+ }
667
705
  const unpaired = unpairedActivity();
668
706
  if (unpaired !== null) {
669
707
  checks.push({
@@ -735,6 +773,8 @@ function createDoctorCommand(deps = {}) {
735
773
  ctx.io.result({
736
774
  ok,
737
775
  checks,
776
+ pairing,
777
+ // gcgp.23: paired | unpaired | unknown — never a bare boolean
738
778
  surfaces: surfaceGroups,
739
779
  queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
740
780
  ...unpaired !== null ? { unpairedActivity: unpaired } : {},
@@ -988,13 +1028,20 @@ function createHookCommand(deps = {}) {
988
1028
  outcome: result.outcome,
989
1029
  eventType: result.eventType,
990
1030
  ...result.send?.decision ? { decision: result.send.decision } : {},
991
- ...result.send?.status !== void 0 ? { status: result.send.status } : {}
1031
+ ...result.send?.status !== void 0 ? { status: result.send.status } : {},
1032
+ ...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
992
1033
  });
993
1034
  if (result.outcome === "unpaired") {
994
1035
  ctx.io.errline(
995
1036
  "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)."
996
1037
  );
997
1038
  }
1039
+ const unavailable = result.send?.tokenStoreUnavailable;
1040
+ if (unavailable !== void 0) {
1041
+ ctx.io.errline(
1042
+ `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.`
1043
+ );
1044
+ }
998
1045
  return EXIT.OK;
999
1046
  }
1000
1047
  };
@@ -1149,6 +1196,7 @@ import { opencodeAdapter as opencodeAdapter3 } from "@birdybeep/opencode";
1149
1196
  import { randomUUID } from "crypto";
1150
1197
  import {
1151
1198
  createSender as defaultCreateSender3,
1199
+ fetchPushReachability as fetchPushReachability2,
1152
1200
  getMachineIdentity as getMachineIdentity2,
1153
1201
  normalizeEvent
1154
1202
  } from "@birdybeep/agent-core";
@@ -1182,16 +1230,33 @@ function createTestCommand(deps = {}) {
1182
1230
  usage: "birdybeep test [--json]",
1183
1231
  run: async (ctx) => {
1184
1232
  const event = buildTestEvent();
1185
- const result = await makeSender(resolveApiUrl()).send(event);
1233
+ const baseUrl = deps.baseUrl ?? resolveApiUrl();
1234
+ const result = await makeSender(baseUrl).send(event);
1186
1235
  if (ctx.flags.json) {
1187
1236
  ctx.io.result({
1188
1237
  outcome: result.outcome,
1189
1238
  ...result.status ? { status: result.status } : {},
1190
- ...result.decision ? { decision: result.decision } : {}
1239
+ ...result.decision ? { decision: result.decision } : {},
1240
+ ...result.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable", tokenStoreReason: result.tokenStoreUnavailable.reason } : {}
1191
1241
  });
1192
1242
  } else if (result.outcome === "delivered") {
1193
1243
  if (result.decision === "notified" || result.decision === void 0) {
1194
- ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
1244
+ const reach = await fetchPushReachability2({
1245
+ baseUrl,
1246
+ ...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
1247
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
1248
+ });
1249
+ if (reach.state === "ok" && reach.data.active_device_count === 0) {
1250
+ ctx.io.line(
1251
+ "\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
+ );
1253
+ } else if (reach.state === "ok") {
1254
+ ctx.io.line(
1255
+ `\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
+ );
1257
+ } else {
1258
+ ctx.io.line("\u2713 Test event accepted by the backend \u2014 check your phone for a test Beep.");
1259
+ }
1195
1260
  } else if (result.decision === "suppressed") {
1196
1261
  ctx.io.line(
1197
1262
  "\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`."
@@ -1209,6 +1274,10 @@ function createTestCommand(deps = {}) {
1209
1274
  ctx.io.line(
1210
1275
  "\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
1211
1276
  );
1277
+ } else if (result.tokenStoreUnavailable !== void 0) {
1278
+ ctx.io.line(
1279
+ `\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.`
1280
+ );
1212
1281
  } else if (result.outcome === "queued") {
1213
1282
  ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
1214
1283
  } else {
@@ -1917,7 +1986,8 @@ function createStatusCommand(deps = {}) {
1917
1986
  usage: "birdybeep status [--json]",
1918
1987
  run: async (ctx) => {
1919
1988
  const machine = machineIdentity();
1920
- const paired = await isPaired(deps.tokenOptions ?? {});
1989
+ const pairing = await pairingReport(deps.tokenOptions ?? {});
1990
+ const paired = pairing.state === "paired";
1921
1991
  const integrations = await gatherIntegrations(adapters);
1922
1992
  const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
1923
1993
  const depthBefore = localQueueDepth();
@@ -1929,6 +1999,8 @@ function createStatusCommand(deps = {}) {
1929
1999
  const report = {
1930
2000
  machine,
1931
2001
  paired,
2002
+ pairing,
2003
+ // gcgp.23: paired | unpaired | unknown — `paired: false` cannot say which
1932
2004
  integrations,
1933
2005
  surfaces,
1934
2006
  queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
@@ -1939,7 +2011,9 @@ function createStatusCommand(deps = {}) {
1939
2011
  ctx.io.result(report);
1940
2012
  } else {
1941
2013
  ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
1942
- ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
2014
+ ctx.io.line(
2015
+ pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no \u2014 run `birdybeep pair`" : `Paired: unknown \u2014 ${describeTokenStoreUnavailable(pairing)}`
2016
+ );
1943
2017
  ctx.io.line("Integrations:");
1944
2018
  for (const i of integrations) {
1945
2019
  ctx.io.line(` ${i.displayName}: ${i.status}`);
@@ -2138,4 +2212,4 @@ export {
2138
2212
  buildCommands,
2139
2213
  runCli
2140
2214
  };
2141
- //# sourceMappingURL=chunk-BY5MNQE3.js.map
2215
+ //# sourceMappingURL=chunk-6RIMM4RO.js.map