@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
package/dist/bin.cjs
CHANGED
|
@@ -24,15 +24,191 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/commands/agent.ts
|
|
27
|
-
var
|
|
27
|
+
var import_claude_code2 = require("@birdybeep/claude-code");
|
|
28
28
|
var import_codex = require("@birdybeep/codex");
|
|
29
29
|
var import_copilot = require("@birdybeep/copilot");
|
|
30
|
-
var
|
|
30
|
+
var import_cursor2 = require("@birdybeep/cursor");
|
|
31
31
|
var import_opencode = require("@birdybeep/opencode");
|
|
32
32
|
|
|
33
|
-
// src/
|
|
33
|
+
// src/diagnostics.ts
|
|
34
34
|
var import_node_fs = require("fs");
|
|
35
|
+
var import_node_os = require("os");
|
|
35
36
|
var import_agent_core = require("@birdybeep/agent-core");
|
|
37
|
+
var import_claude_code = require("@birdybeep/claude-code");
|
|
38
|
+
var import_cursor = require("@birdybeep/cursor");
|
|
39
|
+
async function gatherIntegrations(adapters) {
|
|
40
|
+
return Promise.all(
|
|
41
|
+
adapters.map(async (a) => ({
|
|
42
|
+
harness: a.id,
|
|
43
|
+
displayName: a.displayName,
|
|
44
|
+
status: await a.status()
|
|
45
|
+
}))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
async function isPaired(tokenOptions = {}) {
|
|
49
|
+
return await (0, import_agent_core.getToken)(tokenOptions) !== null;
|
|
50
|
+
}
|
|
51
|
+
function localQueueDepth() {
|
|
52
|
+
return new import_agent_core.LocalEventQueue().size();
|
|
53
|
+
}
|
|
54
|
+
function localQueueOverflowDrops() {
|
|
55
|
+
return new import_agent_core.LocalEventQueue().overflowDropCount();
|
|
56
|
+
}
|
|
57
|
+
function unpairedActivity() {
|
|
58
|
+
return (0, import_agent_core.readUnpairedNotice)();
|
|
59
|
+
}
|
|
60
|
+
function describeUnpairedActivity(notice) {
|
|
61
|
+
const since = new Date(notice.firstAt).toISOString();
|
|
62
|
+
const from = notice.harnesses.length > 0 ? ` from ${notice.harnesses.join(", ")}` : "";
|
|
63
|
+
return `${notice.count} event(s)${from} fired since ${since} and were NOT sent \u2014 this machine is not paired.`;
|
|
64
|
+
}
|
|
65
|
+
function asRecord(value) {
|
|
66
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
67
|
+
}
|
|
68
|
+
function birdyBeepHookCount(path, events, isBirdyBeepEntry) {
|
|
69
|
+
if (!(0, import_node_fs.existsSync)(path)) return 0;
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const hooks = asRecord(asRecord(parsed)["hooks"]);
|
|
77
|
+
let present = 0;
|
|
78
|
+
for (const event of events) {
|
|
79
|
+
const entries = hooks[event];
|
|
80
|
+
if (Array.isArray(entries) && entries.some(isBirdyBeepEntry)) present += 1;
|
|
81
|
+
}
|
|
82
|
+
return present;
|
|
83
|
+
}
|
|
84
|
+
async function cursorBridgeOnly(opts = {}) {
|
|
85
|
+
const home = opts.home ?? (0, import_node_os.homedir)();
|
|
86
|
+
const detection = await (opts.detect ?? (() => (0, import_cursor.detectCursor)({ home })))();
|
|
87
|
+
if (!detection.detected) return false;
|
|
88
|
+
const claude = birdyBeepHookCount((0, import_claude_code.claudeSettingsPath)(home), import_claude_code.BIRDYBEEP_HOOK_EVENTS, import_claude_code.isBirdyBeepEntry);
|
|
89
|
+
if (claude === null || claude === 0) return false;
|
|
90
|
+
return birdyBeepHookCount((0, import_cursor.cursorHooksPath)(home), import_cursor.BIRDYBEEP_HOOK_EVENTS, import_cursor.isBirdyBeepEntry) === 0;
|
|
91
|
+
}
|
|
92
|
+
function filteredActivity() {
|
|
93
|
+
return (0, import_agent_core.readFilteredActivity)();
|
|
94
|
+
}
|
|
95
|
+
function describeFilteredActivity(activity) {
|
|
96
|
+
const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
|
|
97
|
+
const since = new Date(activity.firstAt).toISOString();
|
|
98
|
+
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""} \u2014 hooks are firing; these types never beep, so they are not sent.`;
|
|
99
|
+
}
|
|
100
|
+
function machineIdentity() {
|
|
101
|
+
return (0, import_agent_core.getMachineIdentity)();
|
|
102
|
+
}
|
|
103
|
+
var CONFIGURED_STATUSES = /* @__PURE__ */ new Set([
|
|
104
|
+
"installed",
|
|
105
|
+
"needs_trust",
|
|
106
|
+
"needs_restart"
|
|
107
|
+
]);
|
|
108
|
+
function gradeSurfaces(surfaces, status, observation) {
|
|
109
|
+
const builds = Object.values(observation?.builds ?? {});
|
|
110
|
+
const configured = CONFIGURED_STATUSES.has(status);
|
|
111
|
+
const claimedByKind = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const s of surfaces) {
|
|
113
|
+
if (s.version === void 0) continue;
|
|
114
|
+
const versions = claimedByKind.get(s.kind) ?? /* @__PURE__ */ new Set();
|
|
115
|
+
versions.add(s.version);
|
|
116
|
+
claimedByKind.set(s.kind, versions);
|
|
117
|
+
}
|
|
118
|
+
const graded = surfaces.map((surface) => {
|
|
119
|
+
const exact = builds.filter(
|
|
120
|
+
(b) => b.surface === surface.kind && b.version === surface.version && surface.version !== void 0
|
|
121
|
+
);
|
|
122
|
+
let soleOfKind = [];
|
|
123
|
+
if (surface.version === void 0) {
|
|
124
|
+
const sameKindVersionless = surfaces.filter(
|
|
125
|
+
(s) => s.version === void 0 && s.kind === surface.kind
|
|
126
|
+
);
|
|
127
|
+
const unclaimed = builds.filter(
|
|
128
|
+
(b) => b.surface === surface.kind && !(claimedByKind.get(surface.kind)?.has(b.version) ?? false)
|
|
129
|
+
);
|
|
130
|
+
if (unclaimed.length === 1 && sameKindVersionless.length === 1) soleOfKind = unclaimed;
|
|
131
|
+
}
|
|
132
|
+
const unattributed = surface.version === void 0 ? [] : builds.filter((b) => b.surface === "unknown" && b.version === surface.version);
|
|
133
|
+
const sharesVersion = surface.version !== void 0 && surfaces.some((s) => s !== surface && s.version === surface.version);
|
|
134
|
+
const ambiguous = unattributed.length > 0 && sharesVersion;
|
|
135
|
+
const matched = [...exact, ...soleOfKind, ...ambiguous ? [] : unattributed];
|
|
136
|
+
const events = matched.reduce((total, b) => total + b.count, 0);
|
|
137
|
+
const lastAt = matched.reduce(
|
|
138
|
+
(latest, b) => latest === void 0 || b.lastAt > latest ? b.lastAt : latest,
|
|
139
|
+
void 0
|
|
140
|
+
);
|
|
141
|
+
const observedVersion = surface.version === void 0 ? soleOfKind[0]?.version : void 0;
|
|
142
|
+
return {
|
|
143
|
+
surface,
|
|
144
|
+
events,
|
|
145
|
+
ambiguous,
|
|
146
|
+
...lastAt !== void 0 ? { lastAt } : {},
|
|
147
|
+
...observedVersion !== void 0 ? { observedVersion } : {}
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
const anyActive = graded.some((g) => g.events > 0 && g.surface.shadowed !== true);
|
|
151
|
+
return graded.map(({ ambiguous, ...g }) => ({
|
|
152
|
+
...g,
|
|
153
|
+
coverage: !configured ? "uncovered" : g.events > 0 ? "active" : anyActive && g.surface.shadowed !== true && !ambiguous ? "uncovered" : "wired"
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
async function gatherSurfaces(adapters, options = {}) {
|
|
157
|
+
const observed = (0, import_agent_core.readObservedBuilds)(options.observedBuilds ?? {});
|
|
158
|
+
return Promise.all(
|
|
159
|
+
adapters.map(async (adapter) => {
|
|
160
|
+
const observation = observed[adapter.id];
|
|
161
|
+
const base4 = {
|
|
162
|
+
harness: adapter.id,
|
|
163
|
+
displayName: adapter.displayName,
|
|
164
|
+
unversionedEvents: observation?.unversioned ?? 0
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
const [detection, status] = await Promise.all([adapter.detect(), adapter.status()]);
|
|
168
|
+
return {
|
|
169
|
+
...base4,
|
|
170
|
+
status,
|
|
171
|
+
surfaces: detection.detected ? gradeSurfaces(detection.surfaces ?? [], status, observation) : []
|
|
172
|
+
};
|
|
173
|
+
} catch {
|
|
174
|
+
return { ...base4, status: "unknown", surfaces: [] };
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
function describeSurface(state) {
|
|
180
|
+
const version = state.surface.version ?? state.observedVersion;
|
|
181
|
+
return version !== void 0 ? `${state.surface.label} ${version}` : state.surface.label;
|
|
182
|
+
}
|
|
183
|
+
function describeSurfaceCoverage(state, group) {
|
|
184
|
+
if (state.coverage === "active") {
|
|
185
|
+
const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
|
|
186
|
+
return `covered \u2014 ${state.events} event(s) from this build${last}`;
|
|
187
|
+
}
|
|
188
|
+
if (state.coverage === "wired") {
|
|
189
|
+
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`;
|
|
190
|
+
}
|
|
191
|
+
if (!CONFIGURED_STATUSES.has(group.status)) {
|
|
192
|
+
return `not covered \u2014 ${group.displayName} carries no BirdyBeep hooks, so this build cannot beep`;
|
|
193
|
+
}
|
|
194
|
+
const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
|
|
195
|
+
const delivering = active.join(", ");
|
|
196
|
+
const verb = active.length === 1 ? "is" : "are";
|
|
197
|
+
return `not covered \u2014 nothing has ever fired from this build, while ${delivering} ${verb} delivering through the same config`;
|
|
198
|
+
}
|
|
199
|
+
function installTarget(harness) {
|
|
200
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
201
|
+
}
|
|
202
|
+
function surfaceRemedy(state, group) {
|
|
203
|
+
if (state.coverage !== "uncovered") return void 0;
|
|
204
|
+
if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
|
|
205
|
+
const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
|
|
206
|
+
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.`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/framework.ts
|
|
210
|
+
var import_node_fs2 = require("fs");
|
|
211
|
+
var import_agent_core2 = require("@birdybeep/agent-core");
|
|
36
212
|
var EXIT = { OK: 0, ERROR: 1, USAGE: 2 };
|
|
37
213
|
function createIo(json, stdout, stderr) {
|
|
38
214
|
return {
|
|
@@ -105,11 +281,17 @@ function isUnknownFlag(token, allowed) {
|
|
|
105
281
|
function renderRootHelp(version, commands) {
|
|
106
282
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
107
283
|
const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
284
|
+
const featured = commands.filter((c) => c.gettingStarted !== void 0);
|
|
108
285
|
return [
|
|
109
286
|
`birdybeep ${version} \u2014 stream coding-agent lifecycle events to BirdyBeep.`,
|
|
110
287
|
"",
|
|
111
288
|
"Usage:",
|
|
112
289
|
" birdybeep <command> [options]",
|
|
290
|
+
...featured.length > 0 ? [
|
|
291
|
+
"",
|
|
292
|
+
"Getting started:",
|
|
293
|
+
...featured.map((c) => ` birdybeep ${c.name} ${c.gettingStarted ?? ""}`)
|
|
294
|
+
] : [],
|
|
113
295
|
"",
|
|
114
296
|
"Commands:",
|
|
115
297
|
...lines,
|
|
@@ -164,7 +346,7 @@ async function dispatch(argv, deps) {
|
|
|
164
346
|
const io = createIo(flags.json, deps.stdout, deps.stderr);
|
|
165
347
|
if (deps.ensureConfig !== false) {
|
|
166
348
|
try {
|
|
167
|
-
(0,
|
|
349
|
+
(0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
168
350
|
} catch {
|
|
169
351
|
}
|
|
170
352
|
}
|
|
@@ -191,7 +373,11 @@ async function dispatch(argv, deps) {
|
|
|
191
373
|
if (rest.length === 0 || flags.help && command === void 0) {
|
|
192
374
|
io.emit(renderRootHelp(deps.version, deps.commands), {
|
|
193
375
|
version: deps.version,
|
|
194
|
-
commands: deps.commands.map((c) => ({
|
|
376
|
+
commands: deps.commands.map((c) => ({
|
|
377
|
+
name: c.name,
|
|
378
|
+
summary: c.summary,
|
|
379
|
+
...c.gettingStarted !== void 0 ? { gettingStarted: c.gettingStarted } : {}
|
|
380
|
+
}))
|
|
195
381
|
});
|
|
196
382
|
return EXIT.OK;
|
|
197
383
|
}
|
|
@@ -245,10 +431,10 @@ async function dispatch(argv, deps) {
|
|
|
245
431
|
|
|
246
432
|
// src/commands/agent.ts
|
|
247
433
|
var DEFAULT_ADAPTERS = [
|
|
248
|
-
|
|
434
|
+
import_claude_code2.claudeCodeAdapter,
|
|
249
435
|
import_codex.codexAdapter,
|
|
250
436
|
import_opencode.opencodeAdapter,
|
|
251
|
-
|
|
437
|
+
import_cursor2.cursorAdapter,
|
|
252
438
|
import_copilot.copilotAdapter
|
|
253
439
|
];
|
|
254
440
|
var TARGET_TO_ID = {
|
|
@@ -272,7 +458,10 @@ function selectAdapters(target, adapters) {
|
|
|
272
458
|
if (id === void 0) return "unknown";
|
|
273
459
|
return adapters.filter((a) => a.id === id);
|
|
274
460
|
}
|
|
275
|
-
|
|
461
|
+
function installTarget2(harness) {
|
|
462
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
463
|
+
}
|
|
464
|
+
async function installSelected(adapters, ctx, tokenOptions) {
|
|
276
465
|
const target = ctx.args[0] ?? "all";
|
|
277
466
|
const selected = selectAdapters(target, adapters);
|
|
278
467
|
if (selected === "unknown") {
|
|
@@ -299,8 +488,9 @@ async function installSelected(adapters, ctx) {
|
|
|
299
488
|
requiredActions: result.requiredActions
|
|
300
489
|
});
|
|
301
490
|
}
|
|
491
|
+
const paired = await isPaired(tokenOptions);
|
|
302
492
|
if (ctx.flags.json) {
|
|
303
|
-
ctx.io.result({ target, results: outcomes });
|
|
493
|
+
ctx.io.result({ target, paired, results: outcomes });
|
|
304
494
|
return EXIT.OK;
|
|
305
495
|
}
|
|
306
496
|
if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {
|
|
@@ -308,13 +498,20 @@ async function installSelected(adapters, ctx) {
|
|
|
308
498
|
}
|
|
309
499
|
for (const o of outcomes) {
|
|
310
500
|
if (!o.detected) {
|
|
311
|
-
ctx.io.line(
|
|
501
|
+
ctx.io.line(
|
|
502
|
+
`\u2013 ${o.displayName}: not detected (skipped) \u2014 install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\``
|
|
503
|
+
);
|
|
312
504
|
continue;
|
|
313
505
|
}
|
|
314
506
|
const changed = (o.changedFiles ?? []).length > 0 ? o.changedFiles.join(", ") : "no changes";
|
|
315
507
|
ctx.io.line(`\u2713 ${o.displayName}: ${o.status} (${changed})`);
|
|
316
508
|
for (const action of o.requiredActions ?? []) ctx.io.line(` \u2192 ${action}`);
|
|
317
509
|
}
|
|
510
|
+
if (!paired) {
|
|
511
|
+
ctx.io.line(
|
|
512
|
+
"\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."
|
|
513
|
+
);
|
|
514
|
+
}
|
|
318
515
|
return EXIT.OK;
|
|
319
516
|
}
|
|
320
517
|
async function uninstallSelected(adapters, ctx) {
|
|
@@ -353,6 +550,7 @@ async function uninstallSelected(adapters, ctx) {
|
|
|
353
550
|
}
|
|
354
551
|
function createAgentCommand(deps = {}) {
|
|
355
552
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS;
|
|
553
|
+
const tokenOptions = deps.tokenOptions ?? {};
|
|
356
554
|
return {
|
|
357
555
|
name: "agent",
|
|
358
556
|
summary: "Install or uninstall harness adapters",
|
|
@@ -362,7 +560,7 @@ function createAgentCommand(deps = {}) {
|
|
|
362
560
|
name: "install",
|
|
363
561
|
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
364
562
|
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
365
|
-
run: (ctx) => installSelected(adapters, ctx)
|
|
563
|
+
run: (ctx) => installSelected(adapters, ctx, tokenOptions)
|
|
366
564
|
},
|
|
367
565
|
{
|
|
368
566
|
name: "uninstall",
|
|
@@ -376,24 +574,24 @@ function createAgentCommand(deps = {}) {
|
|
|
376
574
|
|
|
377
575
|
// src/commands/doctor.ts
|
|
378
576
|
var import_agent_core4 = require("@birdybeep/agent-core");
|
|
379
|
-
var
|
|
577
|
+
var import_claude_code3 = require("@birdybeep/claude-code");
|
|
380
578
|
var import_codex2 = require("@birdybeep/codex");
|
|
381
579
|
var import_copilot2 = require("@birdybeep/copilot");
|
|
382
|
-
var
|
|
580
|
+
var import_cursor3 = require("@birdybeep/cursor");
|
|
383
581
|
var import_opencode2 = require("@birdybeep/opencode");
|
|
384
582
|
|
|
385
583
|
// src/config.ts
|
|
386
|
-
var
|
|
584
|
+
var import_node_fs3 = require("fs");
|
|
387
585
|
var import_node_path = require("path");
|
|
388
|
-
var
|
|
586
|
+
var import_agent_core3 = require("@birdybeep/agent-core");
|
|
389
587
|
var DEFAULT_API_URL = "https://api.birdybeep.com";
|
|
390
588
|
var CONFIG_FILE = "config.json";
|
|
391
589
|
function cliConfigPath() {
|
|
392
|
-
return (0, import_node_path.join)((0,
|
|
590
|
+
return (0, import_node_path.join)((0, import_agent_core3.birdyBeepConfigDir)(), CONFIG_FILE);
|
|
393
591
|
}
|
|
394
592
|
function readCliConfig() {
|
|
395
593
|
try {
|
|
396
|
-
const parsed = JSON.parse((0,
|
|
594
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(cliConfigPath(), "utf8"));
|
|
397
595
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
398
596
|
} catch {
|
|
399
597
|
return {};
|
|
@@ -406,8 +604,8 @@ function writeCliConfig(patch) {
|
|
|
406
604
|
if (apiUrl !== void 0) merged.apiUrl = apiUrl;
|
|
407
605
|
const expectEmail = patch.expectEmail ?? current.expectEmail;
|
|
408
606
|
if (expectEmail !== void 0) merged.expectEmail = expectEmail;
|
|
409
|
-
(0,
|
|
410
|
-
(0,
|
|
607
|
+
(0, import_node_fs3.mkdirSync)((0, import_agent_core3.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
608
|
+
(0, import_node_fs3.writeFileSync)(cliConfigPath(), `${JSON.stringify(merged, null, 2)}
|
|
411
609
|
`, { mode: 384 });
|
|
412
610
|
}
|
|
413
611
|
function resolveApiUrl() {
|
|
@@ -422,33 +620,12 @@ function resolveRegistryUrl() {
|
|
|
422
620
|
return DEFAULT_REGISTRY_URL;
|
|
423
621
|
}
|
|
424
622
|
|
|
425
|
-
// src/diagnostics.ts
|
|
426
|
-
var import_agent_core3 = require("@birdybeep/agent-core");
|
|
427
|
-
async function gatherIntegrations(adapters) {
|
|
428
|
-
return Promise.all(
|
|
429
|
-
adapters.map(async (a) => ({
|
|
430
|
-
harness: a.id,
|
|
431
|
-
displayName: a.displayName,
|
|
432
|
-
status: await a.status()
|
|
433
|
-
}))
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
async function isPaired(tokenOptions = {}) {
|
|
437
|
-
return await (0, import_agent_core3.getToken)(tokenOptions) !== null;
|
|
438
|
-
}
|
|
439
|
-
function localQueueDepth() {
|
|
440
|
-
return new import_agent_core3.LocalEventQueue().size();
|
|
441
|
-
}
|
|
442
|
-
function machineIdentity() {
|
|
443
|
-
return (0, import_agent_core3.getMachineIdentity)();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
623
|
// src/commands/doctor.ts
|
|
447
624
|
var DEFAULT_ADAPTERS2 = [
|
|
448
|
-
|
|
625
|
+
import_claude_code3.claudeCodeAdapter,
|
|
449
626
|
import_codex2.codexAdapter,
|
|
450
627
|
import_opencode2.opencodeAdapter,
|
|
451
|
-
|
|
628
|
+
import_cursor3.cursorAdapter,
|
|
452
629
|
import_copilot2.copilotAdapter
|
|
453
630
|
];
|
|
454
631
|
async function defaultProbeNetwork(baseUrl) {
|
|
@@ -485,6 +662,32 @@ function createDoctorCommand(deps = {}) {
|
|
|
485
662
|
remedy: "Run `birdybeep pair` to pair this machine."
|
|
486
663
|
}
|
|
487
664
|
);
|
|
665
|
+
const unpaired = unpairedActivity();
|
|
666
|
+
if (unpaired !== null) {
|
|
667
|
+
checks.push({
|
|
668
|
+
name: "Events lost while unpaired",
|
|
669
|
+
ok: false,
|
|
670
|
+
detail: describeUnpairedActivity(unpaired),
|
|
671
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing are gone \u2014 a first pairing does not replay them."
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
675
|
+
checks.push({
|
|
676
|
+
name: "Approval beeps from Cursor",
|
|
677
|
+
ok: false,
|
|
678
|
+
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.",
|
|
679
|
+
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."
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const filtered = filteredActivity();
|
|
683
|
+
if (filtered !== null) {
|
|
684
|
+
checks.push({
|
|
685
|
+
name: "Local-only events (never notifiable)",
|
|
686
|
+
ok: true,
|
|
687
|
+
detail: describeFilteredActivity(filtered)
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
const surfaceGroups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
488
691
|
for (const adapter of adapters) {
|
|
489
692
|
const result = await adapter.doctor();
|
|
490
693
|
for (const c of result.checks) {
|
|
@@ -495,14 +698,26 @@ function createDoctorCommand(deps = {}) {
|
|
|
495
698
|
...c.remedy !== void 0 ? { remedy: c.remedy } : {}
|
|
496
699
|
});
|
|
497
700
|
}
|
|
701
|
+
const group = surfaceGroups.find((g) => g.harness === adapter.id);
|
|
702
|
+
if (group === void 0) continue;
|
|
703
|
+
for (const state of group.surfaces) {
|
|
704
|
+
const remedy = surfaceRemedy(state, group);
|
|
705
|
+
checks.push({
|
|
706
|
+
name: `${adapter.displayName}: ${describeSurface(state)}`,
|
|
707
|
+
ok: state.coverage !== "uncovered",
|
|
708
|
+
detail: describeSurfaceCoverage(state, group),
|
|
709
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
498
712
|
}
|
|
499
713
|
const depthBefore = localQueueDepth();
|
|
500
714
|
const drain = await makeSender(apiUrl).drainNow();
|
|
501
715
|
const depthAfter = localQueueDepth();
|
|
716
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
502
717
|
checks.push({
|
|
503
718
|
name: "Local queue",
|
|
504
719
|
ok: true,
|
|
505
|
-
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
720
|
+
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `; ${overflowDropped} dropped by the ${import_agent_core4.DEFAULT_QUEUE_MAX_ENTRIES} entry cap` : "")
|
|
506
721
|
});
|
|
507
722
|
const reachable = await probeNetwork(apiUrl);
|
|
508
723
|
checks.push(
|
|
@@ -518,7 +733,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
518
733
|
ctx.io.result({
|
|
519
734
|
ok,
|
|
520
735
|
checks,
|
|
521
|
-
|
|
736
|
+
surfaces: surfaceGroups,
|
|
737
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
738
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
739
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
522
740
|
});
|
|
523
741
|
} else {
|
|
524
742
|
for (const c of checks) {
|
|
@@ -535,20 +753,20 @@ function createDoctorCommand(deps = {}) {
|
|
|
535
753
|
// src/commands/hook.ts
|
|
536
754
|
var import_node_child_process = require("child_process");
|
|
537
755
|
var import_node_crypto = require("crypto");
|
|
538
|
-
var
|
|
539
|
-
var
|
|
756
|
+
var import_node_fs4 = require("fs");
|
|
757
|
+
var import_node_os2 = require("os");
|
|
540
758
|
var import_node_path2 = require("path");
|
|
541
759
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
542
|
-
var
|
|
760
|
+
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
543
761
|
var import_codex3 = require("@birdybeep/codex");
|
|
544
762
|
var import_copilot3 = require("@birdybeep/copilot");
|
|
545
|
-
var
|
|
763
|
+
var import_cursor4 = require("@birdybeep/cursor");
|
|
546
764
|
var import_opencode3 = require("@birdybeep/opencode");
|
|
547
765
|
var RUNNERS = {
|
|
548
|
-
claude:
|
|
766
|
+
claude: import_claude_code4.runClaudeHook,
|
|
549
767
|
codex: import_codex3.runCodexHook,
|
|
550
768
|
opencode: import_opencode3.runOpenCodeHook,
|
|
551
|
-
cursor:
|
|
769
|
+
cursor: import_cursor4.runCursorHook
|
|
552
770
|
};
|
|
553
771
|
var HOOK_HARNESSES = [
|
|
554
772
|
"claude",
|
|
@@ -576,20 +794,34 @@ function isHarnessName(value) {
|
|
|
576
794
|
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
577
795
|
}
|
|
578
796
|
function resolveHookHarness(harness, payload) {
|
|
579
|
-
return harness === "claude" && (0,
|
|
797
|
+
return harness === "claude" && (0, import_cursor4.isCursorHookPayload)(payload) ? "cursor" : harness;
|
|
580
798
|
}
|
|
581
799
|
function recognizesPayload(harness, payload) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
800
|
+
switch (harness) {
|
|
801
|
+
case "claude":
|
|
802
|
+
return (0, import_claude_code4.isClaudeCodeHookPayload)(payload);
|
|
803
|
+
case "cursor":
|
|
804
|
+
return (0, import_cursor4.isCursorHookEventName)(asRecord2(payload)["hook_event_name"]);
|
|
805
|
+
case "codex":
|
|
806
|
+
return (0, import_codex3.isCodexHookPayload)(payload);
|
|
807
|
+
case "opencode":
|
|
808
|
+
return (0, import_opencode3.isOpenCodeEventPayload)(payload);
|
|
809
|
+
case "copilot":
|
|
810
|
+
return (0, import_copilot3.isCopilotHookPayload)(payload);
|
|
811
|
+
}
|
|
585
812
|
}
|
|
586
|
-
function
|
|
813
|
+
function asRecord2(value) {
|
|
587
814
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
588
815
|
}
|
|
589
|
-
function
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
816
|
+
function describeDiscriminator(payload) {
|
|
817
|
+
const record = asRecord2(payload);
|
|
818
|
+
for (const field of ["hook_event_name", "type"]) {
|
|
819
|
+
const value = record[field];
|
|
820
|
+
if (typeof value !== "string") continue;
|
|
821
|
+
const capped = value.length > 64 ? `${value.slice(0, 63)}\u2026` : value;
|
|
822
|
+
return `${field} ${JSON.stringify(capped)}`;
|
|
823
|
+
}
|
|
824
|
+
return "the payload";
|
|
593
825
|
}
|
|
594
826
|
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
595
827
|
const handler = resolveHookHarness(harness, payload);
|
|
@@ -623,10 +855,10 @@ function detachCodexNotifyWorker(payload) {
|
|
|
623
855
|
try {
|
|
624
856
|
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
625
857
|
if (birdybeep === null) return false;
|
|
626
|
-
const tmpFile = (0, import_node_path2.join)((0,
|
|
858
|
+
const tmpFile = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `birdybeep-notify-${(0, import_node_crypto.randomBytes)(16).toString("hex")}.json`);
|
|
627
859
|
file = tmpFile;
|
|
628
|
-
(0,
|
|
629
|
-
fd = (0,
|
|
860
|
+
(0, import_node_fs4.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
861
|
+
fd = (0, import_node_fs4.openSync)(tmpFile, "r");
|
|
630
862
|
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
631
863
|
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
632
864
|
// trusted dir, never the inherited/attacker cwd
|
|
@@ -640,7 +872,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
640
872
|
});
|
|
641
873
|
child.on("error", () => {
|
|
642
874
|
try {
|
|
643
|
-
(0,
|
|
875
|
+
(0, import_node_fs4.rmSync)(tmpFile, { force: true });
|
|
644
876
|
} catch {
|
|
645
877
|
}
|
|
646
878
|
});
|
|
@@ -649,7 +881,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
649
881
|
} catch {
|
|
650
882
|
if (file !== void 0) {
|
|
651
883
|
try {
|
|
652
|
-
(0,
|
|
884
|
+
(0, import_node_fs4.rmSync)(file, { force: true });
|
|
653
885
|
} catch {
|
|
654
886
|
}
|
|
655
887
|
}
|
|
@@ -657,7 +889,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
657
889
|
} finally {
|
|
658
890
|
if (fd !== void 0) {
|
|
659
891
|
try {
|
|
660
|
-
(0,
|
|
892
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
661
893
|
} catch {
|
|
662
894
|
}
|
|
663
895
|
}
|
|
@@ -684,42 +916,75 @@ function createHookCommand(deps = {}) {
|
|
|
684
916
|
return EXIT.OK;
|
|
685
917
|
}
|
|
686
918
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
687
|
-
|
|
919
|
+
if (harness === "copilot" && copilotEventName === void 0) {
|
|
920
|
+
ctx.io.errline(
|
|
921
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")} \u2014 nothing was sent.`
|
|
922
|
+
);
|
|
923
|
+
return EXIT.USAGE;
|
|
924
|
+
}
|
|
925
|
+
const read = await withTimeout(
|
|
688
926
|
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
689
927
|
stdinTimeoutMs,
|
|
690
|
-
|
|
928
|
+
null
|
|
691
929
|
);
|
|
692
930
|
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
693
|
-
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0,
|
|
931
|
+
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0, import_node_os2.tmpdir)() && (0, import_node_path2.basename)(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
694
932
|
try {
|
|
695
|
-
(0,
|
|
933
|
+
(0, import_node_fs4.rmSync)(notifyStdinFile, { force: true });
|
|
696
934
|
} catch {
|
|
697
935
|
}
|
|
698
936
|
}
|
|
937
|
+
const drop = (reason, detail) => {
|
|
938
|
+
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
939
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail} \u2014 nothing was sent.`);
|
|
940
|
+
return EXIT.ERROR;
|
|
941
|
+
};
|
|
942
|
+
if (read === null) {
|
|
943
|
+
return drop(
|
|
944
|
+
"stdin-timeout",
|
|
945
|
+
`timed out after ${stdinTimeoutMs}ms waiting for the payload on stdin`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
const raw = read;
|
|
949
|
+
if (raw.trim().length === 0) {
|
|
950
|
+
return drop("empty-payload", "the payload was empty");
|
|
951
|
+
}
|
|
699
952
|
let payload;
|
|
700
953
|
try {
|
|
701
954
|
payload = JSON.parse(raw);
|
|
702
955
|
} catch {
|
|
703
|
-
|
|
704
|
-
return EXIT.OK;
|
|
956
|
+
return drop("invalid-json", `the ${raw.length}-byte payload is not valid JSON`);
|
|
705
957
|
}
|
|
706
|
-
const sender = makeSender(resolveApiUrl());
|
|
707
958
|
const handler = resolveHookHarness(harness, payload);
|
|
959
|
+
const routedFrom = handler !== harness ? { routedFrom: harness } : {};
|
|
960
|
+
if (!recognizesPayload(handler, payload)) {
|
|
961
|
+
ctx.io.result({
|
|
962
|
+
harness: handler,
|
|
963
|
+
...routedFrom,
|
|
964
|
+
outcome: "skipped",
|
|
965
|
+
reason: "foreign-payload"
|
|
966
|
+
});
|
|
967
|
+
const article = handler === "opencode" ? "an" : "a";
|
|
968
|
+
ctx.io.errline(
|
|
969
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
|
|
970
|
+
);
|
|
971
|
+
return EXIT.ERROR;
|
|
972
|
+
}
|
|
973
|
+
const sender = makeSender(resolveApiUrl());
|
|
708
974
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
709
975
|
ctx.io.result({
|
|
710
976
|
harness: handler,
|
|
711
|
-
...
|
|
977
|
+
...routedFrom,
|
|
712
978
|
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
713
979
|
outcome: result.outcome,
|
|
714
980
|
eventType: result.eventType,
|
|
715
981
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
716
982
|
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
717
983
|
});
|
|
718
|
-
if (result.outcome === "
|
|
984
|
+
if (result.outcome === "unpaired") {
|
|
719
985
|
ctx.io.errline(
|
|
720
|
-
|
|
986
|
+
"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)."
|
|
721
987
|
);
|
|
722
|
-
return EXIT.ERROR;
|
|
723
988
|
}
|
|
724
989
|
return EXIT.OK;
|
|
725
990
|
}
|
|
@@ -778,8 +1043,8 @@ function createUnpairCommand(deps = {}) {
|
|
|
778
1043
|
}
|
|
779
1044
|
|
|
780
1045
|
// src/commands/pair.ts
|
|
781
|
-
var
|
|
782
|
-
var
|
|
1046
|
+
var import_node_fs5 = require("fs");
|
|
1047
|
+
var import_agent_core9 = require("@birdybeep/agent-core");
|
|
783
1048
|
var import_uqr = require("uqr");
|
|
784
1049
|
|
|
785
1050
|
// src/pairing.ts
|
|
@@ -853,7 +1118,277 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
853
1118
|
}
|
|
854
1119
|
|
|
855
1120
|
// src/version.ts
|
|
856
|
-
var CLI_VERSION = "0.
|
|
1121
|
+
var CLI_VERSION = "0.6.1".length > 0 ? "0.6.1" : "0.0.0";
|
|
1122
|
+
|
|
1123
|
+
// src/commands/setup.ts
|
|
1124
|
+
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
1125
|
+
var import_codex4 = require("@birdybeep/codex");
|
|
1126
|
+
var import_copilot4 = require("@birdybeep/copilot");
|
|
1127
|
+
var import_cursor5 = require("@birdybeep/cursor");
|
|
1128
|
+
var import_opencode4 = require("@birdybeep/opencode");
|
|
1129
|
+
|
|
1130
|
+
// src/commands/test.ts
|
|
1131
|
+
var import_node_crypto2 = require("crypto");
|
|
1132
|
+
var import_agent_core8 = require("@birdybeep/agent-core");
|
|
1133
|
+
function buildTestEvent(opts = {}) {
|
|
1134
|
+
const machine = (0, import_agent_core8.getMachineIdentity)();
|
|
1135
|
+
return (0, import_agent_core8.normalizeEvent)(
|
|
1136
|
+
{
|
|
1137
|
+
event_type: "test",
|
|
1138
|
+
status: "running",
|
|
1139
|
+
harness: "claude_code",
|
|
1140
|
+
// schema requires a harness; the "test" type distinguishes it
|
|
1141
|
+
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1142
|
+
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1143
|
+
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1144
|
+
machine: { label: machine.label, os: machine.os },
|
|
1145
|
+
workspace: { cwd: process.cwd() },
|
|
1146
|
+
title: "BirdyBeep test event",
|
|
1147
|
+
body: "If you can see this, your machine is wired up correctly.",
|
|
1148
|
+
metadata: { test: true }
|
|
1149
|
+
},
|
|
1150
|
+
opts
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
function createTestCommand(deps = {}) {
|
|
1154
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core8.createSender)(
|
|
1155
|
+
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1156
|
+
));
|
|
1157
|
+
return {
|
|
1158
|
+
name: "test",
|
|
1159
|
+
summary: "Send a test event end-to-end",
|
|
1160
|
+
usage: "birdybeep test [--json]",
|
|
1161
|
+
run: async (ctx) => {
|
|
1162
|
+
const event = buildTestEvent();
|
|
1163
|
+
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1164
|
+
if (ctx.flags.json) {
|
|
1165
|
+
ctx.io.result({
|
|
1166
|
+
outcome: result.outcome,
|
|
1167
|
+
...result.status ? { status: result.status } : {},
|
|
1168
|
+
...result.decision ? { decision: result.decision } : {}
|
|
1169
|
+
});
|
|
1170
|
+
} else if (result.outcome === "delivered") {
|
|
1171
|
+
if (result.decision === "notified" || result.decision === void 0) {
|
|
1172
|
+
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1173
|
+
} else if (result.decision === "suppressed") {
|
|
1174
|
+
ctx.io.line(
|
|
1175
|
+
"\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`."
|
|
1176
|
+
);
|
|
1177
|
+
} else if (result.decision === "deduped") {
|
|
1178
|
+
ctx.io.line(
|
|
1179
|
+
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1180
|
+
);
|
|
1181
|
+
} else {
|
|
1182
|
+
ctx.io.line(
|
|
1183
|
+
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
} else if (result.outcome === "unpaired") {
|
|
1187
|
+
ctx.io.line(
|
|
1188
|
+
"\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
|
|
1189
|
+
);
|
|
1190
|
+
} else if (result.outcome === "queued") {
|
|
1191
|
+
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1192
|
+
} else {
|
|
1193
|
+
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1194
|
+
}
|
|
1195
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/commands/setup.ts
|
|
1201
|
+
var SETUP_ADAPTERS = [
|
|
1202
|
+
import_claude_code5.claudeCodeAdapter,
|
|
1203
|
+
import_codex4.codexAdapter,
|
|
1204
|
+
import_opencode4.opencodeAdapter,
|
|
1205
|
+
import_cursor5.cursorAdapter,
|
|
1206
|
+
import_copilot4.copilotAdapter
|
|
1207
|
+
];
|
|
1208
|
+
function failedSetupReport(error) {
|
|
1209
|
+
return {
|
|
1210
|
+
harnesses: [],
|
|
1211
|
+
counts: { installed: 0, needsYou: 0, notInstalled: 0, failed: 0 },
|
|
1212
|
+
error,
|
|
1213
|
+
ok: false
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
var PENDING_STATUSES = /* @__PURE__ */ new Set([
|
|
1217
|
+
"needs_trust",
|
|
1218
|
+
"needs_restart"
|
|
1219
|
+
]);
|
|
1220
|
+
async function installDetected(adapters) {
|
|
1221
|
+
const installs = [];
|
|
1222
|
+
for (const adapter of adapters) {
|
|
1223
|
+
try {
|
|
1224
|
+
const detection = await adapter.detect();
|
|
1225
|
+
if (!detection.detected) {
|
|
1226
|
+
installs.push({ adapter, detected: false });
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
installs.push({ adapter, detected: true, result: await adapter.install() });
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
installs.push({
|
|
1232
|
+
adapter,
|
|
1233
|
+
detected: true,
|
|
1234
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return installs;
|
|
1239
|
+
}
|
|
1240
|
+
function rowState(state, group, status) {
|
|
1241
|
+
if (status === "error" || group.status === "error") return "failed";
|
|
1242
|
+
if (status !== void 0 && PENDING_STATUSES.has(status)) return "needs you";
|
|
1243
|
+
if (state.coverage === "active") return "beeping";
|
|
1244
|
+
if (state.coverage === "wired") return "ready";
|
|
1245
|
+
return "not covered";
|
|
1246
|
+
}
|
|
1247
|
+
function buildHarnessReports(installs, groups) {
|
|
1248
|
+
return installs.map((install) => {
|
|
1249
|
+
const { adapter } = install;
|
|
1250
|
+
const base4 = {
|
|
1251
|
+
harness: adapter.id,
|
|
1252
|
+
displayName: adapter.displayName,
|
|
1253
|
+
detected: install.detected,
|
|
1254
|
+
...install.result !== void 0 ? {
|
|
1255
|
+
status: install.result.status,
|
|
1256
|
+
changedFiles: install.result.changedFiles,
|
|
1257
|
+
backupFiles: install.result.backupFiles
|
|
1258
|
+
} : {},
|
|
1259
|
+
...install.error !== void 0 ? { error: install.error } : {}
|
|
1260
|
+
};
|
|
1261
|
+
if (install.error !== void 0) {
|
|
1262
|
+
return {
|
|
1263
|
+
...base4,
|
|
1264
|
+
actions: [
|
|
1265
|
+
`${adapter.displayName} could not be set up: ${install.error}`,
|
|
1266
|
+
`Run \`birdybeep agent install ${installTarget2(adapter.id)}\` to retry it on its own.`
|
|
1267
|
+
],
|
|
1268
|
+
rows: [{ harness: adapter.id, displayName: adapter.displayName, state: "failed" }]
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
if (!install.detected) {
|
|
1272
|
+
return {
|
|
1273
|
+
...base4,
|
|
1274
|
+
actions: [],
|
|
1275
|
+
rows: [
|
|
1276
|
+
{
|
|
1277
|
+
harness: adapter.id,
|
|
1278
|
+
displayName: adapter.displayName,
|
|
1279
|
+
state: "not installed"
|
|
1280
|
+
}
|
|
1281
|
+
]
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
const group = groups.find((g) => g.harness === adapter.id);
|
|
1285
|
+
const status = install.result?.status;
|
|
1286
|
+
const surfaces = group?.surfaces ?? [];
|
|
1287
|
+
const rows = group === void 0 || surfaces.length === 0 ? [
|
|
1288
|
+
{
|
|
1289
|
+
harness: adapter.id,
|
|
1290
|
+
displayName: adapter.displayName,
|
|
1291
|
+
state: status !== void 0 && PENDING_STATUSES.has(status) ? "needs you" : "ready"
|
|
1292
|
+
}
|
|
1293
|
+
] : surfaces.map((state) => {
|
|
1294
|
+
const graded = rowState(state, group, status);
|
|
1295
|
+
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);
|
|
1296
|
+
return {
|
|
1297
|
+
harness: adapter.id,
|
|
1298
|
+
displayName: adapter.displayName,
|
|
1299
|
+
build: describeSurface(state),
|
|
1300
|
+
kind: state.surface.kind,
|
|
1301
|
+
state: graded,
|
|
1302
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
1303
|
+
};
|
|
1304
|
+
});
|
|
1305
|
+
return { ...base4, actions: [...install.result?.requiredActions ?? []], rows };
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
function pad(text, width) {
|
|
1309
|
+
return text.length >= width ? text : text + " ".repeat(width - text.length);
|
|
1310
|
+
}
|
|
1311
|
+
var MARKS = {
|
|
1312
|
+
beeping: "\u2713",
|
|
1313
|
+
ready: "\u2713",
|
|
1314
|
+
"needs you": "!",
|
|
1315
|
+
"not covered": "\u2717",
|
|
1316
|
+
"not installed": "\u2013",
|
|
1317
|
+
failed: "\u2717"
|
|
1318
|
+
};
|
|
1319
|
+
function renderCoverageTable(reports) {
|
|
1320
|
+
const rows = reports.flatMap((r) => r.rows);
|
|
1321
|
+
const nameWidth = Math.max(7, ...rows.map((r) => r.displayName.length));
|
|
1322
|
+
const buildWidth = Math.max(5, ...rows.map((r) => (r.build ?? "\u2014").length));
|
|
1323
|
+
const lines = ["coverage", ` ${pad("harness", nameWidth)} ${pad("build", buildWidth)} state`];
|
|
1324
|
+
for (const report of reports) {
|
|
1325
|
+
for (const row of report.rows) {
|
|
1326
|
+
lines.push(
|
|
1327
|
+
`${MARKS[row.state]} ${pad(row.displayName, nameWidth)} ${pad(row.build ?? "\u2014", buildWidth)} ${row.state}`
|
|
1328
|
+
);
|
|
1329
|
+
if (row.remedy !== void 0) lines.push(` \u2192 ${row.remedy}`);
|
|
1330
|
+
}
|
|
1331
|
+
for (const action of report.actions) lines.push(` \u2192 ${action}`);
|
|
1332
|
+
}
|
|
1333
|
+
return lines;
|
|
1334
|
+
}
|
|
1335
|
+
function describeMissing(reports) {
|
|
1336
|
+
const missing = reports.filter((r) => !r.detected && r.error === void 0);
|
|
1337
|
+
if (missing.length === 0) return [];
|
|
1338
|
+
const names = missing.map((r) => r.displayName);
|
|
1339
|
+
if (missing.length === reports.length) {
|
|
1340
|
+
return [
|
|
1341
|
+
"No supported coding agent is installed on this machine, so there was nothing to wire up.",
|
|
1342
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again \u2014 pairing is already done, so it picks up from here.`
|
|
1343
|
+
];
|
|
1344
|
+
}
|
|
1345
|
+
return [
|
|
1346
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again to wire it up.`
|
|
1347
|
+
];
|
|
1348
|
+
}
|
|
1349
|
+
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
1350
|
+
const adapters = deps.adapters ?? [...SETUP_ADAPTERS];
|
|
1351
|
+
const installs = await installDetected(adapters);
|
|
1352
|
+
const groups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1353
|
+
const reports = buildHarnessReports(installs, groups);
|
|
1354
|
+
ctx.io.line("");
|
|
1355
|
+
for (const line of renderCoverageTable(reports)) ctx.io.line(line);
|
|
1356
|
+
const missing = describeMissing(reports);
|
|
1357
|
+
if (missing.length > 0) {
|
|
1358
|
+
ctx.io.line("");
|
|
1359
|
+
for (const line of missing) ctx.io.line(line);
|
|
1360
|
+
}
|
|
1361
|
+
const counts = {
|
|
1362
|
+
installed: reports.filter((r) => r.detected && r.error === void 0).length,
|
|
1363
|
+
needsYou: reports.filter((r) => r.rows.some((row) => row.state === "needs you")).length,
|
|
1364
|
+
notInstalled: reports.filter((r) => !r.detected && r.error === void 0).length,
|
|
1365
|
+
// A row that graded `failed` counts too: an adapter that returned status "error" never threw,
|
|
1366
|
+
// so counting only thrown errors would report a clean run over a harness that is broken.
|
|
1367
|
+
failed: reports.filter((r) => r.error !== void 0 || r.rows.some((x) => x.state === "failed")).length
|
|
1368
|
+
};
|
|
1369
|
+
let beep;
|
|
1370
|
+
let beepOk = true;
|
|
1371
|
+
if (options.sendTest) {
|
|
1372
|
+
ctx.io.line("");
|
|
1373
|
+
const command = createTestCommand({
|
|
1374
|
+
...deps.createSender !== void 0 ? { createSender: deps.createSender } : {},
|
|
1375
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {}
|
|
1376
|
+
});
|
|
1377
|
+
const beepIo = {
|
|
1378
|
+
...ctx.io,
|
|
1379
|
+
result: (value) => {
|
|
1380
|
+
beep = value;
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
beepOk = await command.run?.({ args: [], flags: ctx.flags, io: beepIo }) === 0;
|
|
1384
|
+
}
|
|
1385
|
+
return {
|
|
1386
|
+
harnesses: reports,
|
|
1387
|
+
counts,
|
|
1388
|
+
...beep !== void 0 ? { beep } : {},
|
|
1389
|
+
ok: counts.failed === 0 && beepOk
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
857
1392
|
|
|
858
1393
|
// src/commands/pair.ts
|
|
859
1394
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -862,11 +1397,15 @@ function renderQrMatrix(qrPayload) {
|
|
|
862
1397
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
863
1398
|
}
|
|
864
1399
|
function parsePairFlags(args) {
|
|
865
|
-
const flags = { yes: false };
|
|
1400
|
+
const flags = { yes: false, noInstall: false, noTest: false };
|
|
866
1401
|
for (let i = 0; i < args.length; i += 1) {
|
|
867
1402
|
const token = args[i] ?? "";
|
|
868
1403
|
if (token === "--yes" || token === "-y") {
|
|
869
1404
|
flags.yes = true;
|
|
1405
|
+
} else if (token === "--no-install") {
|
|
1406
|
+
flags.noInstall = true;
|
|
1407
|
+
} else if (token === "--no-test") {
|
|
1408
|
+
flags.noTest = true;
|
|
870
1409
|
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
871
1410
|
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
872
1411
|
const value = inline ?? args[++i];
|
|
@@ -933,14 +1472,14 @@ function canOpenControllingTerminal(path = controllingTerminalPath(), platform =
|
|
|
933
1472
|
if (platform === "win32") return false;
|
|
934
1473
|
let fd;
|
|
935
1474
|
try {
|
|
936
|
-
fd = (0,
|
|
1475
|
+
fd = (0, import_node_fs5.openSync)(path, "r");
|
|
937
1476
|
return true;
|
|
938
1477
|
} catch {
|
|
939
1478
|
return false;
|
|
940
1479
|
} finally {
|
|
941
1480
|
if (fd !== void 0) {
|
|
942
1481
|
try {
|
|
943
|
-
(0,
|
|
1482
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
944
1483
|
} catch {
|
|
945
1484
|
}
|
|
946
1485
|
}
|
|
@@ -954,7 +1493,7 @@ async function promptForAnswer(question, on) {
|
|
|
954
1493
|
input = process.stdin;
|
|
955
1494
|
} else {
|
|
956
1495
|
const { ReadStream } = await import("tty");
|
|
957
|
-
ttyFd = (0,
|
|
1496
|
+
ttyFd = (0, import_node_fs5.openSync)(controllingTerminalPath(), "r");
|
|
958
1497
|
input = new ReadStream(ttyFd);
|
|
959
1498
|
}
|
|
960
1499
|
return new Promise((resolve) => {
|
|
@@ -974,7 +1513,7 @@ async function promptForAnswer(question, on) {
|
|
|
974
1513
|
}
|
|
975
1514
|
if (ttyFd !== void 0) {
|
|
976
1515
|
try {
|
|
977
|
-
(0,
|
|
1516
|
+
(0, import_node_fs5.closeSync)(ttyFd);
|
|
978
1517
|
} catch {
|
|
979
1518
|
}
|
|
980
1519
|
}
|
|
@@ -986,7 +1525,18 @@ async function promptForAnswer(question, on) {
|
|
|
986
1525
|
input.once?.("error", () => done(""));
|
|
987
1526
|
});
|
|
988
1527
|
}
|
|
989
|
-
function
|
|
1528
|
+
async function runSetupChain(ctx, deps, flags) {
|
|
1529
|
+
try {
|
|
1530
|
+
return await runHarnessSetup(ctx, { sendTest: !flags.noTest }, deps);
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1533
|
+
ctx.io.errline(
|
|
1534
|
+
`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\`.`
|
|
1535
|
+
);
|
|
1536
|
+
return failedSetupReport(message);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function createPairingCommand(verb, deps = {}) {
|
|
990
1540
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
991
1541
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
992
1542
|
const clock = deps.now ?? (() => Date.now());
|
|
@@ -999,9 +1549,10 @@ function createPairCommand(deps = {}) {
|
|
|
999
1549
|
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
1000
1550
|
});
|
|
1001
1551
|
return {
|
|
1002
|
-
name:
|
|
1003
|
-
summary:
|
|
1004
|
-
usage:
|
|
1552
|
+
name: verb.name,
|
|
1553
|
+
summary: verb.summary,
|
|
1554
|
+
usage: verb.usage,
|
|
1555
|
+
...verb.gettingStarted !== void 0 ? { gettingStarted: verb.gettingStarted } : {},
|
|
1005
1556
|
options: [
|
|
1006
1557
|
{
|
|
1007
1558
|
flag: "--yes",
|
|
@@ -1012,18 +1563,43 @@ function createPairCommand(deps = {}) {
|
|
|
1012
1563
|
flag: "--expect-email",
|
|
1013
1564
|
value: "<addr>",
|
|
1014
1565
|
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
flag: "--no-install",
|
|
1569
|
+
summary: "Stop after pairing \u2014 don't detect or wire up any coding agent"
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
flag: "--no-test",
|
|
1573
|
+
summary: "Don't send the test Beep at the end"
|
|
1015
1574
|
}
|
|
1016
1575
|
],
|
|
1017
1576
|
run: async (ctx) => {
|
|
1018
1577
|
const pairFlags = parsePairFlags(ctx.args);
|
|
1019
1578
|
if (pairFlags.error !== void 0) {
|
|
1020
|
-
ctx.io.errline(`birdybeep
|
|
1579
|
+
ctx.io.errline(`birdybeep ${verb.name}: ${pairFlags.error}.`);
|
|
1021
1580
|
return EXIT.USAGE;
|
|
1022
1581
|
}
|
|
1582
|
+
const chain = deps.setup === false || pairFlags.noInstall ? void 0 : deps.setup ?? {};
|
|
1583
|
+
const setupDeps = {
|
|
1584
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {},
|
|
1585
|
+
...chain
|
|
1586
|
+
};
|
|
1587
|
+
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1588
|
+
ctx.io.line(
|
|
1589
|
+
chain !== void 0 ? "\u2713 Already paired \u2014 checking which coding agents are wired up." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1590
|
+
);
|
|
1591
|
+
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1592
|
+
ctx.io.result({
|
|
1593
|
+
paired: true,
|
|
1594
|
+
alreadyPaired: true,
|
|
1595
|
+
...report2 !== void 0 ? { setup: report2 } : {}
|
|
1596
|
+
});
|
|
1597
|
+
return report2 !== void 0 && !report2.ok ? EXIT.ERROR : EXIT.OK;
|
|
1598
|
+
}
|
|
1023
1599
|
const apiUrl = resolveApiUrl();
|
|
1024
|
-
const identity = (0,
|
|
1025
|
-
const codeVerifier = (0,
|
|
1026
|
-
const codeChallenge = (0,
|
|
1600
|
+
const identity = (0, import_agent_core9.getMachineIdentity)();
|
|
1601
|
+
const codeVerifier = (0, import_agent_core9.generateCodeVerifier)();
|
|
1602
|
+
const codeChallenge = (0, import_agent_core9.deriveCodeChallengeS256)(codeVerifier);
|
|
1027
1603
|
const start = await pairStart(
|
|
1028
1604
|
apiUrl,
|
|
1029
1605
|
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
@@ -1119,21 +1695,52 @@ function createPairCommand(deps = {}) {
|
|
|
1119
1695
|
);
|
|
1120
1696
|
return EXIT.ERROR;
|
|
1121
1697
|
}
|
|
1122
|
-
await (0,
|
|
1698
|
+
await (0, import_agent_core9.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
1123
1699
|
writeCliConfig({ apiUrl });
|
|
1700
|
+
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1701
|
+
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1124
1702
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1125
|
-
|
|
1703
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing \u2014 you won't be beeped about them.` : "";
|
|
1704
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to wire up your coding agents." : "";
|
|
1705
|
+
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1706
|
+
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1707
|
+
ctx.io.result({
|
|
1126
1708
|
paired: true,
|
|
1127
1709
|
machineId: paired.machineId,
|
|
1128
|
-
|
|
1710
|
+
discardedPrePairingEvents: discarded,
|
|
1711
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1712
|
+
...report !== void 0 ? { setup: report } : {}
|
|
1129
1713
|
});
|
|
1130
|
-
return EXIT.OK;
|
|
1714
|
+
return report !== void 0 && !report.ok ? EXIT.ERROR : EXIT.OK;
|
|
1131
1715
|
}
|
|
1132
1716
|
};
|
|
1133
1717
|
}
|
|
1718
|
+
function createPairCommand(deps = {}) {
|
|
1719
|
+
return createPairingCommand(
|
|
1720
|
+
{
|
|
1721
|
+
name: "pair",
|
|
1722
|
+
summary: "Pair this machine and wire up every coding agent on it",
|
|
1723
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1724
|
+
skipWhenPaired: false
|
|
1725
|
+
},
|
|
1726
|
+
deps
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
function createSetupCommand(deps = {}) {
|
|
1730
|
+
return createPairingCommand(
|
|
1731
|
+
{
|
|
1732
|
+
name: "setup",
|
|
1733
|
+
summary: "Set up BirdyBeep here: pair, wire up every coding agent, test",
|
|
1734
|
+
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1735
|
+
gettingStarted: "Pair this machine, wire up every coding agent it finds, and send a test Beep.",
|
|
1736
|
+
skipWhenPaired: true
|
|
1737
|
+
},
|
|
1738
|
+
deps
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1134
1741
|
|
|
1135
1742
|
// src/commands/queue.ts
|
|
1136
|
-
var
|
|
1743
|
+
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
1137
1744
|
function createQueueCommand() {
|
|
1138
1745
|
return {
|
|
1139
1746
|
name: "queue",
|
|
@@ -1145,7 +1752,7 @@ function createQueueCommand() {
|
|
|
1145
1752
|
summary: "Clear the local offline event queue (debug)",
|
|
1146
1753
|
usage: "birdybeep queue clear",
|
|
1147
1754
|
run: (ctx) => {
|
|
1148
|
-
const cleared = new
|
|
1755
|
+
const cleared = new import_agent_core10.LocalEventQueue().clear();
|
|
1149
1756
|
ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });
|
|
1150
1757
|
return EXIT.OK;
|
|
1151
1758
|
}
|
|
@@ -1155,25 +1762,25 @@ function createQueueCommand() {
|
|
|
1155
1762
|
}
|
|
1156
1763
|
|
|
1157
1764
|
// src/commands/report-status.ts
|
|
1158
|
-
var
|
|
1159
|
-
var
|
|
1160
|
-
var
|
|
1161
|
-
var
|
|
1162
|
-
var
|
|
1163
|
-
var
|
|
1765
|
+
var import_agent_core11 = require("@birdybeep/agent-core");
|
|
1766
|
+
var import_claude_code6 = require("@birdybeep/claude-code");
|
|
1767
|
+
var import_codex5 = require("@birdybeep/codex");
|
|
1768
|
+
var import_copilot5 = require("@birdybeep/copilot");
|
|
1769
|
+
var import_cursor6 = require("@birdybeep/cursor");
|
|
1770
|
+
var import_opencode5 = require("@birdybeep/opencode");
|
|
1164
1771
|
var DEFAULT_ADAPTERS3 = [
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1772
|
+
import_claude_code6.claudeCodeAdapter,
|
|
1773
|
+
import_codex5.codexAdapter,
|
|
1774
|
+
import_opencode5.opencodeAdapter,
|
|
1775
|
+
import_cursor6.cursorAdapter,
|
|
1776
|
+
import_copilot5.copilotAdapter
|
|
1170
1777
|
];
|
|
1171
1778
|
var ADAPTER_VERSIONS = {
|
|
1172
|
-
claude_code:
|
|
1173
|
-
codex:
|
|
1174
|
-
opencode:
|
|
1175
|
-
cursor:
|
|
1176
|
-
copilot:
|
|
1779
|
+
claude_code: import_claude_code6.CLAUDE_CODE_ADAPTER_VERSION,
|
|
1780
|
+
codex: import_codex5.CODEX_ADAPTER_VERSION,
|
|
1781
|
+
opencode: import_opencode5.OPENCODE_ADAPTER_VERSION,
|
|
1782
|
+
cursor: import_cursor6.CURSOR_ADAPTER_VERSION,
|
|
1783
|
+
copilot: import_copilot5.COPILOT_ADAPTER_VERSION
|
|
1177
1784
|
};
|
|
1178
1785
|
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
1179
1786
|
async function gatherItems(adapters) {
|
|
@@ -1196,7 +1803,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1196
1803
|
summary: "Internal: report integration status to the backend",
|
|
1197
1804
|
usage: "birdybeep report-status [--json]",
|
|
1198
1805
|
run: async (ctx) => {
|
|
1199
|
-
const token = await (0,
|
|
1806
|
+
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1200
1807
|
if (token === null) {
|
|
1201
1808
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
1202
1809
|
return EXIT.ERROR;
|
|
@@ -1217,7 +1824,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1217
1824
|
});
|
|
1218
1825
|
if (res.ok) {
|
|
1219
1826
|
outcome = "reported";
|
|
1220
|
-
const parsed =
|
|
1827
|
+
const parsed = import_agent_core11.integrationStatusResponseSchema.safeParse(
|
|
1221
1828
|
await res.json().catch(() => void 0)
|
|
1222
1829
|
);
|
|
1223
1830
|
if (parsed.success) {
|
|
@@ -1227,7 +1834,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1227
1834
|
}));
|
|
1228
1835
|
}
|
|
1229
1836
|
} else {
|
|
1230
|
-
const env =
|
|
1837
|
+
const env = import_agent_core11.errorEnvelopeSchema.safeParse(await res.json().catch(() => void 0));
|
|
1231
1838
|
errorCode = env.success ? env.data.error.code : void 0;
|
|
1232
1839
|
const terminal = errorCode !== void 0 ? errorCode === "unauthorized" || errorCode === "forbidden" || errorCode === "token_revoked" : res.status === 401 || res.status === 403;
|
|
1233
1840
|
outcome = terminal ? "terminal" : "deferred";
|
|
@@ -1258,22 +1865,22 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1258
1865
|
}
|
|
1259
1866
|
|
|
1260
1867
|
// src/commands/status.ts
|
|
1261
|
-
var
|
|
1262
|
-
var
|
|
1263
|
-
var
|
|
1264
|
-
var
|
|
1265
|
-
var
|
|
1266
|
-
var
|
|
1868
|
+
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1869
|
+
var import_claude_code7 = require("@birdybeep/claude-code");
|
|
1870
|
+
var import_codex6 = require("@birdybeep/codex");
|
|
1871
|
+
var import_copilot6 = require("@birdybeep/copilot");
|
|
1872
|
+
var import_cursor7 = require("@birdybeep/cursor");
|
|
1873
|
+
var import_opencode6 = require("@birdybeep/opencode");
|
|
1267
1874
|
var DEFAULT_ADAPTERS4 = [
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1875
|
+
import_claude_code7.claudeCodeAdapter,
|
|
1876
|
+
import_codex6.codexAdapter,
|
|
1877
|
+
import_opencode6.opencodeAdapter,
|
|
1878
|
+
import_cursor7.cursorAdapter,
|
|
1879
|
+
import_copilot6.copilotAdapter
|
|
1273
1880
|
];
|
|
1274
1881
|
function createStatusCommand(deps = {}) {
|
|
1275
1882
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
1276
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0,
|
|
1883
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1277
1884
|
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1278
1885
|
));
|
|
1279
1886
|
return {
|
|
@@ -1284,14 +1891,21 @@ function createStatusCommand(deps = {}) {
|
|
|
1284
1891
|
const machine = machineIdentity();
|
|
1285
1892
|
const paired = await isPaired(deps.tokenOptions ?? {});
|
|
1286
1893
|
const integrations = await gatherIntegrations(adapters);
|
|
1894
|
+
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1287
1895
|
const depthBefore = localQueueDepth();
|
|
1896
|
+
const unpaired = unpairedActivity();
|
|
1897
|
+
const filtered = filteredActivity();
|
|
1288
1898
|
const drain = await makeSender(resolveApiUrl()).drainNow();
|
|
1289
1899
|
const depthAfter = localQueueDepth();
|
|
1900
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
1290
1901
|
const report = {
|
|
1291
1902
|
machine,
|
|
1292
1903
|
paired,
|
|
1293
1904
|
integrations,
|
|
1294
|
-
|
|
1905
|
+
surfaces,
|
|
1906
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
1907
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
1908
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
1295
1909
|
};
|
|
1296
1910
|
if (ctx.flags.json) {
|
|
1297
1911
|
ctx.io.result(report);
|
|
@@ -1299,85 +1913,29 @@ function createStatusCommand(deps = {}) {
|
|
|
1299
1913
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1300
1914
|
ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
|
|
1301
1915
|
ctx.io.line("Integrations:");
|
|
1302
|
-
for (const i of integrations)
|
|
1916
|
+
for (const i of integrations) {
|
|
1917
|
+
ctx.io.line(` ${i.displayName}: ${i.status}`);
|
|
1918
|
+
const group = surfaces.find((g) => g.harness === i.harness);
|
|
1919
|
+
for (const state of group?.surfaces ?? []) {
|
|
1920
|
+
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
1921
|
+
ctx.io.line(` ${mark} ${describeSurface(state)} \u2014 ${state.coverage}`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1303
1924
|
ctx.io.line(
|
|
1304
|
-
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
1925
|
+
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `, ${overflowDropped} dropped by the queue cap` : "")
|
|
1305
1926
|
);
|
|
1927
|
+
if (unpaired !== null) ctx.io.line(`\u26A0 Lost: ${describeUnpairedActivity(unpaired)}`);
|
|
1928
|
+
if (filtered !== null) ctx.io.line(`Local: ${describeFilteredActivity(filtered)}`);
|
|
1306
1929
|
}
|
|
1307
1930
|
return paired ? EXIT.OK : EXIT.ERROR;
|
|
1308
1931
|
}
|
|
1309
1932
|
};
|
|
1310
1933
|
}
|
|
1311
1934
|
|
|
1312
|
-
// src/commands/test.ts
|
|
1313
|
-
var import_node_crypto2 = require("crypto");
|
|
1314
|
-
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1315
|
-
function buildTestEvent(opts = {}) {
|
|
1316
|
-
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
1317
|
-
return (0, import_agent_core12.normalizeEvent)(
|
|
1318
|
-
{
|
|
1319
|
-
event_type: "test",
|
|
1320
|
-
status: "running",
|
|
1321
|
-
harness: "claude_code",
|
|
1322
|
-
// schema requires a harness; the "test" type distinguishes it
|
|
1323
|
-
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1324
|
-
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1325
|
-
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1326
|
-
machine: { label: machine.label, os: machine.os },
|
|
1327
|
-
workspace: { cwd: process.cwd() },
|
|
1328
|
-
title: "BirdyBeep test event",
|
|
1329
|
-
body: "If you can see this, your machine is wired up correctly.",
|
|
1330
|
-
metadata: { test: true }
|
|
1331
|
-
},
|
|
1332
|
-
opts
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
function createTestCommand(deps = {}) {
|
|
1336
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1337
|
-
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1338
|
-
));
|
|
1339
|
-
return {
|
|
1340
|
-
name: "test",
|
|
1341
|
-
summary: "Send a test event end-to-end",
|
|
1342
|
-
usage: "birdybeep test [--json]",
|
|
1343
|
-
run: async (ctx) => {
|
|
1344
|
-
const event = buildTestEvent();
|
|
1345
|
-
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1346
|
-
if (ctx.flags.json) {
|
|
1347
|
-
ctx.io.result({
|
|
1348
|
-
outcome: result.outcome,
|
|
1349
|
-
...result.status ? { status: result.status } : {},
|
|
1350
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1351
|
-
});
|
|
1352
|
-
} else if (result.outcome === "delivered") {
|
|
1353
|
-
if (result.decision === "notified" || result.decision === void 0) {
|
|
1354
|
-
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1355
|
-
} else if (result.decision === "suppressed") {
|
|
1356
|
-
ctx.io.line(
|
|
1357
|
-
"\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`."
|
|
1358
|
-
);
|
|
1359
|
-
} else if (result.decision === "deduped") {
|
|
1360
|
-
ctx.io.line(
|
|
1361
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1362
|
-
);
|
|
1363
|
-
} else {
|
|
1364
|
-
ctx.io.line(
|
|
1365
|
-
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
} else if (result.outcome === "queued") {
|
|
1369
|
-
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1370
|
-
} else {
|
|
1371
|
-
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1372
|
-
}
|
|
1373
|
-
return result.outcome === "dropped" ? EXIT.ERROR : EXIT.OK;
|
|
1374
|
-
}
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
1935
|
// src/commands.ts
|
|
1379
1936
|
function buildCommands() {
|
|
1380
1937
|
return [
|
|
1938
|
+
createSetupCommand(),
|
|
1381
1939
|
createPairCommand(),
|
|
1382
1940
|
createLogoutCommand(),
|
|
1383
1941
|
createUnpairCommand(),
|
|
@@ -1392,7 +1950,7 @@ function buildCommands() {
|
|
|
1392
1950
|
}
|
|
1393
1951
|
|
|
1394
1952
|
// src/update-check.ts
|
|
1395
|
-
var
|
|
1953
|
+
var import_node_fs6 = require("fs");
|
|
1396
1954
|
var import_node_path3 = require("path");
|
|
1397
1955
|
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1398
1956
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
@@ -1452,7 +2010,7 @@ function updateCachePath() {
|
|
|
1452
2010
|
}
|
|
1453
2011
|
function readUpdateCache() {
|
|
1454
2012
|
try {
|
|
1455
|
-
const parsed = JSON.parse((0,
|
|
2013
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(updateCachePath(), "utf8"));
|
|
1456
2014
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1457
2015
|
const { checkedAt, latest } = parsed;
|
|
1458
2016
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1463,8 +2021,8 @@ function readUpdateCache() {
|
|
|
1463
2021
|
}
|
|
1464
2022
|
}
|
|
1465
2023
|
function writeUpdateCache(cache) {
|
|
1466
|
-
(0,
|
|
1467
|
-
(0,
|
|
2024
|
+
(0, import_node_fs6.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
2025
|
+
(0, import_node_fs6.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
|
|
1468
2026
|
`, { mode: 384 });
|
|
1469
2027
|
}
|
|
1470
2028
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|