@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/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;
|
|
@@ -739,15 +739,15 @@ function createDoctorCommand(deps = {}) {
|
|
|
739
739
|
name: "Events lost while unpaired",
|
|
740
740
|
ok: false,
|
|
741
741
|
detail: describeUnpairedActivity(unpaired),
|
|
742
|
-
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."
|
|
743
743
|
});
|
|
744
744
|
}
|
|
745
745
|
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
746
746
|
checks.push({
|
|
747
747
|
name: "Approval beeps from Cursor",
|
|
748
748
|
ok: false,
|
|
749
|
-
detail: "Cursor is running your agent through the Claude Code hooks
|
|
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
|
|
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."
|
|
751
751
|
});
|
|
752
752
|
}
|
|
753
753
|
const filtered = filteredActivity();
|
|
@@ -796,7 +796,7 @@ function createDoctorCommand(deps = {}) {
|
|
|
796
796
|
name: "Backend reachable",
|
|
797
797
|
ok: false,
|
|
798
798
|
detail: `Could not reach ${apiUrl}.`,
|
|
799
|
-
remedy: "Check your network
|
|
799
|
+
remedy: "Check your network. Queued events retry automatically."
|
|
800
800
|
}
|
|
801
801
|
);
|
|
802
802
|
const ok = checks.every((c) => c.ok);
|
|
@@ -821,10 +821,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
821
821
|
});
|
|
822
822
|
} else {
|
|
823
823
|
for (const c of checks) {
|
|
824
|
-
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}` : ""}`);
|
|
825
825
|
if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
|
|
826
826
|
}
|
|
827
|
-
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed
|
|
827
|
+
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
|
|
828
828
|
}
|
|
829
829
|
return ok ? EXIT.OK : EXIT.ERROR;
|
|
830
830
|
}
|
|
@@ -837,6 +837,7 @@ var import_node_crypto = require("crypto");
|
|
|
837
837
|
var import_node_fs4 = require("fs");
|
|
838
838
|
var import_node_os2 = require("os");
|
|
839
839
|
var import_node_path2 = require("path");
|
|
840
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
840
841
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
841
842
|
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
842
843
|
var import_codex3 = require("@birdybeep/codex");
|
|
@@ -857,6 +858,21 @@ var HOOK_HARNESSES = [
|
|
|
857
858
|
"copilot"
|
|
858
859
|
];
|
|
859
860
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
861
|
+
var LEGACY_HOOK_RUNTIME_BUDGET_MS = 9e3;
|
|
862
|
+
function hookRuntimeBudgetMs(configuredTimeoutSeconds) {
|
|
863
|
+
if (configuredTimeoutSeconds === void 0 || !Number.isFinite(configuredTimeoutSeconds) || configuredTimeoutSeconds <= 0) {
|
|
864
|
+
return LEGACY_HOOK_RUNTIME_BUDGET_MS;
|
|
865
|
+
}
|
|
866
|
+
const configuredBudgetMs = Math.max(1, Math.floor(configuredTimeoutSeconds * 1e3) - 1e3);
|
|
867
|
+
return Math.min(LEGACY_HOOK_RUNTIME_BUDGET_MS, configuredBudgetMs);
|
|
868
|
+
}
|
|
869
|
+
function configuredHookTimeoutSeconds(harness) {
|
|
870
|
+
if (harness === "claude") return (0, import_claude_code4.configuredClaudeHookTimeoutSeconds)();
|
|
871
|
+
if (harness === "codex") return (0, import_codex3.configuredCodexHookTimeoutSeconds)();
|
|
872
|
+
if (harness === "cursor") return (0, import_cursor4.configuredCursorHookTimeoutSeconds)();
|
|
873
|
+
if (harness === "copilot") return (0, import_copilot3.configuredCopilotHookTimeoutSeconds)();
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
860
876
|
function withTimeout(promise, ms, fallback) {
|
|
861
877
|
return new Promise((resolve) => {
|
|
862
878
|
let settled = false;
|
|
@@ -977,15 +993,22 @@ function detachCodexNotifyWorker(payload) {
|
|
|
977
993
|
}
|
|
978
994
|
}
|
|
979
995
|
function createHookCommand(deps = {}) {
|
|
980
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core5.createSender)({
|
|
996
|
+
const makeSender = deps.createSender ?? ((baseUrl, budgetMs) => (0, import_agent_core5.createSender)({
|
|
997
|
+
baseUrl,
|
|
998
|
+
timeoutMs: Math.min(import_agent_core5.DEFAULT_SEND_TIMEOUT_MS, budgetMs),
|
|
999
|
+
totalBudgetMs: budgetMs
|
|
1000
|
+
}));
|
|
981
1001
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
982
1002
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
1003
|
+
const now = deps.now ?? (() => import_node_perf_hooks.performance.now());
|
|
1004
|
+
const readConfiguredHookTimeoutSeconds = deps.configuredHookTimeoutSeconds ?? configuredHookTimeoutSeconds;
|
|
983
1005
|
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
984
1006
|
return {
|
|
985
1007
|
name: "hook",
|
|
986
1008
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
987
1009
|
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
988
1010
|
run: async (ctx) => {
|
|
1011
|
+
const hookStartedAt = now();
|
|
989
1012
|
const harness = ctx.args[0];
|
|
990
1013
|
if (!isHarnessName(harness)) {
|
|
991
1014
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
@@ -999,7 +1022,7 @@ function createHookCommand(deps = {}) {
|
|
|
999
1022
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
1000
1023
|
if (harness === "copilot" && copilotEventName === void 0) {
|
|
1001
1024
|
ctx.io.errline(
|
|
1002
|
-
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}
|
|
1025
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
|
|
1003
1026
|
);
|
|
1004
1027
|
return EXIT.USAGE;
|
|
1005
1028
|
}
|
|
@@ -1017,7 +1040,7 @@ function createHookCommand(deps = {}) {
|
|
|
1017
1040
|
}
|
|
1018
1041
|
const drop = (reason, detail) => {
|
|
1019
1042
|
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
1020
|
-
ctx.io.errline(`birdybeep hook ${harness}: ${detail}
|
|
1043
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
|
|
1021
1044
|
return EXIT.ERROR;
|
|
1022
1045
|
};
|
|
1023
1046
|
if (read === null) {
|
|
@@ -1047,11 +1070,14 @@ function createHookCommand(deps = {}) {
|
|
|
1047
1070
|
});
|
|
1048
1071
|
const article = handler === "opencode" ? "an" : "a";
|
|
1049
1072
|
ctx.io.errline(
|
|
1050
|
-
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event
|
|
1073
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
|
|
1051
1074
|
);
|
|
1052
1075
|
return EXIT.ERROR;
|
|
1053
1076
|
}
|
|
1054
|
-
const
|
|
1077
|
+
const runtimeBudgetMs = hookRuntimeBudgetMs(readConfiguredHookTimeoutSeconds(harness));
|
|
1078
|
+
const elapsedMs = Math.max(0, now() - hookStartedAt);
|
|
1079
|
+
const budgetMs = Math.max(1, Math.min(import_agent_core5.DEFAULT_TOTAL_BUDGET_MS, runtimeBudgetMs - elapsedMs));
|
|
1080
|
+
const sender = makeSender(resolveApiUrl(), budgetMs);
|
|
1055
1081
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
1056
1082
|
ctx.io.result({
|
|
1057
1083
|
harness: handler,
|
|
@@ -1061,17 +1087,23 @@ function createHookCommand(deps = {}) {
|
|
|
1061
1087
|
eventType: result.eventType,
|
|
1062
1088
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
1063
1089
|
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1090
|
+
...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
|
|
1064
1091
|
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
1065
1092
|
});
|
|
1066
1093
|
if (result.outcome === "unpaired") {
|
|
1067
1094
|
ctx.io.errline(
|
|
1068
|
-
"birdybeep: this machine is not paired
|
|
1095
|
+
"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."
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
if (result.outcome === "failed") {
|
|
1099
|
+
ctx.io.errline(
|
|
1100
|
+
"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`."
|
|
1069
1101
|
);
|
|
1070
1102
|
}
|
|
1071
|
-
const unavailable = result.send?.tokenStoreUnavailable;
|
|
1103
|
+
const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
|
|
1072
1104
|
if (unavailable !== void 0) {
|
|
1073
1105
|
ctx.io.errline(
|
|
1074
|
-
`birdybeep:
|
|
1106
|
+
`birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
|
|
1075
1107
|
);
|
|
1076
1108
|
}
|
|
1077
1109
|
return EXIT.OK;
|
|
@@ -1089,7 +1121,7 @@ function createLogoutCommand(deps = {}) {
|
|
|
1089
1121
|
usage: "birdybeep logout",
|
|
1090
1122
|
run: async (ctx) => {
|
|
1091
1123
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1092
|
-
ctx.io.emit("Logged out
|
|
1124
|
+
ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
|
|
1093
1125
|
return EXIT.OK;
|
|
1094
1126
|
}
|
|
1095
1127
|
};
|
|
@@ -1116,14 +1148,14 @@ function createUnpairCommand(deps = {}) {
|
|
|
1116
1148
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
1117
1149
|
return {
|
|
1118
1150
|
name: "unpair",
|
|
1119
|
-
summary: "
|
|
1151
|
+
summary: "Revoke this machine and remove its local token",
|
|
1120
1152
|
usage: "birdybeep unpair",
|
|
1121
1153
|
run: async (ctx) => {
|
|
1122
1154
|
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
1123
1155
|
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
1124
1156
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
1125
1157
|
const serverRevoked = outcome === "revoked";
|
|
1126
|
-
const human = outcome === "revoked" ? "Unpaired
|
|
1158
|
+
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.";
|
|
1127
1159
|
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
1128
1160
|
return EXIT.OK;
|
|
1129
1161
|
}
|
|
@@ -1206,7 +1238,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
1206
1238
|
}
|
|
1207
1239
|
|
|
1208
1240
|
// src/version.ts
|
|
1209
|
-
var CLI_VERSION = "0.8.
|
|
1241
|
+
var CLI_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0";
|
|
1210
1242
|
|
|
1211
1243
|
// src/commands/setup.ts
|
|
1212
1244
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
@@ -1231,8 +1263,8 @@ function buildTestEvent(opts = {}) {
|
|
|
1231
1263
|
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1232
1264
|
machine: { label: machine.label, os: machine.os },
|
|
1233
1265
|
workspace: { cwd: process.cwd() },
|
|
1234
|
-
title: "BirdyBeep test
|
|
1235
|
-
body: "
|
|
1266
|
+
title: "BirdyBeep test",
|
|
1267
|
+
body: "Notifications from this machine are working.",
|
|
1236
1268
|
metadata: { test: true }
|
|
1237
1269
|
},
|
|
1238
1270
|
opts
|
|
@@ -1267,43 +1299,43 @@ function createTestCommand(deps = {}) {
|
|
|
1267
1299
|
});
|
|
1268
1300
|
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1269
1301
|
ctx.io.line(
|
|
1270
|
-
"\u26A0
|
|
1302
|
+
"\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."
|
|
1271
1303
|
);
|
|
1272
1304
|
} else if (reach.state === "ok") {
|
|
1273
1305
|
ctx.io.line(
|
|
1274
|
-
`\u2713 Test event accepted
|
|
1306
|
+
`\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
|
|
1275
1307
|
);
|
|
1276
1308
|
} else {
|
|
1277
|
-
ctx.io.line("\u2713 Test event accepted by the backend
|
|
1309
|
+
ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
|
|
1278
1310
|
}
|
|
1279
1311
|
} else if (result.decision === "suppressed") {
|
|
1280
1312
|
ctx.io.line(
|
|
1281
|
-
"\u26A0
|
|
1313
|
+
"\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
|
|
1282
1314
|
);
|
|
1283
1315
|
} else if (result.decision === "deduped") {
|
|
1284
|
-
ctx.io.line(
|
|
1285
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1286
|
-
);
|
|
1316
|
+
ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
|
|
1287
1317
|
} else {
|
|
1288
1318
|
ctx.io.line(
|
|
1289
|
-
`\u26A0
|
|
1319
|
+
`\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
|
|
1290
1320
|
);
|
|
1291
1321
|
}
|
|
1292
1322
|
} else if (result.outcome === "unpaired") {
|
|
1293
1323
|
ctx.io.line(
|
|
1294
|
-
"\u2717
|
|
1324
|
+
"\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
|
|
1295
1325
|
);
|
|
1296
|
-
} else if (result.
|
|
1326
|
+
} else if (result.outcome === "failed") {
|
|
1297
1327
|
ctx.io.line(
|
|
1298
|
-
|
|
1328
|
+
"\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
|
|
1299
1329
|
);
|
|
1300
|
-
} else if (result.
|
|
1301
|
-
const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
|
|
1330
|
+
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1302
1331
|
ctx.io.line(
|
|
1303
|
-
|
|
1332
|
+
`\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
|
|
1304
1333
|
);
|
|
1334
|
+
} else if (result.outcome === "queued" && result.queueCause === "backend") {
|
|
1335
|
+
const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
|
|
1336
|
+
ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
|
|
1305
1337
|
} else if (result.outcome === "queued") {
|
|
1306
|
-
ctx.io.line("\u2022
|
|
1338
|
+
ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
|
|
1307
1339
|
} else if (result.code === "quota_exceeded") {
|
|
1308
1340
|
const reach = await (0, import_agent_core8.fetchPushReachability)({
|
|
1309
1341
|
baseUrl,
|
|
@@ -1311,18 +1343,18 @@ function createTestCommand(deps = {}) {
|
|
|
1311
1343
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1312
1344
|
});
|
|
1313
1345
|
const quota = reach.state === "ok" ? reach.data.quota : void 0;
|
|
1314
|
-
const exhausted = quota ? (0, import_agent_core8.describeExhaustedQuota)(quota) : void 0;
|
|
1346
|
+
const exhausted = quota !== void 0 && quota.beeps_limit !== null ? (0, import_agent_core8.describeExhaustedQuota)({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
|
|
1315
1347
|
ctx.io.line(
|
|
1316
|
-
quota && exhausted ? `\u2717 Test event
|
|
1348
|
+
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}` : (
|
|
1317
1349
|
// An older backend reports no quota, so there is no period and no reset date to
|
|
1318
1350
|
// give — and `doctor` cannot supply them either, since it reads this same response.
|
|
1319
|
-
"\u2717 Test event
|
|
1351
|
+
"\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."
|
|
1320
1352
|
)
|
|
1321
1353
|
);
|
|
1322
1354
|
} else {
|
|
1323
1355
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1324
1356
|
}
|
|
1325
|
-
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1357
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
|
|
1326
1358
|
}
|
|
1327
1359
|
};
|
|
1328
1360
|
}
|
|
@@ -1422,7 +1454,7 @@ function buildHarnessReports(installs, groups) {
|
|
|
1422
1454
|
}
|
|
1423
1455
|
] : surfaces.map((state) => {
|
|
1424
1456
|
const graded = rowState(state, group, status);
|
|
1425
|
-
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks
|
|
1457
|
+
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);
|
|
1426
1458
|
return {
|
|
1427
1459
|
harness: adapter.id,
|
|
1428
1460
|
displayName: adapter.displayName,
|
|
@@ -1468,12 +1500,12 @@ function describeMissing(reports) {
|
|
|
1468
1500
|
const names = missing.map((r) => r.displayName);
|
|
1469
1501
|
if (missing.length === reports.length) {
|
|
1470
1502
|
return [
|
|
1471
|
-
"No supported coding agent is installed on this machine
|
|
1472
|
-
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again
|
|
1503
|
+
"No supported coding agent is installed on this machine.",
|
|
1504
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
|
|
1473
1505
|
];
|
|
1474
1506
|
}
|
|
1475
1507
|
return [
|
|
1476
|
-
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again
|
|
1508
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
|
|
1477
1509
|
];
|
|
1478
1510
|
}
|
|
1479
1511
|
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
@@ -1696,7 +1728,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1696
1728
|
},
|
|
1697
1729
|
{
|
|
1698
1730
|
flag: "--no-install",
|
|
1699
|
-
summary: "Stop after pairing
|
|
1731
|
+
summary: "Stop after pairing; do not install coding-agent hooks"
|
|
1700
1732
|
},
|
|
1701
1733
|
{
|
|
1702
1734
|
flag: "--no-test",
|
|
@@ -1716,7 +1748,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1716
1748
|
};
|
|
1717
1749
|
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1718
1750
|
ctx.io.line(
|
|
1719
|
-
chain !== void 0 ? "\u2713 Already paired
|
|
1751
|
+
chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1720
1752
|
);
|
|
1721
1753
|
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1722
1754
|
ctx.io.result({
|
|
@@ -1752,7 +1784,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1752
1784
|
ctx.io.line(
|
|
1753
1785
|
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1754
1786
|
);
|
|
1755
|
-
ctx.io.line("Waiting for
|
|
1787
|
+
ctx.io.line("Waiting for approval in the BirdyBeep app.");
|
|
1756
1788
|
}
|
|
1757
1789
|
const deadline = Date.parse(start.expires_at);
|
|
1758
1790
|
const startedAt = clock();
|
|
@@ -1781,7 +1813,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1781
1813
|
}
|
|
1782
1814
|
if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
|
|
1783
1815
|
ctx.io.line(
|
|
1784
|
-
poll.status === "error" ? `
|
|
1816
|
+
poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
|
|
1785
1817
|
);
|
|
1786
1818
|
lastBeat = nowMs;
|
|
1787
1819
|
}
|
|
@@ -1821,7 +1853,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1821
1853
|
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1822
1854
|
ctx.io.result({ paired: false, reason: "declined" });
|
|
1823
1855
|
ctx.io.errline(
|
|
1824
|
-
"Pairing declined
|
|
1856
|
+
"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."
|
|
1825
1857
|
);
|
|
1826
1858
|
return EXIT.ERROR;
|
|
1827
1859
|
}
|
|
@@ -1830,8 +1862,8 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1830
1862
|
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1831
1863
|
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1832
1864
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1833
|
-
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing
|
|
1834
|
-
const nextStep = chain === void 0 ? " Run `birdybeep setup` to
|
|
1865
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
|
|
1866
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
|
|
1835
1867
|
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1836
1868
|
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1837
1869
|
ctx.io.result({
|
|
@@ -1849,7 +1881,7 @@ function createPairCommand(deps = {}) {
|
|
|
1849
1881
|
return createPairingCommand(
|
|
1850
1882
|
{
|
|
1851
1883
|
name: "pair",
|
|
1852
|
-
summary: "Pair this machine and
|
|
1884
|
+
summary: "Pair this machine and install detected coding-agent hooks",
|
|
1853
1885
|
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1854
1886
|
skipWhenPaired: false
|
|
1855
1887
|
},
|
|
@@ -1860,9 +1892,9 @@ function createSetupCommand(deps = {}) {
|
|
|
1860
1892
|
return createPairingCommand(
|
|
1861
1893
|
{
|
|
1862
1894
|
name: "setup",
|
|
1863
|
-
summary: "Set up BirdyBeep
|
|
1895
|
+
summary: "Set up BirdyBeep on this machine",
|
|
1864
1896
|
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1865
|
-
gettingStarted: "
|
|
1897
|
+
gettingStarted: "Connect this machine and install hooks for detected coding agents.",
|
|
1866
1898
|
skipWhenPaired: true
|
|
1867
1899
|
},
|
|
1868
1900
|
deps
|
|
@@ -1935,7 +1967,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1935
1967
|
run: async (ctx) => {
|
|
1936
1968
|
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1937
1969
|
if (token === null) {
|
|
1938
|
-
ctx.io.errline("No machine token
|
|
1970
|
+
ctx.io.errline("No machine token. Run `birdybeep pair` first.");
|
|
1939
1971
|
return EXIT.ERROR;
|
|
1940
1972
|
}
|
|
1941
1973
|
const items = await gatherItems(adapters);
|
|
@@ -1980,12 +2012,12 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1980
2012
|
});
|
|
1981
2013
|
} else if (outcome === "terminal") {
|
|
1982
2014
|
ctx.io.errline(
|
|
1983
|
-
`Report rejected (${errorCode ?? "auth"})
|
|
2015
|
+
`Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
|
|
1984
2016
|
);
|
|
1985
2017
|
} else {
|
|
1986
2018
|
for (const e of effective) {
|
|
1987
2019
|
ctx.io.line(
|
|
1988
|
-
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred
|
|
2020
|
+
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
|
|
1989
2021
|
);
|
|
1990
2022
|
}
|
|
1991
2023
|
}
|
|
@@ -2045,7 +2077,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2045
2077
|
} else {
|
|
2046
2078
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
2047
2079
|
ctx.io.line(
|
|
2048
|
-
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no
|
|
2080
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
|
|
2049
2081
|
);
|
|
2050
2082
|
ctx.io.line("Integrations:");
|
|
2051
2083
|
for (const i of integrations) {
|
|
@@ -2053,7 +2085,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2053
2085
|
const group = surfaces.find((g) => g.harness === i.harness);
|
|
2054
2086
|
for (const state of group?.surfaces ?? []) {
|
|
2055
2087
|
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
2056
|
-
ctx.io.line(` ${mark} ${describeSurface(state)}
|
|
2088
|
+
ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
|
|
2057
2089
|
}
|
|
2058
2090
|
}
|
|
2059
2091
|
ctx.io.line(
|