@birdybeep/cli 0.2.0 → 0.4.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 +489 -75
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-OCS5IDYI.js → chunk-ZYFMLHY4.js} +479 -78
- package/dist/chunk-ZYFMLHY4.js.map +1 -0
- package/dist/index.cjs +477 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +1 -1
- package/package.json +7 -5
- package/dist/chunk-OCS5IDYI.js.map +0 -1
|
@@ -69,8 +69,11 @@ function parseGlobalFlags(argv) {
|
|
|
69
69
|
}
|
|
70
70
|
return { flags, rest };
|
|
71
71
|
}
|
|
72
|
-
function isUnknownFlag(token) {
|
|
73
|
-
|
|
72
|
+
function isUnknownFlag(token, allowed) {
|
|
73
|
+
if (!token.startsWith("-")) return false;
|
|
74
|
+
if (GLOBAL_FLAG_TOKENS.has(token)) return false;
|
|
75
|
+
const eq = token.indexOf("=");
|
|
76
|
+
return !allowed.has(eq >= 0 ? token.slice(0, eq) : token);
|
|
74
77
|
}
|
|
75
78
|
function renderRootHelp(version, commands) {
|
|
76
79
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
@@ -91,6 +94,16 @@ function renderRootHelp(version, commands) {
|
|
|
91
94
|
" -v, --version Show the CLI version"
|
|
92
95
|
].join("\n");
|
|
93
96
|
}
|
|
97
|
+
function commandFlagTokens(...commands) {
|
|
98
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const command of commands) {
|
|
100
|
+
for (const option of command?.options ?? []) {
|
|
101
|
+
tokens.add(option.flag);
|
|
102
|
+
for (const alias of option.aliases ?? []) tokens.add(alias);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return tokens;
|
|
106
|
+
}
|
|
94
107
|
function renderCommandHelp(path, command) {
|
|
95
108
|
const lines = [
|
|
96
109
|
`birdybeep ${path} \u2014 ${command.summary}`,
|
|
@@ -98,6 +111,17 @@ function renderCommandHelp(path, command) {
|
|
|
98
111
|
"Usage:",
|
|
99
112
|
` ${command.usage ?? `birdybeep ${path} [options]`}`
|
|
100
113
|
];
|
|
114
|
+
if (command.options && command.options.length > 0) {
|
|
115
|
+
const labels = command.options.map(
|
|
116
|
+
(o) => `${[o.flag, ...o.aliases ?? []].join(", ")}${o.value ? ` ${o.value}` : ""}`
|
|
117
|
+
);
|
|
118
|
+
const width = Math.max(...labels.map((l) => l.length));
|
|
119
|
+
lines.push(
|
|
120
|
+
"",
|
|
121
|
+
"Options:",
|
|
122
|
+
...command.options.map((o, i) => ` ${labels[i]?.padEnd(width)} ${o.summary}`)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
101
125
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
102
126
|
const width = Math.max(...command.subcommands.map((c) => c.name.length));
|
|
103
127
|
lines.push(
|
|
@@ -122,6 +146,7 @@ async function dispatch(argv, deps) {
|
|
|
122
146
|
return EXIT.OK;
|
|
123
147
|
}
|
|
124
148
|
let command = deps.commands.find((c) => c.name === rest[0]);
|
|
149
|
+
let parent;
|
|
125
150
|
const pathParts = [];
|
|
126
151
|
let argsStart = 1;
|
|
127
152
|
if (command) {
|
|
@@ -129,6 +154,7 @@ async function dispatch(argv, deps) {
|
|
|
129
154
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
130
155
|
const sub = command.subcommands.find((c) => c.name === rest[1]);
|
|
131
156
|
if (sub) {
|
|
157
|
+
parent = command;
|
|
132
158
|
command = sub;
|
|
133
159
|
pathParts.push(sub.name);
|
|
134
160
|
argsStart = 2;
|
|
@@ -152,6 +178,7 @@ async function dispatch(argv, deps) {
|
|
|
152
178
|
name: path,
|
|
153
179
|
summary: command.summary,
|
|
154
180
|
usage: command.usage,
|
|
181
|
+
options: command.options,
|
|
155
182
|
subcommands: command.subcommands?.map((c) => ({ name: c.name, summary: c.summary }))
|
|
156
183
|
});
|
|
157
184
|
return EXIT.OK;
|
|
@@ -161,7 +188,8 @@ async function dispatch(argv, deps) {
|
|
|
161
188
|
return EXIT.USAGE;
|
|
162
189
|
}
|
|
163
190
|
const args = rest.slice(argsStart);
|
|
164
|
-
const
|
|
191
|
+
const allowed = commandFlagTokens(command, parent);
|
|
192
|
+
const unknown = args.find((token) => isUnknownFlag(token, allowed));
|
|
165
193
|
if (unknown !== void 0) {
|
|
166
194
|
io.errline(`birdybeep ${path}: unknown option "${unknown}".`);
|
|
167
195
|
return EXIT.USAGE;
|
|
@@ -189,19 +217,36 @@ async function dispatch(argv, deps) {
|
|
|
189
217
|
}
|
|
190
218
|
|
|
191
219
|
// src/version.ts
|
|
192
|
-
var CLI_VERSION = "0.
|
|
220
|
+
var CLI_VERSION = "0.4.0".length > 0 ? "0.4.0" : "0.0.0";
|
|
193
221
|
|
|
194
222
|
// src/commands/agent.ts
|
|
195
223
|
import { claudeCodeAdapter } from "@birdybeep/claude-code";
|
|
196
224
|
import { codexAdapter } from "@birdybeep/codex";
|
|
225
|
+
import { copilotAdapter } from "@birdybeep/copilot";
|
|
226
|
+
import { cursorAdapter } from "@birdybeep/cursor";
|
|
197
227
|
import { opencodeAdapter } from "@birdybeep/opencode";
|
|
198
|
-
var DEFAULT_ADAPTERS = [
|
|
228
|
+
var DEFAULT_ADAPTERS = [
|
|
229
|
+
claudeCodeAdapter,
|
|
230
|
+
codexAdapter,
|
|
231
|
+
opencodeAdapter,
|
|
232
|
+
cursorAdapter,
|
|
233
|
+
copilotAdapter
|
|
234
|
+
];
|
|
199
235
|
var TARGET_TO_ID = {
|
|
200
236
|
claude: "claude_code",
|
|
201
237
|
codex: "codex",
|
|
202
|
-
opencode: "opencode"
|
|
238
|
+
opencode: "opencode",
|
|
239
|
+
cursor: "cursor",
|
|
240
|
+
copilot: "copilot"
|
|
203
241
|
};
|
|
204
|
-
var AGENT_TARGETS = [
|
|
242
|
+
var AGENT_TARGETS = [
|
|
243
|
+
"all",
|
|
244
|
+
"claude",
|
|
245
|
+
"codex",
|
|
246
|
+
"opencode",
|
|
247
|
+
"cursor",
|
|
248
|
+
"copilot"
|
|
249
|
+
];
|
|
205
250
|
function selectAdapters(target, adapters) {
|
|
206
251
|
if (target === "all") return adapters;
|
|
207
252
|
const id = TARGET_TO_ID[target];
|
|
@@ -292,18 +337,18 @@ function createAgentCommand(deps = {}) {
|
|
|
292
337
|
return {
|
|
293
338
|
name: "agent",
|
|
294
339
|
summary: "Install or uninstall harness adapters",
|
|
295
|
-
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode]",
|
|
340
|
+
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode|cursor|copilot]",
|
|
296
341
|
subcommands: [
|
|
297
342
|
{
|
|
298
343
|
name: "install",
|
|
299
|
-
summary: "Install adapters (all | claude | codex | opencode)",
|
|
300
|
-
usage: "birdybeep agent install [all|claude|codex|opencode]",
|
|
344
|
+
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
345
|
+
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
301
346
|
run: (ctx) => installSelected(adapters, ctx)
|
|
302
347
|
},
|
|
303
348
|
{
|
|
304
349
|
name: "uninstall",
|
|
305
350
|
summary: "Restore harness config to its pre-install state",
|
|
306
|
-
usage: "birdybeep agent uninstall [all|claude|codex|opencode]",
|
|
351
|
+
usage: "birdybeep agent uninstall [all|claude|codex|opencode|cursor|copilot]",
|
|
307
352
|
run: (ctx) => uninstallSelected(adapters, ctx)
|
|
308
353
|
}
|
|
309
354
|
]
|
|
@@ -316,6 +361,8 @@ import {
|
|
|
316
361
|
} from "@birdybeep/agent-core";
|
|
317
362
|
import { claudeCodeAdapter as claudeCodeAdapter2 } from "@birdybeep/claude-code";
|
|
318
363
|
import { codexAdapter as codexAdapter2 } from "@birdybeep/codex";
|
|
364
|
+
import { copilotAdapter as copilotAdapter2 } from "@birdybeep/copilot";
|
|
365
|
+
import { cursorAdapter as cursorAdapter2 } from "@birdybeep/cursor";
|
|
319
366
|
import { opencodeAdapter as opencodeAdapter2 } from "@birdybeep/opencode";
|
|
320
367
|
|
|
321
368
|
// src/config.ts
|
|
@@ -340,6 +387,8 @@ function writeCliConfig(patch) {
|
|
|
340
387
|
const merged = {};
|
|
341
388
|
const apiUrl = patch.apiUrl ?? current.apiUrl;
|
|
342
389
|
if (apiUrl !== void 0) merged.apiUrl = apiUrl;
|
|
390
|
+
const expectEmail = patch.expectEmail ?? current.expectEmail;
|
|
391
|
+
if (expectEmail !== void 0) merged.expectEmail = expectEmail;
|
|
343
392
|
mkdirSync2(birdyBeepConfigDir2(), { recursive: true, mode: 448 });
|
|
344
393
|
writeFileSync(cliConfigPath(), `${JSON.stringify(merged, null, 2)}
|
|
345
394
|
`, { mode: 384 });
|
|
@@ -382,7 +431,13 @@ function machineIdentity() {
|
|
|
382
431
|
}
|
|
383
432
|
|
|
384
433
|
// src/commands/doctor.ts
|
|
385
|
-
var DEFAULT_ADAPTERS2 = [
|
|
434
|
+
var DEFAULT_ADAPTERS2 = [
|
|
435
|
+
claudeCodeAdapter2,
|
|
436
|
+
codexAdapter2,
|
|
437
|
+
opencodeAdapter2,
|
|
438
|
+
cursorAdapter2,
|
|
439
|
+
copilotAdapter2
|
|
440
|
+
];
|
|
386
441
|
async function defaultProbeNetwork(baseUrl) {
|
|
387
442
|
try {
|
|
388
443
|
const controller = new AbortController();
|
|
@@ -465,18 +520,36 @@ function createDoctorCommand(deps = {}) {
|
|
|
465
520
|
}
|
|
466
521
|
|
|
467
522
|
// src/commands/hook.ts
|
|
523
|
+
import { spawn } from "child_process";
|
|
524
|
+
import { randomBytes } from "crypto";
|
|
525
|
+
import { closeSync, openSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
526
|
+
import { tmpdir } from "os";
|
|
527
|
+
import { basename, dirname, join as join2 } from "path";
|
|
468
528
|
import {
|
|
469
|
-
createSender as defaultCreateSender2
|
|
529
|
+
createSender as defaultCreateSender2,
|
|
530
|
+
resolveOnPath
|
|
470
531
|
} from "@birdybeep/agent-core";
|
|
471
|
-
import { runClaudeHook } from "@birdybeep/claude-code";
|
|
532
|
+
import { isClaudeCodeHookPayload, runClaudeHook } from "@birdybeep/claude-code";
|
|
472
533
|
import { runCodexHook } from "@birdybeep/codex";
|
|
534
|
+
import {
|
|
535
|
+
isCopilotHookEventName,
|
|
536
|
+
runCopilotHook
|
|
537
|
+
} from "@birdybeep/copilot";
|
|
538
|
+
import { isCursorHookEventName, isCursorHookPayload, runCursorHook } from "@birdybeep/cursor";
|
|
473
539
|
import { runOpenCodeHook } from "@birdybeep/opencode";
|
|
474
540
|
var RUNNERS = {
|
|
475
541
|
claude: runClaudeHook,
|
|
476
542
|
codex: runCodexHook,
|
|
477
|
-
opencode: runOpenCodeHook
|
|
543
|
+
opencode: runOpenCodeHook,
|
|
544
|
+
cursor: runCursorHook
|
|
478
545
|
};
|
|
479
|
-
var HOOK_HARNESSES = [
|
|
546
|
+
var HOOK_HARNESSES = [
|
|
547
|
+
"claude",
|
|
548
|
+
"codex",
|
|
549
|
+
"opencode",
|
|
550
|
+
"cursor",
|
|
551
|
+
"copilot"
|
|
552
|
+
];
|
|
480
553
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
481
554
|
function withTimeout(promise, ms, fallback) {
|
|
482
555
|
return new Promise((resolve) => {
|
|
@@ -493,10 +566,31 @@ function withTimeout(promise, ms, fallback) {
|
|
|
493
566
|
});
|
|
494
567
|
}
|
|
495
568
|
function isHarnessName(value) {
|
|
496
|
-
return value === "claude" || value === "codex" || value === "opencode";
|
|
569
|
+
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
497
570
|
}
|
|
498
|
-
function
|
|
499
|
-
return
|
|
571
|
+
function resolveHookHarness(harness, payload) {
|
|
572
|
+
return harness === "claude" && isCursorHookPayload(payload) ? "cursor" : harness;
|
|
573
|
+
}
|
|
574
|
+
function recognizesPayload(harness, payload) {
|
|
575
|
+
if (harness === "claude") return isClaudeCodeHookPayload(payload);
|
|
576
|
+
if (harness === "cursor") return isCursorHookEventName(asRecord(payload)["hook_event_name"]);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
function asRecord(value) {
|
|
580
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
581
|
+
}
|
|
582
|
+
function describeEventName(payload) {
|
|
583
|
+
const name = asRecord(payload)["hook_event_name"];
|
|
584
|
+
if (typeof name !== "string") return "(absent)";
|
|
585
|
+
return JSON.stringify(name.length > 64 ? `${name.slice(0, 63)}\u2026` : name);
|
|
586
|
+
}
|
|
587
|
+
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
588
|
+
const handler = resolveHookHarness(harness, payload);
|
|
589
|
+
if (handler === "copilot") {
|
|
590
|
+
if (copilotEventName === void 0) return Promise.resolve({ outcome: "skipped" });
|
|
591
|
+
return runCopilotHook(copilotEventName, payload, { sender });
|
|
592
|
+
}
|
|
593
|
+
return RUNNERS[handler](payload, { sender });
|
|
500
594
|
}
|
|
501
595
|
function readStdinDefault() {
|
|
502
596
|
return new Promise((resolve) => {
|
|
@@ -511,24 +605,90 @@ function readStdinDefault() {
|
|
|
511
605
|
process.stdin.on("error", () => resolve(""));
|
|
512
606
|
});
|
|
513
607
|
}
|
|
514
|
-
async function readHookPayload(args, readStdin) {
|
|
515
|
-
return args[1] ?? await readStdin();
|
|
608
|
+
async function readHookPayload(args, readStdin, stdinOnly = false) {
|
|
609
|
+
return stdinOnly ? readStdin() : args[1] ?? await readStdin();
|
|
610
|
+
}
|
|
611
|
+
var NOTIFY_STDIN_FILE_ENV = "BIRDYBEEP_CODEX_NOTIFY_STDIN_FILE";
|
|
612
|
+
function detachCodexNotifyWorker(payload) {
|
|
613
|
+
if (process.platform === "win32") return false;
|
|
614
|
+
let file;
|
|
615
|
+
let fd;
|
|
616
|
+
try {
|
|
617
|
+
const birdybeep = resolveOnPath("birdybeep");
|
|
618
|
+
if (birdybeep === null) return false;
|
|
619
|
+
const tmpFile = join2(tmpdir(), `birdybeep-notify-${randomBytes(16).toString("hex")}.json`);
|
|
620
|
+
file = tmpFile;
|
|
621
|
+
writeFileSync2(tmpFile, payload, { mode: 384 });
|
|
622
|
+
fd = openSync(tmpFile, "r");
|
|
623
|
+
const child = spawn(birdybeep, ["hook", "codex"], {
|
|
624
|
+
cwd: dirname(birdybeep),
|
|
625
|
+
// trusted dir, never the inherited/attacker cwd
|
|
626
|
+
detached: true,
|
|
627
|
+
// new session (setsid) → survives `codex exec` reaping the group
|
|
628
|
+
stdio: [fd, "ignore", "ignore"],
|
|
629
|
+
// stdin = the temp file; this process holds no pipe
|
|
630
|
+
env: { ...process.env, [NOTIFY_STDIN_FILE_ENV]: tmpFile },
|
|
631
|
+
// worker cleans it up post-read
|
|
632
|
+
windowsHide: true
|
|
633
|
+
});
|
|
634
|
+
child.on("error", () => {
|
|
635
|
+
try {
|
|
636
|
+
rmSync(tmpFile, { force: true });
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
child.unref();
|
|
641
|
+
return true;
|
|
642
|
+
} catch {
|
|
643
|
+
if (file !== void 0) {
|
|
644
|
+
try {
|
|
645
|
+
rmSync(file, { force: true });
|
|
646
|
+
} catch {
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
} finally {
|
|
651
|
+
if (fd !== void 0) {
|
|
652
|
+
try {
|
|
653
|
+
closeSync(fd);
|
|
654
|
+
} catch {
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
516
658
|
}
|
|
517
659
|
function createHookCommand(deps = {}) {
|
|
518
660
|
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender2({ baseUrl }));
|
|
519
661
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
520
662
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
663
|
+
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
521
664
|
return {
|
|
522
665
|
name: "hook",
|
|
523
666
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
524
|
-
usage: "birdybeep hook <claude|codex|opencode>",
|
|
667
|
+
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
525
668
|
run: async (ctx) => {
|
|
526
669
|
const harness = ctx.args[0];
|
|
527
670
|
if (!isHarnessName(harness)) {
|
|
528
671
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
529
672
|
return EXIT.USAGE;
|
|
530
673
|
}
|
|
531
|
-
const
|
|
674
|
+
const notifyPayload = ctx.args[1];
|
|
675
|
+
if (harness === "codex" && notifyPayload !== void 0 && notifyPayload.length > 0 && detachCodexNotify(notifyPayload)) {
|
|
676
|
+
ctx.io.result({ harness, outcome: "detached" });
|
|
677
|
+
return EXIT.OK;
|
|
678
|
+
}
|
|
679
|
+
const copilotEventName = harness === "copilot" && isCopilotHookEventName(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
680
|
+
const raw = await withTimeout(
|
|
681
|
+
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
682
|
+
stdinTimeoutMs,
|
|
683
|
+
""
|
|
684
|
+
);
|
|
685
|
+
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
686
|
+
if (notifyStdinFile !== void 0 && dirname(notifyStdinFile) === tmpdir() && basename(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
687
|
+
try {
|
|
688
|
+
rmSync(notifyStdinFile, { force: true });
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
532
692
|
let payload;
|
|
533
693
|
try {
|
|
534
694
|
payload = JSON.parse(raw);
|
|
@@ -537,52 +697,87 @@ function createHookCommand(deps = {}) {
|
|
|
537
697
|
return EXIT.OK;
|
|
538
698
|
}
|
|
539
699
|
const sender = makeSender(resolveApiUrl());
|
|
540
|
-
const
|
|
541
|
-
|
|
700
|
+
const handler = resolveHookHarness(harness, payload);
|
|
701
|
+
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
702
|
+
ctx.io.result({
|
|
703
|
+
harness: handler,
|
|
704
|
+
...handler !== harness ? { routedFrom: harness } : {},
|
|
705
|
+
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
706
|
+
outcome: result.outcome,
|
|
707
|
+
eventType: result.eventType,
|
|
708
|
+
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
709
|
+
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
710
|
+
});
|
|
711
|
+
if (result.outcome === "skipped" && !recognizesPayload(handler, payload)) {
|
|
712
|
+
ctx.io.errline(
|
|
713
|
+
`birdybeep hook ${harness}: hook_event_name ${describeEventName(payload)} is not a ${handler} hook event \u2014 nothing was sent. Check which tool is running this hook.`
|
|
714
|
+
);
|
|
715
|
+
return EXIT.ERROR;
|
|
716
|
+
}
|
|
542
717
|
return EXIT.OK;
|
|
543
718
|
}
|
|
544
719
|
};
|
|
545
720
|
}
|
|
546
721
|
|
|
547
722
|
// src/commands/logout.ts
|
|
548
|
-
import { clearToken } from "@birdybeep/agent-core";
|
|
549
|
-
|
|
723
|
+
import { clearToken, getToken as getToken2 } from "@birdybeep/agent-core";
|
|
724
|
+
var base = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
725
|
+
function createLogoutCommand(deps = {}) {
|
|
550
726
|
return {
|
|
551
|
-
name:
|
|
552
|
-
summary:
|
|
553
|
-
usage:
|
|
727
|
+
name: "logout",
|
|
728
|
+
summary: "Remove the local machine token (does NOT revoke the machine server-side)",
|
|
729
|
+
usage: "birdybeep logout",
|
|
554
730
|
run: async (ctx) => {
|
|
555
731
|
await clearToken(deps.tokenOptions ?? {});
|
|
556
|
-
ctx.io.emit(
|
|
732
|
+
ctx.io.emit("Logged out \u2014 the machine token was removed.", { loggedOut: true });
|
|
557
733
|
return EXIT.OK;
|
|
558
734
|
}
|
|
559
735
|
};
|
|
560
736
|
}
|
|
561
|
-
function
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
737
|
+
async function revokeSelf(token, fetchImpl, timeoutMs) {
|
|
738
|
+
const controller = new AbortController();
|
|
739
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
740
|
+
try {
|
|
741
|
+
const res = await fetchImpl(`${base(resolveApiUrl())}/v1/machine/revoke-self`, {
|
|
742
|
+
method: "POST",
|
|
743
|
+
headers: { authorization: `Bearer ${token}` },
|
|
744
|
+
signal: controller.signal
|
|
745
|
+
});
|
|
746
|
+
if (res.ok || res.status === 403) return "revoked";
|
|
747
|
+
return "rejected";
|
|
748
|
+
} catch {
|
|
749
|
+
return "unreachable";
|
|
750
|
+
} finally {
|
|
751
|
+
clearTimeout(timer);
|
|
752
|
+
}
|
|
571
753
|
}
|
|
572
754
|
function createUnpairCommand(deps = {}) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
755
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
756
|
+
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
757
|
+
return {
|
|
758
|
+
name: "unpair",
|
|
759
|
+
summary: "Unpair this machine \u2014 revoke it server-side and remove the local token",
|
|
760
|
+
usage: "birdybeep unpair",
|
|
761
|
+
run: async (ctx) => {
|
|
762
|
+
const token = await getToken2(deps.tokenOptions ?? {});
|
|
763
|
+
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
764
|
+
await clearToken(deps.tokenOptions ?? {});
|
|
765
|
+
const serverRevoked = outcome === "revoked";
|
|
766
|
+
const human = outcome === "revoked" ? "Unpaired \u2014 the machine was revoked and removed from your account." : outcome === "no_token" ? "Already unpaired \u2014 there was no local token to remove." : outcome === "unreachable" ? "Unpaired locally, but the server was unreachable \u2014 the machine may still show in the app. Open BirdyBeep and revoke it there to fully remove it." : "Unpaired locally, but the server didn't confirm removal \u2014 if the machine still shows in the app, revoke it there.";
|
|
767
|
+
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
768
|
+
return EXIT.OK;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
582
771
|
}
|
|
583
772
|
|
|
584
773
|
// src/commands/pair.ts
|
|
585
|
-
import {
|
|
774
|
+
import { closeSync as closeSync2, openSync as openSync2 } from "fs";
|
|
775
|
+
import {
|
|
776
|
+
deriveCodeChallengeS256,
|
|
777
|
+
generateCodeVerifier,
|
|
778
|
+
getMachineIdentity as getMachineIdentity2,
|
|
779
|
+
setToken
|
|
780
|
+
} from "@birdybeep/agent-core";
|
|
586
781
|
import { renderUnicodeCompact } from "uqr";
|
|
587
782
|
|
|
588
783
|
// src/pairing.ts
|
|
@@ -591,16 +786,17 @@ import {
|
|
|
591
786
|
pairStartResponseSchema,
|
|
592
787
|
pairTokenResponseSchema
|
|
593
788
|
} from "@birdybeep/agent-core";
|
|
594
|
-
function
|
|
789
|
+
function base2(apiUrl) {
|
|
595
790
|
return apiUrl.replace(/\/$/, "");
|
|
596
791
|
}
|
|
597
792
|
async function pairStart(apiUrl, input, fetchImpl) {
|
|
598
793
|
const body = {
|
|
599
794
|
machine_label: input.machineLabel,
|
|
600
795
|
...input.os !== void 0 ? { os: input.os } : {},
|
|
601
|
-
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {}
|
|
796
|
+
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {},
|
|
797
|
+
...input.codeChallenge !== void 0 ? { code_challenge: input.codeChallenge } : {}
|
|
602
798
|
};
|
|
603
|
-
const res = await fetchImpl(`${
|
|
799
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/start`, {
|
|
604
800
|
method: "POST",
|
|
605
801
|
headers: { "content-type": "application/json" },
|
|
606
802
|
body: JSON.stringify(body)
|
|
@@ -618,12 +814,13 @@ var TERMINAL_TOKEN_ERRORS = /* @__PURE__ */ new Set([
|
|
|
618
814
|
"not_found",
|
|
619
815
|
"payload_too_large"
|
|
620
816
|
]);
|
|
621
|
-
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint) {
|
|
817
|
+
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint, codeVerifier) {
|
|
622
818
|
const body = {
|
|
623
819
|
device_code: deviceCode,
|
|
624
|
-
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {}
|
|
820
|
+
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {},
|
|
821
|
+
...codeVerifier !== void 0 ? { code_verifier: codeVerifier } : {}
|
|
625
822
|
};
|
|
626
|
-
const res = await fetchImpl(`${
|
|
823
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/token`, {
|
|
627
824
|
method: "POST",
|
|
628
825
|
headers: { "content-type": "application/json" },
|
|
629
826
|
body: JSON.stringify(body)
|
|
@@ -634,7 +831,10 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
|
|
|
634
831
|
return {
|
|
635
832
|
status: "paired",
|
|
636
833
|
machineToken: parsed.data.machine_token,
|
|
637
|
-
machineId: parsed.data.machine_id
|
|
834
|
+
machineId: parsed.data.machine_id,
|
|
835
|
+
// Only surface the key when the server reported it (exactOptionalPropertyTypes: no explicit
|
|
836
|
+
// undefined). Older servers omit approved_by_email; newer ones (dgxd) include it.
|
|
837
|
+
...parsed.data.approved_by_email !== void 0 ? { approvedByEmail: parsed.data.approved_by_email } : {}
|
|
638
838
|
};
|
|
639
839
|
}
|
|
640
840
|
let errBody = null;
|
|
@@ -660,22 +860,172 @@ var HEARTBEAT_MS = 15e3;
|
|
|
660
860
|
function renderQrMatrix(qrPayload) {
|
|
661
861
|
return renderUnicodeCompact(qrPayload, { border: 2 });
|
|
662
862
|
}
|
|
863
|
+
function parsePairFlags(args) {
|
|
864
|
+
const flags = { yes: false };
|
|
865
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
866
|
+
const token = args[i] ?? "";
|
|
867
|
+
if (token === "--yes" || token === "-y") {
|
|
868
|
+
flags.yes = true;
|
|
869
|
+
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
870
|
+
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
871
|
+
const value = inline ?? args[++i];
|
|
872
|
+
if (value === void 0 || value.length === 0 || value.startsWith("-")) {
|
|
873
|
+
return { ...flags, error: "--expect-email requires an email address" };
|
|
874
|
+
}
|
|
875
|
+
flags.expectEmail = value;
|
|
876
|
+
} else {
|
|
877
|
+
return { ...flags, error: `unexpected argument "${token}"` };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return flags;
|
|
881
|
+
}
|
|
882
|
+
function sameEmail(a, b) {
|
|
883
|
+
const fold = (v) => v.trim().toLowerCase();
|
|
884
|
+
const rawEqual = fold(a) === fold(b);
|
|
885
|
+
const nfkcEqual = fold(a.normalize("NFKC")) === fold(b.normalize("NFKC"));
|
|
886
|
+
return rawEqual && nfkcEqual;
|
|
887
|
+
}
|
|
888
|
+
function decidePairConfirmation(input) {
|
|
889
|
+
const { approvedByEmail, expectEmail } = input;
|
|
890
|
+
const platform = input.platform ?? process.platform;
|
|
891
|
+
if (expectEmail !== void 0) {
|
|
892
|
+
if (approvedByEmail === void 0) {
|
|
893
|
+
const remedy = input.expectEmailSource === "config" ? `Remove or correct the "expectEmail" key in ${input.configPath ?? "the BirdyBeep CLI config"} (or upgrade the backend to one that reports the approving account) and re-run.` : "Re-run without --expect-email (and confirm interactively) if that is expected.";
|
|
894
|
+
return {
|
|
895
|
+
action: "reject",
|
|
896
|
+
reason: "expected_email_unverifiable",
|
|
897
|
+
message: `Pairing refused: ${expectEmail} was pinned as the expected approving account, but the server did not report which account approved this machine, so the pin could not be verified. The machine token was NOT stored. ${remedy}`
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (sameEmail(approvedByEmail, expectEmail)) {
|
|
901
|
+
return { action: "approve", reason: "expected_email_match" };
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
action: "reject",
|
|
905
|
+
reason: "expected_email_mismatch",
|
|
906
|
+
message: `Pairing refused: this machine was approved by ${approvedByEmail}, but ${expectEmail} was expected. The machine token was NOT stored. If you did not expect that account to approve it, open BirdyBeep and revoke the machine, then re-run \`birdybeep pair\`.`
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
if (input.yes) return { action: "approve", reason: "yes_flag" };
|
|
910
|
+
const question = approvedByEmail !== void 0 ? `Pair this machine to ${approvedByEmail}? [y/N] ` : "The server did not report which account approved this machine. Pair anyway? [y/N] ";
|
|
911
|
+
if (!input.nonInteractive) {
|
|
912
|
+
if (input.stdinIsTTY) return { action: "prompt", question, on: "stdin" };
|
|
913
|
+
if (input.controllingTerminalAvailable) {
|
|
914
|
+
return { action: "prompt", question, on: "controlling-terminal" };
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const who = approvedByEmail !== void 0 ? ` (approved by ${approvedByEmail})` : "";
|
|
918
|
+
const winptyHint = platform === "win32" && !input.nonInteractive ? " In Git Bash / MSYS, `winpty birdybeep pair` attaches a real console so the prompt can appear." : "";
|
|
919
|
+
return {
|
|
920
|
+
action: "reject",
|
|
921
|
+
reason: "non_interactive",
|
|
922
|
+
message: `Pairing needs confirmation${who}, but there is no terminal to ask on, so the machine token was NOT stored. Re-run with \`--expect-email <addr>\` to pin the approving account (recommended for CI), or \`--yes\` to accept whichever account approved it.` + winptyHint
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
function isAffirmative(answer) {
|
|
926
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
927
|
+
}
|
|
928
|
+
function controllingTerminalPath() {
|
|
929
|
+
return "/dev/tty";
|
|
930
|
+
}
|
|
931
|
+
function canOpenControllingTerminal(path = controllingTerminalPath(), platform = process.platform) {
|
|
932
|
+
if (platform === "win32") return false;
|
|
933
|
+
let fd;
|
|
934
|
+
try {
|
|
935
|
+
fd = openSync2(path, "r");
|
|
936
|
+
return true;
|
|
937
|
+
} catch {
|
|
938
|
+
return false;
|
|
939
|
+
} finally {
|
|
940
|
+
if (fd !== void 0) {
|
|
941
|
+
try {
|
|
942
|
+
closeSync2(fd);
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
async function promptForAnswer(question, on) {
|
|
949
|
+
const { createInterface } = await import("readline/promises");
|
|
950
|
+
let ttyFd;
|
|
951
|
+
let input;
|
|
952
|
+
if (on === "stdin") {
|
|
953
|
+
input = process.stdin;
|
|
954
|
+
} else {
|
|
955
|
+
const { ReadStream } = await import("tty");
|
|
956
|
+
ttyFd = openSync2(controllingTerminalPath(), "r");
|
|
957
|
+
input = new ReadStream(ttyFd);
|
|
958
|
+
}
|
|
959
|
+
return new Promise((resolve) => {
|
|
960
|
+
const rl = createInterface({ input, output: process.stderr });
|
|
961
|
+
let settled = false;
|
|
962
|
+
const done = (value) => {
|
|
963
|
+
if (settled) return;
|
|
964
|
+
settled = true;
|
|
965
|
+
rl.close();
|
|
966
|
+
if (on === "stdin") {
|
|
967
|
+
process.stdin.unref?.();
|
|
968
|
+
} else {
|
|
969
|
+
try {
|
|
970
|
+
input.unref?.();
|
|
971
|
+
input.destroy?.();
|
|
972
|
+
} catch {
|
|
973
|
+
}
|
|
974
|
+
if (ttyFd !== void 0) {
|
|
975
|
+
try {
|
|
976
|
+
closeSync2(ttyFd);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
resolve(value);
|
|
982
|
+
};
|
|
983
|
+
rl.question(question).then(done, () => done(""));
|
|
984
|
+
rl.once("close", () => done(""));
|
|
985
|
+
input.once?.("error", () => done(""));
|
|
986
|
+
});
|
|
987
|
+
}
|
|
663
988
|
function createPairCommand(deps = {}) {
|
|
664
989
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
665
990
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
666
991
|
const clock = deps.now ?? (() => Date.now());
|
|
667
992
|
const renderQr = deps.renderQr ?? renderQrMatrix;
|
|
668
993
|
const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
994
|
+
const promptLine = deps.promptLine ?? promptForAnswer;
|
|
995
|
+
const hasControllingTerminal = deps.hasControllingTerminal ?? (() => canOpenControllingTerminal());
|
|
996
|
+
const configuredExpectEmail = deps.configuredExpectEmail ?? (() => {
|
|
997
|
+
const pinned = readCliConfig().expectEmail;
|
|
998
|
+
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
999
|
+
});
|
|
669
1000
|
return {
|
|
670
1001
|
name: "pair",
|
|
671
1002
|
summary: "Pair this machine with your BirdyBeep account (QR or manual)",
|
|
672
|
-
usage: "birdybeep pair [--json]",
|
|
1003
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--json]",
|
|
1004
|
+
options: [
|
|
1005
|
+
{
|
|
1006
|
+
flag: "--yes",
|
|
1007
|
+
aliases: ["-y"],
|
|
1008
|
+
summary: "Skip the approving-account confirmation (headless/CI)"
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
flag: "--expect-email",
|
|
1012
|
+
value: "<addr>",
|
|
1013
|
+
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
1014
|
+
}
|
|
1015
|
+
],
|
|
673
1016
|
run: async (ctx) => {
|
|
1017
|
+
const pairFlags = parsePairFlags(ctx.args);
|
|
1018
|
+
if (pairFlags.error !== void 0) {
|
|
1019
|
+
ctx.io.errline(`birdybeep pair: ${pairFlags.error}.`);
|
|
1020
|
+
return EXIT.USAGE;
|
|
1021
|
+
}
|
|
674
1022
|
const apiUrl = resolveApiUrl();
|
|
675
1023
|
const identity = getMachineIdentity2();
|
|
1024
|
+
const codeVerifier = generateCodeVerifier();
|
|
1025
|
+
const codeChallenge = deriveCodeChallengeS256(codeVerifier);
|
|
676
1026
|
const start = await pairStart(
|
|
677
1027
|
apiUrl,
|
|
678
|
-
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION },
|
|
1028
|
+
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
679
1029
|
fetchImpl
|
|
680
1030
|
);
|
|
681
1031
|
if (ctx.flags.json) {
|
|
@@ -687,12 +1037,14 @@ function createPairCommand(deps = {}) {
|
|
|
687
1037
|
});
|
|
688
1038
|
} else {
|
|
689
1039
|
ctx.io.line(
|
|
690
|
-
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR
|
|
1040
|
+
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR or open the complete link:"
|
|
691
1041
|
);
|
|
692
1042
|
const isTTY = deps.isTTY ?? process.stdout.isTTY === true;
|
|
693
1043
|
if (isTTY) ctx.io.line(renderQr(start.qr_payload));
|
|
694
1044
|
ctx.io.line(` Scan or open: ${start.qr_payload}`);
|
|
695
|
-
ctx.io.line(
|
|
1045
|
+
ctx.io.line(
|
|
1046
|
+
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1047
|
+
);
|
|
696
1048
|
ctx.io.line("Waiting for you to approve this machine in the app\u2026");
|
|
697
1049
|
}
|
|
698
1050
|
const deadline = Date.parse(start.expires_at);
|
|
@@ -708,7 +1060,9 @@ function createPairCommand(deps = {}) {
|
|
|
708
1060
|
apiUrl,
|
|
709
1061
|
start.device_code,
|
|
710
1062
|
fetchImpl,
|
|
711
|
-
identity.fingerprintHash
|
|
1063
|
+
identity.fingerprintHash,
|
|
1064
|
+
codeVerifier
|
|
1065
|
+
// PKCE proof-of-possession (dgxd) — sent on every poll
|
|
712
1066
|
);
|
|
713
1067
|
if (poll.status === "paired") {
|
|
714
1068
|
paired = poll;
|
|
@@ -733,15 +1087,44 @@ function createPairCommand(deps = {}) {
|
|
|
733
1087
|
if (paired === void 0 || paired.status !== "paired") {
|
|
734
1088
|
ctx.io.result({ paired: false, reason: "timeout" });
|
|
735
1089
|
ctx.io.errline(
|
|
736
|
-
"Pairing timed out before you approved it. In the BirdyBeep app, tap \u201Cpair a machine\u201D, scan
|
|
1090
|
+
"Pairing timed out before you approved it. In the BirdyBeep app, tap \u201Cpair a machine\u201D, scan a fresh QR or open its complete link, then run `birdybeep pair` again."
|
|
1091
|
+
);
|
|
1092
|
+
return EXIT.ERROR;
|
|
1093
|
+
}
|
|
1094
|
+
const approvedBy = paired.approvedByEmail;
|
|
1095
|
+
const expectEmail = pairFlags.expectEmail ?? configuredExpectEmail();
|
|
1096
|
+
const stdinIsTTY = deps.isStdinTTY ?? process.stdin.isTTY === true;
|
|
1097
|
+
const decision = decidePairConfirmation({
|
|
1098
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1099
|
+
...expectEmail !== void 0 ? { expectEmail } : {},
|
|
1100
|
+
...expectEmail !== void 0 ? { expectEmailSource: pairFlags.expectEmail !== void 0 ? "flag" : "config" } : {},
|
|
1101
|
+
yes: pairFlags.yes,
|
|
1102
|
+
nonInteractive: ctx.flags.nonInteractive,
|
|
1103
|
+
stdinIsTTY,
|
|
1104
|
+
// Probed ONLY when stdin can't answer — opening /dev/tty is a syscall, and when stdin is
|
|
1105
|
+
// already a terminal the answer is irrelevant.
|
|
1106
|
+
controllingTerminalAvailable: stdinIsTTY ? false : hasControllingTerminal(),
|
|
1107
|
+
configPath: cliConfigPath()
|
|
1108
|
+
});
|
|
1109
|
+
if (decision.action === "reject") {
|
|
1110
|
+
ctx.io.result({ paired: false, reason: decision.reason });
|
|
1111
|
+
ctx.io.errline(decision.message);
|
|
1112
|
+
return EXIT.ERROR;
|
|
1113
|
+
}
|
|
1114
|
+
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1115
|
+
ctx.io.result({ paired: false, reason: "declined" });
|
|
1116
|
+
ctx.io.errline(
|
|
1117
|
+
"Pairing declined \u2014 the machine token was NOT stored, and this machine will send no events. The machine may still appear in the BirdyBeep app; revoke it there if you did not intend to pair it."
|
|
737
1118
|
);
|
|
738
1119
|
return EXIT.ERROR;
|
|
739
1120
|
}
|
|
740
1121
|
await setToken(paired.machineToken, deps.tokenOptions ?? {});
|
|
741
1122
|
writeCliConfig({ apiUrl });
|
|
742
|
-
|
|
1123
|
+
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1124
|
+
ctx.io.emit(`\u2713 Paired${humanSuffix}. Run \`birdybeep test\` to send a test Beep.`, {
|
|
743
1125
|
paired: true,
|
|
744
|
-
machineId: paired.machineId
|
|
1126
|
+
machineId: paired.machineId,
|
|
1127
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {}
|
|
745
1128
|
});
|
|
746
1129
|
return EXIT.OK;
|
|
747
1130
|
}
|
|
@@ -773,19 +1156,29 @@ function createQueueCommand() {
|
|
|
773
1156
|
// src/commands/report-status.ts
|
|
774
1157
|
import {
|
|
775
1158
|
errorEnvelopeSchema as errorEnvelopeSchema2,
|
|
776
|
-
getToken as
|
|
1159
|
+
getToken as getToken3,
|
|
777
1160
|
integrationStatusResponseSchema
|
|
778
1161
|
} from "@birdybeep/agent-core";
|
|
779
1162
|
import { CLAUDE_CODE_ADAPTER_VERSION, claudeCodeAdapter as claudeCodeAdapter3 } from "@birdybeep/claude-code";
|
|
780
1163
|
import { CODEX_ADAPTER_VERSION, codexAdapter as codexAdapter3 } from "@birdybeep/codex";
|
|
1164
|
+
import { COPILOT_ADAPTER_VERSION, copilotAdapter as copilotAdapter3 } from "@birdybeep/copilot";
|
|
1165
|
+
import { CURSOR_ADAPTER_VERSION, cursorAdapter as cursorAdapter3 } from "@birdybeep/cursor";
|
|
781
1166
|
import { OPENCODE_ADAPTER_VERSION, opencodeAdapter as opencodeAdapter3 } from "@birdybeep/opencode";
|
|
782
|
-
var DEFAULT_ADAPTERS3 = [
|
|
1167
|
+
var DEFAULT_ADAPTERS3 = [
|
|
1168
|
+
claudeCodeAdapter3,
|
|
1169
|
+
codexAdapter3,
|
|
1170
|
+
opencodeAdapter3,
|
|
1171
|
+
cursorAdapter3,
|
|
1172
|
+
copilotAdapter3
|
|
1173
|
+
];
|
|
783
1174
|
var ADAPTER_VERSIONS = {
|
|
784
1175
|
claude_code: CLAUDE_CODE_ADAPTER_VERSION,
|
|
785
1176
|
codex: CODEX_ADAPTER_VERSION,
|
|
786
|
-
opencode: OPENCODE_ADAPTER_VERSION
|
|
1177
|
+
opencode: OPENCODE_ADAPTER_VERSION,
|
|
1178
|
+
cursor: CURSOR_ADAPTER_VERSION,
|
|
1179
|
+
copilot: COPILOT_ADAPTER_VERSION
|
|
787
1180
|
};
|
|
788
|
-
var
|
|
1181
|
+
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
789
1182
|
async function gatherItems(adapters) {
|
|
790
1183
|
return Promise.all(
|
|
791
1184
|
adapters.map(async (a) => {
|
|
@@ -806,7 +1199,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
806
1199
|
summary: "Internal: report integration status to the backend",
|
|
807
1200
|
usage: "birdybeep report-status [--json]",
|
|
808
1201
|
run: async (ctx) => {
|
|
809
|
-
const token = await
|
|
1202
|
+
const token = await getToken3(deps.tokenOptions ?? {});
|
|
810
1203
|
if (token === null) {
|
|
811
1204
|
ctx.io.errline("No machine token \u2014 run `birdybeep pair` first.");
|
|
812
1205
|
return EXIT.ERROR;
|
|
@@ -820,7 +1213,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
820
1213
|
let outcome = "deferred";
|
|
821
1214
|
let errorCode;
|
|
822
1215
|
try {
|
|
823
|
-
const res = await fetchImpl(`${
|
|
1216
|
+
const res = await fetchImpl(`${base3(resolveApiUrl())}/v1/integrations/status`, {
|
|
824
1217
|
method: "POST",
|
|
825
1218
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
826
1219
|
body: JSON.stringify({ integrations: items })
|
|
@@ -873,8 +1266,16 @@ import {
|
|
|
873
1266
|
} from "@birdybeep/agent-core";
|
|
874
1267
|
import { claudeCodeAdapter as claudeCodeAdapter4 } from "@birdybeep/claude-code";
|
|
875
1268
|
import { codexAdapter as codexAdapter4 } from "@birdybeep/codex";
|
|
1269
|
+
import { copilotAdapter as copilotAdapter4 } from "@birdybeep/copilot";
|
|
1270
|
+
import { cursorAdapter as cursorAdapter4 } from "@birdybeep/cursor";
|
|
876
1271
|
import { opencodeAdapter as opencodeAdapter4 } from "@birdybeep/opencode";
|
|
877
|
-
var DEFAULT_ADAPTERS4 = [
|
|
1272
|
+
var DEFAULT_ADAPTERS4 = [
|
|
1273
|
+
claudeCodeAdapter4,
|
|
1274
|
+
codexAdapter4,
|
|
1275
|
+
opencodeAdapter4,
|
|
1276
|
+
cursorAdapter4,
|
|
1277
|
+
copilotAdapter4
|
|
1278
|
+
];
|
|
878
1279
|
function createStatusCommand(deps = {}) {
|
|
879
1280
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
880
1281
|
const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender3(
|
|
@@ -1000,8 +1401,8 @@ function buildCommands() {
|
|
|
1000
1401
|
}
|
|
1001
1402
|
|
|
1002
1403
|
// src/update-check.ts
|
|
1003
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as
|
|
1004
|
-
import { join as
|
|
1404
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1405
|
+
import { join as join3 } from "path";
|
|
1005
1406
|
import { birdyBeepConfigDir as birdyBeepConfigDir3 } from "@birdybeep/agent-core";
|
|
1006
1407
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
1007
1408
|
var PACKAGE_PATH = "@birdybeep%2Fcli";
|
|
@@ -1056,7 +1457,7 @@ function isNewer(current, latest) {
|
|
|
1056
1457
|
return cur !== null && lat !== null && compareSemver(cur, lat) < 0;
|
|
1057
1458
|
}
|
|
1058
1459
|
function updateCachePath() {
|
|
1059
|
-
return
|
|
1460
|
+
return join3(birdyBeepConfigDir3(), UPDATE_CACHE_FILE);
|
|
1060
1461
|
}
|
|
1061
1462
|
function readUpdateCache() {
|
|
1062
1463
|
try {
|
|
@@ -1072,7 +1473,7 @@ function readUpdateCache() {
|
|
|
1072
1473
|
}
|
|
1073
1474
|
function writeUpdateCache(cache) {
|
|
1074
1475
|
mkdirSync3(birdyBeepConfigDir3(), { recursive: true, mode: 448 });
|
|
1075
|
-
|
|
1476
|
+
writeFileSync3(updateCachePath(), `${JSON.stringify(cache)}
|
|
1076
1477
|
`, { mode: 384 });
|
|
1077
1478
|
}
|
|
1078
1479
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|
|
@@ -1160,4 +1561,4 @@ export {
|
|
|
1160
1561
|
buildCommands,
|
|
1161
1562
|
runCli
|
|
1162
1563
|
};
|
|
1163
|
-
//# sourceMappingURL=chunk-
|
|
1564
|
+
//# sourceMappingURL=chunk-ZYFMLHY4.js.map
|