@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/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})` : ""}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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}
|
|
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
|
|
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)
|
|
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
|
|
527
|
+
"\u26A0 This machine is not paired. Run `birdybeep setup` before expecting notifications."
|
|
528
528
|
);
|
|
529
529
|
}
|
|
530
530
|
return EXIT.OK;
|
|
@@ -682,13 +682,12 @@ function createDoctorCommand(deps = {}) {
|
|
|
682
682
|
remedy: tokenStoreRemedy(pairing)
|
|
683
683
|
}
|
|
684
684
|
);
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
);
|
|
685
|
+
const reachability = await (0, import_agent_core4.fetchPushReachability)({
|
|
686
|
+
baseUrl: apiUrl,
|
|
687
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
688
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
689
|
+
});
|
|
690
|
+
const reach = (0, import_agent_core4.describeReachability)(reachability);
|
|
692
691
|
if (reach !== null) {
|
|
693
692
|
checks.push({
|
|
694
693
|
name: "Push reachability",
|
|
@@ -697,21 +696,34 @@ function createDoctorCommand(deps = {}) {
|
|
|
697
696
|
...reach.remedy !== void 0 ? { remedy: reach.remedy } : {}
|
|
698
697
|
});
|
|
699
698
|
}
|
|
699
|
+
const checkIn = (0, import_agent_core4.describeCheckIn)(reachability);
|
|
700
|
+
if (checkIn !== null) {
|
|
701
|
+
checks.push({ name: "Device check-in", ok: checkIn.ok, detail: checkIn.detail });
|
|
702
|
+
}
|
|
703
|
+
const quota = (0, import_agent_core4.describeQuota)(reachability);
|
|
704
|
+
if (quota !== null) {
|
|
705
|
+
checks.push({
|
|
706
|
+
name: "Beep quota",
|
|
707
|
+
ok: quota.ok,
|
|
708
|
+
detail: quota.detail,
|
|
709
|
+
...quota.remedy !== void 0 ? { remedy: quota.remedy } : {}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
700
712
|
const unpaired = unpairedActivity();
|
|
701
713
|
if (unpaired !== null) {
|
|
702
714
|
checks.push({
|
|
703
715
|
name: "Events lost while unpaired",
|
|
704
716
|
ok: false,
|
|
705
717
|
detail: describeUnpairedActivity(unpaired),
|
|
706
|
-
remedy: "Run `birdybeep pair`. Events that fired before pairing
|
|
718
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing were not saved and will not be replayed."
|
|
707
719
|
});
|
|
708
720
|
}
|
|
709
721
|
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
710
722
|
checks.push({
|
|
711
723
|
name: "Approval beeps from Cursor",
|
|
712
724
|
ok: false,
|
|
713
|
-
detail: "Cursor is running your agent through the Claude Code hooks
|
|
714
|
-
remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe
|
|
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."
|
|
715
727
|
});
|
|
716
728
|
}
|
|
717
729
|
const filtered = filteredActivity();
|
|
@@ -760,7 +772,7 @@ function createDoctorCommand(deps = {}) {
|
|
|
760
772
|
name: "Backend reachable",
|
|
761
773
|
ok: false,
|
|
762
774
|
detail: `Could not reach ${apiUrl}.`,
|
|
763
|
-
remedy: "Check your network
|
|
775
|
+
remedy: "Check your network. Queued events retry automatically."
|
|
764
776
|
}
|
|
765
777
|
);
|
|
766
778
|
const ok = checks.every((c) => c.ok);
|
|
@@ -770,6 +782,14 @@ function createDoctorCommand(deps = {}) {
|
|
|
770
782
|
checks,
|
|
771
783
|
pairing,
|
|
772
784
|
// gcgp.23: paired | unpaired | unknown — never a bare boolean
|
|
785
|
+
// The backend's own quota numbers, unrendered (58l) — a script (or a bug report) gets
|
|
786
|
+
// the window and the counts, not just the sentence built from them.
|
|
787
|
+
...reachability.state === "ok" && reachability.data.quota !== void 0 ? { quota: reachability.data.quota } : {},
|
|
788
|
+
// The raw check-in (2x9s), unrendered. Present ONLY when the server reported the field
|
|
789
|
+
// at all, so a script can still tell "this backend predates check-ins" (key absent)
|
|
790
|
+
// from "no device has ever checked in" (key present, null) — the same distinction the
|
|
791
|
+
// rendered row is careful about.
|
|
792
|
+
...reachability.state === "ok" && reachability.data.most_recent_check_in_at !== void 0 ? { mostRecentCheckInAt: reachability.data.most_recent_check_in_at } : {},
|
|
773
793
|
surfaces: surfaceGroups,
|
|
774
794
|
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
775
795
|
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
@@ -777,10 +797,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
777
797
|
});
|
|
778
798
|
} else {
|
|
779
799
|
for (const c of checks) {
|
|
780
|
-
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ?
|
|
800
|
+
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
|
|
781
801
|
if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
|
|
782
802
|
}
|
|
783
|
-
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed
|
|
803
|
+
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
|
|
784
804
|
}
|
|
785
805
|
return ok ? EXIT.OK : EXIT.ERROR;
|
|
786
806
|
}
|
|
@@ -955,7 +975,7 @@ function createHookCommand(deps = {}) {
|
|
|
955
975
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
956
976
|
if (harness === "copilot" && copilotEventName === void 0) {
|
|
957
977
|
ctx.io.errline(
|
|
958
|
-
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}
|
|
978
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
|
|
959
979
|
);
|
|
960
980
|
return EXIT.USAGE;
|
|
961
981
|
}
|
|
@@ -973,7 +993,7 @@ function createHookCommand(deps = {}) {
|
|
|
973
993
|
}
|
|
974
994
|
const drop = (reason, detail) => {
|
|
975
995
|
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
976
|
-
ctx.io.errline(`birdybeep hook ${harness}: ${detail}
|
|
996
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
|
|
977
997
|
return EXIT.ERROR;
|
|
978
998
|
};
|
|
979
999
|
if (read === null) {
|
|
@@ -1003,7 +1023,7 @@ function createHookCommand(deps = {}) {
|
|
|
1003
1023
|
});
|
|
1004
1024
|
const article = handler === "opencode" ? "an" : "a";
|
|
1005
1025
|
ctx.io.errline(
|
|
1006
|
-
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event
|
|
1026
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
|
|
1007
1027
|
);
|
|
1008
1028
|
return EXIT.ERROR;
|
|
1009
1029
|
}
|
|
@@ -1017,17 +1037,23 @@ function createHookCommand(deps = {}) {
|
|
|
1017
1037
|
eventType: result.eventType,
|
|
1018
1038
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
1019
1039
|
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1040
|
+
...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
|
|
1020
1041
|
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
1021
1042
|
});
|
|
1022
1043
|
if (result.outcome === "unpaired") {
|
|
1023
1044
|
ctx.io.errline(
|
|
1024
|
-
"birdybeep: this machine is not paired
|
|
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."
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
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`."
|
|
1025
1051
|
);
|
|
1026
1052
|
}
|
|
1027
|
-
const unavailable = result.send?.tokenStoreUnavailable;
|
|
1053
|
+
const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
|
|
1028
1054
|
if (unavailable !== void 0) {
|
|
1029
1055
|
ctx.io.errline(
|
|
1030
|
-
`birdybeep:
|
|
1056
|
+
`birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
|
|
1031
1057
|
);
|
|
1032
1058
|
}
|
|
1033
1059
|
return EXIT.OK;
|
|
@@ -1045,7 +1071,7 @@ function createLogoutCommand(deps = {}) {
|
|
|
1045
1071
|
usage: "birdybeep logout",
|
|
1046
1072
|
run: async (ctx) => {
|
|
1047
1073
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1048
|
-
ctx.io.emit("Logged out
|
|
1074
|
+
ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
|
|
1049
1075
|
return EXIT.OK;
|
|
1050
1076
|
}
|
|
1051
1077
|
};
|
|
@@ -1072,14 +1098,14 @@ function createUnpairCommand(deps = {}) {
|
|
|
1072
1098
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
1073
1099
|
return {
|
|
1074
1100
|
name: "unpair",
|
|
1075
|
-
summary: "
|
|
1101
|
+
summary: "Revoke this machine and remove its local token",
|
|
1076
1102
|
usage: "birdybeep unpair",
|
|
1077
1103
|
run: async (ctx) => {
|
|
1078
1104
|
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
1079
1105
|
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
1080
1106
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1081
1107
|
const serverRevoked = outcome === "revoked";
|
|
1082
|
-
const human = outcome === "revoked" ? "Unpaired
|
|
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.";
|
|
1083
1109
|
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
1084
1110
|
return EXIT.OK;
|
|
1085
1111
|
}
|
|
@@ -1162,7 +1188,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
1162
1188
|
}
|
|
1163
1189
|
|
|
1164
1190
|
// src/version.ts
|
|
1165
|
-
var CLI_VERSION = "0.
|
|
1191
|
+
var CLI_VERSION = "0.8.1".length > 0 ? "0.8.1" : "0.0.0";
|
|
1166
1192
|
|
|
1167
1193
|
// src/commands/setup.ts
|
|
1168
1194
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
@@ -1187,8 +1213,8 @@ function buildTestEvent(opts = {}) {
|
|
|
1187
1213
|
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1188
1214
|
machine: { label: machine.label, os: machine.os },
|
|
1189
1215
|
workspace: { cwd: process.cwd() },
|
|
1190
|
-
title: "BirdyBeep test
|
|
1191
|
-
body: "
|
|
1216
|
+
title: "BirdyBeep test",
|
|
1217
|
+
body: "Notifications from this machine are working.",
|
|
1192
1218
|
metadata: { test: true }
|
|
1193
1219
|
},
|
|
1194
1220
|
opts
|
|
@@ -1211,6 +1237,7 @@ function createTestCommand(deps = {}) {
|
|
|
1211
1237
|
outcome: result.outcome,
|
|
1212
1238
|
...result.status ? { status: result.status } : {},
|
|
1213
1239
|
...result.decision ? { decision: result.decision } : {},
|
|
1240
|
+
...result.queueCause ? { queueCause: result.queueCause } : {},
|
|
1214
1241
|
...result.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable", tokenStoreReason: result.tokenStoreUnavailable.reason } : {}
|
|
1215
1242
|
});
|
|
1216
1243
|
} else if (result.outcome === "delivered") {
|
|
@@ -1222,42 +1249,62 @@ function createTestCommand(deps = {}) {
|
|
|
1222
1249
|
});
|
|
1223
1250
|
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1224
1251
|
ctx.io.line(
|
|
1225
|
-
"\u26A0
|
|
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."
|
|
1226
1253
|
);
|
|
1227
1254
|
} else if (reach.state === "ok") {
|
|
1228
1255
|
ctx.io.line(
|
|
1229
|
-
`\u2713 Test event accepted
|
|
1256
|
+
`\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
|
|
1230
1257
|
);
|
|
1231
1258
|
} else {
|
|
1232
|
-
ctx.io.line("\u2713 Test event accepted by the backend
|
|
1259
|
+
ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
|
|
1233
1260
|
}
|
|
1234
1261
|
} else if (result.decision === "suppressed") {
|
|
1235
1262
|
ctx.io.line(
|
|
1236
|
-
"\u26A0
|
|
1263
|
+
"\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
|
|
1237
1264
|
);
|
|
1238
1265
|
} else if (result.decision === "deduped") {
|
|
1239
|
-
ctx.io.line(
|
|
1240
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1241
|
-
);
|
|
1266
|
+
ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
|
|
1242
1267
|
} else {
|
|
1243
1268
|
ctx.io.line(
|
|
1244
|
-
`\u26A0
|
|
1269
|
+
`\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
|
|
1245
1270
|
);
|
|
1246
1271
|
}
|
|
1247
1272
|
} else if (result.outcome === "unpaired") {
|
|
1248
1273
|
ctx.io.line(
|
|
1249
|
-
"\u2717
|
|
1274
|
+
"\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
|
|
1275
|
+
);
|
|
1276
|
+
} else if (result.outcome === "failed") {
|
|
1277
|
+
ctx.io.line(
|
|
1278
|
+
"\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
|
|
1250
1279
|
);
|
|
1251
1280
|
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1252
1281
|
ctx.io.line(
|
|
1253
|
-
`\u2022
|
|
1282
|
+
`\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
|
|
1254
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.`);
|
|
1255
1287
|
} else if (result.outcome === "queued") {
|
|
1256
|
-
ctx.io.line("\u2022
|
|
1288
|
+
ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
|
|
1289
|
+
} else if (result.code === "quota_exceeded") {
|
|
1290
|
+
const reach = await (0, import_agent_core8.fetchPushReachability)({
|
|
1291
|
+
baseUrl,
|
|
1292
|
+
...deps.tokenOptions ? { tokenOptions: deps.tokenOptions } : {},
|
|
1293
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1294
|
+
});
|
|
1295
|
+
const quota = reach.state === "ok" ? reach.data.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;
|
|
1297
|
+
ctx.io.line(
|
|
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}` : (
|
|
1299
|
+
// An older backend reports no quota, so there is no period and no reset date to
|
|
1300
|
+
// give — and `doctor` cannot supply them either, since it reads this same response.
|
|
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."
|
|
1302
|
+
)
|
|
1303
|
+
);
|
|
1257
1304
|
} else {
|
|
1258
1305
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1259
1306
|
}
|
|
1260
|
-
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;
|
|
1261
1308
|
}
|
|
1262
1309
|
};
|
|
1263
1310
|
}
|
|
@@ -1357,7 +1404,7 @@ function buildHarnessReports(installs, groups) {
|
|
|
1357
1404
|
}
|
|
1358
1405
|
] : surfaces.map((state) => {
|
|
1359
1406
|
const graded = rowState(state, group, status);
|
|
1360
|
-
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks
|
|
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);
|
|
1361
1408
|
return {
|
|
1362
1409
|
harness: adapter.id,
|
|
1363
1410
|
displayName: adapter.displayName,
|
|
@@ -1403,12 +1450,12 @@ function describeMissing(reports) {
|
|
|
1403
1450
|
const names = missing.map((r) => r.displayName);
|
|
1404
1451
|
if (missing.length === reports.length) {
|
|
1405
1452
|
return [
|
|
1406
|
-
"No supported coding agent is installed on this machine
|
|
1407
|
-
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again
|
|
1453
|
+
"No supported coding agent is installed on this machine.",
|
|
1454
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
|
|
1408
1455
|
];
|
|
1409
1456
|
}
|
|
1410
1457
|
return [
|
|
1411
|
-
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again
|
|
1458
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
|
|
1412
1459
|
];
|
|
1413
1460
|
}
|
|
1414
1461
|
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
@@ -1631,7 +1678,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1631
1678
|
},
|
|
1632
1679
|
{
|
|
1633
1680
|
flag: "--no-install",
|
|
1634
|
-
summary: "Stop after pairing
|
|
1681
|
+
summary: "Stop after pairing; do not install coding-agent hooks"
|
|
1635
1682
|
},
|
|
1636
1683
|
{
|
|
1637
1684
|
flag: "--no-test",
|
|
@@ -1651,7 +1698,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1651
1698
|
};
|
|
1652
1699
|
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1653
1700
|
ctx.io.line(
|
|
1654
|
-
chain !== void 0 ? "\u2713 Already paired
|
|
1701
|
+
chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1655
1702
|
);
|
|
1656
1703
|
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1657
1704
|
ctx.io.result({
|
|
@@ -1687,7 +1734,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1687
1734
|
ctx.io.line(
|
|
1688
1735
|
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1689
1736
|
);
|
|
1690
|
-
ctx.io.line("Waiting for
|
|
1737
|
+
ctx.io.line("Waiting for approval in the BirdyBeep app.");
|
|
1691
1738
|
}
|
|
1692
1739
|
const deadline = Date.parse(start.expires_at);
|
|
1693
1740
|
const startedAt = clock();
|
|
@@ -1716,7 +1763,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1716
1763
|
}
|
|
1717
1764
|
if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
|
|
1718
1765
|
ctx.io.line(
|
|
1719
|
-
poll.status === "error" ? `
|
|
1766
|
+
poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
|
|
1720
1767
|
);
|
|
1721
1768
|
lastBeat = nowMs;
|
|
1722
1769
|
}
|
|
@@ -1756,7 +1803,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1756
1803
|
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1757
1804
|
ctx.io.result({ paired: false, reason: "declined" });
|
|
1758
1805
|
ctx.io.errline(
|
|
1759
|
-
"Pairing declined
|
|
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."
|
|
1760
1807
|
);
|
|
1761
1808
|
return EXIT.ERROR;
|
|
1762
1809
|
}
|
|
@@ -1765,8 +1812,8 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1765
1812
|
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1766
1813
|
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1767
1814
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1768
|
-
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing
|
|
1769
|
-
const nextStep = chain === void 0 ? " Run `birdybeep setup` to
|
|
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." : "";
|
|
1770
1817
|
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1771
1818
|
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1772
1819
|
ctx.io.result({
|
|
@@ -1784,7 +1831,7 @@ function createPairCommand(deps = {}) {
|
|
|
1784
1831
|
return createPairingCommand(
|
|
1785
1832
|
{
|
|
1786
1833
|
name: "pair",
|
|
1787
|
-
summary: "Pair this machine and
|
|
1834
|
+
summary: "Pair this machine and install detected coding-agent hooks",
|
|
1788
1835
|
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1789
1836
|
skipWhenPaired: false
|
|
1790
1837
|
},
|
|
@@ -1795,9 +1842,9 @@ function createSetupCommand(deps = {}) {
|
|
|
1795
1842
|
return createPairingCommand(
|
|
1796
1843
|
{
|
|
1797
1844
|
name: "setup",
|
|
1798
|
-
summary: "Set up BirdyBeep
|
|
1845
|
+
summary: "Set up BirdyBeep on this machine",
|
|
1799
1846
|
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1800
|
-
gettingStarted: "
|
|
1847
|
+
gettingStarted: "Connect this machine and install hooks for detected coding agents.",
|
|
1801
1848
|
skipWhenPaired: true
|
|
1802
1849
|
},
|
|
1803
1850
|
deps
|
|
@@ -1870,7 +1917,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1870
1917
|
run: async (ctx) => {
|
|
1871
1918
|
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1872
1919
|
if (token === null) {
|
|
1873
|
-
ctx.io.errline("No machine token
|
|
1920
|
+
ctx.io.errline("No machine token. Run `birdybeep pair` first.");
|
|
1874
1921
|
return EXIT.ERROR;
|
|
1875
1922
|
}
|
|
1876
1923
|
const items = await gatherItems(adapters);
|
|
@@ -1915,12 +1962,12 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1915
1962
|
});
|
|
1916
1963
|
} else if (outcome === "terminal") {
|
|
1917
1964
|
ctx.io.errline(
|
|
1918
|
-
`Report rejected (${errorCode ?? "auth"})
|
|
1965
|
+
`Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
|
|
1919
1966
|
);
|
|
1920
1967
|
} else {
|
|
1921
1968
|
for (const e of effective) {
|
|
1922
1969
|
ctx.io.line(
|
|
1923
|
-
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred
|
|
1970
|
+
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
|
|
1924
1971
|
);
|
|
1925
1972
|
}
|
|
1926
1973
|
}
|
|
@@ -1980,7 +2027,7 @@ function createStatusCommand(deps = {}) {
|
|
|
1980
2027
|
} else {
|
|
1981
2028
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1982
2029
|
ctx.io.line(
|
|
1983
|
-
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no
|
|
2030
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
|
|
1984
2031
|
);
|
|
1985
2032
|
ctx.io.line("Integrations:");
|
|
1986
2033
|
for (const i of integrations) {
|
|
@@ -1988,7 +2035,7 @@ function createStatusCommand(deps = {}) {
|
|
|
1988
2035
|
const group = surfaces.find((g) => g.harness === i.harness);
|
|
1989
2036
|
for (const state of group?.surfaces ?? []) {
|
|
1990
2037
|
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
1991
|
-
ctx.io.line(` ${mark} ${describeSurface(state)}
|
|
2038
|
+
ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
|
|
1992
2039
|
}
|
|
1993
2040
|
}
|
|
1994
2041
|
ctx.io.line(
|