@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/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",
|
|
@@ -599,12 +817,43 @@ function withTimeout(promise, ms, fallback) {
|
|
|
599
817
|
function isHarnessName(value) {
|
|
600
818
|
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
601
819
|
}
|
|
820
|
+
function resolveHookHarness(harness, payload) {
|
|
821
|
+
return harness === "claude" && (0, import_cursor4.isCursorHookPayload)(payload) ? "cursor" : harness;
|
|
822
|
+
}
|
|
823
|
+
function recognizesPayload(harness, payload) {
|
|
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
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function asRecord2(value) {
|
|
838
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
839
|
+
}
|
|
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";
|
|
849
|
+
}
|
|
602
850
|
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
603
|
-
|
|
851
|
+
const handler = resolveHookHarness(harness, payload);
|
|
852
|
+
if (handler === "copilot") {
|
|
604
853
|
if (copilotEventName === void 0) return Promise.resolve({ outcome: "skipped" });
|
|
605
854
|
return (0, import_copilot3.runCopilotHook)(copilotEventName, payload, { sender });
|
|
606
855
|
}
|
|
607
|
-
return RUNNERS[
|
|
856
|
+
return RUNNERS[handler](payload, { sender });
|
|
608
857
|
}
|
|
609
858
|
function readStdinDefault() {
|
|
610
859
|
return new Promise((resolve) => {
|
|
@@ -630,10 +879,10 @@ function detachCodexNotifyWorker(payload) {
|
|
|
630
879
|
try {
|
|
631
880
|
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
632
881
|
if (birdybeep === null) return false;
|
|
633
|
-
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`);
|
|
634
883
|
file = tmpFile;
|
|
635
|
-
(0,
|
|
636
|
-
fd = (0,
|
|
884
|
+
(0, import_node_fs4.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
885
|
+
fd = (0, import_node_fs4.openSync)(tmpFile, "r");
|
|
637
886
|
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
638
887
|
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
639
888
|
// trusted dir, never the inherited/attacker cwd
|
|
@@ -647,7 +896,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
647
896
|
});
|
|
648
897
|
child.on("error", () => {
|
|
649
898
|
try {
|
|
650
|
-
(0,
|
|
899
|
+
(0, import_node_fs4.rmSync)(tmpFile, { force: true });
|
|
651
900
|
} catch {
|
|
652
901
|
}
|
|
653
902
|
});
|
|
@@ -656,7 +905,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
656
905
|
} catch {
|
|
657
906
|
if (file !== void 0) {
|
|
658
907
|
try {
|
|
659
|
-
(0,
|
|
908
|
+
(0, import_node_fs4.rmSync)(file, { force: true });
|
|
660
909
|
} catch {
|
|
661
910
|
}
|
|
662
911
|
}
|
|
@@ -664,7 +913,7 @@ function detachCodexNotifyWorker(payload) {
|
|
|
664
913
|
} finally {
|
|
665
914
|
if (fd !== void 0) {
|
|
666
915
|
try {
|
|
667
|
-
(0,
|
|
916
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
668
917
|
} catch {
|
|
669
918
|
}
|
|
670
919
|
}
|
|
@@ -691,35 +940,76 @@ function createHookCommand(deps = {}) {
|
|
|
691
940
|
return EXIT.OK;
|
|
692
941
|
}
|
|
693
942
|
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
694
|
-
|
|
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(
|
|
695
950
|
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
696
951
|
stdinTimeoutMs,
|
|
697
|
-
|
|
952
|
+
null
|
|
698
953
|
);
|
|
699
954
|
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
700
|
-
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-")) {
|
|
701
956
|
try {
|
|
702
|
-
(0,
|
|
957
|
+
(0, import_node_fs4.rmSync)(notifyStdinFile, { force: true });
|
|
703
958
|
} catch {
|
|
704
959
|
}
|
|
705
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
|
+
}
|
|
706
976
|
let payload;
|
|
707
977
|
try {
|
|
708
978
|
payload = JSON.parse(raw);
|
|
709
979
|
} catch {
|
|
710
|
-
|
|
711
|
-
|
|
980
|
+
return drop("invalid-json", `the ${raw.length}-byte payload is not valid JSON`);
|
|
981
|
+
}
|
|
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;
|
|
712
996
|
}
|
|
713
997
|
const sender = makeSender(resolveApiUrl());
|
|
714
998
|
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
715
999
|
ctx.io.result({
|
|
716
|
-
harness,
|
|
1000
|
+
harness: handler,
|
|
1001
|
+
...routedFrom,
|
|
717
1002
|
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
718
1003
|
outcome: result.outcome,
|
|
719
1004
|
eventType: result.eventType,
|
|
720
1005
|
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
721
1006
|
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
722
1007
|
});
|
|
1008
|
+
if (result.outcome === "unpaired") {
|
|
1009
|
+
ctx.io.errline(
|
|
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)."
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
723
1013
|
return EXIT.OK;
|
|
724
1014
|
}
|
|
725
1015
|
};
|
|
@@ -777,8 +1067,8 @@ function createUnpairCommand(deps = {}) {
|
|
|
777
1067
|
}
|
|
778
1068
|
|
|
779
1069
|
// src/commands/pair.ts
|
|
780
|
-
var
|
|
781
|
-
var
|
|
1070
|
+
var import_node_fs5 = require("fs");
|
|
1071
|
+
var import_agent_core9 = require("@birdybeep/agent-core");
|
|
782
1072
|
var import_uqr = require("uqr");
|
|
783
1073
|
|
|
784
1074
|
// src/pairing.ts
|
|
@@ -852,7 +1142,277 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
|
|
|
852
1142
|
}
|
|
853
1143
|
|
|
854
1144
|
// src/version.ts
|
|
855
|
-
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
|
+
}
|
|
856
1416
|
|
|
857
1417
|
// src/commands/pair.ts
|
|
858
1418
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -861,11 +1421,15 @@ function renderQrMatrix(qrPayload) {
|
|
|
861
1421
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
862
1422
|
}
|
|
863
1423
|
function parsePairFlags(args) {
|
|
864
|
-
const flags = { yes: false };
|
|
1424
|
+
const flags = { yes: false, noInstall: false, noTest: false };
|
|
865
1425
|
for (let i = 0; i < args.length; i += 1) {
|
|
866
1426
|
const token = args[i] ?? "";
|
|
867
1427
|
if (token === "--yes" || token === "-y") {
|
|
868
1428
|
flags.yes = true;
|
|
1429
|
+
} else if (token === "--no-install") {
|
|
1430
|
+
flags.noInstall = true;
|
|
1431
|
+
} else if (token === "--no-test") {
|
|
1432
|
+
flags.noTest = true;
|
|
869
1433
|
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
870
1434
|
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
871
1435
|
const value = inline ?? args[++i];
|
|
@@ -932,14 +1496,14 @@ function canOpenControllingTerminal(path = controllingTerminalPath(), platform =
|
|
|
932
1496
|
if (platform === "win32") return false;
|
|
933
1497
|
let fd;
|
|
934
1498
|
try {
|
|
935
|
-
fd = (0,
|
|
1499
|
+
fd = (0, import_node_fs5.openSync)(path, "r");
|
|
936
1500
|
return true;
|
|
937
1501
|
} catch {
|
|
938
1502
|
return false;
|
|
939
1503
|
} finally {
|
|
940
1504
|
if (fd !== void 0) {
|
|
941
1505
|
try {
|
|
942
|
-
(0,
|
|
1506
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
943
1507
|
} catch {
|
|
944
1508
|
}
|
|
945
1509
|
}
|
|
@@ -953,7 +1517,7 @@ async function promptForAnswer(question, on) {
|
|
|
953
1517
|
input = process.stdin;
|
|
954
1518
|
} else {
|
|
955
1519
|
const { ReadStream } = await import("tty");
|
|
956
|
-
ttyFd = (0,
|
|
1520
|
+
ttyFd = (0, import_node_fs5.openSync)(controllingTerminalPath(), "r");
|
|
957
1521
|
input = new ReadStream(ttyFd);
|
|
958
1522
|
}
|
|
959
1523
|
return new Promise((resolve) => {
|
|
@@ -973,7 +1537,7 @@ async function promptForAnswer(question, on) {
|
|
|
973
1537
|
}
|
|
974
1538
|
if (ttyFd !== void 0) {
|
|
975
1539
|
try {
|
|
976
|
-
(0,
|
|
1540
|
+
(0, import_node_fs5.closeSync)(ttyFd);
|
|
977
1541
|
} catch {
|
|
978
1542
|
}
|
|
979
1543
|
}
|
|
@@ -985,7 +1549,18 @@ async function promptForAnswer(question, on) {
|
|
|
985
1549
|
input.once?.("error", () => done(""));
|
|
986
1550
|
});
|
|
987
1551
|
}
|
|
988
|
-
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 = {}) {
|
|
989
1564
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
990
1565
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
991
1566
|
const clock = deps.now ?? (() => Date.now());
|
|
@@ -998,9 +1573,10 @@ function createPairCommand(deps = {}) {
|
|
|
998
1573
|
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
999
1574
|
});
|
|
1000
1575
|
return {
|
|
1001
|
-
name:
|
|
1002
|
-
summary:
|
|
1003
|
-
usage:
|
|
1576
|
+
name: verb.name,
|
|
1577
|
+
summary: verb.summary,
|
|
1578
|
+
usage: verb.usage,
|
|
1579
|
+
...verb.gettingStarted !== void 0 ? { gettingStarted: verb.gettingStarted } : {},
|
|
1004
1580
|
options: [
|
|
1005
1581
|
{
|
|
1006
1582
|
flag: "--yes",
|
|
@@ -1011,18 +1587,43 @@ function createPairCommand(deps = {}) {
|
|
|
1011
1587
|
flag: "--expect-email",
|
|
1012
1588
|
value: "<addr>",
|
|
1013
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"
|
|
1014
1598
|
}
|
|
1015
1599
|
],
|
|
1016
1600
|
run: async (ctx) => {
|
|
1017
1601
|
const pairFlags = parsePairFlags(ctx.args);
|
|
1018
1602
|
if (pairFlags.error !== void 0) {
|
|
1019
|
-
ctx.io.errline(`birdybeep
|
|
1603
|
+
ctx.io.errline(`birdybeep ${verb.name}: ${pairFlags.error}.`);
|
|
1020
1604
|
return EXIT.USAGE;
|
|
1021
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
|
+
}
|
|
1022
1623
|
const apiUrl = resolveApiUrl();
|
|
1023
|
-
const identity = (0,
|
|
1024
|
-
const codeVerifier = (0,
|
|
1025
|
-
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);
|
|
1026
1627
|
const start = await pairStart(
|
|
1027
1628
|
apiUrl,
|
|
1028
1629
|
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
@@ -1118,21 +1719,52 @@ function createPairCommand(deps = {}) {
|
|
|
1118
1719
|
);
|
|
1119
1720
|
return EXIT.ERROR;
|
|
1120
1721
|
}
|
|
1121
|
-
await (0,
|
|
1722
|
+
await (0, import_agent_core9.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
1122
1723
|
writeCliConfig({ apiUrl });
|
|
1724
|
+
const discarded = new import_agent_core9.LocalEventQueue().discardBefore(clock());
|
|
1725
|
+
(0, import_agent_core9.clearUnpairedNotice)();
|
|
1123
1726
|
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1124
|
-
|
|
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({
|
|
1125
1732
|
paired: true,
|
|
1126
1733
|
machineId: paired.machineId,
|
|
1127
|
-
|
|
1734
|
+
discardedPrePairingEvents: discarded,
|
|
1735
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1736
|
+
...report !== void 0 ? { setup: report } : {}
|
|
1128
1737
|
});
|
|
1129
|
-
return EXIT.OK;
|
|
1738
|
+
return report !== void 0 && !report.ok ? EXIT.ERROR : EXIT.OK;
|
|
1130
1739
|
}
|
|
1131
1740
|
};
|
|
1132
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
|
+
}
|
|
1133
1765
|
|
|
1134
1766
|
// src/commands/queue.ts
|
|
1135
|
-
var
|
|
1767
|
+
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
1136
1768
|
function createQueueCommand() {
|
|
1137
1769
|
return {
|
|
1138
1770
|
name: "queue",
|
|
@@ -1144,7 +1776,7 @@ function createQueueCommand() {
|
|
|
1144
1776
|
summary: "Clear the local offline event queue (debug)",
|
|
1145
1777
|
usage: "birdybeep queue clear",
|
|
1146
1778
|
run: (ctx) => {
|
|
1147
|
-
const cleared = new
|
|
1779
|
+
const cleared = new import_agent_core10.LocalEventQueue().clear();
|
|
1148
1780
|
ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });
|
|
1149
1781
|
return EXIT.OK;
|
|
1150
1782
|
}
|
|
@@ -1154,25 +1786,25 @@ function createQueueCommand() {
|
|
|
1154
1786
|
}
|
|
1155
1787
|
|
|
1156
1788
|
// src/commands/report-status.ts
|
|
1157
|
-
var
|
|
1158
|
-
var
|
|
1159
|
-
var
|
|
1160
|
-
var
|
|
1161
|
-
var
|
|
1162
|
-
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");
|
|
1163
1795
|
var DEFAULT_ADAPTERS3 = [
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1796
|
+
import_claude_code6.claudeCodeAdapter,
|
|
1797
|
+
import_codex5.codexAdapter,
|
|
1798
|
+
import_opencode5.opencodeAdapter,
|
|
1799
|
+
import_cursor6.cursorAdapter,
|
|
1800
|
+
import_copilot5.copilotAdapter
|
|
1169
1801
|
];
|
|
1170
1802
|
var ADAPTER_VERSIONS = {
|
|
1171
|
-
claude_code:
|
|
1172
|
-
codex:
|
|
1173
|
-
opencode:
|
|
1174
|
-
cursor:
|
|
1175
|
-
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
|
|
1176
1808
|
};
|
|
1177
1809
|
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
1178
1810
|
async function gatherItems(adapters) {
|
|
@@ -1195,7 +1827,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1195
1827
|
summary: "Internal: report integration status to the backend",
|
|
1196
1828
|
usage: "birdybeep report-status [--json]",
|
|
1197
1829
|
run: async (ctx) => {
|
|
1198
|
-
const token = await (0,
|
|
1830
|
+
const token = await (0, import_agent_core11.getToken)(deps.tokenOptions ?? {});
|
|
1199
1831
|
if (token === null) {
|
|
1200
1832
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
1201
1833
|
return EXIT.ERROR;
|
|
@@ -1216,7 +1848,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1216
1848
|
});
|
|
1217
1849
|
if (res.ok) {
|
|
1218
1850
|
outcome = "reported";
|
|
1219
|
-
const parsed =
|
|
1851
|
+
const parsed = import_agent_core11.integrationStatusResponseSchema.safeParse(
|
|
1220
1852
|
await res.json().catch(() => void 0)
|
|
1221
1853
|
);
|
|
1222
1854
|
if (parsed.success) {
|
|
@@ -1226,7 +1858,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1226
1858
|
}));
|
|
1227
1859
|
}
|
|
1228
1860
|
} else {
|
|
1229
|
-
const env =
|
|
1861
|
+
const env = import_agent_core11.errorEnvelopeSchema.safeParse(await res.json().catch(() => void 0));
|
|
1230
1862
|
errorCode = env.success ? env.data.error.code : void 0;
|
|
1231
1863
|
const terminal = errorCode !== void 0 ? errorCode === "unauthorized" || errorCode === "forbidden" || errorCode === "token_revoked" : res.status === 401 || res.status === 403;
|
|
1232
1864
|
outcome = terminal ? "terminal" : "deferred";
|
|
@@ -1257,22 +1889,22 @@ function createReportStatusCommand(deps = {}) {
|
|
|
1257
1889
|
}
|
|
1258
1890
|
|
|
1259
1891
|
// src/commands/status.ts
|
|
1260
|
-
var
|
|
1261
|
-
var
|
|
1262
|
-
var
|
|
1263
|
-
var
|
|
1264
|
-
var
|
|
1265
|
-
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");
|
|
1266
1898
|
var DEFAULT_ADAPTERS4 = [
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1899
|
+
import_claude_code7.claudeCodeAdapter,
|
|
1900
|
+
import_codex6.codexAdapter,
|
|
1901
|
+
import_opencode6.opencodeAdapter,
|
|
1902
|
+
import_cursor7.cursorAdapter,
|
|
1903
|
+
import_copilot6.copilotAdapter
|
|
1272
1904
|
];
|
|
1273
1905
|
function createStatusCommand(deps = {}) {
|
|
1274
1906
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
1275
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0,
|
|
1907
|
+
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1276
1908
|
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1277
1909
|
));
|
|
1278
1910
|
return {
|
|
@@ -1283,14 +1915,21 @@ function createStatusCommand(deps = {}) {
|
|
|
1283
1915
|
const machine = machineIdentity();
|
|
1284
1916
|
const paired = await isPaired(deps.tokenOptions ?? {});
|
|
1285
1917
|
const integrations = await gatherIntegrations(adapters);
|
|
1918
|
+
const surfaces = await gatherSurfaces(adapters, deps.surfaceOptions ?? {});
|
|
1286
1919
|
const depthBefore = localQueueDepth();
|
|
1920
|
+
const unpaired = unpairedActivity();
|
|
1921
|
+
const filtered = filteredActivity();
|
|
1287
1922
|
const drain = await makeSender(resolveApiUrl()).drainNow();
|
|
1288
1923
|
const depthAfter = localQueueDepth();
|
|
1924
|
+
const overflowDropped = localQueueOverflowDrops();
|
|
1289
1925
|
const report = {
|
|
1290
1926
|
machine,
|
|
1291
1927
|
paired,
|
|
1292
1928
|
integrations,
|
|
1293
|
-
|
|
1929
|
+
surfaces,
|
|
1930
|
+
queue: { depthBefore, delivered: drain.delivered, depthAfter, overflowDropped },
|
|
1931
|
+
...unpaired !== null ? { unpairedActivity: unpaired } : {},
|
|
1932
|
+
...filtered !== null ? { filteredActivity: filtered } : {}
|
|
1294
1933
|
};
|
|
1295
1934
|
if (ctx.flags.json) {
|
|
1296
1935
|
ctx.io.result(report);
|
|
@@ -1298,85 +1937,29 @@ function createStatusCommand(deps = {}) {
|
|
|
1298
1937
|
ctx.io.line(`Machine: ${machine.label} (${machine.os})`);
|
|
1299
1938
|
ctx.io.line(paired ? "Paired: yes" : "Paired: no \u2014 run `birdybeep pair`");
|
|
1300
1939
|
ctx.io.line("Integrations:");
|
|
1301
|
-
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
|
+
}
|
|
1302
1948
|
ctx.io.line(
|
|
1303
|
-
`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` : "")
|
|
1304
1950
|
);
|
|
1951
|
+
if (unpaired !== null) ctx.io.line(`\u26A0 Lost: ${describeUnpairedActivity(unpaired)}`);
|
|
1952
|
+
if (filtered !== null) ctx.io.line(`Local: ${describeFilteredActivity(filtered)}`);
|
|
1305
1953
|
}
|
|
1306
1954
|
return paired ? EXIT.OK : EXIT.ERROR;
|
|
1307
1955
|
}
|
|
1308
1956
|
};
|
|
1309
1957
|
}
|
|
1310
1958
|
|
|
1311
|
-
// src/commands/test.ts
|
|
1312
|
-
var import_node_crypto2 = require("crypto");
|
|
1313
|
-
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
1314
|
-
function buildTestEvent(opts = {}) {
|
|
1315
|
-
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
1316
|
-
return (0, import_agent_core12.normalizeEvent)(
|
|
1317
|
-
{
|
|
1318
|
-
event_type: "test",
|
|
1319
|
-
status: "running",
|
|
1320
|
-
harness: "claude_code",
|
|
1321
|
-
// schema requires a harness; the "test" type distinguishes it
|
|
1322
|
-
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
1323
|
-
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
1324
|
-
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
1325
|
-
machine: { label: machine.label, os: machine.os },
|
|
1326
|
-
workspace: { cwd: process.cwd() },
|
|
1327
|
-
title: "BirdyBeep test event",
|
|
1328
|
-
body: "If you can see this, your machine is wired up correctly.",
|
|
1329
|
-
metadata: { test: true }
|
|
1330
|
-
},
|
|
1331
|
-
opts
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
|
-
function createTestCommand(deps = {}) {
|
|
1335
|
-
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core12.createSender)(
|
|
1336
|
-
deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl }
|
|
1337
|
-
));
|
|
1338
|
-
return {
|
|
1339
|
-
name: "test",
|
|
1340
|
-
summary: "Send a test event end-to-end",
|
|
1341
|
-
usage: "birdybeep test [--json]",
|
|
1342
|
-
run: async (ctx) => {
|
|
1343
|
-
const event = buildTestEvent();
|
|
1344
|
-
const result = await makeSender(resolveApiUrl()).send(event);
|
|
1345
|
-
if (ctx.flags.json) {
|
|
1346
|
-
ctx.io.result({
|
|
1347
|
-
outcome: result.outcome,
|
|
1348
|
-
...result.status ? { status: result.status } : {},
|
|
1349
|
-
...result.decision ? { decision: result.decision } : {}
|
|
1350
|
-
});
|
|
1351
|
-
} else if (result.outcome === "delivered") {
|
|
1352
|
-
if (result.decision === "notified" || result.decision === void 0) {
|
|
1353
|
-
ctx.io.line("\u2713 Test event delivered \u2014 check your phone for a test Beep.");
|
|
1354
|
-
} else if (result.decision === "suppressed") {
|
|
1355
|
-
ctx.io.line(
|
|
1356
|
-
"\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`."
|
|
1357
|
-
);
|
|
1358
|
-
} else if (result.decision === "deduped") {
|
|
1359
|
-
ctx.io.line(
|
|
1360
|
-
"\u26A0 The backend accepted the test event but folded it into a recent duplicate \u2014 wait ~30s and run `birdybeep test` again."
|
|
1361
|
-
);
|
|
1362
|
-
} else {
|
|
1363
|
-
ctx.io.line(
|
|
1364
|
-
`\u26A0 The backend accepted the test event but decided "${result.decision}" \u2014 no push was sent. Run \`birdybeep doctor\`.`
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
} else if (result.outcome === "queued") {
|
|
1368
|
-
ctx.io.line("\u2022 Offline \u2014 test event queued; it will deliver when you reconnect.");
|
|
1369
|
-
} else {
|
|
1370
|
-
ctx.io.line("\u2717 Test event was rejected by the backend. Run `birdybeep doctor`.");
|
|
1371
|
-
}
|
|
1372
|
-
return result.outcome === "dropped" ? EXIT.ERROR : EXIT.OK;
|
|
1373
|
-
}
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
1959
|
// src/commands.ts
|
|
1378
1960
|
function buildCommands() {
|
|
1379
1961
|
return [
|
|
1962
|
+
createSetupCommand(),
|
|
1380
1963
|
createPairCommand(),
|
|
1381
1964
|
createLogoutCommand(),
|
|
1382
1965
|
createUnpairCommand(),
|
|
@@ -1391,7 +1974,7 @@ function buildCommands() {
|
|
|
1391
1974
|
}
|
|
1392
1975
|
|
|
1393
1976
|
// src/update-check.ts
|
|
1394
|
-
var
|
|
1977
|
+
var import_node_fs6 = require("fs");
|
|
1395
1978
|
var import_node_path3 = require("path");
|
|
1396
1979
|
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1397
1980
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
@@ -1451,7 +2034,7 @@ function updateCachePath() {
|
|
|
1451
2034
|
}
|
|
1452
2035
|
function readUpdateCache() {
|
|
1453
2036
|
try {
|
|
1454
|
-
const parsed = JSON.parse((0,
|
|
2037
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(updateCachePath(), "utf8"));
|
|
1455
2038
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1456
2039
|
const { checkedAt, latest } = parsed;
|
|
1457
2040
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1462,8 +2045,8 @@ function readUpdateCache() {
|
|
|
1462
2045
|
}
|
|
1463
2046
|
}
|
|
1464
2047
|
function writeUpdateCache(cache) {
|
|
1465
|
-
(0,
|
|
1466
|
-
(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)}
|
|
1467
2050
|
`, { mode: 384 });
|
|
1468
2051
|
}
|
|
1469
2052
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|