@birdybeep/cli 0.4.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +760 -202
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-ZYFMLHY4.js → chunk-SAUSKJDH.js} +745 -168
- package/dist/chunk-SAUSKJDH.js.map +1 -0
- package/dist/index.cjs +760 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-ZYFMLHY4.js.map +0 -1
|
@@ -78,11 +78,17 @@ function isUnknownFlag(token, allowed) {
|
|
|
78
78
|
function renderRootHelp(version, commands) {
|
|
79
79
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
80
80
|
const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
81
|
+
const featured = commands.filter((c) => c.gettingStarted !== void 0);
|
|
81
82
|
return [
|
|
82
83
|
`birdybeep ${version} \u2014 stream coding-agent lifecycle events to BirdyBeep.`,
|
|
83
84
|
"",
|
|
84
85
|
"Usage:",
|
|
85
86
|
" birdybeep <command> [options]",
|
|
87
|
+
...featured.length > 0 ? [
|
|
88
|
+
"",
|
|
89
|
+
"Getting started:",
|
|
90
|
+
...featured.map((c) => ` birdybeep ${c.name} ${c.gettingStarted ?? ""}`)
|
|
91
|
+
] : [],
|
|
86
92
|
"",
|
|
87
93
|
"Commands:",
|
|
88
94
|
...lines,
|
|
@@ -164,7 +170,11 @@ async function dispatch(argv, deps) {
|
|
|
164
170
|
if (rest.length === 0 || flags.help && command === void 0) {
|
|
165
171
|
io.emit(renderRootHelp(deps.version, deps.commands), {
|
|
166
172
|
version: deps.version,
|
|
167
|
-
commands: deps.commands.map((c) => ({
|
|
173
|
+
commands: deps.commands.map((c) => ({
|
|
174
|
+
name: c.name,
|
|
175
|
+
summary: c.summary,
|
|
176
|
+
...c.gettingStarted !== void 0 ? { gettingStarted: c.gettingStarted } : {}
|
|
177
|
+
}))
|
|
168
178
|
});
|
|
169
179
|
return EXIT.OK;
|
|
170
180
|
}
|
|
@@ -217,7 +227,7 @@ async function dispatch(argv, deps) {
|
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
// src/version.ts
|
|
220
|
-
var CLI_VERSION = "0.
|
|
230
|
+
var CLI_VERSION = "0.6.1".length > 0 ? "0.6.1" : "0.0.0";
|
|
221
231
|
|
|
222
232
|
// src/commands/agent.ts
|
|
223
233
|
import { claudeCodeAdapter } from "@birdybeep/claude-code";
|
|
@@ -225,6 +235,200 @@ import { codexAdapter } from "@birdybeep/codex";
|
|
|
225
235
|
import { copilotAdapter } from "@birdybeep/copilot";
|
|
226
236
|
import { cursorAdapter } from "@birdybeep/cursor";
|
|
227
237
|
import { opencodeAdapter } from "@birdybeep/opencode";
|
|
238
|
+
|
|
239
|
+
// src/diagnostics.ts
|
|
240
|
+
import { existsSync, readFileSync } from "fs";
|
|
241
|
+
import { homedir } from "os";
|
|
242
|
+
import {
|
|
243
|
+
getMachineIdentity,
|
|
244
|
+
getToken,
|
|
245
|
+
LocalEventQueue,
|
|
246
|
+
readFilteredActivity,
|
|
247
|
+
readObservedBuilds,
|
|
248
|
+
readUnpairedNotice
|
|
249
|
+
} from "@birdybeep/agent-core";
|
|
250
|
+
import {
|
|
251
|
+
BIRDYBEEP_HOOK_EVENTS as CLAUDE_HOOK_EVENTS,
|
|
252
|
+
claudeSettingsPath,
|
|
253
|
+
isBirdyBeepEntry as isClaudeEntry
|
|
254
|
+
} from "@birdybeep/claude-code";
|
|
255
|
+
import {
|
|
256
|
+
BIRDYBEEP_HOOK_EVENTS as CURSOR_HOOK_EVENTS,
|
|
257
|
+
cursorHooksPath,
|
|
258
|
+
detectCursor,
|
|
259
|
+
isBirdyBeepEntry as isCursorEntry
|
|
260
|
+
} from "@birdybeep/cursor";
|
|
261
|
+
async function gatherIntegrations(adapters) {
|
|
262
|
+
return Promise.all(
|
|
263
|
+
adapters.map(async (a) => ({
|
|
264
|
+
harness: a.id,
|
|
265
|
+
displayName: a.displayName,
|
|
266
|
+
status: await a.status()
|
|
267
|
+
}))
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
async function isPaired(tokenOptions = {}) {
|
|
271
|
+
return await getToken(tokenOptions) !== null;
|
|
272
|
+
}
|
|
273
|
+
function localQueueDepth() {
|
|
274
|
+
return new LocalEventQueue().size();
|
|
275
|
+
}
|
|
276
|
+
function localQueueOverflowDrops() {
|
|
277
|
+
return new LocalEventQueue().overflowDropCount();
|
|
278
|
+
}
|
|
279
|
+
function unpairedActivity() {
|
|
280
|
+
return readUnpairedNotice();
|
|
281
|
+
}
|
|
282
|
+
function describeUnpairedActivity(notice) {
|
|
283
|
+
const since = new Date(notice.firstAt).toISOString();
|
|
284
|
+
const from = notice.harnesses.length > 0 ? ` from ${notice.harnesses.join(", ")}` : "";
|
|
285
|
+
return `${notice.count} event(s)${from} fired since ${since} and were NOT sent \u2014 this machine is not paired.`;
|
|
286
|
+
}
|
|
287
|
+
function asRecord(value) {
|
|
288
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
289
|
+
}
|
|
290
|
+
function birdyBeepHookCount(path, events, isBirdyBeepEntry) {
|
|
291
|
+
if (!existsSync(path)) return 0;
|
|
292
|
+
let parsed;
|
|
293
|
+
try {
|
|
294
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const hooks = asRecord(asRecord(parsed)["hooks"]);
|
|
299
|
+
let present = 0;
|
|
300
|
+
for (const event of events) {
|
|
301
|
+
const entries = hooks[event];
|
|
302
|
+
if (Array.isArray(entries) && entries.some(isBirdyBeepEntry)) present += 1;
|
|
303
|
+
}
|
|
304
|
+
return present;
|
|
305
|
+
}
|
|
306
|
+
async function cursorBridgeOnly(opts = {}) {
|
|
307
|
+
const home = opts.home ?? homedir();
|
|
308
|
+
const detection = await (opts.detect ?? (() => detectCursor({ home })))();
|
|
309
|
+
if (!detection.detected) return false;
|
|
310
|
+
const claude = birdyBeepHookCount(claudeSettingsPath(home), CLAUDE_HOOK_EVENTS, isClaudeEntry);
|
|
311
|
+
if (claude === null || claude === 0) return false;
|
|
312
|
+
return birdyBeepHookCount(cursorHooksPath(home), CURSOR_HOOK_EVENTS, isCursorEntry) === 0;
|
|
313
|
+
}
|
|
314
|
+
function filteredActivity() {
|
|
315
|
+
return readFilteredActivity();
|
|
316
|
+
}
|
|
317
|
+
function describeFilteredActivity(activity) {
|
|
318
|
+
const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
|
|
319
|
+
const since = new Date(activity.firstAt).toISOString();
|
|
320
|
+
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""} \u2014 hooks are firing; these types never beep, so they are not sent.`;
|
|
321
|
+
}
|
|
322
|
+
function machineIdentity() {
|
|
323
|
+
return getMachineIdentity();
|
|
324
|
+
}
|
|
325
|
+
var CONFIGURED_STATUSES = /* @__PURE__ */ new Set([
|
|
326
|
+
"installed",
|
|
327
|
+
"needs_trust",
|
|
328
|
+
"needs_restart"
|
|
329
|
+
]);
|
|
330
|
+
function gradeSurfaces(surfaces, status, observation) {
|
|
331
|
+
const builds = Object.values(observation?.builds ?? {});
|
|
332
|
+
const configured = CONFIGURED_STATUSES.has(status);
|
|
333
|
+
const claimedByKind = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const s of surfaces) {
|
|
335
|
+
if (s.version === void 0) continue;
|
|
336
|
+
const versions = claimedByKind.get(s.kind) ?? /* @__PURE__ */ new Set();
|
|
337
|
+
versions.add(s.version);
|
|
338
|
+
claimedByKind.set(s.kind, versions);
|
|
339
|
+
}
|
|
340
|
+
const graded = surfaces.map((surface) => {
|
|
341
|
+
const exact = builds.filter(
|
|
342
|
+
(b) => b.surface === surface.kind && b.version === surface.version && surface.version !== void 0
|
|
343
|
+
);
|
|
344
|
+
let soleOfKind = [];
|
|
345
|
+
if (surface.version === void 0) {
|
|
346
|
+
const sameKindVersionless = surfaces.filter(
|
|
347
|
+
(s) => s.version === void 0 && s.kind === surface.kind
|
|
348
|
+
);
|
|
349
|
+
const unclaimed = builds.filter(
|
|
350
|
+
(b) => b.surface === surface.kind && !(claimedByKind.get(surface.kind)?.has(b.version) ?? false)
|
|
351
|
+
);
|
|
352
|
+
if (unclaimed.length === 1 && sameKindVersionless.length === 1) soleOfKind = unclaimed;
|
|
353
|
+
}
|
|
354
|
+
const unattributed = surface.version === void 0 ? [] : builds.filter((b) => b.surface === "unknown" && b.version === surface.version);
|
|
355
|
+
const sharesVersion = surface.version !== void 0 && surfaces.some((s) => s !== surface && s.version === surface.version);
|
|
356
|
+
const ambiguous = unattributed.length > 0 && sharesVersion;
|
|
357
|
+
const matched = [...exact, ...soleOfKind, ...ambiguous ? [] : unattributed];
|
|
358
|
+
const events = matched.reduce((total, b) => total + b.count, 0);
|
|
359
|
+
const lastAt = matched.reduce(
|
|
360
|
+
(latest, b) => latest === void 0 || b.lastAt > latest ? b.lastAt : latest,
|
|
361
|
+
void 0
|
|
362
|
+
);
|
|
363
|
+
const observedVersion = surface.version === void 0 ? soleOfKind[0]?.version : void 0;
|
|
364
|
+
return {
|
|
365
|
+
surface,
|
|
366
|
+
events,
|
|
367
|
+
ambiguous,
|
|
368
|
+
...lastAt !== void 0 ? { lastAt } : {},
|
|
369
|
+
...observedVersion !== void 0 ? { observedVersion } : {}
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
const anyActive = graded.some((g) => g.events > 0 && g.surface.shadowed !== true);
|
|
373
|
+
return graded.map(({ ambiguous, ...g }) => ({
|
|
374
|
+
...g,
|
|
375
|
+
coverage: !configured ? "uncovered" : g.events > 0 ? "active" : anyActive && g.surface.shadowed !== true && !ambiguous ? "uncovered" : "wired"
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
async function gatherSurfaces(adapters, options = {}) {
|
|
379
|
+
const observed = readObservedBuilds(options.observedBuilds ?? {});
|
|
380
|
+
return Promise.all(
|
|
381
|
+
adapters.map(async (adapter) => {
|
|
382
|
+
const observation = observed[adapter.id];
|
|
383
|
+
const base4 = {
|
|
384
|
+
harness: adapter.id,
|
|
385
|
+
displayName: adapter.displayName,
|
|
386
|
+
unversionedEvents: observation?.unversioned ?? 0
|
|
387
|
+
};
|
|
388
|
+
try {
|
|
389
|
+
const [detection, status] = await Promise.all([adapter.detect(), adapter.status()]);
|
|
390
|
+
return {
|
|
391
|
+
...base4,
|
|
392
|
+
status,
|
|
393
|
+
surfaces: detection.detected ? gradeSurfaces(detection.surfaces ?? [], status, observation) : []
|
|
394
|
+
};
|
|
395
|
+
} catch {
|
|
396
|
+
return { ...base4, status: "unknown", surfaces: [] };
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
function describeSurface(state) {
|
|
402
|
+
const version = state.surface.version ?? state.observedVersion;
|
|
403
|
+
return version !== void 0 ? `${state.surface.label} ${version}` : state.surface.label;
|
|
404
|
+
}
|
|
405
|
+
function describeSurfaceCoverage(state, group) {
|
|
406
|
+
if (state.coverage === "active") {
|
|
407
|
+
const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
|
|
408
|
+
return `covered \u2014 ${state.events} event(s) from this build${last}`;
|
|
409
|
+
}
|
|
410
|
+
if (state.coverage === "wired") {
|
|
411
|
+
return state.surface.shadowed === true ? `${group.displayName}'s hooks are installed and this build shares them, but another install comes first on PATH \u2014 it only runs if that order changes` : `${group.displayName}'s hooks are installed and this build shares them; nothing has fired from it yet`;
|
|
412
|
+
}
|
|
413
|
+
if (!CONFIGURED_STATUSES.has(group.status)) {
|
|
414
|
+
return `not covered \u2014 ${group.displayName} carries no BirdyBeep hooks, so this build cannot beep`;
|
|
415
|
+
}
|
|
416
|
+
const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
|
|
417
|
+
const delivering = active.join(", ");
|
|
418
|
+
const verb = active.length === 1 ? "is" : "are";
|
|
419
|
+
return `not covered \u2014 nothing has ever fired from this build, while ${delivering} ${verb} delivering through the same config`;
|
|
420
|
+
}
|
|
421
|
+
function installTarget(harness) {
|
|
422
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
423
|
+
}
|
|
424
|
+
function surfaceRemedy(state, group) {
|
|
425
|
+
if (state.coverage !== "uncovered") return void 0;
|
|
426
|
+
if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
|
|
427
|
+
const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
|
|
428
|
+
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 \u2014 it rewrites the entry with absolute paths that need no PATH at all.` : `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.`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/commands/agent.ts
|
|
228
432
|
var DEFAULT_ADAPTERS = [
|
|
229
433
|
claudeCodeAdapter,
|
|
230
434
|
codexAdapter,
|
|
@@ -253,7 +457,10 @@ function selectAdapters(target, adapters) {
|
|
|
253
457
|
if (id === void 0) return "unknown";
|
|
254
458
|
return adapters.filter((a) => a.id === id);
|
|
255
459
|
}
|
|
256
|
-
|
|
460
|
+
function installTarget2(harness) {
|
|
461
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
462
|
+
}
|
|
463
|
+
async function installSelected(adapters, ctx, tokenOptions) {
|
|
257
464
|
const target = ctx.args[0] ?? "all";
|
|
258
465
|
const selected = selectAdapters(target, adapters);
|
|
259
466
|
if (selected === "unknown") {
|
|
@@ -280,8 +487,9 @@ async function installSelected(adapters, ctx) {
|
|
|
280
487
|
requiredActions: result.requiredActions
|
|
281
488
|
});
|
|
282
489
|
}
|
|
490
|
+
const paired = await isPaired(tokenOptions);
|
|
283
491
|
if (ctx.flags.json) {
|
|
284
|
-
ctx.io.result({ target, results: outcomes });
|
|
492
|
+
ctx.io.result({ target, paired, results: outcomes });
|
|
285
493
|
return EXIT.OK;
|
|
286
494
|
}
|
|
287
495
|
if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {
|
|
@@ -289,13 +497,20 @@ async function installSelected(adapters, ctx) {
|
|
|
289
497
|
}
|
|
290
498
|
for (const o of outcomes) {
|
|
291
499
|
if (!o.detected) {
|
|
292
|
-
ctx.io.line(
|
|
500
|
+
ctx.io.line(
|
|
501
|
+
`\u2013 ${o.displayName}: not detected (skipped) \u2014 install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\``
|
|
502
|
+
);
|
|
293
503
|
continue;
|
|
294
504
|
}
|
|
295
505
|
const changed = (o.changedFiles ?? []).length > 0 ? o.changedFiles.join(", ") : "no changes";
|
|
296
506
|
ctx.io.line(`\u2713 ${o.displayName}: ${o.status} (${changed})`);
|
|
297
507
|
for (const action of o.requiredActions ?? []) ctx.io.line(` \u2192 ${action}`);
|
|
298
508
|
}
|
|
509
|
+
if (!paired) {
|
|
510
|
+
ctx.io.line(
|
|
511
|
+
"\u26A0 This machine is not paired, so nothing these hooks produce can reach you. Run `birdybeep setup` \u2014 it pairs, wires up every agent, and sends a test Beep."
|
|
512
|
+
);
|
|
513
|
+
}
|
|
299
514
|
return EXIT.OK;
|
|
300
515
|
}
|
|
301
516
|
async function uninstallSelected(adapters, ctx) {
|
|
@@ -334,6 +549,7 @@ async function uninstallSelected(adapters, ctx) {
|
|
|
334
549
|
}
|
|
335
550
|
function createAgentCommand(deps = {}) {
|
|
336
551
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS;
|
|
552
|
+
const tokenOptions = deps.tokenOptions ?? {};
|
|
337
553
|
return {
|
|
338
554
|
name: "agent",
|
|
339
555
|
summary: "Install or uninstall harness adapters",
|
|
@@ -343,7 +559,7 @@ function createAgentCommand(deps = {}) {
|
|
|
343
559
|
name: "install",
|
|
344
560
|
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
345
561
|
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
346
|
-
run: (ctx) => installSelected(adapters, ctx)
|
|
562
|
+
run: (ctx) => installSelected(adapters, ctx, tokenOptions)
|
|
347
563
|
},
|
|
348
564
|
{
|
|
349
565
|
name: "uninstall",
|
|
@@ -357,7 +573,8 @@ function createAgentCommand(deps = {}) {
|
|
|
357
573
|
|
|
358
574
|
// src/commands/doctor.ts
|
|
359
575
|
import {
|
|
360
|
-
createSender as defaultCreateSender
|
|
576
|
+
createSender as defaultCreateSender,
|
|
577
|
+
DEFAULT_QUEUE_MAX_ENTRIES as QUEUE_CAP
|
|
361
578
|
} from "@birdybeep/agent-core";
|
|
362
579
|
import { claudeCodeAdapter as claudeCodeAdapter2 } from "@birdybeep/claude-code";
|
|
363
580
|
import { codexAdapter as codexAdapter2 } from "@birdybeep/codex";
|
|
@@ -366,7 +583,7 @@ import { cursorAdapter as cursorAdapter2 } from "@birdybeep/cursor";
|
|
|
366
583
|
import { opencodeAdapter as opencodeAdapter2 } from "@birdybeep/opencode";
|
|
367
584
|
|
|
368
585
|
// src/config.ts
|
|
369
|
-
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
586
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
370
587
|
import { join } from "path";
|
|
371
588
|
import { birdyBeepConfigDir as birdyBeepConfigDir2 } from "@birdybeep/agent-core";
|
|
372
589
|
var DEFAULT_API_URL = "https://api.birdybeep.com";
|
|
@@ -376,7 +593,7 @@ function cliConfigPath() {
|
|
|
376
593
|
}
|
|
377
594
|
function readCliConfig() {
|
|
378
595
|
try {
|
|
379
|
-
const parsed = JSON.parse(
|
|
596
|
+
const parsed = JSON.parse(readFileSync2(cliConfigPath(), "utf8"));
|
|
380
597
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
381
598
|
} catch {
|
|
382
599
|
return {};
|
|
@@ -405,31 +622,6 @@ function resolveRegistryUrl() {
|
|
|
405
622
|
return DEFAULT_REGISTRY_URL;
|
|
406
623
|
}
|
|
407
624
|
|
|
408
|
-
// src/diagnostics.ts
|
|
409
|
-
import {
|
|
410
|
-
getMachineIdentity,
|
|
411
|
-
getToken,
|
|
412
|
-
LocalEventQueue
|
|
413
|
-
} from "@birdybeep/agent-core";
|
|
414
|
-
async function gatherIntegrations(adapters) {
|
|
415
|
-
return Promise.all(
|
|
416
|
-
adapters.map(async (a) => ({
|
|
417
|
-
harness: a.id,
|
|
418
|
-
displayName: a.displayName,
|
|
419
|
-
status: await a.status()
|
|
420
|
-
}))
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
async function isPaired(tokenOptions = {}) {
|
|
424
|
-
return await getToken(tokenOptions) !== null;
|
|
425
|
-
}
|
|
426
|
-
function localQueueDepth() {
|
|
427
|
-
return new LocalEventQueue().size();
|
|
428
|
-
}
|
|
429
|
-
function machineIdentity() {
|
|
430
|
-
return getMachineIdentity();
|
|
431
|
-
}
|
|
432
|
-
|
|
433
625
|
// src/commands/doctor.ts
|
|
434
626
|
var DEFAULT_ADAPTERS2 = [
|
|
435
627
|
claudeCodeAdapter2,
|
|
@@ -472,6 +664,32 @@ function createDoctorCommand(deps = {}) {
|
|
|
472
664
|
remedy: "Run `birdybeep pair` to pair this machine."
|
|
473
665
|
}
|
|
474
666
|
);
|
|
667
|
+
const unpaired = unpairedActivity();
|
|
668
|
+
if (unpaired !== null) {
|
|
669
|
+
checks.push({
|
|
670
|
+
name: "Events lost while unpaired",
|
|
671
|
+
ok: false,
|
|
672
|
+
detail: describeUnpairedActivity(unpaired),
|
|
673
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing are gone \u2014 a first pairing does not replay them."
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
677
|
+
checks.push({
|
|
678
|
+
name: "Approval beeps from Cursor",
|
|
679
|
+
ok: false,
|
|
680
|
+
detail: "Cursor is running your agent through the Claude Code hooks \u2014 that is why Cursor events arrive without a Cursor install. Its bridge drops Notification and PermissionRequest, so approvals never reach you.",
|
|
681
|
+
remedy: "Run `birdybeep agent install cursor` to get approval beeps from Cursor's own shell and MCP permission prompts. Keeping both installed is safe \u2014 duplicate events are collapsed."
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
const filtered = filteredActivity();
|
|
685
|
+
if (filtered !== null) {
|
|
686
|
+
checks.push({
|
|
687
|
+
name: "Local-only events (never notifiable)",
|
|
688
|
+
ok: true,
|
|
689
|
+
detail: describeFilteredActivity(filtered)
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
const surfaceGroups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
475
693
|
for (const adapter of adapters) {
|
|
476
694
|
const result = await adapter.doctor();
|
|
477
695
|
for (const c of result.checks) {
|
|
@@ -482,14 +700,26 @@ function createDoctorCommand(deps = {}) {
|
|
|
482
700
|
...c.remedy !== void 0 ? { remedy: c.remedy } : {}
|
|
483
701
|
});
|
|
484
702
|
}
|
|
703
|
+
const group = surfaceGroups.find((g) => g.harness === adapter.id);
|
|
704
|
+
if (group === void 0) continue;
|
|
705
|
+
for (const state of group.surfaces) {
|
|
706
|
+
const remedy = surfaceRemedy(state, group);
|
|
707
|
+
checks.push({
|
|
708
|
+
name: `${adapter.displayName}: ${describeSurface(state)}`,
|
|
709
|
+
ok: state.coverage !== "uncovered",
|
|
710
|
+
detail: describeSurfaceCoverage(state, group),
|
|
711
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
485
714
|
}
|
|
486
715
|
const depthBefore = localQueueDepth();
|
|
487
716
|
const drain = await makeSender(apiUrl).drainNow();
|
|
488
717
|
const depthAfter = localQueueDepth();
|
|
718
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
489
719
|
checks.push({
|
|
490
720
|
name: "Local queue",
|
|
491
721
|
ok: true,
|
|
492
|
-
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
722
|
+
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `; ${overflowDropped} dropped by the ${QUEUE_CAP} entry cap` : "")
|
|
493
723
|
});
|
|
494
724
|
const reachable = await probeNetwork(apiUrl);
|
|
495
725
|
checks.push(
|
|
@@ -505,7 +735,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
505
735
|
ctx.io.result({
|
|
506
736
|
ok,
|
|
507
737
|
checks,
|
|
508
|
-
|
|
738
|
+
surfaces: surfaceGroups,
|
|
739
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
740
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
741
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
509
742
|
});
|
|
510
743
|
} else {
|
|
511
744
|
for (const c of checks) {
|
|
@@ -530,13 +763,14 @@ import {
|
|
|
530
763
|
resolveOnPath
|
|
531
764
|
} from "@birdybeep/agent-core";
|
|
532
765
|
import { isClaudeCodeHookPayload, runClaudeHook } from "@birdybeep/claude-code";
|
|
533
|
-
import { runCodexHook } from "@birdybeep/codex";
|
|
766
|
+
import { isCodexHookPayload, runCodexHook } from "@birdybeep/codex";
|
|
534
767
|
import {
|
|
535
768
|
isCopilotHookEventName,
|
|
769
|
+
isCopilotHookPayload,
|
|
536
770
|
runCopilotHook
|
|
537
771
|
} from "@birdybeep/copilot";
|
|
538
772
|
import { isCursorHookEventName, isCursorHookPayload, runCursorHook } from "@birdybeep/cursor";
|
|
539
|
-
import { runOpenCodeHook } from "@birdybeep/opencode";
|
|
773
|
+
import { isOpenCodeEventPayload, runOpenCodeHook } from "@birdybeep/opencode";
|
|
540
774
|
var RUNNERS = {
|
|
541
775
|
claude: runClaudeHook,
|
|
542
776
|
codex: runCodexHook,
|
|
@@ -572,17 +806,31 @@ function resolveHookHarness(harness, payload) {
|
|
|
572
806
|
return harness === "claude" && isCursorHookPayload(payload) ? "cursor" : harness;
|
|
573
807
|
}
|
|
574
808
|
function recognizesPayload(harness, payload) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
809
|
+
switch (harness) {
|
|
810
|
+
case "claude":
|
|
811
|
+
return isClaudeCodeHookPayload(payload);
|
|
812
|
+
case "cursor":
|
|
813
|
+
return isCursorHookEventName(asRecord2(payload)["hook_event_name"]);
|
|
814
|
+
case "codex":
|
|
815
|
+
return isCodexHookPayload(payload);
|
|
816
|
+
case "opencode":
|
|
817
|
+
return isOpenCodeEventPayload(payload);
|
|
818
|
+
case "copilot":
|
|
819
|
+
return isCopilotHookPayload(payload);
|
|
820
|
+
}
|
|
578
821
|
}
|
|
579
|
-
function
|
|
822
|
+
function asRecord2(value) {
|
|
580
823
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
581
824
|
}
|
|
582
|
-
function
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
825
|
+
function describeDiscriminator(payload) {
|
|
826
|
+
const record = asRecord2(payload);
|
|
827
|
+
for (const field of ["hook_event_name", "type"]) {
|
|
828
|
+
const value = record[field];
|
|
829
|
+
if (typeof value !== "string") continue;
|
|
830
|
+
const capped = value.length > 64 ? `${value.slice(0, 63)}\u2026` : value;
|
|
831
|
+
return `${field} ${JSON.stringify(capped)}`;
|
|
832
|
+
}
|
|
833
|
+
return "the payload";
|
|
586
834
|
}
|
|
587
835
|
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
588
836
|
const handler = resolveHookHarness(harness, payload);
|
|
@@ -677,10 +925,16 @@ function createHookCommand(deps = {}) {
|
|
|
677
925
|
return EXIT.OK;
|
|
678
926
|
}
|
|
679
927
|
const copilotEventName = harness === "copilot" && isCopilotHookEventName(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
680
|
-
|
|
928
|
+
if (harness === "copilot" && copilotEventName === void 0) {
|
|
929
|
+
ctx.io.errline(
|
|
930
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")} \u2014 nothing was sent.`
|
|
931
|
+
);
|
|
932
|
+
return EXIT.USAGE;
|
|
933
|
+
}
|
|
934
|
+
const read = await withTimeout(
|
|
681
935
|
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
682
936
|
stdinTimeoutMs,
|
|
683
|
-
|
|
937
|
+
null
|
|
684
938
|
);
|
|
685
939
|
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
686
940
|
if (notifyStdinFile !== void 0 && dirname(notifyStdinFile) === tmpdir() && basename(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
@@ -689,30 +943,57 @@ function createHookCommand(deps = {}) {
|
|
|
689
943
|
} catch {
|
|
690
944
|
}
|
|
691
945
|
}
|
|
946
|
+
const drop = (reason, detail) => {
|
|
947
|
+
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
948
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail} \u2014 nothing was sent.`);
|
|
949
|
+
return EXIT.ERROR;
|
|
950
|
+
};
|
|
951
|
+
if (read === null) {
|
|
952
|
+
return drop(
|
|
953
|
+
"stdin-timeout",
|
|
954
|
+
`timed out after ${stdinTimeoutMs}ms waiting for the payload on stdin`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
const raw = read;
|
|
958
|
+
if (raw.trim().length === 0) {
|
|
959
|
+
return drop("empty-payload", "the payload was empty");
|
|
960
|
+
}
|
|
692
961
|
let payload;
|
|
693
962
|
try {
|
|
694
963
|
payload = JSON.parse(raw);
|
|
695
964
|
} catch {
|
|
696
|
-
|
|
697
|
-
return EXIT.OK;
|
|
965
|
+
return drop("invalid-json", `the ${raw.length}-byte payload is not valid JSON`);
|
|
698
966
|
}
|
|
699
|
-
const sender = makeSender(resolveApiUrl());
|
|
700
967
|
const handler = resolveHookHarness(harness, payload);
|
|
968
|
+
const routedFrom = handler !== harness ? { routedFrom: harness } : {};
|
|
969
|
+
if (!recognizesPayload(handler, payload)) {
|
|
970
|
+
ctx.io.result({
|
|
971
|
+
harness: handler,
|
|
972
|
+
...routedFrom,
|
|
973
|
+
outcome: "skipped",
|
|
974
|
+
reason: "foreign-payload"
|
|
975
|
+
});
|
|
976
|
+
const article = handler === "opencode" ? "an" : "a";
|
|
977
|
+
ctx.io.errline(
|
|
978
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
|
|
979
|
+
);
|
|
980
|
+
return EXIT.ERROR;
|
|
981
|
+
}
|
|
982
|
+
const sender = makeSender(resolveApiUrl());
|
|
701
983
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
702
984
|
ctx.io.result({
|
|
703
985
|
harness: handler,
|
|
704
|
-
...
|
|
986
|
+
...routedFrom,
|
|
705
987
|
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
706
988
|
outcome: result.outcome,
|
|
707
989
|
eventType: result.eventType,
|
|
708
990
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
709
991
|
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
710
992
|
});
|
|
711
|
-
if (result.outcome === "
|
|
993
|
+
if (result.outcome === "unpaired") {
|
|
712
994
|
ctx.io.errline(
|
|
713
|
-
|
|
995
|
+
"birdybeep: this machine is not paired \u2014 the event was not sent and was not queued. Run `birdybeep pair` (or `birdybeep doctor` to see how many events this has cost)."
|
|
714
996
|
);
|
|
715
|
-
return EXIT.ERROR;
|
|
716
997
|
}
|
|
717
998
|
return EXIT.OK;
|
|
718
999
|
}
|
|
@@ -773,9 +1054,12 @@ function createUnpairCommand(deps = {}) {
|
|
|
773
1054
|
// src/commands/pair.ts
|
|
774
1055
|
import { closeSync as closeSync2, openSync as openSync2 } from "fs";
|
|
775
1056
|
import {
|
|
1057
|
+
clearUnpairedNotice,
|
|
776
1058
|
deriveCodeChallengeS256,
|
|
777
1059
|
generateCodeVerifier,
|
|
778
|
-
getMachineIdentity as
|
|
1060
|
+
getMachineIdentity as getMachineIdentity3,
|
|
1061
|
+
getToken as getToken3,
|
|
1062
|
+
LocalEventQueue as LocalEventQueue2,
|
|
779
1063
|
setToken
|
|
780
1064
|
} from "@birdybeep/agent-core";
|
|
781
1065
|
import { renderUnicodeCompact } from "uqr";
|
|
@@ -854,6 +1138,280 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
854
1138
|
return { status: "error", code: code ?? "unknown", message, retryable: true };
|
|
855
1139
|
}
|
|
856
1140
|
|
|
1141
|
+
// src/commands/setup.ts
|
|
1142
|
+
import { claudeCodeAdapter as claudeCodeAdapter3 } from "@birdybeep/claude-code";
|
|
1143
|
+
import { codexAdapter as codexAdapter3 } from "@birdybeep/codex";
|
|
1144
|
+
import { copilotAdapter as copilotAdapter3 } from "@birdybeep/copilot";
|
|
1145
|
+
import { cursorAdapter as cursorAdapter3 } from "@birdybeep/cursor";
|
|
1146
|
+
import { opencodeAdapter as opencodeAdapter3 } from "@birdybeep/opencode";
|
|
1147
|
+
|
|
1148
|
+
// src/commands/test.ts
|
|
1149
|
+
import { randomUUID } from "crypto";
|
|
1150
|
+
import {
|
|
1151
|
+
createSender as defaultCreateSender3,
|
|
1152
|
+
getMachineIdentity as getMachineIdentity2,
|
|
1153
|
+
normalizeEvent
|
|
1154
|
+
} from "@birdybeep/agent-core";
|
|
1155
|
+
function buildTestEvent(opts = {}) {
|
|
1156
|
+
const machine = getMachineIdentity2();
|
|
1157
|
+
return normalizeEvent(
|
|
1158
|
+
{
|
|
1159
|
+
event_type: "test",
|
|
1160
|
+
status: "running",
|
|
1161
|
+
harness: "claude_code",
|
|
1162
|
+
// schema requires a harness; the "test" type distinguishes it
|
|
1163
|
+
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1164
|
+
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1165
|
+
source_session_id: `birdybeep-cli-test-${randomUUID()}`,
|
|
1166
|
+
machine: { label: machine.label, os: machine.os },
|
|
1167
|
+
workspace: { cwd: process.cwd() },
|
|
1168
|
+
title: "BirdyBeep test event",
|
|
1169
|
+
body: "If you can see this, your machine is wired up correctly.",
|
|
1170
|
+
metadata: { test: true }
|
|
1171
|
+
},
|
|
1172
|
+
opts
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
function createTestCommand(deps = {}) {
|
|
1176
|
+
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender3(
|
|
1177
|
+
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1178
|
+
));
|
|
1179
|
+
return {
|
|
1180
|
+
name: "test",
|
|
1181
|
+
summary: "Send a test event end-to-end",
|
|
1182
|
+
usage: "birdybeep test [--json]",
|
|
1183
|
+
run: async (ctx) => {
|
|
1184
|
+
const event = buildTestEvent();
|
|
1185
|
+
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1186
|
+
if (ctx.flags.json) {
|
|
1187
|
+
ctx.io.result({
|
|
1188
|
+
outcome: result.outcome,
|
|
1189
|
+
...result.status ? { status: result.status } : {},
|
|
1190
|
+
...result.decision ? { decision: result.decision } : {}
|
|
1191
|
+
});
|
|
1192
|
+
} else if (result.outcome === "delivered") {
|
|
1193
|
+
if (result.decision === "notified" || result.decision === void 0) {
|
|
1194
|
+
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1195
|
+
} else if (result.decision === "suppressed") {
|
|
1196
|
+
ctx.io.line(
|
|
1197
|
+
"\u26A0 The backend accepted the test event but suppressed the push \u2014 this machine or integration is probably muted. Check mutes in the app, or run `birdybeep doctor`."
|
|
1198
|
+
);
|
|
1199
|
+
} else if (result.decision === "deduped") {
|
|
1200
|
+
ctx.io.line(
|
|
1201
|
+
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1202
|
+
);
|
|
1203
|
+
} else {
|
|
1204
|
+
ctx.io.line(
|
|
1205
|
+
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
} else if (result.outcome === "unpaired") {
|
|
1209
|
+
ctx.io.line(
|
|
1210
|
+
"\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
|
|
1211
|
+
);
|
|
1212
|
+
} else if (result.outcome === "queued") {
|
|
1213
|
+
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1214
|
+
} else {
|
|
1215
|
+
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1216
|
+
}
|
|
1217
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/commands/setup.ts
|
|
1223
|
+
var SETUP_ADAPTERS = [
|
|
1224
|
+
claudeCodeAdapter3,
|
|
1225
|
+
codexAdapter3,
|
|
1226
|
+
opencodeAdapter3,
|
|
1227
|
+
cursorAdapter3,
|
|
1228
|
+
copilotAdapter3
|
|
1229
|
+
];
|
|
1230
|
+
function failedSetupReport(error) {
|
|
1231
|
+
return {
|
|
1232
|
+
harnesses: [],
|
|
1233
|
+
counts: { installed: 0, needsYou: 0, notInstalled: 0, failed: 0 },
|
|
1234
|
+
error,
|
|
1235
|
+
ok: false
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
var PENDING_STATUSES = /* @__PURE__ */ new Set([
|
|
1239
|
+
"needs_trust",
|
|
1240
|
+
"needs_restart"
|
|
1241
|
+
]);
|
|
1242
|
+
async function installDetected(adapters) {
|
|
1243
|
+
const installs = [];
|
|
1244
|
+
for (const adapter of adapters) {
|
|
1245
|
+
try {
|
|
1246
|
+
const detection = await adapter.detect();
|
|
1247
|
+
if (!detection.detected) {
|
|
1248
|
+
installs.push({ adapter, detected: false });
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
installs.push({ adapter, detected: true, result: await adapter.install() });
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
installs.push({
|
|
1254
|
+
adapter,
|
|
1255
|
+
detected: true,
|
|
1256
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return installs;
|
|
1261
|
+
}
|
|
1262
|
+
function rowState(state, group, status) {
|
|
1263
|
+
if (status === "error" || group.status === "error") return "failed";
|
|
1264
|
+
if (status !== void 0 && PENDING_STATUSES.has(status)) return "needs you";
|
|
1265
|
+
if (state.coverage === "active") return "beeping";
|
|
1266
|
+
if (state.coverage === "wired") return "ready";
|
|
1267
|
+
return "not covered";
|
|
1268
|
+
}
|
|
1269
|
+
function buildHarnessReports(installs, groups) {
|
|
1270
|
+
return installs.map((install) => {
|
|
1271
|
+
const { adapter } = install;
|
|
1272
|
+
const base4 = {
|
|
1273
|
+
harness: adapter.id,
|
|
1274
|
+
displayName: adapter.displayName,
|
|
1275
|
+
detected: install.detected,
|
|
1276
|
+
...install.result !== void 0 ? {
|
|
1277
|
+
status: install.result.status,
|
|
1278
|
+
changedFiles: install.result.changedFiles,
|
|
1279
|
+
backupFiles: install.result.backupFiles
|
|
1280
|
+
} : {},
|
|
1281
|
+
...install.error !== void 0 ? { error: install.error } : {}
|
|
1282
|
+
};
|
|
1283
|
+
if (install.error !== void 0) {
|
|
1284
|
+
return {
|
|
1285
|
+
...base4,
|
|
1286
|
+
actions: [
|
|
1287
|
+
`${adapter.displayName} could not be set up: ${install.error}`,
|
|
1288
|
+
`Run \`birdybeep agent install ${installTarget2(adapter.id)}\` to retry it on its own.`
|
|
1289
|
+
],
|
|
1290
|
+
rows: [{ harness: adapter.id, displayName: adapter.displayName, state: "failed" }]
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
if (!install.detected) {
|
|
1294
|
+
return {
|
|
1295
|
+
...base4,
|
|
1296
|
+
actions: [],
|
|
1297
|
+
rows: [
|
|
1298
|
+
{
|
|
1299
|
+
harness: adapter.id,
|
|
1300
|
+
displayName: adapter.displayName,
|
|
1301
|
+
state: "not installed"
|
|
1302
|
+
}
|
|
1303
|
+
]
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
const group = groups.find((g) => g.harness === adapter.id);
|
|
1307
|
+
const status = install.result?.status;
|
|
1308
|
+
const surfaces = group?.surfaces ?? [];
|
|
1309
|
+
const rows = group === void 0 || surfaces.length === 0 ? [
|
|
1310
|
+
{
|
|
1311
|
+
harness: adapter.id,
|
|
1312
|
+
displayName: adapter.displayName,
|
|
1313
|
+
state: status !== void 0 && PENDING_STATUSES.has(status) ? "needs you" : "ready"
|
|
1314
|
+
}
|
|
1315
|
+
] : surfaces.map((state) => {
|
|
1316
|
+
const graded = rowState(state, group, status);
|
|
1317
|
+
const remedy = surfaceRemedy(state, group) ?? (graded === "not covered" ? `${adapter.displayName} carries no BirdyBeep hooks \u2014 re-run \`birdybeep agent install ${installTarget2(adapter.id)}\` from a shell where \`birdybeep\` resolves.` : void 0);
|
|
1318
|
+
return {
|
|
1319
|
+
harness: adapter.id,
|
|
1320
|
+
displayName: adapter.displayName,
|
|
1321
|
+
build: describeSurface(state),
|
|
1322
|
+
kind: state.surface.kind,
|
|
1323
|
+
state: graded,
|
|
1324
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
1325
|
+
};
|
|
1326
|
+
});
|
|
1327
|
+
return { ...base4, actions: [...install.result?.requiredActions ?? []], rows };
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
function pad(text, width) {
|
|
1331
|
+
return text.length >= width ? text : text + " ".repeat(width - text.length);
|
|
1332
|
+
}
|
|
1333
|
+
var MARKS = {
|
|
1334
|
+
beeping: "\u2713",
|
|
1335
|
+
ready: "\u2713",
|
|
1336
|
+
"needs you": "!",
|
|
1337
|
+
"not covered": "\u2717",
|
|
1338
|
+
"not installed": "\u2013",
|
|
1339
|
+
failed: "\u2717"
|
|
1340
|
+
};
|
|
1341
|
+
function renderCoverageTable(reports) {
|
|
1342
|
+
const rows = reports.flatMap((r) => r.rows);
|
|
1343
|
+
const nameWidth = Math.max(7, ...rows.map((r) => r.displayName.length));
|
|
1344
|
+
const buildWidth = Math.max(5, ...rows.map((r) => (r.build ?? "\u2014").length));
|
|
1345
|
+
const lines = ["coverage", ` ${pad("harness", nameWidth)} ${pad("build", buildWidth)} state`];
|
|
1346
|
+
for (const report of reports) {
|
|
1347
|
+
for (const row of report.rows) {
|
|
1348
|
+
lines.push(
|
|
1349
|
+
`${MARKS[row.state]} ${pad(row.displayName, nameWidth)} ${pad(row.build ?? "\u2014", buildWidth)} ${row.state}`
|
|
1350
|
+
);
|
|
1351
|
+
if (row.remedy !== void 0) lines.push(` \u2192 ${row.remedy}`);
|
|
1352
|
+
}
|
|
1353
|
+
for (const action of report.actions) lines.push(` \u2192 ${action}`);
|
|
1354
|
+
}
|
|
1355
|
+
return lines;
|
|
1356
|
+
}
|
|
1357
|
+
function describeMissing(reports) {
|
|
1358
|
+
const missing = reports.filter((r) => !r.detected && r.error === void 0);
|
|
1359
|
+
if (missing.length === 0) return [];
|
|
1360
|
+
const names = missing.map((r) => r.displayName);
|
|
1361
|
+
if (missing.length === reports.length) {
|
|
1362
|
+
return [
|
|
1363
|
+
"No supported coding agent is installed on this machine, so there was nothing to wire up.",
|
|
1364
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again \u2014 pairing is already done, so it picks up from here.`
|
|
1365
|
+
];
|
|
1366
|
+
}
|
|
1367
|
+
return [
|
|
1368
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again to wire it up.`
|
|
1369
|
+
];
|
|
1370
|
+
}
|
|
1371
|
+
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
1372
|
+
const adapters = deps.adapters ?? [...SETUP_ADAPTERS];
|
|
1373
|
+
const installs = await installDetected(adapters);
|
|
1374
|
+
const groups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1375
|
+
const reports = buildHarnessReports(installs, groups);
|
|
1376
|
+
ctx.io.line("");
|
|
1377
|
+
for (const line of renderCoverageTable(reports)) ctx.io.line(line);
|
|
1378
|
+
const missing = describeMissing(reports);
|
|
1379
|
+
if (missing.length > 0) {
|
|
1380
|
+
ctx.io.line("");
|
|
1381
|
+
for (const line of missing) ctx.io.line(line);
|
|
1382
|
+
}
|
|
1383
|
+
const counts = {
|
|
1384
|
+
installed: reports.filter((r) => r.detected && r.error === void 0).length,
|
|
1385
|
+
needsYou: reports.filter((r) => r.rows.some((row) => row.state === "needs you")).length,
|
|
1386
|
+
notInstalled: reports.filter((r) => !r.detected && r.error === void 0).length,
|
|
1387
|
+
// A row that graded `failed` counts too: an adapter that returned status "error" never threw,
|
|
1388
|
+
// so counting only thrown errors would report a clean run over a harness that is broken.
|
|
1389
|
+
failed: reports.filter((r) => r.error !== void 0 || r.rows.some((x) => x.state === "failed")).length
|
|
1390
|
+
};
|
|
1391
|
+
let beep;
|
|
1392
|
+
let beepOk = true;
|
|
1393
|
+
if (options.sendTest) {
|
|
1394
|
+
ctx.io.line("");
|
|
1395
|
+
const command = createTestCommand({
|
|
1396
|
+
...deps.createSender !== void 0 ? { createSender: deps.createSender } : {},
|
|
1397
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {}
|
|
1398
|
+
});
|
|
1399
|
+
const beepIo = {
|
|
1400
|
+
...ctx.io,
|
|
1401
|
+
result: (value) => {
|
|
1402
|
+
beep = value;
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
beepOk = await command.run?.({ args: [], flags: ctx.flags, io: beepIo }) === 0;
|
|
1406
|
+
}
|
|
1407
|
+
return {
|
|
1408
|
+
harnesses: reports,
|
|
1409
|
+
counts,
|
|
1410
|
+
...beep !== void 0 ? { beep } : {},
|
|
1411
|
+
ok: counts.failed === 0 && beepOk
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
857
1415
|
// src/commands/pair.ts
|
|
858
1416
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
859
1417
|
var HEARTBEAT_MS = 15e3;
|
|
@@ -861,11 +1419,15 @@ function renderQrMatrix(qrPayload) {
|
|
|
861
1419
|
return renderUnicodeCompact(qrPayload, { border: 2 });
|
|
862
1420
|
}
|
|
863
1421
|
function parsePairFlags(args) {
|
|
864
|
-
const flags = { yes: false };
|
|
1422
|
+
const flags = { yes: false, noInstall: false, noTest: false };
|
|
865
1423
|
for (let i = 0; i < args.length; i += 1) {
|
|
866
1424
|
const token = args[i] ?? "";
|
|
867
1425
|
if (token === "--yes" || token === "-y") {
|
|
868
1426
|
flags.yes = true;
|
|
1427
|
+
} else if (token === "--no-install") {
|
|
1428
|
+
flags.noInstall = true;
|
|
1429
|
+
} else if (token === "--no-test") {
|
|
1430
|
+
flags.noTest = true;
|
|
869
1431
|
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
870
1432
|
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
871
1433
|
const value = inline ?? args[++i];
|
|
@@ -985,7 +1547,18 @@ async function promptForAnswer(question, on) {
|
|
|
985
1547
|
input.once?.("error", () => done(""));
|
|
986
1548
|
});
|
|
987
1549
|
}
|
|
988
|
-
function
|
|
1550
|
+
async function runSetupChain(ctx, deps, flags) {
|
|
1551
|
+
try {
|
|
1552
|
+
return await runHarnessSetup(ctx, { sendTest: !flags.noTest }, deps);
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1555
|
+
ctx.io.errline(
|
|
1556
|
+
`This machine is paired, but wiring up your coding agents failed: ${message}. Run \`birdybeep agent install all\` to do it on its own, then \`birdybeep doctor\`.`
|
|
1557
|
+
);
|
|
1558
|
+
return failedSetupReport(message);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function createPairingCommand(verb, deps = {}) {
|
|
989
1562
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
990
1563
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
991
1564
|
const clock = deps.now ?? (() => Date.now());
|
|
@@ -998,9 +1571,10 @@ function createPairCommand(deps = {}) {
|
|
|
998
1571
|
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
999
1572
|
});
|
|
1000
1573
|
return {
|
|
1001
|
-
name:
|
|
1002
|
-
summary:
|
|
1003
|
-
usage:
|
|
1574
|
+
name: verb.name,
|
|
1575
|
+
summary: verb.summary,
|
|
1576
|
+
usage: verb.usage,
|
|
1577
|
+
...verb.gettingStarted !== void 0 ? { gettingStarted: verb.gettingStarted } : {},
|
|
1004
1578
|
options: [
|
|
1005
1579
|
{
|
|
1006
1580
|
flag: "--yes",
|
|
@@ -1011,16 +1585,41 @@ function createPairCommand(deps = {}) {
|
|
|
1011
1585
|
flag: "--expect-email",
|
|
1012
1586
|
value: "<addr>",
|
|
1013
1587
|
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
flag: "--no-install",
|
|
1591
|
+
summary: "Stop after pairing \u2014 don't detect or wire up any coding agent"
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
flag: "--no-test",
|
|
1595
|
+
summary: "Don't send the test Beep at the end"
|
|
1014
1596
|
}
|
|
1015
1597
|
],
|
|
1016
1598
|
run: async (ctx) => {
|
|
1017
1599
|
const pairFlags = parsePairFlags(ctx.args);
|
|
1018
1600
|
if (pairFlags.error !== void 0) {
|
|
1019
|
-
ctx.io.errline(`birdybeep
|
|
1601
|
+
ctx.io.errline(`birdybeep ${verb.name}: ${pairFlags.error}.`);
|
|
1020
1602
|
return EXIT.USAGE;
|
|
1021
1603
|
}
|
|
1604
|
+
const chain = deps.setup === false || pairFlags.noInstall ? void 0 : deps.setup ?? {};
|
|
1605
|
+
const setupDeps = {
|
|
1606
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {},
|
|
1607
|
+
...chain
|
|
1608
|
+
};
|
|
1609
|
+
if (verb.skipWhenPaired && await getToken3(deps.tokenOptions ?? {}) !== null) {
|
|
1610
|
+
ctx.io.line(
|
|
1611
|
+
chain !== void 0 ? "\u2713 Already paired \u2014 checking which coding agents are wired up." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1612
|
+
);
|
|
1613
|
+
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1614
|
+
ctx.io.result({
|
|
1615
|
+
paired: true,
|
|
1616
|
+
alreadyPaired: true,
|
|
1617
|
+
...report2 !== void 0 ? { setup: report2 } : {}
|
|
1618
|
+
});
|
|
1619
|
+
return report2 !== void 0 && !report2.ok ? EXIT.ERROR : EXIT.OK;
|
|
1620
|
+
}
|
|
1022
1621
|
const apiUrl = resolveApiUrl();
|
|
1023
|
-
const identity =
|
|
1622
|
+
const identity = getMachineIdentity3();
|
|
1024
1623
|
const codeVerifier = generateCodeVerifier();
|
|
1025
1624
|
const codeChallenge = deriveCodeChallengeS256(codeVerifier);
|
|
1026
1625
|
const start = await pairStart(
|
|
@@ -1120,19 +1719,50 @@ function createPairCommand(deps = {}) {
|
|
|
1120
1719
|
}
|
|
1121
1720
|
await setToken(paired.machineToken, deps.tokenOptions ?? {});
|
|
1122
1721
|
writeCliConfig({ apiUrl });
|
|
1722
|
+
const discarded = new LocalEventQueue2().discardBefore(clock());
|
|
1723
|
+
clearUnpairedNotice();
|
|
1123
1724
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1124
|
-
|
|
1725
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing \u2014 you won't be beeped about them.` : "";
|
|
1726
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to wire up your coding agents." : "";
|
|
1727
|
+
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1728
|
+
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1729
|
+
ctx.io.result({
|
|
1125
1730
|
paired: true,
|
|
1126
1731
|
machineId: paired.machineId,
|
|
1127
|
-
|
|
1732
|
+
discardedPrePairingEvents: discarded,
|
|
1733
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1734
|
+
...report !== void 0 ? { setup: report } : {}
|
|
1128
1735
|
});
|
|
1129
|
-
return EXIT.OK;
|
|
1736
|
+
return report !== void 0 && !report.ok ? EXIT.ERROR : EXIT.OK;
|
|
1130
1737
|
}
|
|
1131
1738
|
};
|
|
1132
1739
|
}
|
|
1740
|
+
function createPairCommand(deps = {}) {
|
|
1741
|
+
return createPairingCommand(
|
|
1742
|
+
{
|
|
1743
|
+
name: "pair",
|
|
1744
|
+
summary: "Pair this machine and wire up every coding agent on it",
|
|
1745
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1746
|
+
skipWhenPaired: false
|
|
1747
|
+
},
|
|
1748
|
+
deps
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
function createSetupCommand(deps = {}) {
|
|
1752
|
+
return createPairingCommand(
|
|
1753
|
+
{
|
|
1754
|
+
name: "setup",
|
|
1755
|
+
summary: "Set up BirdyBeep here: pair, wire up every coding agent, test",
|
|
1756
|
+
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1757
|
+
gettingStarted: "Pair this machine, wire up every coding agent it finds, and send a test Beep.",
|
|
1758
|
+
skipWhenPaired: true
|
|
1759
|
+
},
|
|
1760
|
+
deps
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1133
1763
|
|
|
1134
1764
|
// src/commands/queue.ts
|
|
1135
|
-
import { LocalEventQueue as
|
|
1765
|
+
import { LocalEventQueue as LocalEventQueue3 } from "@birdybeep/agent-core";
|
|
1136
1766
|
function createQueueCommand() {
|
|
1137
1767
|
return {
|
|
1138
1768
|
name: "queue",
|
|
@@ -1144,7 +1774,7 @@ function createQueueCommand() {
|
|
|
1144
1774
|
summary: "Clear the local offline event queue (debug)",
|
|
1145
1775
|
usage: "birdybeep queue clear",
|
|
1146
1776
|
run: (ctx) => {
|
|
1147
|
-
const cleared = new
|
|
1777
|
+
const cleared = new LocalEventQueue3().clear();
|
|
1148
1778
|
ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });
|
|
1149
1779
|
return EXIT.OK;
|
|
1150
1780
|
}
|
|
@@ -1156,20 +1786,20 @@ function createQueueCommand() {
|
|
|
1156
1786
|
// src/commands/report-status.ts
|
|
1157
1787
|
import {
|
|
1158
1788
|
errorEnvelopeSchema as errorEnvelopeSchema2,
|
|
1159
|
-
getToken as
|
|
1789
|
+
getToken as getToken4,
|
|
1160
1790
|
integrationStatusResponseSchema
|
|
1161
1791
|
} from "@birdybeep/agent-core";
|
|
1162
|
-
import { CLAUDE_CODE_ADAPTER_VERSION, claudeCodeAdapter as
|
|
1163
|
-
import { CODEX_ADAPTER_VERSION, codexAdapter as
|
|
1164
|
-
import { COPILOT_ADAPTER_VERSION, copilotAdapter as
|
|
1165
|
-
import { CURSOR_ADAPTER_VERSION, cursorAdapter as
|
|
1166
|
-
import { OPENCODE_ADAPTER_VERSION, opencodeAdapter as
|
|
1792
|
+
import { CLAUDE_CODE_ADAPTER_VERSION, claudeCodeAdapter as claudeCodeAdapter4 } from "@birdybeep/claude-code";
|
|
1793
|
+
import { CODEX_ADAPTER_VERSION, codexAdapter as codexAdapter4 } from "@birdybeep/codex";
|
|
1794
|
+
import { COPILOT_ADAPTER_VERSION, copilotAdapter as copilotAdapter4 } from "@birdybeep/copilot";
|
|
1795
|
+
import { CURSOR_ADAPTER_VERSION, cursorAdapter as cursorAdapter4 } from "@birdybeep/cursor";
|
|
1796
|
+
import { OPENCODE_ADAPTER_VERSION, opencodeAdapter as opencodeAdapter4 } from "@birdybeep/opencode";
|
|
1167
1797
|
var DEFAULT_ADAPTERS3 = [
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1798
|
+
claudeCodeAdapter4,
|
|
1799
|
+
codexAdapter4,
|
|
1800
|
+
opencodeAdapter4,
|
|
1801
|
+
cursorAdapter4,
|
|
1802
|
+
copilotAdapter4
|
|
1173
1803
|
];
|
|
1174
1804
|
var ADAPTER_VERSIONS = {
|
|
1175
1805
|
claude_code: CLAUDE_CODE_ADAPTER_VERSION,
|
|
@@ -1199,7 +1829,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1199
1829
|
summary: "Internal: report integration status to the backend",
|
|
1200
1830
|
usage: "birdybeep report-status [--json]",
|
|
1201
1831
|
run: async (ctx) => {
|
|
1202
|
-
const token = await
|
|
1832
|
+
const token = await getToken4(deps.tokenOptions ?? {});
|
|
1203
1833
|
if (token === null) {
|
|
1204
1834
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
1205
1835
|
return EXIT.ERROR;
|
|
@@ -1262,23 +1892,23 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1262
1892
|
|
|
1263
1893
|
// src/commands/status.ts
|
|
1264
1894
|
import {
|
|
1265
|
-
createSender as
|
|
1895
|
+
createSender as defaultCreateSender4
|
|
1266
1896
|
} from "@birdybeep/agent-core";
|
|
1267
|
-
import { claudeCodeAdapter as
|
|
1268
|
-
import { codexAdapter as
|
|
1269
|
-
import { copilotAdapter as
|
|
1270
|
-
import { cursorAdapter as
|
|
1271
|
-
import { opencodeAdapter as
|
|
1897
|
+
import { claudeCodeAdapter as claudeCodeAdapter5 } from "@birdybeep/claude-code";
|
|
1898
|
+
import { codexAdapter as codexAdapter5 } from "@birdybeep/codex";
|
|
1899
|
+
import { copilotAdapter as copilotAdapter5 } from "@birdybeep/copilot";
|
|
1900
|
+
import { cursorAdapter as cursorAdapter5 } from "@birdybeep/cursor";
|
|
1901
|
+
import { opencodeAdapter as opencodeAdapter5 } from "@birdybeep/opencode";
|
|
1272
1902
|
var DEFAULT_ADAPTERS4 = [
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1903
|
+
claudeCodeAdapter5,
|
|
1904
|
+
codexAdapter5,
|
|
1905
|
+
opencodeAdapter5,
|
|
1906
|
+
cursorAdapter5,
|
|
1907
|
+
copilotAdapter5
|
|
1278
1908
|
];
|
|
1279
1909
|
function createStatusCommand(deps = {}) {
|
|
1280
1910
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
1281
|
-
const makeSender = deps.createSender ?? ((baseUrl) =>
|
|
1911
|
+
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender4(
|
|
1282
1912
|
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1283
1913
|
));
|
|
1284
1914
|
return {
|
|
@@ -1289,14 +1919,21 @@ function createStatusCommand(deps = {}) {
|
|
|
1289
1919
|
const machine = machineIdentity();
|
|
1290
1920
|
const paired = await isPaired(deps.tokenOptions ?? {});
|
|
1291
1921
|
const integrations = await gatherIntegrations(adapters);
|
|
1922
|
+
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1292
1923
|
const depthBefore = localQueueDepth();
|
|
1924
|
+
const unpaired = unpairedActivity();
|
|
1925
|
+
const filtered = filteredActivity();
|
|
1293
1926
|
const drain = await makeSender(resolveApiUrl()).drainNow();
|
|
1294
1927
|
const depthAfter = localQueueDepth();
|
|
1928
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
1295
1929
|
const report = {
|
|
1296
1930
|
machine,
|
|
1297
1931
|
paired,
|
|
1298
1932
|
integrations,
|
|
1299
|
-
|
|
1933
|
+
surfaces,
|
|
1934
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
1935
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
1936
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
1300
1937
|
};
|
|
1301
1938
|
if (ctx.flags.json) {
|
|
1302
1939
|
ctx.io.result(report);
|
|
@@ -1304,89 +1941,29 @@ function createStatusCommand(deps = {}) {
|
|
|
1304
1941
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1305
1942
|
ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
|
|
1306
1943
|
ctx.io.line("Integrations:");
|
|
1307
|
-
for (const i of integrations)
|
|
1944
|
+
for (const i of integrations) {
|
|
1945
|
+
ctx.io.line(` ${i.displayName}: ${i.status}`);
|
|
1946
|
+
const group = surfaces.find((g) => g.harness === i.harness);
|
|
1947
|
+
for (const state of group?.surfaces ?? []) {
|
|
1948
|
+
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
1949
|
+
ctx.io.line(` ${mark} ${describeSurface(state)} \u2014 ${state.coverage}`);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1308
1952
|
ctx.io.line(
|
|
1309
|
-
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
1953
|
+
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `, ${overflowDropped} dropped by the queue cap` : "")
|
|
1310
1954
|
);
|
|
1955
|
+
if (unpaired !== null) ctx.io.line(`\u26A0 Lost: ${describeUnpairedActivity(unpaired)}`);
|
|
1956
|
+
if (filtered !== null) ctx.io.line(`Local: ${describeFilteredActivity(filtered)}`);
|
|
1311
1957
|
}
|
|
1312
1958
|
return paired ? EXIT.OK : EXIT.ERROR;
|
|
1313
1959
|
}
|
|
1314
1960
|
};
|
|
1315
1961
|
}
|
|
1316
1962
|
|
|
1317
|
-
// src/commands/test.ts
|
|
1318
|
-
import { randomUUID } from "crypto";
|
|
1319
|
-
import {
|
|
1320
|
-
createSender as defaultCreateSender4,
|
|
1321
|
-
getMachineIdentity as getMachineIdentity3,
|
|
1322
|
-
normalizeEvent
|
|
1323
|
-
} from "@birdybeep/agent-core";
|
|
1324
|
-
function buildTestEvent(opts = {}) {
|
|
1325
|
-
const machine = getMachineIdentity3();
|
|
1326
|
-
return normalizeEvent(
|
|
1327
|
-
{
|
|
1328
|
-
event_type: "test",
|
|
1329
|
-
status: "running",
|
|
1330
|
-
harness: "claude_code",
|
|
1331
|
-
// schema requires a harness; the "test" type distinguishes it
|
|
1332
|
-
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1333
|
-
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1334
|
-
source_session_id: `birdybeep-cli-test-${randomUUID()}`,
|
|
1335
|
-
machine: { label: machine.label, os: machine.os },
|
|
1336
|
-
workspace: { cwd: process.cwd() },
|
|
1337
|
-
title: "BirdyBeep test event",
|
|
1338
|
-
body: "If you can see this, your machine is wired up correctly.",
|
|
1339
|
-
metadata: { test: true }
|
|
1340
|
-
},
|
|
1341
|
-
opts
|
|
1342
|
-
);
|
|
1343
|
-
}
|
|
1344
|
-
function createTestCommand(deps = {}) {
|
|
1345
|
-
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender4(
|
|
1346
|
-
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1347
|
-
));
|
|
1348
|
-
return {
|
|
1349
|
-
name: "test",
|
|
1350
|
-
summary: "Send a test event end-to-end",
|
|
1351
|
-
usage: "birdybeep test [--json]",
|
|
1352
|
-
run: async (ctx) => {
|
|
1353
|
-
const event = buildTestEvent();
|
|
1354
|
-
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1355
|
-
if (ctx.flags.json) {
|
|
1356
|
-
ctx.io.result({
|
|
1357
|
-
outcome: result.outcome,
|
|
1358
|
-
...result.status ? { status: result.status } : {},
|
|
1359
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1360
|
-
});
|
|
1361
|
-
} else if (result.outcome === "delivered") {
|
|
1362
|
-
if (result.decision === "notified" || result.decision === void 0) {
|
|
1363
|
-
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1364
|
-
} else if (result.decision === "suppressed") {
|
|
1365
|
-
ctx.io.line(
|
|
1366
|
-
"\u26A0 The backend accepted the test event but suppressed the push \u2014 this machine or integration is probably muted. Check mutes in the app, or run `birdybeep doctor`."
|
|
1367
|
-
);
|
|
1368
|
-
} else if (result.decision === "deduped") {
|
|
1369
|
-
ctx.io.line(
|
|
1370
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1371
|
-
);
|
|
1372
|
-
} else {
|
|
1373
|
-
ctx.io.line(
|
|
1374
|
-
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
} else if (result.outcome === "queued") {
|
|
1378
|
-
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1379
|
-
} else {
|
|
1380
|
-
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1381
|
-
}
|
|
1382
|
-
return result.outcome === "dropped" ? EXIT.ERROR : EXIT.OK;
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
1963
|
// src/commands.ts
|
|
1388
1964
|
function buildCommands() {
|
|
1389
1965
|
return [
|
|
1966
|
+
createSetupCommand(),
|
|
1390
1967
|
createPairCommand(),
|
|
1391
1968
|
createLogoutCommand(),
|
|
1392
1969
|
createUnpairCommand(),
|
|
@@ -1401,7 +1978,7 @@ function buildCommands() {
|
|
|
1401
1978
|
}
|
|
1402
1979
|
|
|
1403
1980
|
// src/update-check.ts
|
|
1404
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
1981
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1405
1982
|
import { join as join3 } from "path";
|
|
1406
1983
|
import { birdyBeepConfigDir as birdyBeepConfigDir3 } from "@birdybeep/agent-core";
|
|
1407
1984
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
@@ -1461,7 +2038,7 @@ function updateCachePath() {
|
|
|
1461
2038
|
}
|
|
1462
2039
|
function readUpdateCache() {
|
|
1463
2040
|
try {
|
|
1464
|
-
const parsed = JSON.parse(
|
|
2041
|
+
const parsed = JSON.parse(readFileSync3(updateCachePath(), "utf8"));
|
|
1465
2042
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1466
2043
|
const { checkedAt, latest } = parsed;
|
|
1467
2044
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1561,4 +2138,4 @@ export {
|
|
|
1561
2138
|
buildCommands,
|
|
1562
2139
|
runCli
|
|
1563
2140
|
};
|
|
1564
|
-
//# sourceMappingURL=chunk-
|
|
2141
|
+
//# sourceMappingURL=chunk-SAUSKJDH.js.map
|