@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.js
CHANGED
|
@@ -80,7 +80,7 @@ function renderRootHelp(version, commands) {
|
|
|
80
80
|
const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
81
81
|
const featured = commands.filter((c) => c.gettingStarted !== void 0);
|
|
82
82
|
return [
|
|
83
|
-
`birdybeep ${version}
|
|
83
|
+
`birdybeep ${version}: phone alerts for coding agents.`,
|
|
84
84
|
"",
|
|
85
85
|
"Usage:",
|
|
86
86
|
" birdybeep <command> [options]",
|
|
@@ -112,7 +112,7 @@ function commandFlagTokens(...commands) {
|
|
|
112
112
|
}
|
|
113
113
|
function renderCommandHelp(path, command) {
|
|
114
114
|
const lines = [
|
|
115
|
-
`birdybeep ${path}
|
|
115
|
+
`birdybeep ${path}: ${command.summary}`,
|
|
116
116
|
"",
|
|
117
117
|
"Usage:",
|
|
118
118
|
` ${command.usage ?? `birdybeep ${path} [options]`}`
|
|
@@ -227,7 +227,7 @@ async function dispatch(argv, deps) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// src/version.ts
|
|
230
|
-
var CLI_VERSION = "0.8.
|
|
230
|
+
var CLI_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0";
|
|
231
231
|
|
|
232
232
|
// src/commands/agent.ts
|
|
233
233
|
import { claudeCodeAdapter } from "@birdybeep/claude-code";
|
|
@@ -333,7 +333,7 @@ function filteredActivity() {
|
|
|
333
333
|
function describeFilteredActivity(activity) {
|
|
334
334
|
const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
|
|
335
335
|
const since = new Date(activity.firstAt).toISOString();
|
|
336
|
-
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}
|
|
336
|
+
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""}. Hooks are firing; these types never produce notifications, so they are not sent.`;
|
|
337
337
|
}
|
|
338
338
|
function machineIdentity() {
|
|
339
339
|
return getMachineIdentity();
|
|
@@ -421,18 +421,18 @@ function describeSurface(state) {
|
|
|
421
421
|
function describeSurfaceCoverage(state, group) {
|
|
422
422
|
if (state.coverage === "active") {
|
|
423
423
|
const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
|
|
424
|
-
return `covered
|
|
424
|
+
return `covered: ${state.events} event(s) from this build${last}`;
|
|
425
425
|
}
|
|
426
426
|
if (state.coverage === "wired") {
|
|
427
|
-
return state.surface.shadowed === true ? `${group.displayName}'s hooks are installed and this build shares them, but another install comes first on PATH
|
|
427
|
+
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`;
|
|
428
428
|
}
|
|
429
429
|
if (!CONFIGURED_STATUSES.has(group.status)) {
|
|
430
|
-
return `not covered
|
|
430
|
+
return `not covered: ${group.displayName} carries no BirdyBeep hooks, so this build cannot produce notifications`;
|
|
431
431
|
}
|
|
432
432
|
const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
|
|
433
433
|
const delivering = active.join(", ");
|
|
434
434
|
const verb = active.length === 1 ? "is" : "are";
|
|
435
|
-
return `not covered
|
|
435
|
+
return `not covered: nothing has fired from this build, while ${delivering} ${verb} delivering through the same config`;
|
|
436
436
|
}
|
|
437
437
|
function installTarget(harness) {
|
|
438
438
|
return harness === "claude_code" ? "claude" : harness;
|
|
@@ -441,7 +441,7 @@ function surfaceRemedy(state, group) {
|
|
|
441
441
|
if (state.coverage !== "uncovered") return void 0;
|
|
442
442
|
if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
|
|
443
443
|
const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
|
|
444
|
-
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
|
|
444
|
+
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.`;
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
// src/commands/agent.ts
|
|
@@ -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;
|
|
@@ -722,15 +722,15 @@ function createDoctorCommand(deps = {}) {
|
|
|
722
722
|
name: "Events lost while unpaired",
|
|
723
723
|
ok: false,
|
|
724
724
|
detail: describeUnpairedActivity(unpaired),
|
|
725
|
-
remedy: "Run `birdybeep pair`. Events that fired before pairing
|
|
725
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing were not saved and will not be replayed."
|
|
726
726
|
});
|
|
727
727
|
}
|
|
728
728
|
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
729
729
|
checks.push({
|
|
730
730
|
name: "Approval beeps from Cursor",
|
|
731
731
|
ok: false,
|
|
732
|
-
detail: "Cursor is running your agent through the Claude Code hooks
|
|
733
|
-
remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe
|
|
732
|
+
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.",
|
|
733
|
+
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."
|
|
734
734
|
});
|
|
735
735
|
}
|
|
736
736
|
const filtered = filteredActivity();
|
|
@@ -779,7 +779,7 @@ function createDoctorCommand(deps = {}) {
|
|
|
779
779
|
name: "Backend reachable",
|
|
780
780
|
ok: false,
|
|
781
781
|
detail: `Could not reach ${apiUrl}.`,
|
|
782
|
-
remedy: "Check your network
|
|
782
|
+
remedy: "Check your network. Queued events retry automatically."
|
|
783
783
|
}
|
|
784
784
|
);
|
|
785
785
|
const ok = checks.every((c) => c.ok);
|
|
@@ -804,10 +804,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
804
804
|
});
|
|
805
805
|
} else {
|
|
806
806
|
for (const c of checks) {
|
|
807
|
-
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ?
|
|
807
|
+
ctx.io.line(`${c.ok ? "\u2713" : "\u2717"} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
|
|
808
808
|
if (!c.ok && c.remedy) ctx.io.line(` \u2192 ${c.remedy}`);
|
|
809
809
|
}
|
|
810
|
-
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed
|
|
810
|
+
ctx.io.line(ok ? "\nAll checks passed." : "\nSome checks failed. See fixes above.");
|
|
811
811
|
}
|
|
812
812
|
return ok ? EXIT.OK : EXIT.ERROR;
|
|
813
813
|
}
|
|
@@ -820,18 +820,35 @@ import { randomBytes } from "crypto";
|
|
|
820
820
|
import { closeSync, openSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
821
821
|
import { tmpdir } from "os";
|
|
822
822
|
import { basename, dirname, join as join2 } from "path";
|
|
823
|
+
import { performance } from "perf_hooks";
|
|
823
824
|
import {
|
|
824
825
|
createSender as defaultCreateSender2,
|
|
826
|
+
DEFAULT_SEND_TIMEOUT_MS,
|
|
827
|
+
DEFAULT_TOTAL_BUDGET_MS,
|
|
825
828
|
resolveOnPath
|
|
826
829
|
} from "@birdybeep/agent-core";
|
|
827
|
-
import { isClaudeCodeHookPayload, runClaudeHook } from "@birdybeep/claude-code";
|
|
828
|
-
import { isCodexHookPayload, runCodexHook } from "@birdybeep/codex";
|
|
829
830
|
import {
|
|
831
|
+
configuredClaudeHookTimeoutSeconds,
|
|
832
|
+
isClaudeCodeHookPayload,
|
|
833
|
+
runClaudeHook
|
|
834
|
+
} from "@birdybeep/claude-code";
|
|
835
|
+
import {
|
|
836
|
+
configuredCodexHookTimeoutSeconds,
|
|
837
|
+
isCodexHookPayload,
|
|
838
|
+
runCodexHook
|
|
839
|
+
} from "@birdybeep/codex";
|
|
840
|
+
import {
|
|
841
|
+
configuredCopilotHookTimeoutSeconds,
|
|
830
842
|
isCopilotHookEventName,
|
|
831
843
|
isCopilotHookPayload,
|
|
832
844
|
runCopilotHook
|
|
833
845
|
} from "@birdybeep/copilot";
|
|
834
|
-
import {
|
|
846
|
+
import {
|
|
847
|
+
configuredCursorHookTimeoutSeconds,
|
|
848
|
+
isCursorHookEventName,
|
|
849
|
+
isCursorHookPayload,
|
|
850
|
+
runCursorHook
|
|
851
|
+
} from "@birdybeep/cursor";
|
|
835
852
|
import { isOpenCodeEventPayload, runOpenCodeHook } from "@birdybeep/opencode";
|
|
836
853
|
var RUNNERS = {
|
|
837
854
|
claude: runClaudeHook,
|
|
@@ -847,6 +864,21 @@ var HOOK_HARNESSES = [
|
|
|
847
864
|
"copilot"
|
|
848
865
|
];
|
|
849
866
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
867
|
+
var LEGACY_HOOK_RUNTIME_BUDGET_MS = 9e3;
|
|
868
|
+
function hookRuntimeBudgetMs(configuredTimeoutSeconds) {
|
|
869
|
+
if (configuredTimeoutSeconds === void 0 || !Number.isFinite(configuredTimeoutSeconds) || configuredTimeoutSeconds <= 0) {
|
|
870
|
+
return LEGACY_HOOK_RUNTIME_BUDGET_MS;
|
|
871
|
+
}
|
|
872
|
+
const configuredBudgetMs = Math.max(1, Math.floor(configuredTimeoutSeconds * 1e3) - 1e3);
|
|
873
|
+
return Math.min(LEGACY_HOOK_RUNTIME_BUDGET_MS, configuredBudgetMs);
|
|
874
|
+
}
|
|
875
|
+
function configuredHookTimeoutSeconds(harness) {
|
|
876
|
+
if (harness === "claude") return configuredClaudeHookTimeoutSeconds();
|
|
877
|
+
if (harness === "codex") return configuredCodexHookTimeoutSeconds();
|
|
878
|
+
if (harness === "cursor") return configuredCursorHookTimeoutSeconds();
|
|
879
|
+
if (harness === "copilot") return configuredCopilotHookTimeoutSeconds();
|
|
880
|
+
return void 0;
|
|
881
|
+
}
|
|
850
882
|
function withTimeout(promise, ms, fallback) {
|
|
851
883
|
return new Promise((resolve) => {
|
|
852
884
|
let settled = false;
|
|
@@ -967,15 +999,22 @@ function detachCodexNotifyWorker(payload) {
|
|
|
967
999
|
}
|
|
968
1000
|
}
|
|
969
1001
|
function createHookCommand(deps = {}) {
|
|
970
|
-
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender2({
|
|
1002
|
+
const makeSender = deps.createSender ?? ((baseUrl, budgetMs) => defaultCreateSender2({
|
|
1003
|
+
baseUrl,
|
|
1004
|
+
timeoutMs: Math.min(DEFAULT_SEND_TIMEOUT_MS, budgetMs),
|
|
1005
|
+
totalBudgetMs: budgetMs
|
|
1006
|
+
}));
|
|
971
1007
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
972
1008
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
1009
|
+
const now = deps.now ?? (() => performance.now());
|
|
1010
|
+
const readConfiguredHookTimeoutSeconds = deps.configuredHookTimeoutSeconds ?? configuredHookTimeoutSeconds;
|
|
973
1011
|
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
974
1012
|
return {
|
|
975
1013
|
name: "hook",
|
|
976
1014
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
977
1015
|
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
978
1016
|
run: async (ctx) => {
|
|
1017
|
+
const hookStartedAt = now();
|
|
979
1018
|
const harness = ctx.args[0];
|
|
980
1019
|
if (!isHarnessName(harness)) {
|
|
981
1020
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
@@ -989,7 +1028,7 @@ function createHookCommand(deps = {}) {
|
|
|
989
1028
|
const copilotEventName = harness === "copilot" && isCopilotHookEventName(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
990
1029
|
if (harness === "copilot" && copilotEventName === void 0) {
|
|
991
1030
|
ctx.io.errline(
|
|
992
|
-
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}
|
|
1031
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")}. Nothing was sent.`
|
|
993
1032
|
);
|
|
994
1033
|
return EXIT.USAGE;
|
|
995
1034
|
}
|
|
@@ -1007,7 +1046,7 @@ function createHookCommand(deps = {}) {
|
|
|
1007
1046
|
}
|
|
1008
1047
|
const drop = (reason, detail) => {
|
|
1009
1048
|
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
1010
|
-
ctx.io.errline(`birdybeep hook ${harness}: ${detail}
|
|
1049
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail}. Nothing was sent.`);
|
|
1011
1050
|
return EXIT.ERROR;
|
|
1012
1051
|
};
|
|
1013
1052
|
if (read === null) {
|
|
@@ -1037,11 +1076,14 @@ function createHookCommand(deps = {}) {
|
|
|
1037
1076
|
});
|
|
1038
1077
|
const article = handler === "opencode" ? "an" : "a";
|
|
1039
1078
|
ctx.io.errline(
|
|
1040
|
-
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event
|
|
1079
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event. Nothing was sent. Check which tool is running this hook.`
|
|
1041
1080
|
);
|
|
1042
1081
|
return EXIT.ERROR;
|
|
1043
1082
|
}
|
|
1044
|
-
const
|
|
1083
|
+
const runtimeBudgetMs = hookRuntimeBudgetMs(readConfiguredHookTimeoutSeconds(harness));
|
|
1084
|
+
const elapsedMs = Math.max(0, now() - hookStartedAt);
|
|
1085
|
+
const budgetMs = Math.max(1, Math.min(DEFAULT_TOTAL_BUDGET_MS, runtimeBudgetMs - elapsedMs));
|
|
1086
|
+
const sender = makeSender(resolveApiUrl(), budgetMs);
|
|
1045
1087
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
1046
1088
|
ctx.io.result({
|
|
1047
1089
|
harness: handler,
|
|
@@ -1051,17 +1093,23 @@ function createHookCommand(deps = {}) {
|
|
|
1051
1093
|
eventType: result.eventType,
|
|
1052
1094
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
1053
1095
|
...result.send?.status !== void 0 ? { status: result.send.status } : {},
|
|
1096
|
+
...result.send?.queueCause !== void 0 ? { queueCause: result.send.queueCause } : {},
|
|
1054
1097
|
...result.send?.tokenStoreUnavailable !== void 0 ? { tokenStore: "unavailable" } : {}
|
|
1055
1098
|
});
|
|
1056
1099
|
if (result.outcome === "unpaired") {
|
|
1057
1100
|
ctx.io.errline(
|
|
1058
|
-
"birdybeep: this machine is not paired
|
|
1101
|
+
"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."
|
|
1059
1102
|
);
|
|
1060
1103
|
}
|
|
1061
|
-
|
|
1104
|
+
if (result.outcome === "failed") {
|
|
1105
|
+
ctx.io.errline(
|
|
1106
|
+
"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`."
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
const unavailable = result.outcome === "queued" ? result.send?.tokenStoreUnavailable : void 0;
|
|
1062
1110
|
if (unavailable !== void 0) {
|
|
1063
1111
|
ctx.io.errline(
|
|
1064
|
-
`birdybeep:
|
|
1112
|
+
`birdybeep: the machine token is unreadable (${unavailable.reason}). The event is queued. Restore token-store access; \`birdybeep doctor\` drains the queue.`
|
|
1065
1113
|
);
|
|
1066
1114
|
}
|
|
1067
1115
|
return EXIT.OK;
|
|
@@ -1079,7 +1127,7 @@ function createLogoutCommand(deps = {}) {
|
|
|
1079
1127
|
usage: "birdybeep logout",
|
|
1080
1128
|
run: async (ctx) => {
|
|
1081
1129
|
await clearToken(deps.tokenOptions ?? {});
|
|
1082
|
-
ctx.io.emit("Logged out
|
|
1130
|
+
ctx.io.emit("Logged out. The machine token was removed.", { loggedOut: true });
|
|
1083
1131
|
return EXIT.OK;
|
|
1084
1132
|
}
|
|
1085
1133
|
};
|
|
@@ -1106,14 +1154,14 @@ function createUnpairCommand(deps = {}) {
|
|
|
1106
1154
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
1107
1155
|
return {
|
|
1108
1156
|
name: "unpair",
|
|
1109
|
-
summary: "
|
|
1157
|
+
summary: "Revoke this machine and remove its local token",
|
|
1110
1158
|
usage: "birdybeep unpair",
|
|
1111
1159
|
run: async (ctx) => {
|
|
1112
1160
|
const token = await getToken2(deps.tokenOptions ?? {});
|
|
1113
1161
|
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
1114
1162
|
await clearToken(deps.tokenOptions ?? {});
|
|
1115
1163
|
const serverRevoked = outcome === "revoked";
|
|
1116
|
-
const human = outcome === "revoked" ? "Unpaired
|
|
1164
|
+
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.";
|
|
1117
1165
|
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
1118
1166
|
return EXIT.OK;
|
|
1119
1167
|
}
|
|
@@ -1236,8 +1284,8 @@ function buildTestEvent(opts = {}) {
|
|
|
1236
1284
|
source_session_id: `birdybeep-cli-test-${randomUUID()}`,
|
|
1237
1285
|
machine: { label: machine.label, os: machine.os },
|
|
1238
1286
|
workspace: { cwd: process.cwd() },
|
|
1239
|
-
title: "BirdyBeep test
|
|
1240
|
-
body: "
|
|
1287
|
+
title: "BirdyBeep test",
|
|
1288
|
+
body: "Notifications from this machine are working.",
|
|
1241
1289
|
metadata: { test: true }
|
|
1242
1290
|
},
|
|
1243
1291
|
opts
|
|
@@ -1272,43 +1320,43 @@ function createTestCommand(deps = {}) {
|
|
|
1272
1320
|
});
|
|
1273
1321
|
if (reach.state === "ok" && reach.data.active_device_count === 0) {
|
|
1274
1322
|
ctx.io.line(
|
|
1275
|
-
"\u26A0
|
|
1323
|
+
"\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."
|
|
1276
1324
|
);
|
|
1277
1325
|
} else if (reach.state === "ok") {
|
|
1278
1326
|
ctx.io.line(
|
|
1279
|
-
`\u2713 Test event accepted
|
|
1327
|
+
`\u2713 Test event accepted for ${String(reach.data.active_device_count)} registered device(s). Check your phone for a test Beep.`
|
|
1280
1328
|
);
|
|
1281
1329
|
} else {
|
|
1282
|
-
ctx.io.line("\u2713 Test event accepted by the backend
|
|
1330
|
+
ctx.io.line("\u2713 Test event accepted by the backend. Check your phone for a test Beep.");
|
|
1283
1331
|
}
|
|
1284
1332
|
} else if (result.decision === "suppressed") {
|
|
1285
1333
|
ctx.io.line(
|
|
1286
|
-
"\u26A0
|
|
1334
|
+
"\u26A0 Test event accepted; push suppressed. Check machine and integration mutes in the app."
|
|
1287
1335
|
);
|
|
1288
1336
|
} else if (result.decision === "deduped") {
|
|
1289
|
-
ctx.io.line(
|
|
1290
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1291
|
-
);
|
|
1337
|
+
ctx.io.line("\u26A0 Test event matched a recent duplicate. Retry in 30 seconds.");
|
|
1292
1338
|
} else {
|
|
1293
1339
|
ctx.io.line(
|
|
1294
|
-
`\u26A0
|
|
1340
|
+
`\u26A0 Test event accepted; push decision was "${result.decision}". Run \`birdybeep doctor\`.`
|
|
1295
1341
|
);
|
|
1296
1342
|
}
|
|
1297
1343
|
} else if (result.outcome === "unpaired") {
|
|
1298
1344
|
ctx.io.line(
|
|
1299
|
-
"\u2717
|
|
1345
|
+
"\u2717 This machine is not paired. Run `birdybeep pair`. No event was sent or queued."
|
|
1300
1346
|
);
|
|
1301
|
-
} else if (result.
|
|
1347
|
+
} else if (result.outcome === "failed") {
|
|
1302
1348
|
ctx.io.line(
|
|
1303
|
-
|
|
1349
|
+
"\u2717 The test event could not be sent or saved locally. Check BirdyBeep's data-directory permissions, then retry."
|
|
1304
1350
|
);
|
|
1305
|
-
} else if (result.
|
|
1306
|
-
const status = result.status !== void 0 ? ` (HTTP ${String(result.status)})` : "";
|
|
1351
|
+
} else if (result.tokenStoreUnavailable !== void 0) {
|
|
1307
1352
|
ctx.io.line(
|
|
1308
|
-
|
|
1353
|
+
`\u2022 The machine token is unreadable (${result.tokenStoreUnavailable.reason}). The test event is queued. Restore token-store access, then retry.`
|
|
1309
1354
|
);
|
|
1355
|
+
} else if (result.outcome === "queued" && result.queueCause === "backend") {
|
|
1356
|
+
const status = result.status !== void 0 ? ` HTTP ${String(result.status)}` : " an error";
|
|
1357
|
+
ctx.io.line(`\u2022 Backend returned${status}. The test event is queued for retry.`);
|
|
1310
1358
|
} else if (result.outcome === "queued") {
|
|
1311
|
-
ctx.io.line("\u2022
|
|
1359
|
+
ctx.io.line("\u2022 Could not reach the backend. The test event is queued.");
|
|
1312
1360
|
} else if (result.code === "quota_exceeded") {
|
|
1313
1361
|
const reach = await fetchPushReachability2({
|
|
1314
1362
|
baseUrl,
|
|
@@ -1316,18 +1364,18 @@ function createTestCommand(deps = {}) {
|
|
|
1316
1364
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1317
1365
|
});
|
|
1318
1366
|
const quota = reach.state === "ok" ? reach.data.quota : void 0;
|
|
1319
|
-
const exhausted = quota ? describeExhaustedQuota(quota) : void 0;
|
|
1367
|
+
const exhausted = quota !== void 0 && quota.beeps_limit !== null ? describeExhaustedQuota({ ...quota, beeps_limit: quota.beeps_limit }) : void 0;
|
|
1320
1368
|
ctx.io.line(
|
|
1321
|
-
quota && exhausted ? `\u2717 Test event
|
|
1369
|
+
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}` : (
|
|
1322
1370
|
// An older backend reports no quota, so there is no period and no reset date to
|
|
1323
1371
|
// give — and `doctor` cannot supply them either, since it reads this same response.
|
|
1324
|
-
"\u2717 Test event
|
|
1372
|
+
"\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."
|
|
1325
1373
|
)
|
|
1326
1374
|
);
|
|
1327
1375
|
} else {
|
|
1328
1376
|
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1329
1377
|
}
|
|
1330
|
-
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1378
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" || result.outcome === "failed" ? EXIT.ERROR : EXIT.OK;
|
|
1331
1379
|
}
|
|
1332
1380
|
};
|
|
1333
1381
|
}
|
|
@@ -1427,7 +1475,7 @@ function buildHarnessReports(installs, groups) {
|
|
|
1427
1475
|
}
|
|
1428
1476
|
] : surfaces.map((state) => {
|
|
1429
1477
|
const graded = rowState(state, group, status);
|
|
1430
|
-
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks
|
|
1478
|
+
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);
|
|
1431
1479
|
return {
|
|
1432
1480
|
harness: adapter.id,
|
|
1433
1481
|
displayName: adapter.displayName,
|
|
@@ -1473,12 +1521,12 @@ function describeMissing(reports) {
|
|
|
1473
1521
|
const names = missing.map((r) => r.displayName);
|
|
1474
1522
|
if (missing.length === reports.length) {
|
|
1475
1523
|
return [
|
|
1476
|
-
"No supported coding agent is installed on this machine
|
|
1477
|
-
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again
|
|
1524
|
+
"No supported coding agent is installed on this machine.",
|
|
1525
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again.`
|
|
1478
1526
|
];
|
|
1479
1527
|
}
|
|
1480
1528
|
return [
|
|
1481
|
-
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again
|
|
1529
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again.`
|
|
1482
1530
|
];
|
|
1483
1531
|
}
|
|
1484
1532
|
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
@@ -1701,7 +1749,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1701
1749
|
},
|
|
1702
1750
|
{
|
|
1703
1751
|
flag: "--no-install",
|
|
1704
|
-
summary: "Stop after pairing
|
|
1752
|
+
summary: "Stop after pairing; do not install coding-agent hooks"
|
|
1705
1753
|
},
|
|
1706
1754
|
{
|
|
1707
1755
|
flag: "--no-test",
|
|
@@ -1721,7 +1769,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1721
1769
|
};
|
|
1722
1770
|
if (verb.skipWhenPaired && await getToken3(deps.tokenOptions ?? {}) !== null) {
|
|
1723
1771
|
ctx.io.line(
|
|
1724
|
-
chain !== void 0 ? "\u2713 Already paired
|
|
1772
|
+
chain !== void 0 ? "\u2713 Already paired. Checking installed coding-agent hooks." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1725
1773
|
);
|
|
1726
1774
|
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1727
1775
|
ctx.io.result({
|
|
@@ -1757,7 +1805,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1757
1805
|
ctx.io.line(
|
|
1758
1806
|
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1759
1807
|
);
|
|
1760
|
-
ctx.io.line("Waiting for
|
|
1808
|
+
ctx.io.line("Waiting for approval in the BirdyBeep app.");
|
|
1761
1809
|
}
|
|
1762
1810
|
const deadline = Date.parse(start.expires_at);
|
|
1763
1811
|
const startedAt = clock();
|
|
@@ -1786,7 +1834,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1786
1834
|
}
|
|
1787
1835
|
if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
|
|
1788
1836
|
ctx.io.line(
|
|
1789
|
-
poll.status === "error" ? `
|
|
1837
|
+
poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
|
|
1790
1838
|
);
|
|
1791
1839
|
lastBeat = nowMs;
|
|
1792
1840
|
}
|
|
@@ -1826,7 +1874,7 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1826
1874
|
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1827
1875
|
ctx.io.result({ paired: false, reason: "declined" });
|
|
1828
1876
|
ctx.io.errline(
|
|
1829
|
-
"Pairing declined
|
|
1877
|
+
"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."
|
|
1830
1878
|
);
|
|
1831
1879
|
return EXIT.ERROR;
|
|
1832
1880
|
}
|
|
@@ -1835,8 +1883,8 @@ function createPairingCommand(verb, deps = {}) {
|
|
|
1835
1883
|
const discarded = new LocalEventQueue2().discardBefore(clock());
|
|
1836
1884
|
clearUnpairedNotice();
|
|
1837
1885
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1838
|
-
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing
|
|
1839
|
-
const nextStep = chain === void 0 ? " Run `birdybeep setup` to
|
|
1886
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
|
|
1887
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
|
|
1840
1888
|
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1841
1889
|
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1842
1890
|
ctx.io.result({
|
|
@@ -1854,7 +1902,7 @@ function createPairCommand(deps = {}) {
|
|
|
1854
1902
|
return createPairingCommand(
|
|
1855
1903
|
{
|
|
1856
1904
|
name: "pair",
|
|
1857
|
-
summary: "Pair this machine and
|
|
1905
|
+
summary: "Pair this machine and install detected coding-agent hooks",
|
|
1858
1906
|
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1859
1907
|
skipWhenPaired: false
|
|
1860
1908
|
},
|
|
@@ -1865,9 +1913,9 @@ function createSetupCommand(deps = {}) {
|
|
|
1865
1913
|
return createPairingCommand(
|
|
1866
1914
|
{
|
|
1867
1915
|
name: "setup",
|
|
1868
|
-
summary: "Set up BirdyBeep
|
|
1916
|
+
summary: "Set up BirdyBeep on this machine",
|
|
1869
1917
|
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1870
|
-
gettingStarted: "
|
|
1918
|
+
gettingStarted: "Connect this machine and install hooks for detected coding agents.",
|
|
1871
1919
|
skipWhenPaired: true
|
|
1872
1920
|
},
|
|
1873
1921
|
deps
|
|
@@ -1944,7 +1992,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1944
1992
|
run: async (ctx) => {
|
|
1945
1993
|
const token = await getToken4(deps.tokenOptions ?? {});
|
|
1946
1994
|
if (token === null) {
|
|
1947
|
-
ctx.io.errline("No machine token
|
|
1995
|
+
ctx.io.errline("No machine token. Run `birdybeep pair` first.");
|
|
1948
1996
|
return EXIT.ERROR;
|
|
1949
1997
|
}
|
|
1950
1998
|
const items = await gatherItems(adapters);
|
|
@@ -1989,12 +2037,12 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1989
2037
|
});
|
|
1990
2038
|
} else if (outcome === "terminal") {
|
|
1991
2039
|
ctx.io.errline(
|
|
1992
|
-
`Report rejected (${errorCode ?? "auth"})
|
|
2040
|
+
`Report rejected (${errorCode ?? "auth"}). Your token may be revoked. Re-run \`birdybeep pair\`.`
|
|
1993
2041
|
);
|
|
1994
2042
|
} else {
|
|
1995
2043
|
for (const e of effective) {
|
|
1996
2044
|
ctx.io.line(
|
|
1997
|
-
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred
|
|
2045
|
+
outcome === "reported" ? `\u2713 ${e.harness}: ${e.status} (reported)` : `\u2022 ${e.harness}: ${e.status} (deferred; backend unreachable)`
|
|
1998
2046
|
);
|
|
1999
2047
|
}
|
|
2000
2048
|
}
|
|
@@ -2056,7 +2104,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2056
2104
|
} else {
|
|
2057
2105
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
2058
2106
|
ctx.io.line(
|
|
2059
|
-
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no
|
|
2107
|
+
pairing.state === "paired" ? "Paired: yes" : pairing.state === "unpaired" ? "Paired: no. Run `birdybeep pair`." : `Paired: unknown. ${describeTokenStoreUnavailable(pairing)}`
|
|
2060
2108
|
);
|
|
2061
2109
|
ctx.io.line("Integrations:");
|
|
2062
2110
|
for (const i of integrations) {
|
|
@@ -2064,7 +2112,7 @@ function createStatusCommand(deps = {}) {
|
|
|
2064
2112
|
const group = surfaces.find((g) => g.harness === i.harness);
|
|
2065
2113
|
for (const state of group?.surfaces ?? []) {
|
|
2066
2114
|
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
2067
|
-
ctx.io.line(` ${mark} ${describeSurface(state)}
|
|
2115
|
+
ctx.io.line(` ${mark} ${describeSurface(state)}: ${state.coverage}`);
|
|
2068
2116
|
}
|
|
2069
2117
|
}
|
|
2070
2118
|
ctx.io.line(
|
|
@@ -2256,4 +2304,4 @@ export {
|
|
|
2256
2304
|
buildCommands,
|
|
2257
2305
|
runCli
|
|
2258
2306
|
};
|
|
2259
|
-
//# sourceMappingURL=chunk-
|
|
2307
|
+
//# sourceMappingURL=chunk-CZ4G735V.js.map
|