@birdybeep/cli 0.8.0 → 0.8.2
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 +101 -69
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-G72IJS7A.js → chunk-CZ4G735V.js} +121 -73
- package/dist/chunk-CZ4G735V.js.map +1 -0
- package/dist/index.cjs +101 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -8
- package/dist/chunk-G72IJS7A.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;
|
|
@@ -715,15 +715,15 @@ function createDoctorCommand(deps = {}) {
|
|
|
715
715
|
name: "Events lost while unpaired",
|
|
716
716
|
ok: false,
|
|
717
717
|
detail: describeUnpairedActivity(unpaired),
|
|
718
|
-
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."
|
|
719
719
|
});
|
|
720
720
|
}
|
|
721
721
|
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
722
722
|
checks.push({
|
|
723
723
|
name: "Approval beeps from Cursor",
|
|
724
724
|
ok: false,
|
|
725
|
-
detail: "Cursor is running your agent through the Claude Code hooks
|
|
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
|
|
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."
|
|
727
727
|
});
|
|
728
728
|
}
|
|
729
729
|
const filtered = filteredActivity();
|
|
@@ -772,7 +772,7 @@ function createDoctorCommand(deps = {}) {
|
|
|
772
772
|
name: "Backend reachable",
|
|
773
773
|
ok: false,
|
|
774
774
|
detail: `Could not reach ${apiUrl}.`,
|
|
775
|
-
remedy: "Check your network
|
|
775
|
+
remedy: "Check your network. Queued events retry automatically."
|
|
776
776
|
}
|
|
777
777
|
);
|
|
778
778
|
const ok = checks.every((c) => c.ok);
|
|
@@ -797,10 +797,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
797
797
|
});
|
|
798
798
|
} else {
|
|
799
799
|
for (const c of checks) {
|
|
800
|
-
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}` : ""}`);
|
|
801
801
|
if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
|
|
802
802
|
}
|
|
803
|
-
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed
|
|
803
|
+
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
|
|
804
804
|
}
|
|
805
805
|
return ok ? EXIT.OK : EXIT.ERROR;
|
|
806
806
|
}
|
|
@@ -813,6 +813,7 @@ var import_node_crypto = require("crypto");
|
|
|
813
813
|
var import_node_fs4 = require("fs");
|
|
814
814
|
var import_node_os2 = require("os");
|
|
815
815
|
var import_node_path2 = require("path");
|
|
816
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
816
817
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
817
818
|
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
818
819
|
var import_codex3 = require("@birdybeep/codex");
|
|
@@ -833,6 +834,21 @@ var HOOK_HARNESSES = [
|
|
|
833
834
|
"copilot"
|
|
834
835
|
];
|
|
835
836
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
837
|
+
var LEGACY_HOOK_RUNTIME_BUDGET_MS = 9e3;
|
|
838
|
+
function hookRuntimeBudgetMs(configuredTimeoutSeconds) {
|
|
839
|
+
if (configuredTimeoutSeconds === void 0 || !Number.isFinite(configuredTimeoutSeconds) || configuredTimeoutSeconds <= 0) {
|
|
840
|
+
return LEGACY_HOOK_RUNTIME_BUDGET_MS;
|
|
841
|
+
}
|
|
842
|
+
const configuredBudgetMs = Math.max(1, Math.floor(configuredTimeoutSeconds * 1e3) - 1e3);
|
|
843
|
+
return Math.min(LEGACY_HOOK_RUNTIME_BUDGET_MS, configuredBudgetMs);
|
|
844
|
+
}
|
|
845
|
+
function configuredHookTimeoutSeconds(harness) {
|
|
846
|
+
if (harness === "claude") return (0, import_claude_code4.configuredClaudeHookTimeoutSeconds)();
|
|
847
|
+
if (harness === "codex") return (0, import_codex3.configuredCodexHookTimeoutSeconds)();
|
|
848
|
+
if (harness === "cursor") return (0, import_cursor4.configuredCursorHookTimeoutSeconds)();
|
|
849
|
+
if (harness === "copilot") return (0, import_copilot3.configuredCopilotHookTimeoutSeconds)();
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
836
852
|
function withTimeout(promise, ms, fallback) {
|
|
837
853
|
return new Promise((resolve) => {
|
|
838
854
|
let settled = false;
|
|
@@ -953,15 +969,22 @@ function detachCodexNotifyWorker(payload) {
|
|
|
953
969
|
}
|
|
954
970
|
}
|
|
955
971
|
function createHookCommand(deps = {}) {
|
|
956
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core5.createSender)({
|
|
972
|
+
const makeSender = deps.createSender ?? ((baseUrl, budgetMs) => (0, import_agent_core5.createSender)({
|
|
973
|
+
baseUrl,
|
|
974
|
+
timeoutMs: Math.min(import_agent_core5.DEFAULT_SEND_TIMEOUT_MS, budgetMs),
|
|
975
|
+
totalBudgetMs: budgetMs
|
|
976
|
+
}));
|
|
957
977
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
958
978
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
979
|
+
const now = deps.now ?? (() => import_node_perf_hooks.performance.now());
|
|
980
|
+
const readConfiguredHookTimeoutSeconds = deps.configuredHookTimeoutSeconds ?? configuredHookTimeoutSeconds;
|
|
959
981
|
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
960
982
|
return {
|
|
961
983
|
name: "hook",
|
|
962
984
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
963
985
|
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
964
986
|
run: async (ctx) => {
|
|
987
|
+
const hookStartedAt = now();
|
|
965
988
|
const harness = ctx.args[0];
|
|
966
989
|
if (!isHarnessName(harness)) {
|
|
967
990
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
@@ -975,7 +998,7 @@ function createHookCommand(deps = {}) {
|
|
|
975
998
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
976
999
|
if (harness === "copilot" && copilotEventName === void 0) {
|
|
977
1000
|
ctx.io.errline(
|
|
978
|
-
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}
|
|
1001
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
|
|
979
1002
|
);
|
|
980
1003
|
return EXIT.USAGE;
|
|
981
1004
|
}
|
|
@@ -993,7 +1016,7 @@ function createHookCommand(deps = {}) {
|
|
|
993
1016
|
}
|
|
994
1017
|
const drop = (reason, detail) => {
|
|
995
1018
|
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
996
|
-
ctx.io.errline(`birdybeep hook ${harness}: ${detail}
|
|
1019
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
|
|
997
1020
|
return EXIT.ERROR;
|
|
998
1021
|
};
|
|
999
1022
|
if (read === null) {
|
|
@@ -1023,11 +1046,14 @@ function createHookCommand(deps = {}) {
|
|
|
1023
1046
|
});
|
|
1024
1047
|
const article = handler === "opencode" ? "an" : "a";
|
|
1025
1048
|
ctx.io.errline(
|
|
1026
|
-
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event
|
|
1049
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
|
|
1027
1050
|
);
|
|
1028
1051
|
return EXIT.ERROR;
|
|
1029
1052
|
}
|
|
1030
|
-
const
|
|
1053
|
+
const runtimeBudgetMs = hookRuntimeBudgetMs(readConfiguredHookTimeoutSeconds(harness));
|
|
1054
|
+
const elapsedMs = Math.max(0, now() - hookStartedAt);
|
|
1055
|
+
const budgetMs = Math.max(1, Math.min(import_agent_core5.DEFAULT_TOTAL_BUDGET_MS, runtimeBudgetMs - elapsedMs));
|
|
1056
|
+
const sender = makeSender(resolveApiUrl(), budgetMs);
|
|
1031
1057
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
1032
1058
|
ctx.io.result({
|
|
1033
1059
|
harness: handler,
|
|
@@ -1037,17 +1063,23 @@ function createHookCommand(deps = {}) {
|
|
|
1037
1063
|
eventType: result.eventType,
|
|
1038
1064
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
1039
1065
|
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1066
|
+
...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
|
|
1040
1067
|
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
1041
1068
|
});
|
|
1042
1069
|
if (result.outcome === "unpaired") {
|
|
1043
1070
|
ctx.io.errline(
|
|
1044
|
-
"birdybeep: this machine is not paired
|
|
1071
|
+
"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."
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
if (result.outcome === "failed") {
|
|
1075
|
+
ctx.io.errline(
|
|
1076
|
+
"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`."
|
|
1045
1077
|
);
|
|
1046
1078
|
}
|
|
1047
|
-
const unavailable = result.send?.tokenStoreUnavailable;
|
|
1079
|
+
const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
|
|
1048
1080
|
if (unavailable !== void 0) {
|
|
1049
1081
|
ctx.io.errline(
|
|
1050
|
-
`birdybeep:
|
|
1082
|
+
`birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
|
|
1051
1083
|
);
|
|
1052
1084
|
}
|
|
1053
1085
|
return EXIT.OK;
|
|
@@ -1065,7 +1097,7 @@ function createLogoutCommand(deps = {}) {
|
|
|
1065
1097
|
usage: "birdybeep logout",
|
|
1066
1098
|
run: async (ctx) => {
|
|
1067
1099
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1068
|
-
ctx.io.emit("Logged out
|
|
1100
|
+
ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
|
|
1069
1101
|
return EXIT.OK;
|
|
1070
1102
|
}
|
|
1071
1103
|
};
|
|
@@ -1092,14 +1124,14 @@ function createUnpairCommand(deps = {}) {
|
|
|
1092
1124
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
1093
1125
|
return {
|
|
1094
1126
|
name: "unpair",
|
|
1095
|
-
summary: "
|
|
1127
|
+
summary: "Revoke this machine and remove its local token",
|
|
1096
1128
|
usage: "birdybeep unpair",
|
|
1097
1129
|
run: async (ctx) => {
|
|
1098
1130
|
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
1099
1131
|
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
1100
1132
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1101
1133
|
const serverRevoked = outcome === "revoked";
|
|
1102
|
-
const human = outcome === "revoked" ? "Unpaired
|
|
1134
|
+
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.";
|
|
1103
1135
|
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
1104
1136
|
return EXIT.OK;
|
|
1105
1137
|
}
|
|
@@ -1182,7 +1214,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
1182
1214
|
}
|
|
1183
1215
|
|
|
1184
1216
|
// src/version.ts
|
|
1185
|
-
var CLI_VERSION = "0.8.
|
|
1217
|
+
var CLI_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0";
|
|
1186
1218
|
|
|
1187
1219
|
// src/commands/setup.ts
|
|
1188
1220
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
@@ -1207,8 +1239,8 @@ function buildTestEvent(opts = {}) {
|
|
|
1207
1239
|
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1208
1240
|
machine: { label: machine.label, os: machine.os },
|
|
1209
1241
|
workspace: { cwd: process.cwd() },
|
|
1210
|
-
title: "BirdyBeep test
|
|
1211
|
-
body: "
|
|
1242
|
+
title: "BirdyBeep test",
|
|
1243
|
+
body: "Notifications from this machine are working.",
|
|
1212
1244
|
metadata: { test: true }
|
|
1213
1245
|
},
|
|
1214
1246
|
opts
|
|
@@ -1243,43 +1275,43 @@ function createTestCommand(deps = {}) {
|
|
|
1243
1275
|
});
|
|
1244
1276
|
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1245
1277
|
ctx.io.line(
|
|
1246
|
-
"\u26A0
|
|
1278
|
+
"\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."
|
|
1247
1279
|
);
|
|
1248
1280
|
} else if (reach.state === "ok") {
|
|
1249
1281
|
ctx.io.line(
|
|
1250
|
-
`\u2713 Test event accepted
|
|
1282
|
+
`\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
|
|
1251
1283
|
);
|
|
1252
1284
|
} else {
|
|
1253
|
-
ctx.io.line("\u2713 Test event accepted by the backend
|
|
1285
|
+
ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
|
|
1254
1286
|
}
|
|
1255
1287
|
} else if (result.decision === "suppressed") {
|
|
1256
1288
|
ctx.io.line(
|
|
1257
|
-
"\u26A0
|
|
1289
|
+
"\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
|
|
1258
1290
|
);
|
|
1259
1291
|
} else if (result.decision === "deduped") {
|
|
1260
|
-
ctx.io.line(
|
|
1261
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1262
|
-
);
|
|
1292
|
+
ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
|
|
1263
1293
|
} else {
|
|
1264
1294
|
ctx.io.line(
|
|
1265
|
-
`\u26A0
|
|
1295
|
+
`\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
|
|
1266
1296
|
);
|
|
1267
1297
|
}
|
|
1268
1298
|
} else if (result.outcome === "unpaired") {
|
|
1269
1299
|
ctx.io.line(
|
|
1270
|
-
"\u2717
|
|
1300
|
+
"\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
|
|
1271
1301
|
);
|
|
1272
|
-
} else if (result.
|
|
1302
|
+
} else if (result.outcome === "failed") {
|
|
1273
1303
|
ctx.io.line(
|
|
1274
|
-
|
|
1304
|
+
"\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
|
|
1275
1305
|
);
|
|
1276
|
-
} else if (result.
|
|
1277
|
-
const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
|
|
1306
|
+
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1278
1307
|
ctx.io.line(
|
|
1279
|
-
|
|
1308
|
+
`\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
|
|
1280
1309
|
);
|
|
1310
|
+
} else if (result.outcome === "queued" && result.queueCause === "backend") {
|
|
1311
|
+
const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
|
|
1312
|
+
ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
|
|
1281
1313
|
} else if (result.outcome === "queued") {
|
|
1282
|
-
ctx.io.line("\u2022
|
|
1314
|
+
ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
|
|
1283
1315
|
} else if (result.code === "quota_exceeded") {
|
|
1284
1316
|
const reach = await (0, import_agent_core8.fetchPushReachability)({
|
|
1285
1317
|
baseUrl,
|
|
@@ -1287,18 +1319,18 @@ function createTestCommand(deps = {}) {
|
|
|
1287
1319
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1288
1320
|
});
|
|
1289
1321
|
const quota = reach.state === "ok" ? reach.data.quota : void 0;
|
|
1290
|
-
const exhausted = quota ? (0, import_agent_core8.describeExhaustedQuota)(quota) : void 0;
|
|
1322
|
+
const exhausted = quota !== void 0 && quota.beeps_limit !== null ? (0, import_agent_core8.describeExhaustedQuota)({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
|
|
1291
1323
|
ctx.io.line(
|
|
1292
|
-
quota && exhausted ? `\u2717 Test event
|
|
1324
|
+
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}` : (
|
|
1293
1325
|
// An older backend reports no quota, so there is no period and no reset date to
|
|
1294
1326
|
// give — and `doctor` cannot supply them either, since it reads this same response.
|
|
1295
|
-
"\u2717 Test event
|
|
1327
|
+
"\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."
|
|
1296
1328
|
)
|
|
1297
1329
|
);
|
|
1298
1330
|
} else {
|
|
1299
1331
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1300
1332
|
}
|
|
1301
|
-
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1333
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
|
|
1302
1334
|
}
|
|
1303
1335
|
};
|
|
1304
1336
|
}
|
|
@@ -1398,7 +1430,7 @@ function buildHarnessReports(installs, groups) {
|
|
|
1398
1430
|
}
|
|
1399
1431
|
] : surfaces.map((state) => {
|
|
1400
1432
|
const graded = rowState(state, group, status);
|
|
1401
|
-
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks
|
|
1433
|
+
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);
|
|
1402
1434
|
return {
|
|
1403
1435
|
harness: adapter.id,
|
|
1404
1436
|
displayName: adapter.displayName,
|
|
@@ -1444,12 +1476,12 @@ function describeMissing(reports) {
|
|
|
1444
1476
|
const names = missing.map((r) => r.displayName);
|
|
1445
1477
|
if (missing.length === reports.length) {
|
|
1446
1478
|
return [
|
|
1447
|
-
"No supported coding agent is installed on this machine
|
|
1448
|
-
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again
|
|
1479
|
+
"No supported coding agent is installed on this machine.",
|
|
1480
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
|
|
1449
1481
|
];
|
|
1450
1482
|
}
|
|
1451
1483
|
return [
|
|
1452
|
-
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again
|
|
1484
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
|
|
1453
1485
|
];
|
|
1454
1486
|
}
|
|
1455
1487
|
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
@@ -1672,7 +1704,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1672
1704
|
},
|
|
1673
1705
|
{
|
|
1674
1706
|
flag: "--no-install",
|
|
1675
|
-
summary: "Stop after pairing
|
|
1707
|
+
summary: "Stop after pairing; do not install coding-agent hooks"
|
|
1676
1708
|
},
|
|
1677
1709
|
{
|
|
1678
1710
|
flag: "--no-test",
|
|
@@ -1692,7 +1724,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1692
1724
|
};
|
|
1693
1725
|
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1694
1726
|
ctx.io.line(
|
|
1695
|
-
chain !== void 0 ? "\u2713 Already paired
|
|
1727
|
+
chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1696
1728
|
);
|
|
1697
1729
|
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1698
1730
|
ctx.io.result({
|
|
@@ -1728,7 +1760,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1728
1760
|
ctx.io.line(
|
|
1729
1761
|
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1730
1762
|
);
|
|
1731
|
-
ctx.io.line("Waiting for
|
|
1763
|
+
ctx.io.line("Waiting for approval in the BirdyBeep app.");
|
|
1732
1764
|
}
|
|
1733
1765
|
const deadline = Date.parse(start.expires_at);
|
|
1734
1766
|
const startedAt = clock();
|
|
@@ -1757,7 +1789,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1757
1789
|
}
|
|
1758
1790
|
if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
|
|
1759
1791
|
ctx.io.line(
|
|
1760
|
-
poll.status === "error" ? `
|
|
1792
|
+
poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
|
|
1761
1793
|
);
|
|
1762
1794
|
lastBeat = nowMs;
|
|
1763
1795
|
}
|
|
@@ -1797,7 +1829,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1797
1829
|
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1798
1830
|
ctx.io.result({ paired: false, reason: "declined" });
|
|
1799
1831
|
ctx.io.errline(
|
|
1800
|
-
"Pairing declined
|
|
1832
|
+
"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."
|
|
1801
1833
|
);
|
|
1802
1834
|
return EXIT.ERROR;
|
|
1803
1835
|
}
|
|
@@ -1806,8 +1838,8 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1806
1838
|
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1807
1839
|
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1808
1840
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1809
|
-
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing
|
|
1810
|
-
const nextStep = chain === void 0 ? " Run `birdybeep setup` to
|
|
1841
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
|
|
1842
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
|
|
1811
1843
|
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1812
1844
|
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1813
1845
|
ctx.io.result({
|
|
@@ -1825,7 +1857,7 @@ function createPairCommand(deps = {}) {
|
|
|
1825
1857
|
return createPairingCommand(
|
|
1826
1858
|
{
|
|
1827
1859
|
name: "pair",
|
|
1828
|
-
summary: "Pair this machine and
|
|
1860
|
+
summary: "Pair this machine and install detected coding-agent hooks",
|
|
1829
1861
|
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1830
1862
|
skipWhenPaired: false
|
|
1831
1863
|
},
|
|
@@ -1836,9 +1868,9 @@ function createSetupCommand(deps = {}) {
|
|
|
1836
1868
|
return createPairingCommand(
|
|
1837
1869
|
{
|
|
1838
1870
|
name: "setup",
|
|
1839
|
-
summary: "Set up BirdyBeep
|
|
1871
|
+
summary: "Set up BirdyBeep on this machine",
|
|
1840
1872
|
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1841
|
-
gettingStarted: "
|
|
1873
|
+
gettingStarted: "Connect this machine and install hooks for detected coding agents.",
|
|
1842
1874
|
skipWhenPaired: true
|
|
1843
1875
|
},
|
|
1844
1876
|
deps
|
|
@@ -1911,7 +1943,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1911
1943
|
run: async (ctx) => {
|
|
1912
1944
|
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1913
1945
|
if (token === null) {
|
|
1914
|
-
ctx.io.errline("No machine token
|
|
1946
|
+
ctx.io.errline("No machine token. Run `birdybeep pair` first.");
|
|
1915
1947
|
return EXIT.ERROR;
|
|
1916
1948
|
}
|
|
1917
1949
|
const items = await gatherItems(adapters);
|
|
@@ -1956,12 +1988,12 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1956
1988
|
});
|
|
1957
1989
|
} else if (outcome === "terminal") {
|
|
1958
1990
|
ctx.io.errline(
|
|
1959
|
-
`Report rejected (${errorCode ?? "auth"})
|
|
1991
|
+
`Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
|
|
1960
1992
|
);
|
|
1961
1993
|
} else {
|
|
1962
1994
|
for (const e of effective) {
|
|
1963
1995
|
ctx.io.line(
|
|
1964
|
-
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred
|
|
1996
|
+
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
|
|
1965
1997
|
);
|
|
1966
1998
|
}
|
|
1967
1999
|
}
|
|
@@ -2021,7 +2053,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2021
2053
|
} else {
|
|
2022
2054
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
2023
2055
|
ctx.io.line(
|
|
2024
|
-
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no
|
|
2056
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
|
|
2025
2057
|
);
|
|
2026
2058
|
ctx.io.line("Integrations:");
|
|
2027
2059
|
for (const i of integrations) {
|
|
@@ -2029,7 +2061,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2029
2061
|
const group = surfaces.find((g) => g.harness === i.harness);
|
|
2030
2062
|
for (const state of group?.surfaces ?? []) {
|
|
2031
2063
|
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
2032
|
-
ctx.io.line(` ${mark} ${describeSurface(state)}
|
|
2064
|
+
ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
|
|
2033
2065
|
}
|
|
2034
2066
|
}
|
|
2035
2067
|
ctx.io.line(
|