@birdybeep/cli 0.6.1 → 0.8.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.cjs +121 -10
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-SAUSKJDH.js → chunk-G72IJS7A.js} +130 -12
- package/dist/chunk-G72IJS7A.js.map +1 -0
- package/dist/index.cjs +121 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-SAUSKJDH.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -227,7 +227,7 @@ async function dispatch(argv, deps) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// src/version.ts
|
|
230
|
-
var CLI_VERSION = "0.
|
|
230
|
+
var CLI_VERSION = "0.8.0".length > 0 ? "0.8.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,11 @@ 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
|
+
describeCheckIn,
|
|
595
|
+
describeQuota,
|
|
596
|
+
describeReachability,
|
|
597
|
+
fetchPushReachability
|
|
578
598
|
} from "@birdybeep/agent-core";
|
|
579
599
|
import { claudeCodeAdapter as claudeCodeAdapter2 } from "@birdybeep/claude-code";
|
|
580
600
|
import { codexAdapter as codexAdapter2 } from "@birdybeep/codex";
|
|
@@ -654,16 +674,48 @@ function createDoctorCommand(deps = {}) {
|
|
|
654
674
|
usage: "birdybeep doctor [--json]",
|
|
655
675
|
run: async (ctx) => {
|
|
656
676
|
const checks = [];
|
|
657
|
-
const apiUrl = resolveApiUrl();
|
|
658
|
-
const
|
|
677
|
+
const apiUrl = deps.baseUrl ?? resolveApiUrl();
|
|
678
|
+
const pairing = await pairingReport(deps.tokenOptions ?? {});
|
|
659
679
|
checks.push(
|
|
660
|
-
paired ? { name: "Machine token", ok: true } : {
|
|
680
|
+
pairing.state === "paired" ? { name: "Machine token", ok: true } : pairing.state === "unpaired" ? {
|
|
661
681
|
name: "Machine token",
|
|
662
682
|
ok: false,
|
|
663
683
|
detail: "No machine token found.",
|
|
664
684
|
remedy: "Run `birdybeep pair` to pair this machine."
|
|
685
|
+
} : {
|
|
686
|
+
name: "Machine token",
|
|
687
|
+
ok: false,
|
|
688
|
+
detail: describeTokenStoreUnavailable(pairing),
|
|
689
|
+
remedy: tokenStoreRemedy(pairing)
|
|
665
690
|
}
|
|
666
691
|
);
|
|
692
|
+
const reachability = await fetchPushReachability({
|
|
693
|
+
baseUrl: apiUrl,
|
|
694
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
695
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
696
|
+
});
|
|
697
|
+
const reach = describeReachability(reachability);
|
|
698
|
+
if (reach !== null) {
|
|
699
|
+
checks.push({
|
|
700
|
+
name: "Push reachability",
|
|
701
|
+
ok: reach.ok,
|
|
702
|
+
detail: reach.detail,
|
|
703
|
+
...reach.remedy !== void 0 ? { remedy: reach.remedy } : {}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const checkIn = describeCheckIn(reachability);
|
|
707
|
+
if (checkIn !== null) {
|
|
708
|
+
checks.push({ name: "Device check-in", ok: checkIn.ok, detail: checkIn.detail });
|
|
709
|
+
}
|
|
710
|
+
const quota = describeQuota(reachability);
|
|
711
|
+
if (quota !== null) {
|
|
712
|
+
checks.push({
|
|
713
|
+
name: "Beep quota",
|
|
714
|
+
ok: quota.ok,
|
|
715
|
+
detail: quota.detail,
|
|
716
|
+
...quota.remedy !== void 0 ? { remedy: quota.remedy } : {}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
667
719
|
const unpaired = unpairedActivity();
|
|
668
720
|
if (unpaired !== null) {
|
|
669
721
|
checks.push({
|
|
@@ -735,6 +787,16 @@ function createDoctorCommand(deps = {}) {
|
|
|
735
787
|
ctx.io.result({
|
|
736
788
|
ok,
|
|
737
789
|
checks,
|
|
790
|
+
pairing,
|
|
791
|
+
// gcgp.23: paired | unpaired | unknown — never a bare boolean
|
|
792
|
+
// The backend's own quota numbers, unrendered (58l) — a script (or a bug report) gets
|
|
793
|
+
// the window and the counts, not just the sentence built from them.
|
|
794
|
+
...reachability.state === "ok" && reachability.data.quota !== void 0 ? { quota: reachability.data.quota } : {},
|
|
795
|
+
// The raw check-in (2x9s), unrendered. Present ONLY when the server reported the field
|
|
796
|
+
// at all, so a script can still tell "this backend predates check-ins" (key absent)
|
|
797
|
+
// from "no device has ever checked in" (key present, null) — the same distinction the
|
|
798
|
+
// rendered row is careful about.
|
|
799
|
+
...reachability.state === "ok" && reachability.data.most_recent_check_in_at !== void 0 ? { mostRecentCheckInAt: reachability.data.most_recent_check_in_at } : {},
|
|
738
800
|
surfaces: surfaceGroups,
|
|
739
801
|
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
740
802
|
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
@@ -988,13 +1050,20 @@ function createHookCommand(deps = {}) {
|
|
|
988
1050
|
outcome: result.outcome,
|
|
989
1051
|
eventType: result.eventType,
|
|
990
1052
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
991
|
-
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
1053
|
+
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1054
|
+
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
992
1055
|
});
|
|
993
1056
|
if (result.outcome === "unpaired") {
|
|
994
1057
|
ctx.io.errline(
|
|
995
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)."
|
|
996
1059
|
);
|
|
997
1060
|
}
|
|
1061
|
+
const unavailable = result.send?.tokenStoreUnavailable;
|
|
1062
|
+
if (unavailable !== void 0) {
|
|
1063
|
+
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.`
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
998
1067
|
return EXIT.OK;
|
|
999
1068
|
}
|
|
1000
1069
|
};
|
|
@@ -1149,6 +1218,8 @@ import { opencodeAdapter as opencodeAdapter3 } from "@birdybeep/opencode";
|
|
|
1149
1218
|
import { randomUUID } from "crypto";
|
|
1150
1219
|
import {
|
|
1151
1220
|
createSender as defaultCreateSender3,
|
|
1221
|
+
describeExhaustedQuota,
|
|
1222
|
+
fetchPushReachability as fetchPushReachability2,
|
|
1152
1223
|
getMachineIdentity as getMachineIdentity2,
|
|
1153
1224
|
normalizeEvent
|
|
1154
1225
|
} from "@birdybeep/agent-core";
|
|
@@ -1182,16 +1253,34 @@ function createTestCommand(deps = {}) {
|
|
|
1182
1253
|
usage: "birdybeep test [--json]",
|
|
1183
1254
|
run: async (ctx) => {
|
|
1184
1255
|
const event = buildTestEvent();
|
|
1185
|
-
const
|
|
1256
|
+
const baseUrl = deps.baseUrl ?? resolveApiUrl();
|
|
1257
|
+
const result = await makeSender(baseUrl).send(event);
|
|
1186
1258
|
if (ctx.flags.json) {
|
|
1187
1259
|
ctx.io.result({
|
|
1188
1260
|
outcome: result.outcome,
|
|
1189
1261
|
...result.status ? { status: result.status } : {},
|
|
1190
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1262
|
+
...result.decision ? { decision: result.decision } : {},
|
|
1263
|
+
...result.queueCause ? { queueCause: result.queueCause } : {},
|
|
1264
|
+
...result.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable", tokenStoreReason: result.tokenStoreUnavailable.reason } : {}
|
|
1191
1265
|
});
|
|
1192
1266
|
} else if (result.outcome === "delivered") {
|
|
1193
1267
|
if (result.decision === "notified" || result.decision === void 0) {
|
|
1194
|
-
|
|
1268
|
+
const reach = await fetchPushReachability2({
|
|
1269
|
+
baseUrl,
|
|
1270
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
1271
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1272
|
+
});
|
|
1273
|
+
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1274
|
+
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)."
|
|
1276
|
+
);
|
|
1277
|
+
} else if (reach.state === "ok") {
|
|
1278
|
+
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.`
|
|
1280
|
+
);
|
|
1281
|
+
} else {
|
|
1282
|
+
ctx.io.line("\u2713 Test event accepted by the backend \u2014 check your phone for a test Beep.");
|
|
1283
|
+
}
|
|
1195
1284
|
} else if (result.decision === "suppressed") {
|
|
1196
1285
|
ctx.io.line(
|
|
1197
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`."
|
|
@@ -1209,8 +1298,32 @@ function createTestCommand(deps = {}) {
|
|
|
1209
1298
|
ctx.io.line(
|
|
1210
1299
|
"\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
|
|
1211
1300
|
);
|
|
1301
|
+
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1302
|
+
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.`
|
|
1304
|
+
);
|
|
1305
|
+
} else if (result.outcome === "queued" && result.queueCause === "backend") {
|
|
1306
|
+
const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
|
|
1307
|
+
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.`
|
|
1309
|
+
);
|
|
1212
1310
|
} else if (result.outcome === "queued") {
|
|
1213
1311
|
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1312
|
+
} else if (result.code === "quota_exceeded") {
|
|
1313
|
+
const reach = await fetchPushReachability2({
|
|
1314
|
+
baseUrl,
|
|
1315
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
1316
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1317
|
+
});
|
|
1318
|
+
const quota = reach.state === "ok" ? reach.data.quota : void 0;
|
|
1319
|
+
const exhausted = quota ? describeExhaustedQuota(quota) : void 0;
|
|
1320
|
+
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}` : (
|
|
1322
|
+
// An older backend reports no quota, so there is no period and no reset date to
|
|
1323
|
+
// 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."
|
|
1325
|
+
)
|
|
1326
|
+
);
|
|
1214
1327
|
} else {
|
|
1215
1328
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1216
1329
|
}
|
|
@@ -1917,7 +2030,8 @@ function createStatusCommand(deps = {}) {
|
|
|
1917
2030
|
usage: "birdybeep status [--json]",
|
|
1918
2031
|
run: async (ctx) => {
|
|
1919
2032
|
const machine = machineIdentity();
|
|
1920
|
-
const
|
|
2033
|
+
const pairing = await pairingReport(deps.tokenOptions ?? {});
|
|
2034
|
+
const paired = pairing.state === "paired";
|
|
1921
2035
|
const integrations = await gatherIntegrations(adapters);
|
|
1922
2036
|
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1923
2037
|
const depthBefore = localQueueDepth();
|
|
@@ -1929,6 +2043,8 @@ function createStatusCommand(deps = {}) {
|
|
|
1929
2043
|
const report = {
|
|
1930
2044
|
machine,
|
|
1931
2045
|
paired,
|
|
2046
|
+
pairing,
|
|
2047
|
+
// gcgp.23: paired | unpaired | unknown — `paired: false` cannot say which
|
|
1932
2048
|
integrations,
|
|
1933
2049
|
surfaces,
|
|
1934
2050
|
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
@@ -1939,7 +2055,9 @@ function createStatusCommand(deps = {}) {
|
|
|
1939
2055
|
ctx.io.result(report);
|
|
1940
2056
|
} else {
|
|
1941
2057
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1942
|
-
ctx.io.line(
|
|
2058
|
+
ctx.io.line(
|
|
2059
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no \u2014 run `birdybeep pair`" : `Paired: unknown \u2014 ${describeTokenStoreUnavailable(pairing)}`
|
|
2060
|
+
);
|
|
1943
2061
|
ctx.io.line("Integrations:");
|
|
1944
2062
|
for (const i of integrations) {
|
|
1945
2063
|
ctx.io.line(` ${i.displayName}: ${i.status}`);
|
|
@@ -2138,4 +2256,4 @@ export {
|
|
|
2138
2256
|
buildCommands,
|
|
2139
2257
|
runCli
|
|
2140
2258
|
};
|
|
2141
|
-
//# sourceMappingURL=chunk-
|
|
2259
|
+
//# sourceMappingURL=chunk-G72IJS7A.js.map
|