@birdybeep/cli 0.3.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 +774 -191
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-U4EIHC5C.js → chunk-BY5MNQE3.js} +762 -160
- package/dist/chunk-BY5MNQE3.js.map +1 -0
- package/dist/index.cjs +774 -191
- 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-U4EIHC5C.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",
|
|
@@ -575,12 +793,43 @@ function withTimeout(promise, ms, fallback) {
|
|
|
575
793
|
function isHarnessName(value) {
|
|
576
794
|
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
577
795
|
}
|
|
796
|
+
function resolveHookHarness(harness, payload) {
|
|
797
|
+
return harness === "claude" && (0, import_cursor4.isCursorHookPayload)(payload) ? "cursor" : harness;
|
|
798
|
+
}
|
|
799
|
+
function recognizesPayload(harness, payload) {
|
|
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
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function asRecord2(value) {
|
|
814
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
815
|
+
}
|
|
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";
|
|
825
|
+
}
|
|
578
826
|
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
579
|
-
|
|
827
|
+
const handler = resolveHookHarness(harness, payload);
|
|
828
|
+
if (handler === "copilot") {
|
|
580
829
|
if (copilotEventName === void 0) return Promise.resolve({ outcome: "skipped" });
|
|
581
830
|
return (0, import_copilot3.runCopilotHook)(copilotEventName, payload, { sender });
|
|
582
831
|
}
|
|
583
|
-
return RUNNERS[
|
|
832
|
+
return RUNNERS[handler](payload, { sender });
|
|
584
833
|
}
|
|
585
834
|
function readStdinDefault() {
|
|
586
835
|
return new Promise((resolve) => {
|
|
@@ -606,10 +855,10 @@ function detachCodexNotifyWorker(payload) {
|
|
|
606
855
|
try {
|
|
607
856
|
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
608
857
|
if (birdybeep === null) return false;
|
|
609
|
-
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`);
|
|
610
859
|
file = tmpFile;
|
|
611
|
-
(0,
|
|
612
|
-
fd = (0,
|
|
860
|
+
(0, import_node_fs4.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
861
|
+
fd = (0, import_node_fs4.openSync)(tmpFile, "r");
|
|
613
862
|
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
614
863
|
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
615
864
|
// trusted dir, never the inherited/attacker cwd
|
|
@@ -623,7 +872,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
623
872
|
});
|
|
624
873
|
child.on("error", () => {
|
|
625
874
|
try {
|
|
626
|
-
(0,
|
|
875
|
+
(0, import_node_fs4.rmSync)(tmpFile, { force: true });
|
|
627
876
|
} catch {
|
|
628
877
|
}
|
|
629
878
|
});
|
|
@@ -632,7 +881,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
632
881
|
} catch {
|
|
633
882
|
if (file !== void 0) {
|
|
634
883
|
try {
|
|
635
|
-
(0,
|
|
884
|
+
(0, import_node_fs4.rmSync)(file, { force: true });
|
|
636
885
|
} catch {
|
|
637
886
|
}
|
|
638
887
|
}
|
|
@@ -640,7 +889,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
640
889
|
} finally {
|
|
641
890
|
if (fd !== void 0) {
|
|
642
891
|
try {
|
|
643
|
-
(0,
|
|
892
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
644
893
|
} catch {
|
|
645
894
|
}
|
|
646
895
|
}
|
|
@@ -667,35 +916,76 @@ function createHookCommand(deps = {}) {
|
|
|
667
916
|
return EXIT.OK;
|
|
668
917
|
}
|
|
669
918
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
670
|
-
|
|
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(
|
|
671
926
|
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
672
927
|
stdinTimeoutMs,
|
|
673
|
-
|
|
928
|
+
null
|
|
674
929
|
);
|
|
675
930
|
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
676
|
-
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-")) {
|
|
677
932
|
try {
|
|
678
|
-
(0,
|
|
933
|
+
(0, import_node_fs4.rmSync)(notifyStdinFile, { force: true });
|
|
679
934
|
} catch {
|
|
680
935
|
}
|
|
681
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
|
+
}
|
|
682
952
|
let payload;
|
|
683
953
|
try {
|
|
684
954
|
payload = JSON.parse(raw);
|
|
685
955
|
} catch {
|
|
686
|
-
|
|
687
|
-
|
|
956
|
+
return drop("invalid-json", `the ${raw.length}-byte payload is not valid JSON`);
|
|
957
|
+
}
|
|
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;
|
|
688
972
|
}
|
|
689
973
|
const sender = makeSender(resolveApiUrl());
|
|
690
974
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
691
975
|
ctx.io.result({
|
|
692
|
-
harness,
|
|
976
|
+
harness: handler,
|
|
977
|
+
...routedFrom,
|
|
693
978
|
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
694
979
|
outcome: result.outcome,
|
|
695
980
|
eventType: result.eventType,
|
|
696
981
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
697
982
|
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
698
983
|
});
|
|
984
|
+
if (result.outcome === "unpaired") {
|
|
985
|
+
ctx.io.errline(
|
|
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)."
|
|
987
|
+
);
|
|
988
|
+
}
|
|
699
989
|
return EXIT.OK;
|
|
700
990
|
}
|
|
701
991
|
};
|
|
@@ -753,8 +1043,8 @@ function createUnpairCommand(deps = {}) {
|
|
|
753
1043
|
}
|
|
754
1044
|
|
|
755
1045
|
// src/commands/pair.ts
|
|
756
|
-
var
|
|
757
|
-
var
|
|
1046
|
+
var import_node_fs5 = require("fs");
|
|
1047
|
+
var import_agent_core9 = require("@birdybeep/agent-core");
|
|
758
1048
|
var import_uqr = require("uqr");
|
|
759
1049
|
|
|
760
1050
|
// src/pairing.ts
|
|
@@ -828,7 +1118,277 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
828
1118
|
}
|
|
829
1119
|
|
|
830
1120
|
// src/version.ts
|
|
831
|
-
var CLI_VERSION = "0.
|
|
1121
|
+
var CLI_VERSION = "0.6.0".length > 0 ? "0.6.0" : "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
|
+
}
|
|
832
1392
|
|
|
833
1393
|
// src/commands/pair.ts
|
|
834
1394
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -837,11 +1397,15 @@ function renderQrMatrix(qrPayload) {
|
|
|
837
1397
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
838
1398
|
}
|
|
839
1399
|
function parsePairFlags(args) {
|
|
840
|
-
const flags = { yes: false };
|
|
1400
|
+
const flags = { yes: false, noInstall: false, noTest: false };
|
|
841
1401
|
for (let i = 0; i < args.length; i += 1) {
|
|
842
1402
|
const token = args[i] ?? "";
|
|
843
1403
|
if (token === "--yes" || token === "-y") {
|
|
844
1404
|
flags.yes = true;
|
|
1405
|
+
} else if (token === "--no-install") {
|
|
1406
|
+
flags.noInstall = true;
|
|
1407
|
+
} else if (token === "--no-test") {
|
|
1408
|
+
flags.noTest = true;
|
|
845
1409
|
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
846
1410
|
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
847
1411
|
const value = inline ?? args[++i];
|
|
@@ -908,14 +1472,14 @@ function canOpenControllingTerminal(path = controllingTerminalPath(), platform =
|
|
|
908
1472
|
if (platform === "win32") return false;
|
|
909
1473
|
let fd;
|
|
910
1474
|
try {
|
|
911
|
-
fd = (0,
|
|
1475
|
+
fd = (0, import_node_fs5.openSync)(path, "r");
|
|
912
1476
|
return true;
|
|
913
1477
|
} catch {
|
|
914
1478
|
return false;
|
|
915
1479
|
} finally {
|
|
916
1480
|
if (fd !== void 0) {
|
|
917
1481
|
try {
|
|
918
|
-
(0,
|
|
1482
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
919
1483
|
} catch {
|
|
920
1484
|
}
|
|
921
1485
|
}
|
|
@@ -929,7 +1493,7 @@ async function promptForAnswer(question, on) {
|
|
|
929
1493
|
input = process.stdin;
|
|
930
1494
|
} else {
|
|
931
1495
|
const { ReadStream } = await import("tty");
|
|
932
|
-
ttyFd = (0,
|
|
1496
|
+
ttyFd = (0, import_node_fs5.openSync)(controllingTerminalPath(), "r");
|
|
933
1497
|
input = new ReadStream(ttyFd);
|
|
934
1498
|
}
|
|
935
1499
|
return new Promise((resolve) => {
|
|
@@ -949,7 +1513,7 @@ async function promptForAnswer(question, on) {
|
|
|
949
1513
|
}
|
|
950
1514
|
if (ttyFd !== void 0) {
|
|
951
1515
|
try {
|
|
952
|
-
(0,
|
|
1516
|
+
(0, import_node_fs5.closeSync)(ttyFd);
|
|
953
1517
|
} catch {
|
|
954
1518
|
}
|
|
955
1519
|
}
|
|
@@ -961,7 +1525,18 @@ async function promptForAnswer(question, on) {
|
|
|
961
1525
|
input.once?.("error", () => done(""));
|
|
962
1526
|
});
|
|
963
1527
|
}
|
|
964
|
-
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 = {}) {
|
|
965
1540
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
966
1541
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
967
1542
|
const clock = deps.now ?? (() => Date.now());
|
|
@@ -974,9 +1549,10 @@ function createPairCommand(deps = {}) {
|
|
|
974
1549
|
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
975
1550
|
});
|
|
976
1551
|
return {
|
|
977
|
-
name:
|
|
978
|
-
summary:
|
|
979
|
-
usage:
|
|
1552
|
+
name: verb.name,
|
|
1553
|
+
summary: verb.summary,
|
|
1554
|
+
usage: verb.usage,
|
|
1555
|
+
...verb.gettingStarted !== void 0 ? { gettingStarted: verb.gettingStarted } : {},
|
|
980
1556
|
options: [
|
|
981
1557
|
{
|
|
982
1558
|
flag: "--yes",
|
|
@@ -987,18 +1563,43 @@ function createPairCommand(deps = {}) {
|
|
|
987
1563
|
flag: "--expect-email",
|
|
988
1564
|
value: "<addr>",
|
|
989
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"
|
|
990
1574
|
}
|
|
991
1575
|
],
|
|
992
1576
|
run: async (ctx) => {
|
|
993
1577
|
const pairFlags = parsePairFlags(ctx.args);
|
|
994
1578
|
if (pairFlags.error !== void 0) {
|
|
995
|
-
ctx.io.errline(`birdybeep
|
|
1579
|
+
ctx.io.errline(`birdybeep ${verb.name}: ${pairFlags.error}.`);
|
|
996
1580
|
return EXIT.USAGE;
|
|
997
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
|
+
}
|
|
998
1599
|
const apiUrl = resolveApiUrl();
|
|
999
|
-
const identity = (0,
|
|
1000
|
-
const codeVerifier = (0,
|
|
1001
|
-
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);
|
|
1002
1603
|
const start = await pairStart(
|
|
1003
1604
|
apiUrl,
|
|
1004
1605
|
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
@@ -1094,21 +1695,52 @@ function createPairCommand(deps = {}) {
|
|
|
1094
1695
|
);
|
|
1095
1696
|
return EXIT.ERROR;
|
|
1096
1697
|
}
|
|
1097
|
-
await (0,
|
|
1698
|
+
await (0, import_agent_core9.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
1098
1699
|
writeCliConfig({ apiUrl });
|
|
1700
|
+
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1701
|
+
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1099
1702
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1100
|
-
|
|
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({
|
|
1101
1708
|
paired: true,
|
|
1102
1709
|
machineId: paired.machineId,
|
|
1103
|
-
|
|
1710
|
+
discardedPrePairingEvents: discarded,
|
|
1711
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1712
|
+
...report !== void 0 ? { setup: report } : {}
|
|
1104
1713
|
});
|
|
1105
|
-
return EXIT.OK;
|
|
1714
|
+
return report !== void 0 && !report.ok ? EXIT.ERROR : EXIT.OK;
|
|
1106
1715
|
}
|
|
1107
1716
|
};
|
|
1108
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
|
+
}
|
|
1109
1741
|
|
|
1110
1742
|
// src/commands/queue.ts
|
|
1111
|
-
var
|
|
1743
|
+
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
1112
1744
|
function createQueueCommand() {
|
|
1113
1745
|
return {
|
|
1114
1746
|
name: "queue",
|
|
@@ -1120,7 +1752,7 @@ function createQueueCommand() {
|
|
|
1120
1752
|
summary: "Clear the local offline event queue (debug)",
|
|
1121
1753
|
usage: "birdybeep queue clear",
|
|
1122
1754
|
run: (ctx) => {
|
|
1123
|
-
const cleared = new
|
|
1755
|
+
const cleared = new import_agent_core10.LocalEventQueue().clear();
|
|
1124
1756
|
ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });
|
|
1125
1757
|
return EXIT.OK;
|
|
1126
1758
|
}
|
|
@@ -1130,25 +1762,25 @@ function createQueueCommand() {
|
|
|
1130
1762
|
}
|
|
1131
1763
|
|
|
1132
1764
|
// src/commands/report-status.ts
|
|
1133
|
-
var
|
|
1134
|
-
var
|
|
1135
|
-
var
|
|
1136
|
-
var
|
|
1137
|
-
var
|
|
1138
|
-
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");
|
|
1139
1771
|
var DEFAULT_ADAPTERS3 = [
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1772
|
+
import_claude_code6.claudeCodeAdapter,
|
|
1773
|
+
import_codex5.codexAdapter,
|
|
1774
|
+
import_opencode5.opencodeAdapter,
|
|
1775
|
+
import_cursor6.cursorAdapter,
|
|
1776
|
+
import_copilot5.copilotAdapter
|
|
1145
1777
|
];
|
|
1146
1778
|
var ADAPTER_VERSIONS = {
|
|
1147
|
-
claude_code:
|
|
1148
|
-
codex:
|
|
1149
|
-
opencode:
|
|
1150
|
-
cursor:
|
|
1151
|
-
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
|
|
1152
1784
|
};
|
|
1153
1785
|
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
1154
1786
|
async function gatherItems(adapters) {
|
|
@@ -1171,7 +1803,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1171
1803
|
summary: "Internal: report integration status to the backend",
|
|
1172
1804
|
usage: "birdybeep report-status [--json]",
|
|
1173
1805
|
run: async (ctx) => {
|
|
1174
|
-
const token = await (0,
|
|
1806
|
+
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1175
1807
|
if (token === null) {
|
|
1176
1808
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
1177
1809
|
return EXIT.ERROR;
|
|
@@ -1192,7 +1824,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1192
1824
|
});
|
|
1193
1825
|
if (res.ok) {
|
|
1194
1826
|
outcome = "reported";
|
|
1195
|
-
const parsed =
|
|
1827
|
+
const parsed = import_agent_core11.integrationStatusResponseSchema.safeParse(
|
|
1196
1828
|
await res.json().catch(() => void 0)
|
|
1197
1829
|
);
|
|
1198
1830
|
if (parsed.success) {
|
|
@@ -1202,7 +1834,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1202
1834
|
}));
|
|
1203
1835
|
}
|
|
1204
1836
|
} else {
|
|
1205
|
-
const env =
|
|
1837
|
+
const env = import_agent_core11.errorEnvelopeSchema.safeParse(await res.json().catch(() => void 0));
|
|
1206
1838
|
errorCode = env.success ? env.data.error.code : void 0;
|
|
1207
1839
|
const terminal = errorCode !== void 0 ? errorCode === "unauthorized" || errorCode === "forbidden" || errorCode === "token_revoked" : res.status === 401 || res.status === 403;
|
|
1208
1840
|
outcome = terminal ? "terminal" : "deferred";
|
|
@@ -1233,22 +1865,22 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1233
1865
|
}
|
|
1234
1866
|
|
|
1235
1867
|
// src/commands/status.ts
|
|
1236
|
-
var
|
|
1237
|
-
var
|
|
1238
|
-
var
|
|
1239
|
-
var
|
|
1240
|
-
var
|
|
1241
|
-
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");
|
|
1242
1874
|
var DEFAULT_ADAPTERS4 = [
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1875
|
+
import_claude_code7.claudeCodeAdapter,
|
|
1876
|
+
import_codex6.codexAdapter,
|
|
1877
|
+
import_opencode6.opencodeAdapter,
|
|
1878
|
+
import_cursor7.cursorAdapter,
|
|
1879
|
+
import_copilot6.copilotAdapter
|
|
1248
1880
|
];
|
|
1249
1881
|
function createStatusCommand(deps = {}) {
|
|
1250
1882
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
1251
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0,
|
|
1883
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1252
1884
|
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1253
1885
|
));
|
|
1254
1886
|
return {
|
|
@@ -1259,14 +1891,21 @@ function createStatusCommand(deps = {}) {
|
|
|
1259
1891
|
const machine = machineIdentity();
|
|
1260
1892
|
const paired = await isPaired(deps.tokenOptions ?? {});
|
|
1261
1893
|
const integrations = await gatherIntegrations(adapters);
|
|
1894
|
+
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1262
1895
|
const depthBefore = localQueueDepth();
|
|
1896
|
+
const unpaired = unpairedActivity();
|
|
1897
|
+
const filtered = filteredActivity();
|
|
1263
1898
|
const drain = await makeSender(resolveApiUrl()).drainNow();
|
|
1264
1899
|
const depthAfter = localQueueDepth();
|
|
1900
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
1265
1901
|
const report = {
|
|
1266
1902
|
machine,
|
|
1267
1903
|
paired,
|
|
1268
1904
|
integrations,
|
|
1269
|
-
|
|
1905
|
+
surfaces,
|
|
1906
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
1907
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
1908
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
1270
1909
|
};
|
|
1271
1910
|
if (ctx.flags.json) {
|
|
1272
1911
|
ctx.io.result(report);
|
|
@@ -1274,85 +1913,29 @@ function createStatusCommand(deps = {}) {
|
|
|
1274
1913
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1275
1914
|
ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
|
|
1276
1915
|
ctx.io.line("Integrations:");
|
|
1277
|
-
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
|
+
}
|
|
1278
1924
|
ctx.io.line(
|
|
1279
|
-
`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` : "")
|
|
1280
1926
|
);
|
|
1927
|
+
if (unpaired !== null) ctx.io.line(`\u26A0 Lost: ${describeUnpairedActivity(unpaired)}`);
|
|
1928
|
+
if (filtered !== null) ctx.io.line(`Local: ${describeFilteredActivity(filtered)}`);
|
|
1281
1929
|
}
|
|
1282
1930
|
return paired ? EXIT.OK : EXIT.ERROR;
|
|
1283
1931
|
}
|
|
1284
1932
|
};
|
|
1285
1933
|
}
|
|
1286
1934
|
|
|
1287
|
-
// src/commands/test.ts
|
|
1288
|
-
var import_node_crypto2 = require("crypto");
|
|
1289
|
-
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1290
|
-
function buildTestEvent(opts = {}) {
|
|
1291
|
-
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
1292
|
-
return (0, import_agent_core12.normalizeEvent)(
|
|
1293
|
-
{
|
|
1294
|
-
event_type: "test",
|
|
1295
|
-
status: "running",
|
|
1296
|
-
harness: "claude_code",
|
|
1297
|
-
// schema requires a harness; the "test" type distinguishes it
|
|
1298
|
-
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1299
|
-
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1300
|
-
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1301
|
-
machine: { label: machine.label, os: machine.os },
|
|
1302
|
-
workspace: { cwd: process.cwd() },
|
|
1303
|
-
title: "BirdyBeep test event",
|
|
1304
|
-
body: "If you can see this, your machine is wired up correctly.",
|
|
1305
|
-
metadata: { test: true }
|
|
1306
|
-
},
|
|
1307
|
-
opts
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
function createTestCommand(deps = {}) {
|
|
1311
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1312
|
-
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1313
|
-
));
|
|
1314
|
-
return {
|
|
1315
|
-
name: "test",
|
|
1316
|
-
summary: "Send a test event end-to-end",
|
|
1317
|
-
usage: "birdybeep test [--json]",
|
|
1318
|
-
run: async (ctx) => {
|
|
1319
|
-
const event = buildTestEvent();
|
|
1320
|
-
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1321
|
-
if (ctx.flags.json) {
|
|
1322
|
-
ctx.io.result({
|
|
1323
|
-
outcome: result.outcome,
|
|
1324
|
-
...result.status ? { status: result.status } : {},
|
|
1325
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1326
|
-
});
|
|
1327
|
-
} else if (result.outcome === "delivered") {
|
|
1328
|
-
if (result.decision === "notified" || result.decision === void 0) {
|
|
1329
|
-
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1330
|
-
} else if (result.decision === "suppressed") {
|
|
1331
|
-
ctx.io.line(
|
|
1332
|
-
"\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`."
|
|
1333
|
-
);
|
|
1334
|
-
} else if (result.decision === "deduped") {
|
|
1335
|
-
ctx.io.line(
|
|
1336
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1337
|
-
);
|
|
1338
|
-
} else {
|
|
1339
|
-
ctx.io.line(
|
|
1340
|
-
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
} else if (result.outcome === "queued") {
|
|
1344
|
-
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1345
|
-
} else {
|
|
1346
|
-
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1347
|
-
}
|
|
1348
|
-
return result.outcome === "dropped" ? EXIT.ERROR : EXIT.OK;
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
1935
|
// src/commands.ts
|
|
1354
1936
|
function buildCommands() {
|
|
1355
1937
|
return [
|
|
1938
|
+
createSetupCommand(),
|
|
1356
1939
|
createPairCommand(),
|
|
1357
1940
|
createLogoutCommand(),
|
|
1358
1941
|
createUnpairCommand(),
|
|
@@ -1367,7 +1950,7 @@ function buildCommands() {
|
|
|
1367
1950
|
}
|
|
1368
1951
|
|
|
1369
1952
|
// src/update-check.ts
|
|
1370
|
-
var
|
|
1953
|
+
var import_node_fs6 = require("fs");
|
|
1371
1954
|
var import_node_path3 = require("path");
|
|
1372
1955
|
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1373
1956
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
@@ -1427,7 +2010,7 @@ function updateCachePath() {
|
|
|
1427
2010
|
}
|
|
1428
2011
|
function readUpdateCache() {
|
|
1429
2012
|
try {
|
|
1430
|
-
const parsed = JSON.parse((0,
|
|
2013
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(updateCachePath(), "utf8"));
|
|
1431
2014
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1432
2015
|
const { checkedAt, latest } = parsed;
|
|
1433
2016
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1438,8 +2021,8 @@ function readUpdateCache() {
|
|
|
1438
2021
|
}
|
|
1439
2022
|
}
|
|
1440
2023
|
function writeUpdateCache(cache) {
|
|
1441
|
-
(0,
|
|
1442
|
-
(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)}
|
|
1443
2026
|
`, { mode: 384 });
|
|
1444
2027
|
}
|
|
1445
2028
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|