@birdybeep/cli 0.4.0 → 0.6.0
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-BY5MNQE3.js} +745 -168
- package/dist/chunk-BY5MNQE3.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/index.cjs
CHANGED
|
@@ -43,15 +43,191 @@ __export(index_exports, {
|
|
|
43
43
|
module.exports = __toCommonJS(index_exports);
|
|
44
44
|
|
|
45
45
|
// src/commands/agent.ts
|
|
46
|
-
var
|
|
46
|
+
var import_claude_code2 = require("@birdybeep/claude-code");
|
|
47
47
|
var import_codex = require("@birdybeep/codex");
|
|
48
48
|
var import_copilot = require("@birdybeep/copilot");
|
|
49
|
-
var
|
|
49
|
+
var import_cursor2 = require("@birdybeep/cursor");
|
|
50
50
|
var import_opencode = require("@birdybeep/opencode");
|
|
51
51
|
|
|
52
|
-
// src/
|
|
52
|
+
// src/diagnostics.ts
|
|
53
53
|
var import_node_fs = require("fs");
|
|
54
|
+
var import_node_os = require("os");
|
|
54
55
|
var import_agent_core = require("@birdybeep/agent-core");
|
|
56
|
+
var import_claude_code = require("@birdybeep/claude-code");
|
|
57
|
+
var import_cursor = require("@birdybeep/cursor");
|
|
58
|
+
async function gatherIntegrations(adapters) {
|
|
59
|
+
return Promise.all(
|
|
60
|
+
adapters.map(async (a) => ({
|
|
61
|
+
harness: a.id,
|
|
62
|
+
displayName: a.displayName,
|
|
63
|
+
status: await a.status()
|
|
64
|
+
}))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
async function isPaired(tokenOptions = {}) {
|
|
68
|
+
return await (0, import_agent_core.getToken)(tokenOptions) !== null;
|
|
69
|
+
}
|
|
70
|
+
function localQueueDepth() {
|
|
71
|
+
return new import_agent_core.LocalEventQueue().size();
|
|
72
|
+
}
|
|
73
|
+
function localQueueOverflowDrops() {
|
|
74
|
+
return new import_agent_core.LocalEventQueue().overflowDropCount();
|
|
75
|
+
}
|
|
76
|
+
function unpairedActivity() {
|
|
77
|
+
return (0, import_agent_core.readUnpairedNotice)();
|
|
78
|
+
}
|
|
79
|
+
function describeUnpairedActivity(notice) {
|
|
80
|
+
const since = new Date(notice.firstAt).toISOString();
|
|
81
|
+
const from = notice.harnesses.length > 0 ? ` from ${notice.harnesses.join(", ")}` : "";
|
|
82
|
+
return `${notice.count} event(s)${from} fired since ${since} and were NOT sent \u2014 this machine is not paired.`;
|
|
83
|
+
}
|
|
84
|
+
function asRecord(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
86
|
+
}
|
|
87
|
+
function birdyBeepHookCount(path, events, isBirdyBeepEntry) {
|
|
88
|
+
if (!(0, import_node_fs.existsSync)(path)) return 0;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const hooks = asRecord(asRecord(parsed)["hooks"]);
|
|
96
|
+
let present = 0;
|
|
97
|
+
for (const event of events) {
|
|
98
|
+
const entries = hooks[event];
|
|
99
|
+
if (Array.isArray(entries) && entries.some(isBirdyBeepEntry)) present += 1;
|
|
100
|
+
}
|
|
101
|
+
return present;
|
|
102
|
+
}
|
|
103
|
+
async function cursorBridgeOnly(opts = {}) {
|
|
104
|
+
const home = opts.home ?? (0, import_node_os.homedir)();
|
|
105
|
+
const detection = await (opts.detect ?? (() => (0, import_cursor.detectCursor)({ home })))();
|
|
106
|
+
if (!detection.detected) return false;
|
|
107
|
+
const claude = birdyBeepHookCount((0, import_claude_code.claudeSettingsPath)(home), import_claude_code.BIRDYBEEP_HOOK_EVENTS, import_claude_code.isBirdyBeepEntry);
|
|
108
|
+
if (claude === null || claude === 0) return false;
|
|
109
|
+
return birdyBeepHookCount((0, import_cursor.cursorHooksPath)(home), import_cursor.BIRDYBEEP_HOOK_EVENTS, import_cursor.isBirdyBeepEntry) === 0;
|
|
110
|
+
}
|
|
111
|
+
function filteredActivity() {
|
|
112
|
+
return (0, import_agent_core.readFilteredActivity)();
|
|
113
|
+
}
|
|
114
|
+
function describeFilteredActivity(activity) {
|
|
115
|
+
const types = Object.entries(activity.byType).sort(([, a], [, b]) => b - a).map(([type, n]) => `${type} \xD7${n}`).join(", ");
|
|
116
|
+
const since = new Date(activity.firstAt).toISOString();
|
|
117
|
+
return `${activity.count} local-only event(s) since ${since}${types ? ` (${types})` : ""} \u2014 hooks are firing; these types never beep, so they are not sent.`;
|
|
118
|
+
}
|
|
119
|
+
function machineIdentity() {
|
|
120
|
+
return (0, import_agent_core.getMachineIdentity)();
|
|
121
|
+
}
|
|
122
|
+
var CONFIGURED_STATUSES = /* @__PURE__ */ new Set([
|
|
123
|
+
"installed",
|
|
124
|
+
"needs_trust",
|
|
125
|
+
"needs_restart"
|
|
126
|
+
]);
|
|
127
|
+
function gradeSurfaces(surfaces, status, observation) {
|
|
128
|
+
const builds = Object.values(observation?.builds ?? {});
|
|
129
|
+
const configured = CONFIGURED_STATUSES.has(status);
|
|
130
|
+
const claimedByKind = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const s of surfaces) {
|
|
132
|
+
if (s.version === void 0) continue;
|
|
133
|
+
const versions = claimedByKind.get(s.kind) ?? /* @__PURE__ */ new Set();
|
|
134
|
+
versions.add(s.version);
|
|
135
|
+
claimedByKind.set(s.kind, versions);
|
|
136
|
+
}
|
|
137
|
+
const graded = surfaces.map((surface) => {
|
|
138
|
+
const exact = builds.filter(
|
|
139
|
+
(b) => b.surface === surface.kind && b.version === surface.version && surface.version !== void 0
|
|
140
|
+
);
|
|
141
|
+
let soleOfKind = [];
|
|
142
|
+
if (surface.version === void 0) {
|
|
143
|
+
const sameKindVersionless = surfaces.filter(
|
|
144
|
+
(s) => s.version === void 0 && s.kind === surface.kind
|
|
145
|
+
);
|
|
146
|
+
const unclaimed = builds.filter(
|
|
147
|
+
(b) => b.surface === surface.kind && !(claimedByKind.get(surface.kind)?.has(b.version) ?? false)
|
|
148
|
+
);
|
|
149
|
+
if (unclaimed.length === 1 && sameKindVersionless.length === 1) soleOfKind = unclaimed;
|
|
150
|
+
}
|
|
151
|
+
const unattributed = surface.version === void 0 ? [] : builds.filter((b) => b.surface === "unknown" && b.version === surface.version);
|
|
152
|
+
const sharesVersion = surface.version !== void 0 && surfaces.some((s) => s !== surface && s.version === surface.version);
|
|
153
|
+
const ambiguous = unattributed.length > 0 && sharesVersion;
|
|
154
|
+
const matched = [...exact, ...soleOfKind, ...ambiguous ? [] : unattributed];
|
|
155
|
+
const events = matched.reduce((total, b) => total + b.count, 0);
|
|
156
|
+
const lastAt = matched.reduce(
|
|
157
|
+
(latest, b) => latest === void 0 || b.lastAt > latest ? b.lastAt : latest,
|
|
158
|
+
void 0
|
|
159
|
+
);
|
|
160
|
+
const observedVersion = surface.version === void 0 ? soleOfKind[0]?.version : void 0;
|
|
161
|
+
return {
|
|
162
|
+
surface,
|
|
163
|
+
events,
|
|
164
|
+
ambiguous,
|
|
165
|
+
...lastAt !== void 0 ? { lastAt } : {},
|
|
166
|
+
...observedVersion !== void 0 ? { observedVersion } : {}
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const anyActive = graded.some((g) => g.events > 0 && g.surface.shadowed !== true);
|
|
170
|
+
return graded.map(({ ambiguous, ...g }) => ({
|
|
171
|
+
...g,
|
|
172
|
+
coverage: !configured ? "uncovered" : g.events > 0 ? "active" : anyActive && g.surface.shadowed !== true && !ambiguous ? "uncovered" : "wired"
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
async function gatherSurfaces(adapters, options = {}) {
|
|
176
|
+
const observed = (0, import_agent_core.readObservedBuilds)(options.observedBuilds ?? {});
|
|
177
|
+
return Promise.all(
|
|
178
|
+
adapters.map(async (adapter) => {
|
|
179
|
+
const observation = observed[adapter.id];
|
|
180
|
+
const base4 = {
|
|
181
|
+
harness: adapter.id,
|
|
182
|
+
displayName: adapter.displayName,
|
|
183
|
+
unversionedEvents: observation?.unversioned ?? 0
|
|
184
|
+
};
|
|
185
|
+
try {
|
|
186
|
+
const [detection, status] = await Promise.all([adapter.detect(), adapter.status()]);
|
|
187
|
+
return {
|
|
188
|
+
...base4,
|
|
189
|
+
status,
|
|
190
|
+
surfaces: detection.detected ? gradeSurfaces(detection.surfaces ?? [], status, observation) : []
|
|
191
|
+
};
|
|
192
|
+
} catch {
|
|
193
|
+
return { ...base4, status: "unknown", surfaces: [] };
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function describeSurface(state) {
|
|
199
|
+
const version = state.surface.version ?? state.observedVersion;
|
|
200
|
+
return version !== void 0 ? `${state.surface.label} ${version}` : state.surface.label;
|
|
201
|
+
}
|
|
202
|
+
function describeSurfaceCoverage(state, group) {
|
|
203
|
+
if (state.coverage === "active") {
|
|
204
|
+
const last = state.lastAt !== void 0 ? `, last ${new Date(state.lastAt).toISOString()}` : "";
|
|
205
|
+
return `covered \u2014 ${state.events} event(s) from this build${last}`;
|
|
206
|
+
}
|
|
207
|
+
if (state.coverage === "wired") {
|
|
208
|
+
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`;
|
|
209
|
+
}
|
|
210
|
+
if (!CONFIGURED_STATUSES.has(group.status)) {
|
|
211
|
+
return `not covered \u2014 ${group.displayName} carries no BirdyBeep hooks, so this build cannot beep`;
|
|
212
|
+
}
|
|
213
|
+
const active = group.surfaces.filter((s) => s.coverage === "active").map(describeSurface);
|
|
214
|
+
const delivering = active.join(", ");
|
|
215
|
+
const verb = active.length === 1 ? "is" : "are";
|
|
216
|
+
return `not covered \u2014 nothing has ever fired from this build, while ${delivering} ${verb} delivering through the same config`;
|
|
217
|
+
}
|
|
218
|
+
function installTarget(harness) {
|
|
219
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
220
|
+
}
|
|
221
|
+
function surfaceRemedy(state, group) {
|
|
222
|
+
if (state.coverage !== "uncovered") return void 0;
|
|
223
|
+
if (!CONFIGURED_STATUSES.has(group.status)) return void 0;
|
|
224
|
+
const install = `\`birdybeep agent install ${installTarget(group.harness)}\``;
|
|
225
|
+
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.`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/framework.ts
|
|
229
|
+
var import_node_fs2 = require("fs");
|
|
230
|
+
var import_agent_core2 = require("@birdybeep/agent-core");
|
|
55
231
|
var EXIT = { OK: 0, ERROR: 1, USAGE: 2 };
|
|
56
232
|
function createIo(json, stdout, stderr) {
|
|
57
233
|
return {
|
|
@@ -129,11 +305,17 @@ function isUnknownFlag(token, allowed) {
|
|
|
129
305
|
function renderRootHelp(version, commands) {
|
|
130
306
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
131
307
|
const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
308
|
+
const featured = commands.filter((c) => c.gettingStarted !== void 0);
|
|
132
309
|
return [
|
|
133
310
|
`birdybeep ${version} \u2014 stream coding-agent lifecycle events to BirdyBeep.`,
|
|
134
311
|
"",
|
|
135
312
|
"Usage:",
|
|
136
313
|
" birdybeep <command> [options]",
|
|
314
|
+
...featured.length > 0 ? [
|
|
315
|
+
"",
|
|
316
|
+
"Getting started:",
|
|
317
|
+
...featured.map((c) => ` birdybeep ${c.name} ${c.gettingStarted ?? ""}`)
|
|
318
|
+
] : [],
|
|
137
319
|
"",
|
|
138
320
|
"Commands:",
|
|
139
321
|
...lines,
|
|
@@ -188,7 +370,7 @@ async function dispatch(argv, deps) {
|
|
|
188
370
|
const io = createIo(flags.json, deps.stdout, deps.stderr);
|
|
189
371
|
if (deps.ensureConfig !== false) {
|
|
190
372
|
try {
|
|
191
|
-
(0,
|
|
373
|
+
(0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
192
374
|
} catch {
|
|
193
375
|
}
|
|
194
376
|
}
|
|
@@ -215,7 +397,11 @@ async function dispatch(argv, deps) {
|
|
|
215
397
|
if (rest.length === 0 || flags.help && command === void 0) {
|
|
216
398
|
io.emit(renderRootHelp(deps.version, deps.commands), {
|
|
217
399
|
version: deps.version,
|
|
218
|
-
commands: deps.commands.map((c) => ({
|
|
400
|
+
commands: deps.commands.map((c) => ({
|
|
401
|
+
name: c.name,
|
|
402
|
+
summary: c.summary,
|
|
403
|
+
...c.gettingStarted !== void 0 ? { gettingStarted: c.gettingStarted } : {}
|
|
404
|
+
}))
|
|
219
405
|
});
|
|
220
406
|
return EXIT.OK;
|
|
221
407
|
}
|
|
@@ -269,10 +455,10 @@ async function dispatch(argv, deps) {
|
|
|
269
455
|
|
|
270
456
|
// src/commands/agent.ts
|
|
271
457
|
var DEFAULT_ADAPTERS = [
|
|
272
|
-
|
|
458
|
+
import_claude_code2.claudeCodeAdapter,
|
|
273
459
|
import_codex.codexAdapter,
|
|
274
460
|
import_opencode.opencodeAdapter,
|
|
275
|
-
|
|
461
|
+
import_cursor2.cursorAdapter,
|
|
276
462
|
import_copilot.copilotAdapter
|
|
277
463
|
];
|
|
278
464
|
var TARGET_TO_ID = {
|
|
@@ -296,7 +482,10 @@ function selectAdapters(target, adapters) {
|
|
|
296
482
|
if (id === void 0) return "unknown";
|
|
297
483
|
return adapters.filter((a) => a.id === id);
|
|
298
484
|
}
|
|
299
|
-
|
|
485
|
+
function installTarget2(harness) {
|
|
486
|
+
return harness === "claude_code" ? "claude" : harness;
|
|
487
|
+
}
|
|
488
|
+
async function installSelected(adapters, ctx, tokenOptions) {
|
|
300
489
|
const target = ctx.args[0] ?? "all";
|
|
301
490
|
const selected = selectAdapters(target, adapters);
|
|
302
491
|
if (selected === "unknown") {
|
|
@@ -323,8 +512,9 @@ async function installSelected(adapters, ctx) {
|
|
|
323
512
|
requiredActions: result.requiredActions
|
|
324
513
|
});
|
|
325
514
|
}
|
|
515
|
+
const paired = await isPaired(tokenOptions);
|
|
326
516
|
if (ctx.flags.json) {
|
|
327
|
-
ctx.io.result({ target, results: outcomes });
|
|
517
|
+
ctx.io.result({ target, paired, results: outcomes });
|
|
328
518
|
return EXIT.OK;
|
|
329
519
|
}
|
|
330
520
|
if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {
|
|
@@ -332,13 +522,20 @@ async function installSelected(adapters, ctx) {
|
|
|
332
522
|
}
|
|
333
523
|
for (const o of outcomes) {
|
|
334
524
|
if (!o.detected) {
|
|
335
|
-
ctx.io.line(
|
|
525
|
+
ctx.io.line(
|
|
526
|
+
`\u2013 ${o.displayName}: not detected (skipped) \u2014 install it, then run \`birdybeep agent install ${installTarget2(o.harness)}\``
|
|
527
|
+
);
|
|
336
528
|
continue;
|
|
337
529
|
}
|
|
338
530
|
const changed = (o.changedFiles ?? []).length > 0 ? o.changedFiles.join(", ") : "no changes";
|
|
339
531
|
ctx.io.line(`\u2713 ${o.displayName}: ${o.status} (${changed})`);
|
|
340
532
|
for (const action of o.requiredActions ?? []) ctx.io.line(` \u2192 ${action}`);
|
|
341
533
|
}
|
|
534
|
+
if (!paired) {
|
|
535
|
+
ctx.io.line(
|
|
536
|
+
"\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."
|
|
537
|
+
);
|
|
538
|
+
}
|
|
342
539
|
return EXIT.OK;
|
|
343
540
|
}
|
|
344
541
|
async function uninstallSelected(adapters, ctx) {
|
|
@@ -377,6 +574,7 @@ async function uninstallSelected(adapters, ctx) {
|
|
|
377
574
|
}
|
|
378
575
|
function createAgentCommand(deps = {}) {
|
|
379
576
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS;
|
|
577
|
+
const tokenOptions = deps.tokenOptions ?? {};
|
|
380
578
|
return {
|
|
381
579
|
name: "agent",
|
|
382
580
|
summary: "Install or uninstall harness adapters",
|
|
@@ -386,7 +584,7 @@ function createAgentCommand(deps = {}) {
|
|
|
386
584
|
name: "install",
|
|
387
585
|
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
388
586
|
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
389
|
-
run: (ctx) => installSelected(adapters, ctx)
|
|
587
|
+
run: (ctx) => installSelected(adapters, ctx, tokenOptions)
|
|
390
588
|
},
|
|
391
589
|
{
|
|
392
590
|
name: "uninstall",
|
|
@@ -400,24 +598,24 @@ function createAgentCommand(deps = {}) {
|
|
|
400
598
|
|
|
401
599
|
// src/commands/doctor.ts
|
|
402
600
|
var import_agent_core4 = require("@birdybeep/agent-core");
|
|
403
|
-
var
|
|
601
|
+
var import_claude_code3 = require("@birdybeep/claude-code");
|
|
404
602
|
var import_codex2 = require("@birdybeep/codex");
|
|
405
603
|
var import_copilot2 = require("@birdybeep/copilot");
|
|
406
|
-
var
|
|
604
|
+
var import_cursor3 = require("@birdybeep/cursor");
|
|
407
605
|
var import_opencode2 = require("@birdybeep/opencode");
|
|
408
606
|
|
|
409
607
|
// src/config.ts
|
|
410
|
-
var
|
|
608
|
+
var import_node_fs3 = require("fs");
|
|
411
609
|
var import_node_path = require("path");
|
|
412
|
-
var
|
|
610
|
+
var import_agent_core3 = require("@birdybeep/agent-core");
|
|
413
611
|
var DEFAULT_API_URL = "https://api.birdybeep.com";
|
|
414
612
|
var CONFIG_FILE = "config.json";
|
|
415
613
|
function cliConfigPath() {
|
|
416
|
-
return (0, import_node_path.join)((0,
|
|
614
|
+
return (0, import_node_path.join)((0, import_agent_core3.birdyBeepConfigDir)(), CONFIG_FILE);
|
|
417
615
|
}
|
|
418
616
|
function readCliConfig() {
|
|
419
617
|
try {
|
|
420
|
-
const parsed = JSON.parse((0,
|
|
618
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(cliConfigPath(), "utf8"));
|
|
421
619
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
422
620
|
} catch {
|
|
423
621
|
return {};
|
|
@@ -430,8 +628,8 @@ function writeCliConfig(patch) {
|
|
|
430
628
|
if (apiUrl !== void 0) merged.apiUrl = apiUrl;
|
|
431
629
|
const expectEmail = patch.expectEmail ?? current.expectEmail;
|
|
432
630
|
if (expectEmail !== void 0) merged.expectEmail = expectEmail;
|
|
433
|
-
(0,
|
|
434
|
-
(0,
|
|
631
|
+
(0, import_node_fs3.mkdirSync)((0, import_agent_core3.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
632
|
+
(0, import_node_fs3.writeFileSync)(cliConfigPath(), `${JSON.stringify(merged, null, 2)}
|
|
435
633
|
`, { mode: 384 });
|
|
436
634
|
}
|
|
437
635
|
function resolveApiUrl() {
|
|
@@ -446,33 +644,12 @@ function resolveRegistryUrl() {
|
|
|
446
644
|
return DEFAULT_REGISTRY_URL;
|
|
447
645
|
}
|
|
448
646
|
|
|
449
|
-
// src/diagnostics.ts
|
|
450
|
-
var import_agent_core3 = require("@birdybeep/agent-core");
|
|
451
|
-
async function gatherIntegrations(adapters) {
|
|
452
|
-
return Promise.all(
|
|
453
|
-
adapters.map(async (a) => ({
|
|
454
|
-
harness: a.id,
|
|
455
|
-
displayName: a.displayName,
|
|
456
|
-
status: await a.status()
|
|
457
|
-
}))
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
async function isPaired(tokenOptions = {}) {
|
|
461
|
-
return await (0, import_agent_core3.getToken)(tokenOptions) !== null;
|
|
462
|
-
}
|
|
463
|
-
function localQueueDepth() {
|
|
464
|
-
return new import_agent_core3.LocalEventQueue().size();
|
|
465
|
-
}
|
|
466
|
-
function machineIdentity() {
|
|
467
|
-
return (0, import_agent_core3.getMachineIdentity)();
|
|
468
|
-
}
|
|
469
|
-
|
|
470
647
|
// src/commands/doctor.ts
|
|
471
648
|
var DEFAULT_ADAPTERS2 = [
|
|
472
|
-
|
|
649
|
+
import_claude_code3.claudeCodeAdapter,
|
|
473
650
|
import_codex2.codexAdapter,
|
|
474
651
|
import_opencode2.opencodeAdapter,
|
|
475
|
-
|
|
652
|
+
import_cursor3.cursorAdapter,
|
|
476
653
|
import_copilot2.copilotAdapter
|
|
477
654
|
];
|
|
478
655
|
async function defaultProbeNetwork(baseUrl) {
|
|
@@ -509,6 +686,32 @@ function createDoctorCommand(deps = {}) {
|
|
|
509
686
|
remedy: "Run `birdybeep pair` to pair this machine."
|
|
510
687
|
}
|
|
511
688
|
);
|
|
689
|
+
const unpaired = unpairedActivity();
|
|
690
|
+
if (unpaired !== null) {
|
|
691
|
+
checks.push({
|
|
692
|
+
name: "Events lost while unpaired",
|
|
693
|
+
ok: false,
|
|
694
|
+
detail: describeUnpairedActivity(unpaired),
|
|
695
|
+
remedy: "Run `birdybeep pair`. Events that fired before pairing are gone \u2014 a first pairing does not replay them."
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
if (await cursorBridgeOnly(deps.detectCursor ? { detect: deps.detectCursor } : {})) {
|
|
699
|
+
checks.push({
|
|
700
|
+
name: "Approval beeps from Cursor",
|
|
701
|
+
ok: false,
|
|
702
|
+
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.",
|
|
703
|
+
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."
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const filtered = filteredActivity();
|
|
707
|
+
if (filtered !== null) {
|
|
708
|
+
checks.push({
|
|
709
|
+
name: "Local-only events (never notifiable)",
|
|
710
|
+
ok: true,
|
|
711
|
+
detail: describeFilteredActivity(filtered)
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const surfaceGroups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
512
715
|
for (const adapter of adapters) {
|
|
513
716
|
const result = await adapter.doctor();
|
|
514
717
|
for (const c of result.checks) {
|
|
@@ -519,14 +722,26 @@ function createDoctorCommand(deps = {}) {
|
|
|
519
722
|
...c.remedy !== void 0 ? { remedy: c.remedy } : {}
|
|
520
723
|
});
|
|
521
724
|
}
|
|
725
|
+
const group = surfaceGroups.find((g) => g.harness === adapter.id);
|
|
726
|
+
if (group === void 0) continue;
|
|
727
|
+
for (const state of group.surfaces) {
|
|
728
|
+
const remedy = surfaceRemedy(state, group);
|
|
729
|
+
checks.push({
|
|
730
|
+
name: `${adapter.displayName}: ${describeSurface(state)}`,
|
|
731
|
+
ok: state.coverage !== "uncovered",
|
|
732
|
+
detail: describeSurfaceCoverage(state, group),
|
|
733
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
522
736
|
}
|
|
523
737
|
const depthBefore = localQueueDepth();
|
|
524
738
|
const drain = await makeSender(apiUrl).drainNow();
|
|
525
739
|
const depthAfter = localQueueDepth();
|
|
740
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
526
741
|
checks.push({
|
|
527
742
|
name: "Local queue",
|
|
528
743
|
ok: true,
|
|
529
|
-
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
744
|
+
detail: `${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `; ${overflowDropped} dropped by the ${import_agent_core4.DEFAULT_QUEUE_MAX_ENTRIES} entry cap` : "")
|
|
530
745
|
});
|
|
531
746
|
const reachable = await probeNetwork(apiUrl);
|
|
532
747
|
checks.push(
|
|
@@ -542,7 +757,10 @@ function createDoctorCommand(deps = {}) {
|
|
|
542
757
|
ctx.io.result({
|
|
543
758
|
ok,
|
|
544
759
|
checks,
|
|
545
|
-
|
|
760
|
+
surfaces: surfaceGroups,
|
|
761
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
762
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
763
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
546
764
|
});
|
|
547
765
|
} else {
|
|
548
766
|
for (const c of checks) {
|
|
@@ -559,20 +777,20 @@ function createDoctorCommand(deps = {}) {
|
|
|
559
777
|
// src/commands/hook.ts
|
|
560
778
|
var import_node_child_process = require("child_process");
|
|
561
779
|
var import_node_crypto = require("crypto");
|
|
562
|
-
var
|
|
563
|
-
var
|
|
780
|
+
var import_node_fs4 = require("fs");
|
|
781
|
+
var import_node_os2 = require("os");
|
|
564
782
|
var import_node_path2 = require("path");
|
|
565
783
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
566
|
-
var
|
|
784
|
+
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
567
785
|
var import_codex3 = require("@birdybeep/codex");
|
|
568
786
|
var import_copilot3 = require("@birdybeep/copilot");
|
|
569
|
-
var
|
|
787
|
+
var import_cursor4 = require("@birdybeep/cursor");
|
|
570
788
|
var import_opencode3 = require("@birdybeep/opencode");
|
|
571
789
|
var RUNNERS = {
|
|
572
|
-
claude:
|
|
790
|
+
claude: import_claude_code4.runClaudeHook,
|
|
573
791
|
codex: import_codex3.runCodexHook,
|
|
574
792
|
opencode: import_opencode3.runOpenCodeHook,
|
|
575
|
-
cursor:
|
|
793
|
+
cursor: import_cursor4.runCursorHook
|
|
576
794
|
};
|
|
577
795
|
var HOOK_HARNESSES = [
|
|
578
796
|
"claude",
|
|
@@ -600,20 +818,34 @@ function isHarnessName(value) {
|
|
|
600
818
|
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
601
819
|
}
|
|
602
820
|
function resolveHookHarness(harness, payload) {
|
|
603
|
-
return harness === "claude" && (0,
|
|
821
|
+
return harness === "claude" && (0, import_cursor4.isCursorHookPayload)(payload) ? "cursor" : harness;
|
|
604
822
|
}
|
|
605
823
|
function recognizesPayload(harness, payload) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
824
|
+
switch (harness) {
|
|
825
|
+
case "claude":
|
|
826
|
+
return (0, import_claude_code4.isClaudeCodeHookPayload)(payload);
|
|
827
|
+
case "cursor":
|
|
828
|
+
return (0, import_cursor4.isCursorHookEventName)(asRecord2(payload)["hook_event_name"]);
|
|
829
|
+
case "codex":
|
|
830
|
+
return (0, import_codex3.isCodexHookPayload)(payload);
|
|
831
|
+
case "opencode":
|
|
832
|
+
return (0, import_opencode3.isOpenCodeEventPayload)(payload);
|
|
833
|
+
case "copilot":
|
|
834
|
+
return (0, import_copilot3.isCopilotHookPayload)(payload);
|
|
835
|
+
}
|
|
609
836
|
}
|
|
610
|
-
function
|
|
837
|
+
function asRecord2(value) {
|
|
611
838
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
612
839
|
}
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
840
|
+
function describeDiscriminator(payload) {
|
|
841
|
+
const record = asRecord2(payload);
|
|
842
|
+
for (const field of ["hook_event_name", "type"]) {
|
|
843
|
+
const value = record[field];
|
|
844
|
+
if (typeof value !== "string") continue;
|
|
845
|
+
const capped = value.length > 64 ? `${value.slice(0, 63)}\u2026` : value;
|
|
846
|
+
return `${field} ${JSON.stringify(capped)}`;
|
|
847
|
+
}
|
|
848
|
+
return "the payload";
|
|
617
849
|
}
|
|
618
850
|
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
619
851
|
const handler = resolveHookHarness(harness, payload);
|
|
@@ -647,10 +879,10 @@ function detachCodexNotifyWorker(payload) {
|
|
|
647
879
|
try {
|
|
648
880
|
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
649
881
|
if (birdybeep === null) return false;
|
|
650
|
-
const tmpFile = (0, import_node_path2.join)((0,
|
|
882
|
+
const tmpFile = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `birdybeep-notify-${(0, import_node_crypto.randomBytes)(16).toString("hex")}.json`);
|
|
651
883
|
file = tmpFile;
|
|
652
|
-
(0,
|
|
653
|
-
fd = (0,
|
|
884
|
+
(0, import_node_fs4.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
885
|
+
fd = (0, import_node_fs4.openSync)(tmpFile, "r");
|
|
654
886
|
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
655
887
|
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
656
888
|
// trusted dir, never the inherited/attacker cwd
|
|
@@ -664,7 +896,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
664
896
|
});
|
|
665
897
|
child.on("error", () => {
|
|
666
898
|
try {
|
|
667
|
-
(0,
|
|
899
|
+
(0, import_node_fs4.rmSync)(tmpFile, { force: true });
|
|
668
900
|
} catch {
|
|
669
901
|
}
|
|
670
902
|
});
|
|
@@ -673,7 +905,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
673
905
|
} catch {
|
|
674
906
|
if (file !== void 0) {
|
|
675
907
|
try {
|
|
676
|
-
(0,
|
|
908
|
+
(0, import_node_fs4.rmSync)(file, { force: true });
|
|
677
909
|
} catch {
|
|
678
910
|
}
|
|
679
911
|
}
|
|
@@ -681,7 +913,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
681
913
|
} finally {
|
|
682
914
|
if (fd !== void 0) {
|
|
683
915
|
try {
|
|
684
|
-
(0,
|
|
916
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
685
917
|
} catch {
|
|
686
918
|
}
|
|
687
919
|
}
|
|
@@ -708,42 +940,75 @@ function createHookCommand(deps = {}) {
|
|
|
708
940
|
return EXIT.OK;
|
|
709
941
|
}
|
|
710
942
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
711
|
-
|
|
943
|
+
if (harness === "copilot" && copilotEventName === void 0) {
|
|
944
|
+
ctx.io.errline(
|
|
945
|
+
`birdybeep hook copilot: second argument must be a Copilot hook event name, got ${JSON.stringify(ctx.args[1] ?? "(none)")} \u2014 nothing was sent.`
|
|
946
|
+
);
|
|
947
|
+
return EXIT.USAGE;
|
|
948
|
+
}
|
|
949
|
+
const read = await withTimeout(
|
|
712
950
|
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
713
951
|
stdinTimeoutMs,
|
|
714
|
-
|
|
952
|
+
null
|
|
715
953
|
);
|
|
716
954
|
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
717
|
-
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0,
|
|
955
|
+
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0, import_node_os2.tmpdir)() && (0, import_node_path2.basename)(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
718
956
|
try {
|
|
719
|
-
(0,
|
|
957
|
+
(0, import_node_fs4.rmSync)(notifyStdinFile, { force: true });
|
|
720
958
|
} catch {
|
|
721
959
|
}
|
|
722
960
|
}
|
|
961
|
+
const drop = (reason, detail) => {
|
|
962
|
+
ctx.io.result({ harness, outcome: "skipped", reason });
|
|
963
|
+
ctx.io.errline(`birdybeep hook ${harness}: ${detail} \u2014 nothing was sent.`);
|
|
964
|
+
return EXIT.ERROR;
|
|
965
|
+
};
|
|
966
|
+
if (read === null) {
|
|
967
|
+
return drop(
|
|
968
|
+
"stdin-timeout",
|
|
969
|
+
`timed out after ${stdinTimeoutMs}ms waiting for the payload on stdin`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
const raw = read;
|
|
973
|
+
if (raw.trim().length === 0) {
|
|
974
|
+
return drop("empty-payload", "the payload was empty");
|
|
975
|
+
}
|
|
723
976
|
let payload;
|
|
724
977
|
try {
|
|
725
978
|
payload = JSON.parse(raw);
|
|
726
979
|
} catch {
|
|
727
|
-
|
|
728
|
-
return EXIT.OK;
|
|
980
|
+
return drop("invalid-json", `the ${raw.length}-byte payload is not valid JSON`);
|
|
729
981
|
}
|
|
730
|
-
const sender = makeSender(resolveApiUrl());
|
|
731
982
|
const handler = resolveHookHarness(harness, payload);
|
|
983
|
+
const routedFrom = handler !== harness ? { routedFrom: harness } : {};
|
|
984
|
+
if (!recognizesPayload(handler, payload)) {
|
|
985
|
+
ctx.io.result({
|
|
986
|
+
harness: handler,
|
|
987
|
+
...routedFrom,
|
|
988
|
+
outcome: "skipped",
|
|
989
|
+
reason: "foreign-payload"
|
|
990
|
+
});
|
|
991
|
+
const article = handler === "opencode" ? "an" : "a";
|
|
992
|
+
ctx.io.errline(
|
|
993
|
+
`birdybeep hook ${harness}: ${describeDiscriminator(payload)} is not ${article} ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
|
|
994
|
+
);
|
|
995
|
+
return EXIT.ERROR;
|
|
996
|
+
}
|
|
997
|
+
const sender = makeSender(resolveApiUrl());
|
|
732
998
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
733
999
|
ctx.io.result({
|
|
734
1000
|
harness: handler,
|
|
735
|
-
...
|
|
1001
|
+
...routedFrom,
|
|
736
1002
|
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
737
1003
|
outcome: result.outcome,
|
|
738
1004
|
eventType: result.eventType,
|
|
739
1005
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
740
1006
|
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
741
1007
|
});
|
|
742
|
-
if (result.outcome === "
|
|
1008
|
+
if (result.outcome === "unpaired") {
|
|
743
1009
|
ctx.io.errline(
|
|
744
|
-
|
|
1010
|
+
"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)."
|
|
745
1011
|
);
|
|
746
|
-
return EXIT.ERROR;
|
|
747
1012
|
}
|
|
748
1013
|
return EXIT.OK;
|
|
749
1014
|
}
|
|
@@ -802,8 +1067,8 @@ function createUnpairCommand(deps = {}) {
|
|
|
802
1067
|
}
|
|
803
1068
|
|
|
804
1069
|
// src/commands/pair.ts
|
|
805
|
-
var
|
|
806
|
-
var
|
|
1070
|
+
var import_node_fs5 = require("fs");
|
|
1071
|
+
var import_agent_core9 = require("@birdybeep/agent-core");
|
|
807
1072
|
var import_uqr = require("uqr");
|
|
808
1073
|
|
|
809
1074
|
// src/pairing.ts
|
|
@@ -877,7 +1142,277 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
877
1142
|
}
|
|
878
1143
|
|
|
879
1144
|
// src/version.ts
|
|
880
|
-
var CLI_VERSION = "0.
|
|
1145
|
+
var CLI_VERSION = "0.6.0".length > 0 ? "0.6.0" : "0.0.0";
|
|
1146
|
+
|
|
1147
|
+
// src/commands/setup.ts
|
|
1148
|
+
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
1149
|
+
var import_codex4 = require("@birdybeep/codex");
|
|
1150
|
+
var import_copilot4 = require("@birdybeep/copilot");
|
|
1151
|
+
var import_cursor5 = require("@birdybeep/cursor");
|
|
1152
|
+
var import_opencode4 = require("@birdybeep/opencode");
|
|
1153
|
+
|
|
1154
|
+
// src/commands/test.ts
|
|
1155
|
+
var import_node_crypto2 = require("crypto");
|
|
1156
|
+
var import_agent_core8 = require("@birdybeep/agent-core");
|
|
1157
|
+
function buildTestEvent(opts = {}) {
|
|
1158
|
+
const machine = (0, import_agent_core8.getMachineIdentity)();
|
|
1159
|
+
return (0, import_agent_core8.normalizeEvent)(
|
|
1160
|
+
{
|
|
1161
|
+
event_type: "test",
|
|
1162
|
+
status: "running",
|
|
1163
|
+
harness: "claude_code",
|
|
1164
|
+
// schema requires a harness; the "test" type distinguishes it
|
|
1165
|
+
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1166
|
+
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1167
|
+
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1168
|
+
machine: { label: machine.label, os: machine.os },
|
|
1169
|
+
workspace: { cwd: process.cwd() },
|
|
1170
|
+
title: "BirdyBeep test event",
|
|
1171
|
+
body: "If you can see this, your machine is wired up correctly.",
|
|
1172
|
+
metadata: { test: true }
|
|
1173
|
+
},
|
|
1174
|
+
opts
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
function createTestCommand(deps = {}) {
|
|
1178
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core8.createSender)(
|
|
1179
|
+
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1180
|
+
));
|
|
1181
|
+
return {
|
|
1182
|
+
name: "test",
|
|
1183
|
+
summary: "Send a test event end-to-end",
|
|
1184
|
+
usage: "birdybeep test [--json]",
|
|
1185
|
+
run: async (ctx) => {
|
|
1186
|
+
const event = buildTestEvent();
|
|
1187
|
+
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1188
|
+
if (ctx.flags.json) {
|
|
1189
|
+
ctx.io.result({
|
|
1190
|
+
outcome: result.outcome,
|
|
1191
|
+
...result.status ? { status: result.status } : {},
|
|
1192
|
+
...result.decision ? { decision: result.decision } : {}
|
|
1193
|
+
});
|
|
1194
|
+
} else if (result.outcome === "delivered") {
|
|
1195
|
+
if (result.decision === "notified" || result.decision === void 0) {
|
|
1196
|
+
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1197
|
+
} else if (result.decision === "suppressed") {
|
|
1198
|
+
ctx.io.line(
|
|
1199
|
+
"\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`."
|
|
1200
|
+
);
|
|
1201
|
+
} else if (result.decision === "deduped") {
|
|
1202
|
+
ctx.io.line(
|
|
1203
|
+
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1204
|
+
);
|
|
1205
|
+
} else {
|
|
1206
|
+
ctx.io.line(
|
|
1207
|
+
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
} else if (result.outcome === "unpaired") {
|
|
1211
|
+
ctx.io.line(
|
|
1212
|
+
"\u2717 NOT PAIRED \u2014 this machine has no BirdyBeep machine token, so nothing was sent (and nothing was queued). Run `birdybeep pair`."
|
|
1213
|
+
);
|
|
1214
|
+
} else if (result.outcome === "queued") {
|
|
1215
|
+
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1216
|
+
} else {
|
|
1217
|
+
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1218
|
+
}
|
|
1219
|
+
return result.outcome === "dropped" || result.outcome === "unpaired" ? EXIT.ERROR : EXIT.OK;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/commands/setup.ts
|
|
1225
|
+
var SETUP_ADAPTERS = [
|
|
1226
|
+
import_claude_code5.claudeCodeAdapter,
|
|
1227
|
+
import_codex4.codexAdapter,
|
|
1228
|
+
import_opencode4.opencodeAdapter,
|
|
1229
|
+
import_cursor5.cursorAdapter,
|
|
1230
|
+
import_copilot4.copilotAdapter
|
|
1231
|
+
];
|
|
1232
|
+
function failedSetupReport(error) {
|
|
1233
|
+
return {
|
|
1234
|
+
harnesses: [],
|
|
1235
|
+
counts: { installed: 0, needsYou: 0, notInstalled: 0, failed: 0 },
|
|
1236
|
+
error,
|
|
1237
|
+
ok: false
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
var PENDING_STATUSES = /* @__PURE__ */ new Set([
|
|
1241
|
+
"needs_trust",
|
|
1242
|
+
"needs_restart"
|
|
1243
|
+
]);
|
|
1244
|
+
async function installDetected(adapters) {
|
|
1245
|
+
const installs = [];
|
|
1246
|
+
for (const adapter of adapters) {
|
|
1247
|
+
try {
|
|
1248
|
+
const detection = await adapter.detect();
|
|
1249
|
+
if (!detection.detected) {
|
|
1250
|
+
installs.push({ adapter, detected: false });
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
installs.push({ adapter, detected: true, result: await adapter.install() });
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
installs.push({
|
|
1256
|
+
adapter,
|
|
1257
|
+
detected: true,
|
|
1258
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return installs;
|
|
1263
|
+
}
|
|
1264
|
+
function rowState(state, group, status) {
|
|
1265
|
+
if (status === "error" || group.status === "error") return "failed";
|
|
1266
|
+
if (status !== void 0 && PENDING_STATUSES.has(status)) return "needs you";
|
|
1267
|
+
if (state.coverage === "active") return "beeping";
|
|
1268
|
+
if (state.coverage === "wired") return "ready";
|
|
1269
|
+
return "not covered";
|
|
1270
|
+
}
|
|
1271
|
+
function buildHarnessReports(installs, groups) {
|
|
1272
|
+
return installs.map((install) => {
|
|
1273
|
+
const { adapter } = install;
|
|
1274
|
+
const base4 = {
|
|
1275
|
+
harness: adapter.id,
|
|
1276
|
+
displayName: adapter.displayName,
|
|
1277
|
+
detected: install.detected,
|
|
1278
|
+
...install.result !== void 0 ? {
|
|
1279
|
+
status: install.result.status,
|
|
1280
|
+
changedFiles: install.result.changedFiles,
|
|
1281
|
+
backupFiles: install.result.backupFiles
|
|
1282
|
+
} : {},
|
|
1283
|
+
...install.error !== void 0 ? { error: install.error } : {}
|
|
1284
|
+
};
|
|
1285
|
+
if (install.error !== void 0) {
|
|
1286
|
+
return {
|
|
1287
|
+
...base4,
|
|
1288
|
+
actions: [
|
|
1289
|
+
`${adapter.displayName} could not be set up: ${install.error}`,
|
|
1290
|
+
`Run \`birdybeep agent install ${installTarget2(adapter.id)}\` to retry it on its own.`
|
|
1291
|
+
],
|
|
1292
|
+
rows: [{ harness: adapter.id, displayName: adapter.displayName, state: "failed" }]
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
if (!install.detected) {
|
|
1296
|
+
return {
|
|
1297
|
+
...base4,
|
|
1298
|
+
actions: [],
|
|
1299
|
+
rows: [
|
|
1300
|
+
{
|
|
1301
|
+
harness: adapter.id,
|
|
1302
|
+
displayName: adapter.displayName,
|
|
1303
|
+
state: "not installed"
|
|
1304
|
+
}
|
|
1305
|
+
]
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
const group = groups.find((g) => g.harness === adapter.id);
|
|
1309
|
+
const status = install.result?.status;
|
|
1310
|
+
const surfaces = group?.surfaces ?? [];
|
|
1311
|
+
const rows = group === void 0 || surfaces.length === 0 ? [
|
|
1312
|
+
{
|
|
1313
|
+
harness: adapter.id,
|
|
1314
|
+
displayName: adapter.displayName,
|
|
1315
|
+
state: status !== void 0 && PENDING_STATUSES.has(status) ? "needs you" : "ready"
|
|
1316
|
+
}
|
|
1317
|
+
] : surfaces.map((state) => {
|
|
1318
|
+
const graded = rowState(state, group, status);
|
|
1319
|
+
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);
|
|
1320
|
+
return {
|
|
1321
|
+
harness: adapter.id,
|
|
1322
|
+
displayName: adapter.displayName,
|
|
1323
|
+
build: describeSurface(state),
|
|
1324
|
+
kind: state.surface.kind,
|
|
1325
|
+
state: graded,
|
|
1326
|
+
...remedy !== void 0 ? { remedy } : {}
|
|
1327
|
+
};
|
|
1328
|
+
});
|
|
1329
|
+
return { ...base4, actions: [...install.result?.requiredActions ?? []], rows };
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
function pad(text, width) {
|
|
1333
|
+
return text.length >= width ? text : text + " ".repeat(width - text.length);
|
|
1334
|
+
}
|
|
1335
|
+
var MARKS = {
|
|
1336
|
+
beeping: "\u2713",
|
|
1337
|
+
ready: "\u2713",
|
|
1338
|
+
"needs you": "!",
|
|
1339
|
+
"not covered": "\u2717",
|
|
1340
|
+
"not installed": "\u2013",
|
|
1341
|
+
failed: "\u2717"
|
|
1342
|
+
};
|
|
1343
|
+
function renderCoverageTable(reports) {
|
|
1344
|
+
const rows = reports.flatMap((r) => r.rows);
|
|
1345
|
+
const nameWidth = Math.max(7, ...rows.map((r) => r.displayName.length));
|
|
1346
|
+
const buildWidth = Math.max(5, ...rows.map((r) => (r.build ?? "\u2014").length));
|
|
1347
|
+
const lines = ["coverage", ` ${pad("harness", nameWidth)} ${pad("build", buildWidth)} state`];
|
|
1348
|
+
for (const report of reports) {
|
|
1349
|
+
for (const row of report.rows) {
|
|
1350
|
+
lines.push(
|
|
1351
|
+
`${MARKS[row.state]} ${pad(row.displayName, nameWidth)} ${pad(row.build ?? "\u2014", buildWidth)} ${row.state}`
|
|
1352
|
+
);
|
|
1353
|
+
if (row.remedy !== void 0) lines.push(` \u2192 ${row.remedy}`);
|
|
1354
|
+
}
|
|
1355
|
+
for (const action of report.actions) lines.push(` \u2192 ${action}`);
|
|
1356
|
+
}
|
|
1357
|
+
return lines;
|
|
1358
|
+
}
|
|
1359
|
+
function describeMissing(reports) {
|
|
1360
|
+
const missing = reports.filter((r) => !r.detected && r.error === void 0);
|
|
1361
|
+
if (missing.length === 0) return [];
|
|
1362
|
+
const names = missing.map((r) => r.displayName);
|
|
1363
|
+
if (missing.length === reports.length) {
|
|
1364
|
+
return [
|
|
1365
|
+
"No supported coding agent is installed on this machine, so there was nothing to wire up.",
|
|
1366
|
+
`Install one of ${names.join(", ")}, then run \`birdybeep setup\` again \u2014 pairing is already done, so it picks up from here.`
|
|
1367
|
+
];
|
|
1368
|
+
}
|
|
1369
|
+
return [
|
|
1370
|
+
`Not installed: ${names.join(", ")}. Install any of them, then run \`birdybeep setup\` again to wire it up.`
|
|
1371
|
+
];
|
|
1372
|
+
}
|
|
1373
|
+
async function runHarnessSetup(ctx, options, deps = {}) {
|
|
1374
|
+
const adapters = deps.adapters ?? [...SETUP_ADAPTERS];
|
|
1375
|
+
const installs = await installDetected(adapters);
|
|
1376
|
+
const groups = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1377
|
+
const reports = buildHarnessReports(installs, groups);
|
|
1378
|
+
ctx.io.line("");
|
|
1379
|
+
for (const line of renderCoverageTable(reports)) ctx.io.line(line);
|
|
1380
|
+
const missing = describeMissing(reports);
|
|
1381
|
+
if (missing.length > 0) {
|
|
1382
|
+
ctx.io.line("");
|
|
1383
|
+
for (const line of missing) ctx.io.line(line);
|
|
1384
|
+
}
|
|
1385
|
+
const counts = {
|
|
1386
|
+
installed: reports.filter((r) => r.detected && r.error === void 0).length,
|
|
1387
|
+
needsYou: reports.filter((r) => r.rows.some((row) => row.state === "needs you")).length,
|
|
1388
|
+
notInstalled: reports.filter((r) => !r.detected && r.error === void 0).length,
|
|
1389
|
+
// A row that graded `failed` counts too: an adapter that returned status "error" never threw,
|
|
1390
|
+
// so counting only thrown errors would report a clean run over a harness that is broken.
|
|
1391
|
+
failed: reports.filter((r) => r.error !== void 0 || r.rows.some((x) => x.state === "failed")).length
|
|
1392
|
+
};
|
|
1393
|
+
let beep;
|
|
1394
|
+
let beepOk = true;
|
|
1395
|
+
if (options.sendTest) {
|
|
1396
|
+
ctx.io.line("");
|
|
1397
|
+
const command = createTestCommand({
|
|
1398
|
+
...deps.createSender !== void 0 ? { createSender: deps.createSender } : {},
|
|
1399
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {}
|
|
1400
|
+
});
|
|
1401
|
+
const beepIo = {
|
|
1402
|
+
...ctx.io,
|
|
1403
|
+
result: (value) => {
|
|
1404
|
+
beep = value;
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
beepOk = await command.run?.({ args: [], flags: ctx.flags, io: beepIo }) === 0;
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
harnesses: reports,
|
|
1411
|
+
counts,
|
|
1412
|
+
...beep !== void 0 ? { beep } : {},
|
|
1413
|
+
ok: counts.failed === 0 && beepOk
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
881
1416
|
|
|
882
1417
|
// src/commands/pair.ts
|
|
883
1418
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -886,11 +1421,15 @@ function renderQrMatrix(qrPayload) {
|
|
|
886
1421
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
887
1422
|
}
|
|
888
1423
|
function parsePairFlags(args) {
|
|
889
|
-
const flags = { yes: false };
|
|
1424
|
+
const flags = { yes: false, noInstall: false, noTest: false };
|
|
890
1425
|
for (let i = 0; i < args.length; i += 1) {
|
|
891
1426
|
const token = args[i] ?? "";
|
|
892
1427
|
if (token === "--yes" || token === "-y") {
|
|
893
1428
|
flags.yes = true;
|
|
1429
|
+
} else if (token === "--no-install") {
|
|
1430
|
+
flags.noInstall = true;
|
|
1431
|
+
} else if (token === "--no-test") {
|
|
1432
|
+
flags.noTest = true;
|
|
894
1433
|
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
895
1434
|
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
896
1435
|
const value = inline ?? args[++i];
|
|
@@ -957,14 +1496,14 @@ function canOpenControllingTerminal(path = controllingTerminalPath(), platform =
|
|
|
957
1496
|
if (platform === "win32") return false;
|
|
958
1497
|
let fd;
|
|
959
1498
|
try {
|
|
960
|
-
fd = (0,
|
|
1499
|
+
fd = (0, import_node_fs5.openSync)(path, "r");
|
|
961
1500
|
return true;
|
|
962
1501
|
} catch {
|
|
963
1502
|
return false;
|
|
964
1503
|
} finally {
|
|
965
1504
|
if (fd !== void 0) {
|
|
966
1505
|
try {
|
|
967
|
-
(0,
|
|
1506
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
968
1507
|
} catch {
|
|
969
1508
|
}
|
|
970
1509
|
}
|
|
@@ -978,7 +1517,7 @@ async function promptForAnswer(question, on) {
|
|
|
978
1517
|
input = process.stdin;
|
|
979
1518
|
} else {
|
|
980
1519
|
const { ReadStream } = await import("tty");
|
|
981
|
-
ttyFd = (0,
|
|
1520
|
+
ttyFd = (0, import_node_fs5.openSync)(controllingTerminalPath(), "r");
|
|
982
1521
|
input = new ReadStream(ttyFd);
|
|
983
1522
|
}
|
|
984
1523
|
return new Promise((resolve) => {
|
|
@@ -998,7 +1537,7 @@ async function promptForAnswer(question, on) {
|
|
|
998
1537
|
}
|
|
999
1538
|
if (ttyFd !== void 0) {
|
|
1000
1539
|
try {
|
|
1001
|
-
(0,
|
|
1540
|
+
(0, import_node_fs5.closeSync)(ttyFd);
|
|
1002
1541
|
} catch {
|
|
1003
1542
|
}
|
|
1004
1543
|
}
|
|
@@ -1010,7 +1549,18 @@ async function promptForAnswer(question, on) {
|
|
|
1010
1549
|
input.once?.("error", () => done(""));
|
|
1011
1550
|
});
|
|
1012
1551
|
}
|
|
1013
|
-
function
|
|
1552
|
+
async function runSetupChain(ctx, deps, flags) {
|
|
1553
|
+
try {
|
|
1554
|
+
return await runHarnessSetup(ctx, { sendTest: !flags.noTest }, deps);
|
|
1555
|
+
} catch (err) {
|
|
1556
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1557
|
+
ctx.io.errline(
|
|
1558
|
+
`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\`.`
|
|
1559
|
+
);
|
|
1560
|
+
return failedSetupReport(message);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
function createPairingCommand(verb, deps = {}) {
|
|
1014
1564
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1015
1565
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1016
1566
|
const clock = deps.now ?? (() => Date.now());
|
|
@@ -1023,9 +1573,10 @@ function createPairCommand(deps = {}) {
|
|
|
1023
1573
|
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
1024
1574
|
});
|
|
1025
1575
|
return {
|
|
1026
|
-
name:
|
|
1027
|
-
summary:
|
|
1028
|
-
usage:
|
|
1576
|
+
name: verb.name,
|
|
1577
|
+
summary: verb.summary,
|
|
1578
|
+
usage: verb.usage,
|
|
1579
|
+
...verb.gettingStarted !== void 0 ? { gettingStarted: verb.gettingStarted } : {},
|
|
1029
1580
|
options: [
|
|
1030
1581
|
{
|
|
1031
1582
|
flag: "--yes",
|
|
@@ -1036,18 +1587,43 @@ function createPairCommand(deps = {}) {
|
|
|
1036
1587
|
flag: "--expect-email",
|
|
1037
1588
|
value: "<addr>",
|
|
1038
1589
|
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
flag: "--no-install",
|
|
1593
|
+
summary: "Stop after pairing \u2014 don't detect or wire up any coding agent"
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
flag: "--no-test",
|
|
1597
|
+
summary: "Don't send the test Beep at the end"
|
|
1039
1598
|
}
|
|
1040
1599
|
],
|
|
1041
1600
|
run: async (ctx) => {
|
|
1042
1601
|
const pairFlags = parsePairFlags(ctx.args);
|
|
1043
1602
|
if (pairFlags.error !== void 0) {
|
|
1044
|
-
ctx.io.errline(`birdybeep
|
|
1603
|
+
ctx.io.errline(`birdybeep ${verb.name}: ${pairFlags.error}.`);
|
|
1045
1604
|
return EXIT.USAGE;
|
|
1046
1605
|
}
|
|
1606
|
+
const chain = deps.setup === false || pairFlags.noInstall ? void 0 : deps.setup ?? {};
|
|
1607
|
+
const setupDeps = {
|
|
1608
|
+
...deps.tokenOptions !== void 0 ? { tokenOptions: deps.tokenOptions } : {},
|
|
1609
|
+
...chain
|
|
1610
|
+
};
|
|
1611
|
+
if (verb.skipWhenPaired && await (0, import_agent_core9.getToken)(deps.tokenOptions ?? {}) !== null) {
|
|
1612
|
+
ctx.io.line(
|
|
1613
|
+
chain !== void 0 ? "\u2713 Already paired \u2014 checking which coding agents are wired up." : "\u2713 Already paired. Nothing else to do with --no-install."
|
|
1614
|
+
);
|
|
1615
|
+
const report2 = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1616
|
+
ctx.io.result({
|
|
1617
|
+
paired: true,
|
|
1618
|
+
alreadyPaired: true,
|
|
1619
|
+
...report2 !== void 0 ? { setup: report2 } : {}
|
|
1620
|
+
});
|
|
1621
|
+
return report2 !== void 0 && !report2.ok ? EXIT.ERROR : EXIT.OK;
|
|
1622
|
+
}
|
|
1047
1623
|
const apiUrl = resolveApiUrl();
|
|
1048
|
-
const identity = (0,
|
|
1049
|
-
const codeVerifier = (0,
|
|
1050
|
-
const codeChallenge = (0,
|
|
1624
|
+
const identity = (0, import_agent_core9.getMachineIdentity)();
|
|
1625
|
+
const codeVerifier = (0, import_agent_core9.generateCodeVerifier)();
|
|
1626
|
+
const codeChallenge = (0, import_agent_core9.deriveCodeChallengeS256)(codeVerifier);
|
|
1051
1627
|
const start = await pairStart(
|
|
1052
1628
|
apiUrl,
|
|
1053
1629
|
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
@@ -1143,21 +1719,52 @@ function createPairCommand(deps = {}) {
|
|
|
1143
1719
|
);
|
|
1144
1720
|
return EXIT.ERROR;
|
|
1145
1721
|
}
|
|
1146
|
-
await (0,
|
|
1722
|
+
await (0, import_agent_core9.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
1147
1723
|
writeCliConfig({ apiUrl });
|
|
1724
|
+
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1725
|
+
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1148
1726
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1149
|
-
|
|
1727
|
+
const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing \u2014 you won't be beeped about them.` : "";
|
|
1728
|
+
const nextStep = chain === void 0 ? " Run `birdybeep setup` to wire up your coding agents." : "";
|
|
1729
|
+
ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
|
|
1730
|
+
const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
|
|
1731
|
+
ctx.io.result({
|
|
1150
1732
|
paired: true,
|
|
1151
1733
|
machineId: paired.machineId,
|
|
1152
|
-
|
|
1734
|
+
discardedPrePairingEvents: discarded,
|
|
1735
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1736
|
+
...report !== void 0 ? { setup: report } : {}
|
|
1153
1737
|
});
|
|
1154
|
-
return EXIT.OK;
|
|
1738
|
+
return report !== void 0 && !report.ok ? EXIT.ERROR : EXIT.OK;
|
|
1155
1739
|
}
|
|
1156
1740
|
};
|
|
1157
1741
|
}
|
|
1742
|
+
function createPairCommand(deps = {}) {
|
|
1743
|
+
return createPairingCommand(
|
|
1744
|
+
{
|
|
1745
|
+
name: "pair",
|
|
1746
|
+
summary: "Pair this machine and wire up every coding agent on it",
|
|
1747
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1748
|
+
skipWhenPaired: false
|
|
1749
|
+
},
|
|
1750
|
+
deps
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
function createSetupCommand(deps = {}) {
|
|
1754
|
+
return createPairingCommand(
|
|
1755
|
+
{
|
|
1756
|
+
name: "setup",
|
|
1757
|
+
summary: "Set up BirdyBeep here: pair, wire up every coding agent, test",
|
|
1758
|
+
usage: "birdybeep setup [--yes] [--expect-email <addr>] [--no-install] [--no-test] [--json]",
|
|
1759
|
+
gettingStarted: "Pair this machine, wire up every coding agent it finds, and send a test Beep.",
|
|
1760
|
+
skipWhenPaired: true
|
|
1761
|
+
},
|
|
1762
|
+
deps
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1158
1765
|
|
|
1159
1766
|
// src/commands/queue.ts
|
|
1160
|
-
var
|
|
1767
|
+
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
1161
1768
|
function createQueueCommand() {
|
|
1162
1769
|
return {
|
|
1163
1770
|
name: "queue",
|
|
@@ -1169,7 +1776,7 @@ function createQueueCommand() {
|
|
|
1169
1776
|
summary: "Clear the local offline event queue (debug)",
|
|
1170
1777
|
usage: "birdybeep queue clear",
|
|
1171
1778
|
run: (ctx) => {
|
|
1172
|
-
const cleared = new
|
|
1779
|
+
const cleared = new import_agent_core10.LocalEventQueue().clear();
|
|
1173
1780
|
ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });
|
|
1174
1781
|
return EXIT.OK;
|
|
1175
1782
|
}
|
|
@@ -1179,25 +1786,25 @@ function createQueueCommand() {
|
|
|
1179
1786
|
}
|
|
1180
1787
|
|
|
1181
1788
|
// src/commands/report-status.ts
|
|
1182
|
-
var
|
|
1183
|
-
var
|
|
1184
|
-
var
|
|
1185
|
-
var
|
|
1186
|
-
var
|
|
1187
|
-
var
|
|
1789
|
+
var import_agent_core11 = require("@birdybeep/agent-core");
|
|
1790
|
+
var import_claude_code6 = require("@birdybeep/claude-code");
|
|
1791
|
+
var import_codex5 = require("@birdybeep/codex");
|
|
1792
|
+
var import_copilot5 = require("@birdybeep/copilot");
|
|
1793
|
+
var import_cursor6 = require("@birdybeep/cursor");
|
|
1794
|
+
var import_opencode5 = require("@birdybeep/opencode");
|
|
1188
1795
|
var DEFAULT_ADAPTERS3 = [
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1796
|
+
import_claude_code6.claudeCodeAdapter,
|
|
1797
|
+
import_codex5.codexAdapter,
|
|
1798
|
+
import_opencode5.opencodeAdapter,
|
|
1799
|
+
import_cursor6.cursorAdapter,
|
|
1800
|
+
import_copilot5.copilotAdapter
|
|
1194
1801
|
];
|
|
1195
1802
|
var ADAPTER_VERSIONS = {
|
|
1196
|
-
claude_code:
|
|
1197
|
-
codex:
|
|
1198
|
-
opencode:
|
|
1199
|
-
cursor:
|
|
1200
|
-
copilot:
|
|
1803
|
+
claude_code: import_claude_code6.CLAUDE_CODE_ADAPTER_VERSION,
|
|
1804
|
+
codex: import_codex5.CODEX_ADAPTER_VERSION,
|
|
1805
|
+
opencode: import_opencode5.OPENCODE_ADAPTER_VERSION,
|
|
1806
|
+
cursor: import_cursor6.CURSOR_ADAPTER_VERSION,
|
|
1807
|
+
copilot: import_copilot5.COPILOT_ADAPTER_VERSION
|
|
1201
1808
|
};
|
|
1202
1809
|
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
1203
1810
|
async function gatherItems(adapters) {
|
|
@@ -1220,7 +1827,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1220
1827
|
summary: "Internal: report integration status to the backend",
|
|
1221
1828
|
usage: "birdybeep report-status [--json]",
|
|
1222
1829
|
run: async (ctx) => {
|
|
1223
|
-
const token = await (0,
|
|
1830
|
+
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1224
1831
|
if (token === null) {
|
|
1225
1832
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
1226
1833
|
return EXIT.ERROR;
|
|
@@ -1241,7 +1848,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1241
1848
|
});
|
|
1242
1849
|
if (res.ok) {
|
|
1243
1850
|
outcome = "reported";
|
|
1244
|
-
const parsed =
|
|
1851
|
+
const parsed = import_agent_core11.integrationStatusResponseSchema.safeParse(
|
|
1245
1852
|
await res.json().catch(() => void 0)
|
|
1246
1853
|
);
|
|
1247
1854
|
if (parsed.success) {
|
|
@@ -1251,7 +1858,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1251
1858
|
}));
|
|
1252
1859
|
}
|
|
1253
1860
|
} else {
|
|
1254
|
-
const env =
|
|
1861
|
+
const env = import_agent_core11.errorEnvelopeSchema.safeParse(await res.json().catch(() => void 0));
|
|
1255
1862
|
errorCode = env.success ? env.data.error.code : void 0;
|
|
1256
1863
|
const terminal = errorCode !== void 0 ? errorCode === "unauthorized" || errorCode === "forbidden" || errorCode === "token_revoked" : res.status === 401 || res.status === 403;
|
|
1257
1864
|
outcome = terminal ? "terminal" : "deferred";
|
|
@@ -1282,22 +1889,22 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1282
1889
|
}
|
|
1283
1890
|
|
|
1284
1891
|
// src/commands/status.ts
|
|
1285
|
-
var
|
|
1286
|
-
var
|
|
1287
|
-
var
|
|
1288
|
-
var
|
|
1289
|
-
var
|
|
1290
|
-
var
|
|
1892
|
+
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1893
|
+
var import_claude_code7 = require("@birdybeep/claude-code");
|
|
1894
|
+
var import_codex6 = require("@birdybeep/codex");
|
|
1895
|
+
var import_copilot6 = require("@birdybeep/copilot");
|
|
1896
|
+
var import_cursor7 = require("@birdybeep/cursor");
|
|
1897
|
+
var import_opencode6 = require("@birdybeep/opencode");
|
|
1291
1898
|
var DEFAULT_ADAPTERS4 = [
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1899
|
+
import_claude_code7.claudeCodeAdapter,
|
|
1900
|
+
import_codex6.codexAdapter,
|
|
1901
|
+
import_opencode6.opencodeAdapter,
|
|
1902
|
+
import_cursor7.cursorAdapter,
|
|
1903
|
+
import_copilot6.copilotAdapter
|
|
1297
1904
|
];
|
|
1298
1905
|
function createStatusCommand(deps = {}) {
|
|
1299
1906
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
1300
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0,
|
|
1907
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1301
1908
|
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1302
1909
|
));
|
|
1303
1910
|
return {
|
|
@@ -1308,14 +1915,21 @@ function createStatusCommand(deps = {}) {
|
|
|
1308
1915
|
const machine = machineIdentity();
|
|
1309
1916
|
const paired = await isPaired(deps.tokenOptions ?? {});
|
|
1310
1917
|
const integrations = await gatherIntegrations(adapters);
|
|
1918
|
+
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1311
1919
|
const depthBefore = localQueueDepth();
|
|
1920
|
+
const unpaired = unpairedActivity();
|
|
1921
|
+
const filtered = filteredActivity();
|
|
1312
1922
|
const drain = await makeSender(resolveApiUrl()).drainNow();
|
|
1313
1923
|
const depthAfter = localQueueDepth();
|
|
1924
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
1314
1925
|
const report = {
|
|
1315
1926
|
machine,
|
|
1316
1927
|
paired,
|
|
1317
1928
|
integrations,
|
|
1318
|
-
|
|
1929
|
+
surfaces,
|
|
1930
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
1931
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
1932
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
1319
1933
|
};
|
|
1320
1934
|
if (ctx.flags.json) {
|
|
1321
1935
|
ctx.io.result(report);
|
|
@@ -1323,85 +1937,29 @@ function createStatusCommand(deps = {}) {
|
|
|
1323
1937
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1324
1938
|
ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
|
|
1325
1939
|
ctx.io.line("Integrations:");
|
|
1326
|
-
for (const i of integrations)
|
|
1940
|
+
for (const i of integrations) {
|
|
1941
|
+
ctx.io.line(` ${i.displayName}: ${i.status}`);
|
|
1942
|
+
const group = surfaces.find((g) => g.harness === i.harness);
|
|
1943
|
+
for (const state of group?.surfaces ?? []) {
|
|
1944
|
+
const mark = state.coverage === "active" ? "\u2713" : state.coverage === "wired" ? "\xB7" : "\u2717";
|
|
1945
|
+
ctx.io.line(` ${mark} ${describeSurface(state)} \u2014 ${state.coverage}`);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1327
1948
|
ctx.io.line(
|
|
1328
|
-
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining`
|
|
1949
|
+
`Queue: ${depthBefore} queued \u2192 ${drain.delivered} delivered, ${depthAfter} remaining` + (overflowDropped > 0 ? `, ${overflowDropped} dropped by the queue cap` : "")
|
|
1329
1950
|
);
|
|
1951
|
+
if (unpaired !== null) ctx.io.line(`\u26A0 Lost: ${describeUnpairedActivity(unpaired)}`);
|
|
1952
|
+
if (filtered !== null) ctx.io.line(`Local: ${describeFilteredActivity(filtered)}`);
|
|
1330
1953
|
}
|
|
1331
1954
|
return paired ? EXIT.OK : EXIT.ERROR;
|
|
1332
1955
|
}
|
|
1333
1956
|
};
|
|
1334
1957
|
}
|
|
1335
1958
|
|
|
1336
|
-
// src/commands/test.ts
|
|
1337
|
-
var import_node_crypto2 = require("crypto");
|
|
1338
|
-
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1339
|
-
function buildTestEvent(opts = {}) {
|
|
1340
|
-
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
1341
|
-
return (0, import_agent_core12.normalizeEvent)(
|
|
1342
|
-
{
|
|
1343
|
-
event_type: "test",
|
|
1344
|
-
status: "running",
|
|
1345
|
-
harness: "claude_code",
|
|
1346
|
-
// schema requires a harness; the "test" type distinguishes it
|
|
1347
|
-
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1348
|
-
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1349
|
-
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1350
|
-
machine: { label: machine.label, os: machine.os },
|
|
1351
|
-
workspace: { cwd: process.cwd() },
|
|
1352
|
-
title: "BirdyBeep test event",
|
|
1353
|
-
body: "If you can see this, your machine is wired up correctly.",
|
|
1354
|
-
metadata: { test: true }
|
|
1355
|
-
},
|
|
1356
|
-
opts
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
function createTestCommand(deps = {}) {
|
|
1360
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1361
|
-
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1362
|
-
));
|
|
1363
|
-
return {
|
|
1364
|
-
name: "test",
|
|
1365
|
-
summary: "Send a test event end-to-end",
|
|
1366
|
-
usage: "birdybeep test [--json]",
|
|
1367
|
-
run: async (ctx) => {
|
|
1368
|
-
const event = buildTestEvent();
|
|
1369
|
-
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1370
|
-
if (ctx.flags.json) {
|
|
1371
|
-
ctx.io.result({
|
|
1372
|
-
outcome: result.outcome,
|
|
1373
|
-
...result.status ? { status: result.status } : {},
|
|
1374
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1375
|
-
});
|
|
1376
|
-
} else if (result.outcome === "delivered") {
|
|
1377
|
-
if (result.decision === "notified" || result.decision === void 0) {
|
|
1378
|
-
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1379
|
-
} else if (result.decision === "suppressed") {
|
|
1380
|
-
ctx.io.line(
|
|
1381
|
-
"\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`."
|
|
1382
|
-
);
|
|
1383
|
-
} else if (result.decision === "deduped") {
|
|
1384
|
-
ctx.io.line(
|
|
1385
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1386
|
-
);
|
|
1387
|
-
} else {
|
|
1388
|
-
ctx.io.line(
|
|
1389
|
-
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
} else if (result.outcome === "queued") {
|
|
1393
|
-
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1394
|
-
} else {
|
|
1395
|
-
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1396
|
-
}
|
|
1397
|
-
return result.outcome === "dropped" ? EXIT.ERROR : EXIT.OK;
|
|
1398
|
-
}
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
1959
|
// src/commands.ts
|
|
1403
1960
|
function buildCommands() {
|
|
1404
1961
|
return [
|
|
1962
|
+
createSetupCommand(),
|
|
1405
1963
|
createPairCommand(),
|
|
1406
1964
|
createLogoutCommand(),
|
|
1407
1965
|
createUnpairCommand(),
|
|
@@ -1416,7 +1974,7 @@ function buildCommands() {
|
|
|
1416
1974
|
}
|
|
1417
1975
|
|
|
1418
1976
|
// src/update-check.ts
|
|
1419
|
-
var
|
|
1977
|
+
var import_node_fs6 = require("fs");
|
|
1420
1978
|
var import_node_path3 = require("path");
|
|
1421
1979
|
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1422
1980
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
@@ -1476,7 +2034,7 @@ function updateCachePath() {
|
|
|
1476
2034
|
}
|
|
1477
2035
|
function readUpdateCache() {
|
|
1478
2036
|
try {
|
|
1479
|
-
const parsed = JSON.parse((0,
|
|
2037
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(updateCachePath(), "utf8"));
|
|
1480
2038
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1481
2039
|
const { checkedAt, latest } = parsed;
|
|
1482
2040
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1487,8 +2045,8 @@ function readUpdateCache() {
|
|
|
1487
2045
|
}
|
|
1488
2046
|
}
|
|
1489
2047
|
function writeUpdateCache(cache) {
|
|
1490
|
-
(0,
|
|
1491
|
-
(0,
|
|
2048
|
+
(0, import_node_fs6.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
2049
|
+
(0, import_node_fs6.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
|
|
1492
2050
|
`, { mode: 384 });
|
|
1493
2051
|
}
|
|
1494
2052
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|