@birdybeep/cli 0.7.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 +114 -67
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-6RIMM4RO.js → chunk-J45QGUDO.js} +118 -68
- package/dist/chunk-J45QGUDO.js.map +1 -0
- package/dist/index.cjs +114 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -8
- package/dist/chunk-6RIMM4RO.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -129,7 +129,7 @@ function filteredActivity() {
|
|
|
129
129
|
function describeFilteredActivity(activity) {
|
|
130
130
|
const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
|
|
131
131
|
const since = new Date(activity.firstAt).toISOString();
|
|
132
|
-
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}
|
|
132
|
+
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}. Hooks are firing; these types never produce notifications, so they are not sent.`;
|
|
133
133
|
}
|
|
134
134
|
function machineIdentity() {
|
|
135
135
|
return (0, import_agent_core.getMachineIdentity)();
|
|
@@ -217,18 +217,18 @@ function describeSurface(state) {
|
|
|
217
217
|
function describeSurfaceCoverage(state, group) {
|
|
218
218
|
if (state.coverage === "active") {
|
|
219
219
|
const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
|
|
220
|
-
return `covered
|
|
220
|
+
return `covered: ${state.events} event(s) from this build${last}`;
|
|
221
221
|
}
|
|
222
222
|
if (state.coverage === "wired") {
|
|
223
|
-
return state.surface.shadowed === true ? `${group.displayName}'s hooks are installed and this build shares them, but another install comes first on PATH
|
|
223
|
+
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`;
|
|
224
224
|
}
|
|
225
225
|
if (!CONFIGURED_STATUSES.has(group.status)) {
|
|
226
|
-
return `not covered
|
|
226
|
+
return `not covered: ${group.displayName} carries no BirdyBeep hooks, so this build cannot produce notifications`;
|
|
227
227
|
}
|
|
228
228
|
const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
|
|
229
229
|
const delivering = active.join(", ");
|
|
230
230
|
const verb = active.length === 1 ? "is" : "are";
|
|
231
|
-
return `not covered
|
|
231
|
+
return `not covered: nothing has fired from this build, while ${delivering} ${verb} delivering through the same config`;
|
|
232
232
|
}
|
|
233
233
|
function installTarget(harness) {
|
|
234
234
|
return harness === "claude_code" ? "claude" : harness;
|
|
@@ -237,7 +237,7 @@ function surfaceRemedy(state, group) {
|
|
|
237
237
|
if (state.coverage !== "uncovered") return void 0;
|
|
238
238
|
if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
|
|
239
239
|
const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
|
|
240
|
-
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
|
|
240
|
+
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.`;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
// src/framework.ts
|
|
@@ -322,7 +322,7 @@ function renderRootHelp(version, commands) {
|
|
|
322
322
|
const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
323
323
|
const featured = commands.filter((c) => c.gettingStarted !== void 0);
|
|
324
324
|
return [
|
|
325
|
-
`birdybeep ${version}
|
|
325
|
+
`birdybeep ${version}: phone alerts for coding agents.`,
|
|
326
326
|
"",
|
|
327
327
|
"Usage:",
|
|
328
328
|
" birdybeep <command> [options]",
|
|
@@ -354,7 +354,7 @@ function commandFlagTokens(...commands) {
|
|
|
354
354
|
}
|
|
355
355
|
function renderCommandHelp(path, command) {
|
|
356
356
|
const lines = [
|
|
357
|
-
`birdybeep ${path}
|
|
357
|
+
`birdybeep ${path}: ${command.summary}`,
|
|
358
358
|
"",
|
|
359
359
|
"Usage:",
|
|
360
360
|
` ${command.usage ?? `birdybeep ${path} [options]`}`
|
|
@@ -533,12 +533,12 @@ async function installSelected(adapters, ctx, tokenOptions) {
|
|
|
533
533
|
return EXIT.OK;
|
|
534
534
|
}
|
|
535
535
|
if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {
|
|
536
|
-
ctx.io.line("No supported harnesses detected
|
|
536
|
+
ctx.io.line("No supported harnesses detected. Nothing was installed.");
|
|
537
537
|
}
|
|
538
538
|
for (const o of outcomes) {
|
|
539
539
|
if (!o.detected) {
|
|
540
540
|
ctx.io.line(
|
|
541
|
-
`\u2013 ${o.displayName}: not detected (skipped)
|
|
541
|
+
`\u2013 ${o.displayName}: not detected (skipped). Install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\`.`
|
|
542
542
|
);
|
|
543
543
|
continue;
|
|
544
544
|
}
|
|
@@ -548,7 +548,7 @@ async function installSelected(adapters, ctx, tokenOptions) {
|
|
|
548
548
|
}
|
|
549
549
|
if (!paired) {
|
|
550
550
|
ctx.io.line(
|
|
551
|
-
"\u26A0 This machine is not paired
|
|
551
|
+
"\u26A0 This machine is not paired. Run `birdybeep setup` before expecting notifications."
|
|
552
552
|
);
|
|
553
553
|
}
|
|
554
554
|
return EXIT.OK;
|
|
@@ -706,13 +706,12 @@ function createDoctorCommand(deps = {}) {
|
|
|
706
706
|
remedy: tokenStoreRemedy(pairing)
|
|
707
707
|
}
|
|
708
708
|
);
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
);
|
|
709
|
+
const reachability = await (0, import_agent_core4.fetchPushReachability)({
|
|
710
|
+
baseUrl: apiUrl,
|
|
711
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
712
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
713
|
+
});
|
|
714
|
+
const reach = (0, import_agent_core4.describeReachability)(reachability);
|
|
716
715
|
if (reach !== null) {
|
|
717
716
|
checks.push({
|
|
718
717
|
name: "Push reachability",
|
|
@@ -721,21 +720,34 @@ function createDoctorCommand(deps = {}) {
|
|
|
721
720
|
...reach.remedy !== void 0 ? { remedy: reach.remedy } : {}
|
|
722
721
|
});
|
|
723
722
|
}
|
|
723
|
+
const checkIn = (0, import_agent_core4.describeCheckIn)(reachability);
|
|
724
|
+
if (checkIn !== null) {
|
|
725
|
+
checks.push({ name: "Device check-in", ok: checkIn.ok, detail: checkIn.detail });
|
|
726
|
+
}
|
|
727
|
+
const quota = (0, import_agent_core4.describeQuota)(reachability);
|
|
728
|
+
if (quota !== null) {
|
|
729
|
+
checks.push({
|
|
730
|
+
name: "Beep quota",
|
|
731
|
+
ok: quota.ok,
|
|
732
|
+
detail: quota.detail,
|
|
733
|
+
...quota.remedy !== void 0 ? { remedy: quota.remedy } : {}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
724
736
|
const unpaired = unpairedActivity();
|
|
725
737
|
if (unpaired !== null) {
|
|
726
738
|
checks.push({
|
|
727
739
|
name: "Events lost while unpaired",
|
|
728
740
|
ok: false,
|
|
729
741
|
detail: describeUnpairedActivity(unpaired),
|
|
730
|
-
remedy: "Run `birdybeep pair`. Events that fired before pairing
|
|
742
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing were not saved and will not be replayed."
|
|
731
743
|
});
|
|
732
744
|
}
|
|
733
745
|
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
734
746
|
checks.push({
|
|
735
747
|
name: "Approval beeps from Cursor",
|
|
736
748
|
ok: false,
|
|
737
|
-
detail: "Cursor is running your agent through the Claude Code hooks
|
|
738
|
-
remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe
|
|
749
|
+
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.",
|
|
750
|
+
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."
|
|
739
751
|
});
|
|
740
752
|
}
|
|
741
753
|
const filtered = filteredActivity();
|
|
@@ -784,7 +796,7 @@ function createDoctorCommand(deps = {}) {
|
|
|
784
796
|
name: "Backend reachable",
|
|
785
797
|
ok: false,
|
|
786
798
|
detail: `Could not reach ${apiUrl}.`,
|
|
787
|
-
remedy: "Check your network
|
|
799
|
+
remedy: "Check your network. Queued events retry automatically."
|
|
788
800
|
}
|
|
789
801
|
);
|
|
790
802
|
const ok = checks.every((c) => c.ok);
|
|
@@ -794,6 +806,14 @@ function createDoctorCommand(deps = {}) {
|
|
|
794
806
|
checks,
|
|
795
807
|
pairing,
|
|
796
808
|
// gcgp.23: paired | unpaired | unknown — never a bare boolean
|
|
809
|
+
// The backend's own quota numbers, unrendered (58l) — a script (or a bug report) gets
|
|
810
|
+
// the window and the counts, not just the sentence built from them.
|
|
811
|
+
...reachability.state === "ok" && reachability.data.quota !== void 0 ? { quota: reachability.data.quota } : {},
|
|
812
|
+
// The raw check-in (2x9s), unrendered. Present ONLY when the server reported the field
|
|
813
|
+
// at all, so a script can still tell "this backend predates check-ins" (key absent)
|
|
814
|
+
// from "no device has ever checked in" (key present, null) — the same distinction the
|
|
815
|
+
// rendered row is careful about.
|
|
816
|
+
...reachability.state === "ok" && reachability.data.most_recent_check_in_at !== void 0 ? { mostRecentCheckInAt: reachability.data.most_recent_check_in_at } : {},
|
|
797
817
|
surfaces: surfaceGroups,
|
|
798
818
|
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
799
819
|
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
@@ -801,10 +821,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
801
821
|
});
|
|
802
822
|
} else {
|
|
803
823
|
for (const c of checks) {
|
|
804
|
-
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ?
|
|
824
|
+
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
|
|
805
825
|
if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
|
|
806
826
|
}
|
|
807
|
-
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed
|
|
827
|
+
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
|
|
808
828
|
}
|
|
809
829
|
return ok ? EXIT.OK : EXIT.ERROR;
|
|
810
830
|
}
|
|
@@ -979,7 +999,7 @@ function createHookCommand(deps = {}) {
|
|
|
979
999
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
980
1000
|
if (harness === "copilot" && copilotEventName === void 0) {
|
|
981
1001
|
ctx.io.errline(
|
|
982
|
-
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}
|
|
1002
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
|
|
983
1003
|
);
|
|
984
1004
|
return EXIT.USAGE;
|
|
985
1005
|
}
|
|
@@ -997,7 +1017,7 @@ function createHookCommand(deps = {}) {
|
|
|
997
1017
|
}
|
|
998
1018
|
const drop = (reason, detail) => {
|
|
999
1019
|
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
1000
|
-
ctx.io.errline(`birdybeep hook ${harness}: ${detail}
|
|
1020
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
|
|
1001
1021
|
return EXIT.ERROR;
|
|
1002
1022
|
};
|
|
1003
1023
|
if (read === null) {
|
|
@@ -1027,7 +1047,7 @@ function createHookCommand(deps = {}) {
|
|
|
1027
1047
|
});
|
|
1028
1048
|
const article = handler === "opencode" ? "an" : "a";
|
|
1029
1049
|
ctx.io.errline(
|
|
1030
|
-
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event
|
|
1050
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
|
|
1031
1051
|
);
|
|
1032
1052
|
return EXIT.ERROR;
|
|
1033
1053
|
}
|
|
@@ -1041,17 +1061,23 @@ function createHookCommand(deps = {}) {
|
|
|
1041
1061
|
eventType: result.eventType,
|
|
1042
1062
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
1043
1063
|
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1064
|
+
...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
|
|
1044
1065
|
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
1045
1066
|
});
|
|
1046
1067
|
if (result.outcome === "unpaired") {
|
|
1047
1068
|
ctx.io.errline(
|
|
1048
|
-
"birdybeep: this machine is not paired
|
|
1069
|
+
"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."
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (result.outcome === "failed") {
|
|
1073
|
+
ctx.io.errline(
|
|
1074
|
+
"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`."
|
|
1049
1075
|
);
|
|
1050
1076
|
}
|
|
1051
|
-
const unavailable = result.send?.tokenStoreUnavailable;
|
|
1077
|
+
const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
|
|
1052
1078
|
if (unavailable !== void 0) {
|
|
1053
1079
|
ctx.io.errline(
|
|
1054
|
-
`birdybeep:
|
|
1080
|
+
`birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
|
|
1055
1081
|
);
|
|
1056
1082
|
}
|
|
1057
1083
|
return EXIT.OK;
|
|
@@ -1069,7 +1095,7 @@ function createLogoutCommand(deps = {}) {
|
|
|
1069
1095
|
usage: "birdybeep logout",
|
|
1070
1096
|
run: async (ctx) => {
|
|
1071
1097
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1072
|
-
ctx.io.emit("Logged out
|
|
1098
|
+
ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
|
|
1073
1099
|
return EXIT.OK;
|
|
1074
1100
|
}
|
|
1075
1101
|
};
|
|
@@ -1096,14 +1122,14 @@ function createUnpairCommand(deps = {}) {
|
|
|
1096
1122
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
1097
1123
|
return {
|
|
1098
1124
|
name: "unpair",
|
|
1099
|
-
summary: "
|
|
1125
|
+
summary: "Revoke this machine and remove its local token",
|
|
1100
1126
|
usage: "birdybeep unpair",
|
|
1101
1127
|
run: async (ctx) => {
|
|
1102
1128
|
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
1103
1129
|
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
1104
1130
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1105
1131
|
const serverRevoked = outcome === "revoked";
|
|
1106
|
-
const human = outcome === "revoked" ? "Unpaired
|
|
1132
|
+
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.";
|
|
1107
1133
|
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
1108
1134
|
return EXIT.OK;
|
|
1109
1135
|
}
|
|
@@ -1186,7 +1212,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
1186
1212
|
}
|
|
1187
1213
|
|
|
1188
1214
|
// src/version.ts
|
|
1189
|
-
var CLI_VERSION = "0.
|
|
1215
|
+
var CLI_VERSION = "0.8.1".length > 0 ? "0.8.1" : "0.0.0";
|
|
1190
1216
|
|
|
1191
1217
|
// src/commands/setup.ts
|
|
1192
1218
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
@@ -1211,8 +1237,8 @@ function buildTestEvent(opts = {}) {
|
|
|
1211
1237
|
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1212
1238
|
machine: { label: machine.label, os: machine.os },
|
|
1213
1239
|
workspace: { cwd: process.cwd() },
|
|
1214
|
-
title: "BirdyBeep test
|
|
1215
|
-
body: "
|
|
1240
|
+
title: "BirdyBeep test",
|
|
1241
|
+
body: "Notifications from this machine are working.",
|
|
1216
1242
|
metadata: { test: true }
|
|
1217
1243
|
},
|
|
1218
1244
|
opts
|
|
@@ -1235,6 +1261,7 @@ function createTestCommand(deps = {}) {
|
|
|
1235
1261
|
outcome: result.outcome,
|
|
1236
1262
|
...result.status ? { status: result.status } : {},
|
|
1237
1263
|
...result.decision ? { decision: result.decision } : {},
|
|
1264
|
+
...result.queueCause ? { queueCause: result.queueCause } : {},
|
|
1238
1265
|
...result.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable", tokenStoreReason: result.tokenStoreUnavailable.reason } : {}
|
|
1239
1266
|
});
|
|
1240
1267
|
} else if (result.outcome === "delivered") {
|
|
@@ -1246,42 +1273,62 @@ function createTestCommand(deps = {}) {
|
|
|
1246
1273
|
});
|
|
1247
1274
|
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1248
1275
|
ctx.io.line(
|
|
1249
|
-
"\u26A0
|
|
1276
|
+
"\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."
|
|
1250
1277
|
);
|
|
1251
1278
|
} else if (reach.state === "ok") {
|
|
1252
1279
|
ctx.io.line(
|
|
1253
|
-
`\u2713 Test event accepted
|
|
1280
|
+
`\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
|
|
1254
1281
|
);
|
|
1255
1282
|
} else {
|
|
1256
|
-
ctx.io.line("\u2713 Test event accepted by the backend
|
|
1283
|
+
ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
|
|
1257
1284
|
}
|
|
1258
1285
|
} else if (result.decision === "suppressed") {
|
|
1259
1286
|
ctx.io.line(
|
|
1260
|
-
"\u26A0
|
|
1287
|
+
"\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
|
|
1261
1288
|
);
|
|
1262
1289
|
} else if (result.decision === "deduped") {
|
|
1263
|
-
ctx.io.line(
|
|
1264
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1265
|
-
);
|
|
1290
|
+
ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
|
|
1266
1291
|
} else {
|
|
1267
1292
|
ctx.io.line(
|
|
1268
|
-
`\u26A0
|
|
1293
|
+
`\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
|
|
1269
1294
|
);
|
|
1270
1295
|
}
|
|
1271
1296
|
} else if (result.outcome === "unpaired") {
|
|
1272
1297
|
ctx.io.line(
|
|
1273
|
-
"\u2717
|
|
1298
|
+
"\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
|
|
1299
|
+
);
|
|
1300
|
+
} else if (result.outcome === "failed") {
|
|
1301
|
+
ctx.io.line(
|
|
1302
|
+
"\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
|
|
1274
1303
|
);
|
|
1275
1304
|
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1276
1305
|
ctx.io.line(
|
|
1277
|
-
`\u2022
|
|
1306
|
+
`\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
|
|
1278
1307
|
);
|
|
1308
|
+
} else if (result.outcome === "queued" && result.queueCause === "backend") {
|
|
1309
|
+
const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
|
|
1310
|
+
ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
|
|
1279
1311
|
} else if (result.outcome === "queued") {
|
|
1280
|
-
ctx.io.line("\u2022
|
|
1312
|
+
ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
|
|
1313
|
+
} else if (result.code === "quota_exceeded") {
|
|
1314
|
+
const reach = await (0, import_agent_core8.fetchPushReachability)({
|
|
1315
|
+
baseUrl,
|
|
1316
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
1317
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1318
|
+
});
|
|
1319
|
+
const quota = reach.state === "ok" ? reach.data.quota : void 0;
|
|
1320
|
+
const exhausted = quota !== void 0 && quota.beeps_limit !== null ? (0, import_agent_core8.describeExhaustedQuota)({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
|
|
1321
|
+
ctx.io.line(
|
|
1322
|
+
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}` : (
|
|
1323
|
+
// An older backend reports no quota, so there is no period and no reset date to
|
|
1324
|
+
// give — and `doctor` cannot supply them either, since it reads this same response.
|
|
1325
|
+
"\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."
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1281
1328
|
} else {
|
|
1282
1329
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1283
1330
|
}
|
|
1284
|
-
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1331
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
|
|
1285
1332
|
}
|
|
1286
1333
|
};
|
|
1287
1334
|
}
|
|
@@ -1381,7 +1428,7 @@ function buildHarnessReports(installs, groups) {
|
|
|
1381
1428
|
}
|
|
1382
1429
|
] : surfaces.map((state) => {
|
|
1383
1430
|
const graded = rowState(state, group, status);
|
|
1384
|
-
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks
|
|
1431
|
+
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);
|
|
1385
1432
|
return {
|
|
1386
1433
|
harness: adapter.id,
|
|
1387
1434
|
displayName: adapter.displayName,
|
|
@@ -1427,12 +1474,12 @@ function describeMissing(reports) {
|
|
|
1427
1474
|
const names = missing.map((r) => r.displayName);
|
|
1428
1475
|
if (missing.length === reports.length) {
|
|
1429
1476
|
return [
|
|
1430
|
-
"No supported coding agent is installed on this machine
|
|
1431
|
-
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again
|
|
1477
|
+
"No supported coding agent is installed on this machine.",
|
|
1478
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
|
|
1432
1479
|
];
|
|
1433
1480
|
}
|
|
1434
1481
|
return [
|
|
1435
|
-
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again
|
|
1482
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
|
|
1436
1483
|
];
|
|
1437
1484
|
}
|
|
1438
1485
|
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
@@ -1655,7 +1702,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1655
1702
|
},
|
|
1656
1703
|
{
|
|
1657
1704
|
flag: "--no-install",
|
|
1658
|
-
summary: "Stop after pairing
|
|
1705
|
+
summary: "Stop after pairing; do not install coding-agent hooks"
|
|
1659
1706
|
},
|
|
1660
1707
|
{
|
|
1661
1708
|
flag: "--no-test",
|
|
@@ -1675,7 +1722,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1675
1722
|
};
|
|
1676
1723
|
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1677
1724
|
ctx.io.line(
|
|
1678
|
-
chain !== void 0 ? "\u2713 Already paired
|
|
1725
|
+
chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1679
1726
|
);
|
|
1680
1727
|
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1681
1728
|
ctx.io.result({
|
|
@@ -1711,7 +1758,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1711
1758
|
ctx.io.line(
|
|
1712
1759
|
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1713
1760
|
);
|
|
1714
|
-
ctx.io.line("Waiting for
|
|
1761
|
+
ctx.io.line("Waiting for approval in the BirdyBeep app.");
|
|
1715
1762
|
}
|
|
1716
1763
|
const deadline = Date.parse(start.expires_at);
|
|
1717
1764
|
const startedAt = clock();
|
|
@@ -1740,7 +1787,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1740
1787
|
}
|
|
1741
1788
|
if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
|
|
1742
1789
|
ctx.io.line(
|
|
1743
|
-
poll.status === "error" ? `
|
|
1790
|
+
poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
|
|
1744
1791
|
);
|
|
1745
1792
|
lastBeat = nowMs;
|
|
1746
1793
|
}
|
|
@@ -1780,7 +1827,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1780
1827
|
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1781
1828
|
ctx.io.result({ paired: false, reason: "declined" });
|
|
1782
1829
|
ctx.io.errline(
|
|
1783
|
-
"Pairing declined
|
|
1830
|
+
"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."
|
|
1784
1831
|
);
|
|
1785
1832
|
return EXIT.ERROR;
|
|
1786
1833
|
}
|
|
@@ -1789,8 +1836,8 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1789
1836
|
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1790
1837
|
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1791
1838
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1792
|
-
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing
|
|
1793
|
-
const nextStep = chain === void 0 ? " Run `birdybeep setup` to
|
|
1839
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
|
|
1840
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
|
|
1794
1841
|
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1795
1842
|
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1796
1843
|
ctx.io.result({
|
|
@@ -1808,7 +1855,7 @@ function createPairCommand(deps = {}) {
|
|
|
1808
1855
|
return createPairingCommand(
|
|
1809
1856
|
{
|
|
1810
1857
|
name: "pair",
|
|
1811
|
-
summary: "Pair this machine and
|
|
1858
|
+
summary: "Pair this machine and install detected coding-agent hooks",
|
|
1812
1859
|
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1813
1860
|
skipWhenPaired: false
|
|
1814
1861
|
},
|
|
@@ -1819,9 +1866,9 @@ function createSetupCommand(deps = {}) {
|
|
|
1819
1866
|
return createPairingCommand(
|
|
1820
1867
|
{
|
|
1821
1868
|
name: "setup",
|
|
1822
|
-
summary: "Set up BirdyBeep
|
|
1869
|
+
summary: "Set up BirdyBeep on this machine",
|
|
1823
1870
|
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1824
|
-
gettingStarted: "
|
|
1871
|
+
gettingStarted: "Connect this machine and install hooks for detected coding agents.",
|
|
1825
1872
|
skipWhenPaired: true
|
|
1826
1873
|
},
|
|
1827
1874
|
deps
|
|
@@ -1894,7 +1941,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1894
1941
|
run: async (ctx) => {
|
|
1895
1942
|
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1896
1943
|
if (token === null) {
|
|
1897
|
-
ctx.io.errline("No machine token
|
|
1944
|
+
ctx.io.errline("No machine token. Run `birdybeep pair` first.");
|
|
1898
1945
|
return EXIT.ERROR;
|
|
1899
1946
|
}
|
|
1900
1947
|
const items = await gatherItems(adapters);
|
|
@@ -1939,12 +1986,12 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1939
1986
|
});
|
|
1940
1987
|
} else if (outcome === "terminal") {
|
|
1941
1988
|
ctx.io.errline(
|
|
1942
|
-
`Report rejected (${errorCode ?? "auth"})
|
|
1989
|
+
`Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
|
|
1943
1990
|
);
|
|
1944
1991
|
} else {
|
|
1945
1992
|
for (const e of effective) {
|
|
1946
1993
|
ctx.io.line(
|
|
1947
|
-
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred
|
|
1994
|
+
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
|
|
1948
1995
|
);
|
|
1949
1996
|
}
|
|
1950
1997
|
}
|
|
@@ -2004,7 +2051,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2004
2051
|
} else {
|
|
2005
2052
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
2006
2053
|
ctx.io.line(
|
|
2007
|
-
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no
|
|
2054
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
|
|
2008
2055
|
);
|
|
2009
2056
|
ctx.io.line("Integrations:");
|
|
2010
2057
|
for (const i of integrations) {
|
|
@@ -2012,7 +2059,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2012
2059
|
const group = surfaces.find((g) => g.harness === i.harness);
|
|
2013
2060
|
for (const state of group?.surfaces ?? []) {
|
|
2014
2061
|
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
2015
|
-
ctx.io.line(` ${mark} ${describeSurface(state)}
|
|
2062
|
+
ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
|
|
2016
2063
|
}
|
|
2017
2064
|
}
|
|
2018
2065
|
ctx.io.line(
|