@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
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -35,6 +45,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
35
45
|
// src/commands/agent.ts
|
|
36
46
|
var import_claude_code = require("@birdybeep/claude-code");
|
|
37
47
|
var import_codex = require("@birdybeep/codex");
|
|
48
|
+
var import_copilot = require("@birdybeep/copilot");
|
|
49
|
+
var import_cursor = require("@birdybeep/cursor");
|
|
38
50
|
var import_opencode = require("@birdybeep/opencode");
|
|
39
51
|
|
|
40
52
|
// src/framework.ts
|
|
@@ -108,8 +120,11 @@ function parseGlobalFlags(argv) {
|
|
|
108
120
|
}
|
|
109
121
|
return { flags, rest };
|
|
110
122
|
}
|
|
111
|
-
function isUnknownFlag(token) {
|
|
112
|
-
|
|
123
|
+
function isUnknownFlag(token, allowed) {
|
|
124
|
+
if (!token.startsWith("-")) return false;
|
|
125
|
+
if (GLOBAL_FLAG_TOKENS.has(token)) return false;
|
|
126
|
+
const eq = token.indexOf("=");
|
|
127
|
+
return !allowed.has(eq >= 0 ? token.slice(0, eq) : token);
|
|
113
128
|
}
|
|
114
129
|
function renderRootHelp(version, commands) {
|
|
115
130
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
@@ -130,6 +145,16 @@ function renderRootHelp(version, commands) {
|
|
|
130
145
|
" -v, --version Show the CLI version"
|
|
131
146
|
].join("\n");
|
|
132
147
|
}
|
|
148
|
+
function commandFlagTokens(...commands) {
|
|
149
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
150
|
+
for (const command of commands) {
|
|
151
|
+
for (const option of command?.options ?? []) {
|
|
152
|
+
tokens.add(option.flag);
|
|
153
|
+
for (const alias of option.aliases ?? []) tokens.add(alias);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return tokens;
|
|
157
|
+
}
|
|
133
158
|
function renderCommandHelp(path, command) {
|
|
134
159
|
const lines = [
|
|
135
160
|
`birdybeep ${path} \u2014 ${command.summary}`,
|
|
@@ -137,6 +162,17 @@ function renderCommandHelp(path, command) {
|
|
|
137
162
|
"Usage:",
|
|
138
163
|
` ${command.usage ?? `birdybeep ${path} [options]`}`
|
|
139
164
|
];
|
|
165
|
+
if (command.options && command.options.length > 0) {
|
|
166
|
+
const labels = command.options.map(
|
|
167
|
+
(o) => `${[o.flag, ...o.aliases ?? []].join(", ")}${o.value ? ` ${o.value}` : ""}`
|
|
168
|
+
);
|
|
169
|
+
const width = Math.max(...labels.map((l) => l.length));
|
|
170
|
+
lines.push(
|
|
171
|
+
"",
|
|
172
|
+
"Options:",
|
|
173
|
+
...command.options.map((o, i) => ` ${labels[i]?.padEnd(width)} ${o.summary}`)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
140
176
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
141
177
|
const width = Math.max(...command.subcommands.map((c) => c.name.length));
|
|
142
178
|
lines.push(
|
|
@@ -161,6 +197,7 @@ async function dispatch(argv, deps) {
|
|
|
161
197
|
return EXIT.OK;
|
|
162
198
|
}
|
|
163
199
|
let command = deps.commands.find((c) => c.name === rest[0]);
|
|
200
|
+
let parent;
|
|
164
201
|
const pathParts = [];
|
|
165
202
|
let argsStart = 1;
|
|
166
203
|
if (command) {
|
|
@@ -168,6 +205,7 @@ async function dispatch(argv, deps) {
|
|
|
168
205
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
169
206
|
const sub = command.subcommands.find((c) => c.name === rest[1]);
|
|
170
207
|
if (sub) {
|
|
208
|
+
parent = command;
|
|
171
209
|
command = sub;
|
|
172
210
|
pathParts.push(sub.name);
|
|
173
211
|
argsStart = 2;
|
|
@@ -191,6 +229,7 @@ async function dispatch(argv, deps) {
|
|
|
191
229
|
name: path,
|
|
192
230
|
summary: command.summary,
|
|
193
231
|
usage: command.usage,
|
|
232
|
+
options: command.options,
|
|
194
233
|
subcommands: command.subcommands?.map((c) => ({ name: c.name, summary: c.summary }))
|
|
195
234
|
});
|
|
196
235
|
return EXIT.OK;
|
|
@@ -200,7 +239,8 @@ async function dispatch(argv, deps) {
|
|
|
200
239
|
return EXIT.USAGE;
|
|
201
240
|
}
|
|
202
241
|
const args = rest.slice(argsStart);
|
|
203
|
-
const
|
|
242
|
+
const allowed = commandFlagTokens(command, parent);
|
|
243
|
+
const unknown = args.find((token) => isUnknownFlag(token, allowed));
|
|
204
244
|
if (unknown !== void 0) {
|
|
205
245
|
io.errline(`birdybeep ${path}: unknown option "${unknown}".`);
|
|
206
246
|
return EXIT.USAGE;
|
|
@@ -228,13 +268,28 @@ async function dispatch(argv, deps) {
|
|
|
228
268
|
}
|
|
229
269
|
|
|
230
270
|
// src/commands/agent.ts
|
|
231
|
-
var DEFAULT_ADAPTERS = [
|
|
271
|
+
var DEFAULT_ADAPTERS = [
|
|
272
|
+
import_claude_code.claudeCodeAdapter,
|
|
273
|
+
import_codex.codexAdapter,
|
|
274
|
+
import_opencode.opencodeAdapter,
|
|
275
|
+
import_cursor.cursorAdapter,
|
|
276
|
+
import_copilot.copilotAdapter
|
|
277
|
+
];
|
|
232
278
|
var TARGET_TO_ID = {
|
|
233
279
|
claude: "claude_code",
|
|
234
280
|
codex: "codex",
|
|
235
|
-
opencode: "opencode"
|
|
281
|
+
opencode: "opencode",
|
|
282
|
+
cursor: "cursor",
|
|
283
|
+
copilot: "copilot"
|
|
236
284
|
};
|
|
237
|
-
var AGENT_TARGETS = [
|
|
285
|
+
var AGENT_TARGETS = [
|
|
286
|
+
"all",
|
|
287
|
+
"claude",
|
|
288
|
+
"codex",
|
|
289
|
+
"opencode",
|
|
290
|
+
"cursor",
|
|
291
|
+
"copilot"
|
|
292
|
+
];
|
|
238
293
|
function selectAdapters(target, adapters) {
|
|
239
294
|
if (target === "all") return adapters;
|
|
240
295
|
const id = TARGET_TO_ID[target];
|
|
@@ -325,18 +380,18 @@ function createAgentCommand(deps = {}) {
|
|
|
325
380
|
return {
|
|
326
381
|
name: "agent",
|
|
327
382
|
summary: "Install or uninstall harness adapters",
|
|
328
|
-
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode]",
|
|
383
|
+
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode|cursor|copilot]",
|
|
329
384
|
subcommands: [
|
|
330
385
|
{
|
|
331
386
|
name: "install",
|
|
332
|
-
summary: "Install adapters (all | claude | codex | opencode)",
|
|
333
|
-
usage: "birdybeep agent install [all|claude|codex|opencode]",
|
|
387
|
+
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
388
|
+
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
334
389
|
run: (ctx) => installSelected(adapters, ctx)
|
|
335
390
|
},
|
|
336
391
|
{
|
|
337
392
|
name: "uninstall",
|
|
338
393
|
summary: "Restore harness config to its pre-install state",
|
|
339
|
-
usage: "birdybeep agent uninstall [all|claude|codex|opencode]",
|
|
394
|
+
usage: "birdybeep agent uninstall [all|claude|codex|opencode|cursor|copilot]",
|
|
340
395
|
run: (ctx) => uninstallSelected(adapters, ctx)
|
|
341
396
|
}
|
|
342
397
|
]
|
|
@@ -347,6 +402,8 @@ function createAgentCommand(deps = {}) {
|
|
|
347
402
|
var import_agent_core4 = require("@birdybeep/agent-core");
|
|
348
403
|
var import_claude_code2 = require("@birdybeep/claude-code");
|
|
349
404
|
var import_codex2 = require("@birdybeep/codex");
|
|
405
|
+
var import_copilot2 = require("@birdybeep/copilot");
|
|
406
|
+
var import_cursor2 = require("@birdybeep/cursor");
|
|
350
407
|
var import_opencode2 = require("@birdybeep/opencode");
|
|
351
408
|
|
|
352
409
|
// src/config.ts
|
|
@@ -371,6 +428,8 @@ function writeCliConfig(patch) {
|
|
|
371
428
|
const merged = {};
|
|
372
429
|
const apiUrl = patch.apiUrl ?? current.apiUrl;
|
|
373
430
|
if (apiUrl !== void 0) merged.apiUrl = apiUrl;
|
|
431
|
+
const expectEmail = patch.expectEmail ?? current.expectEmail;
|
|
432
|
+
if (expectEmail !== void 0) merged.expectEmail = expectEmail;
|
|
374
433
|
(0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
375
434
|
(0, import_node_fs2.writeFileSync)(cliConfigPath(), `${JSON.stringify(merged, null, 2)}
|
|
376
435
|
`, { mode: 384 });
|
|
@@ -409,7 +468,13 @@ function machineIdentity() {
|
|
|
409
468
|
}
|
|
410
469
|
|
|
411
470
|
// src/commands/doctor.ts
|
|
412
|
-
var DEFAULT_ADAPTERS2 = [
|
|
471
|
+
var DEFAULT_ADAPTERS2 = [
|
|
472
|
+
import_claude_code2.claudeCodeAdapter,
|
|
473
|
+
import_codex2.codexAdapter,
|
|
474
|
+
import_opencode2.opencodeAdapter,
|
|
475
|
+
import_cursor2.cursorAdapter,
|
|
476
|
+
import_copilot2.copilotAdapter
|
|
477
|
+
];
|
|
413
478
|
async function defaultProbeNetwork(baseUrl) {
|
|
414
479
|
try {
|
|
415
480
|
const controller = new AbortController();
|
|
@@ -492,16 +557,30 @@ function createDoctorCommand(deps = {}) {
|
|
|
492
557
|
}
|
|
493
558
|
|
|
494
559
|
// src/commands/hook.ts
|
|
560
|
+
var import_node_child_process = require("child_process");
|
|
561
|
+
var import_node_crypto = require("crypto");
|
|
562
|
+
var import_node_fs3 = require("fs");
|
|
563
|
+
var import_node_os = require("os");
|
|
564
|
+
var import_node_path2 = require("path");
|
|
495
565
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
496
566
|
var import_claude_code3 = require("@birdybeep/claude-code");
|
|
497
567
|
var import_codex3 = require("@birdybeep/codex");
|
|
568
|
+
var import_copilot3 = require("@birdybeep/copilot");
|
|
569
|
+
var import_cursor3 = require("@birdybeep/cursor");
|
|
498
570
|
var import_opencode3 = require("@birdybeep/opencode");
|
|
499
571
|
var RUNNERS = {
|
|
500
572
|
claude: import_claude_code3.runClaudeHook,
|
|
501
573
|
codex: import_codex3.runCodexHook,
|
|
502
|
-
opencode: import_opencode3.runOpenCodeHook
|
|
574
|
+
opencode: import_opencode3.runOpenCodeHook,
|
|
575
|
+
cursor: import_cursor3.runCursorHook
|
|
503
576
|
};
|
|
504
|
-
var HOOK_HARNESSES = [
|
|
577
|
+
var HOOK_HARNESSES = [
|
|
578
|
+
"claude",
|
|
579
|
+
"codex",
|
|
580
|
+
"opencode",
|
|
581
|
+
"cursor",
|
|
582
|
+
"copilot"
|
|
583
|
+
];
|
|
505
584
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
506
585
|
function withTimeout(promise, ms, fallback) {
|
|
507
586
|
return new Promise((resolve) => {
|
|
@@ -518,10 +597,31 @@ function withTimeout(promise, ms, fallback) {
|
|
|
518
597
|
});
|
|
519
598
|
}
|
|
520
599
|
function isHarnessName(value) {
|
|
521
|
-
return value === "claude" || value === "codex" || value === "opencode";
|
|
600
|
+
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
601
|
+
}
|
|
602
|
+
function resolveHookHarness(harness, payload) {
|
|
603
|
+
return harness === "claude" && (0, import_cursor3.isCursorHookPayload)(payload) ? "cursor" : harness;
|
|
604
|
+
}
|
|
605
|
+
function recognizesPayload(harness, payload) {
|
|
606
|
+
if (harness === "claude") return (0, import_claude_code3.isClaudeCodeHookPayload)(payload);
|
|
607
|
+
if (harness === "cursor") return (0, import_cursor3.isCursorHookEventName)(asRecord(payload)["hook_event_name"]);
|
|
608
|
+
return true;
|
|
522
609
|
}
|
|
523
|
-
function
|
|
524
|
-
return
|
|
610
|
+
function asRecord(value) {
|
|
611
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
612
|
+
}
|
|
613
|
+
function describeEventName(payload) {
|
|
614
|
+
const name = asRecord(payload)["hook_event_name"];
|
|
615
|
+
if (typeof name !== "string") return "(absent)";
|
|
616
|
+
return JSON.stringify(name.length > 64 ? `${name.slice(0, 63)}\u2026` : name);
|
|
617
|
+
}
|
|
618
|
+
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
619
|
+
const handler = resolveHookHarness(harness, payload);
|
|
620
|
+
if (handler === "copilot") {
|
|
621
|
+
if (copilotEventName === void 0) return Promise.resolve({ outcome: "skipped" });
|
|
622
|
+
return (0, import_copilot3.runCopilotHook)(copilotEventName, payload, { sender });
|
|
623
|
+
}
|
|
624
|
+
return RUNNERS[handler](payload, { sender });
|
|
525
625
|
}
|
|
526
626
|
function readStdinDefault() {
|
|
527
627
|
return new Promise((resolve) => {
|
|
@@ -536,24 +636,90 @@ function readStdinDefault() {
|
|
|
536
636
|
process.stdin.on("error", () => resolve(""));
|
|
537
637
|
});
|
|
538
638
|
}
|
|
539
|
-
async function readHookPayload(args, readStdin) {
|
|
540
|
-
return args[1] ?? await readStdin();
|
|
639
|
+
async function readHookPayload(args, readStdin, stdinOnly = false) {
|
|
640
|
+
return stdinOnly ? readStdin() : args[1] ?? await readStdin();
|
|
641
|
+
}
|
|
642
|
+
var NOTIFY_STDIN_FILE_ENV = "BIRDYBEEP_CODEX_NOTIFY_STDIN_FILE";
|
|
643
|
+
function detachCodexNotifyWorker(payload) {
|
|
644
|
+
if (process.platform === "win32") return false;
|
|
645
|
+
let file;
|
|
646
|
+
let fd;
|
|
647
|
+
try {
|
|
648
|
+
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
649
|
+
if (birdybeep === null) return false;
|
|
650
|
+
const tmpFile = (0, import_node_path2.join)((0, import_node_os.tmpdir)(), `birdybeep-notify-${(0, import_node_crypto.randomBytes)(16).toString("hex")}.json`);
|
|
651
|
+
file = tmpFile;
|
|
652
|
+
(0, import_node_fs3.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
653
|
+
fd = (0, import_node_fs3.openSync)(tmpFile, "r");
|
|
654
|
+
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
655
|
+
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
656
|
+
// trusted dir, never the inherited/attacker cwd
|
|
657
|
+
detached: true,
|
|
658
|
+
// new session (setsid) → survives `codex exec` reaping the group
|
|
659
|
+
stdio: [fd, "ignore", "ignore"],
|
|
660
|
+
// stdin = the temp file; this process holds no pipe
|
|
661
|
+
env: { ...process.env, [NOTIFY_STDIN_FILE_ENV]: tmpFile },
|
|
662
|
+
// worker cleans it up post-read
|
|
663
|
+
windowsHide: true
|
|
664
|
+
});
|
|
665
|
+
child.on("error", () => {
|
|
666
|
+
try {
|
|
667
|
+
(0, import_node_fs3.rmSync)(tmpFile, { force: true });
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
child.unref();
|
|
672
|
+
return true;
|
|
673
|
+
} catch {
|
|
674
|
+
if (file !== void 0) {
|
|
675
|
+
try {
|
|
676
|
+
(0, import_node_fs3.rmSync)(file, { force: true });
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
} finally {
|
|
682
|
+
if (fd !== void 0) {
|
|
683
|
+
try {
|
|
684
|
+
(0, import_node_fs3.closeSync)(fd);
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
541
689
|
}
|
|
542
690
|
function createHookCommand(deps = {}) {
|
|
543
691
|
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core5.createSender)({ baseUrl }));
|
|
544
692
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
545
693
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
694
|
+
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
546
695
|
return {
|
|
547
696
|
name: "hook",
|
|
548
697
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
549
|
-
usage: "birdybeep hook <claude|codex|opencode>",
|
|
698
|
+
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
550
699
|
run: async (ctx) => {
|
|
551
700
|
const harness = ctx.args[0];
|
|
552
701
|
if (!isHarnessName(harness)) {
|
|
553
702
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
554
703
|
return EXIT.USAGE;
|
|
555
704
|
}
|
|
556
|
-
const
|
|
705
|
+
const notifyPayload = ctx.args[1];
|
|
706
|
+
if (harness === "codex" && notifyPayload !== void 0 && notifyPayload.length > 0 && detachCodexNotify(notifyPayload)) {
|
|
707
|
+
ctx.io.result({ harness, outcome: "detached" });
|
|
708
|
+
return EXIT.OK;
|
|
709
|
+
}
|
|
710
|
+
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
711
|
+
const raw = await withTimeout(
|
|
712
|
+
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
713
|
+
stdinTimeoutMs,
|
|
714
|
+
""
|
|
715
|
+
);
|
|
716
|
+
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
717
|
+
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0, import_node_os.tmpdir)() && (0, import_node_path2.basename)(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
718
|
+
try {
|
|
719
|
+
(0, import_node_fs3.rmSync)(notifyStdinFile, { force: true });
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
557
723
|
let payload;
|
|
558
724
|
try {
|
|
559
725
|
payload = JSON.parse(raw);
|
|
@@ -562,8 +728,23 @@ function createHookCommand(deps = {}) {
|
|
|
562
728
|
return EXIT.OK;
|
|
563
729
|
}
|
|
564
730
|
const sender = makeSender(resolveApiUrl());
|
|
565
|
-
const
|
|
566
|
-
|
|
731
|
+
const handler = resolveHookHarness(harness, payload);
|
|
732
|
+
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
733
|
+
ctx.io.result({
|
|
734
|
+
harness: handler,
|
|
735
|
+
...handler !== harness ? { routedFrom: harness } : {},
|
|
736
|
+
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
737
|
+
outcome: result.outcome,
|
|
738
|
+
eventType: result.eventType,
|
|
739
|
+
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
740
|
+
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
741
|
+
});
|
|
742
|
+
if (result.outcome === "skipped" && !recognizesPayload(handler, payload)) {
|
|
743
|
+
ctx.io.errline(
|
|
744
|
+
`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.`
|
|
745
|
+
);
|
|
746
|
+
return EXIT.ERROR;
|
|
747
|
+
}
|
|
567
748
|
return EXIT.OK;
|
|
568
749
|
}
|
|
569
750
|
};
|
|
@@ -571,57 +752,73 @@ function createHookCommand(deps = {}) {
|
|
|
571
752
|
|
|
572
753
|
// src/commands/logout.ts
|
|
573
754
|
var import_agent_core6 = require("@birdybeep/agent-core");
|
|
574
|
-
|
|
755
|
+
var base = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
756
|
+
function createLogoutCommand(deps = {}) {
|
|
575
757
|
return {
|
|
576
|
-
name:
|
|
577
|
-
summary:
|
|
578
|
-
usage:
|
|
758
|
+
name: "logout",
|
|
759
|
+
summary: "Remove the local machine token (does NOT revoke the machine server-side)",
|
|
760
|
+
usage: "birdybeep logout",
|
|
579
761
|
run: async (ctx) => {
|
|
580
762
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
581
|
-
ctx.io.emit(
|
|
763
|
+
ctx.io.emit("Logged out \u2014 the machine token was removed.", { loggedOut: true });
|
|
582
764
|
return EXIT.OK;
|
|
583
765
|
}
|
|
584
766
|
};
|
|
585
767
|
}
|
|
586
|
-
function
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
768
|
+
async function revokeSelf(token, fetchImpl, timeoutMs) {
|
|
769
|
+
const controller = new AbortController();
|
|
770
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
771
|
+
try {
|
|
772
|
+
const res = await fetchImpl(`${base(resolveApiUrl())}/v1/machine/revoke-self`, {
|
|
773
|
+
method: "POST",
|
|
774
|
+
headers: { authorization: `Bearer ${token}` },
|
|
775
|
+
signal: controller.signal
|
|
776
|
+
});
|
|
777
|
+
if (res.ok || res.status === 403) return "revoked";
|
|
778
|
+
return "rejected";
|
|
779
|
+
} catch {
|
|
780
|
+
return "unreachable";
|
|
781
|
+
} finally {
|
|
782
|
+
clearTimeout(timer);
|
|
783
|
+
}
|
|
596
784
|
}
|
|
597
785
|
function createUnpairCommand(deps = {}) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
786
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
787
|
+
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
788
|
+
return {
|
|
789
|
+
name: "unpair",
|
|
790
|
+
summary: "Unpair this machine \u2014 revoke it server-side and remove the local token",
|
|
791
|
+
usage: "birdybeep unpair",
|
|
792
|
+
run: async (ctx) => {
|
|
793
|
+
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
794
|
+
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
795
|
+
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
796
|
+
const serverRevoked = outcome === "revoked";
|
|
797
|
+
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.";
|
|
798
|
+
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
799
|
+
return EXIT.OK;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
607
802
|
}
|
|
608
803
|
|
|
609
804
|
// src/commands/pair.ts
|
|
805
|
+
var import_node_fs4 = require("fs");
|
|
610
806
|
var import_agent_core8 = require("@birdybeep/agent-core");
|
|
611
807
|
var import_uqr = require("uqr");
|
|
612
808
|
|
|
613
809
|
// src/pairing.ts
|
|
614
810
|
var import_agent_core7 = require("@birdybeep/agent-core");
|
|
615
|
-
function
|
|
811
|
+
function base2(apiUrl) {
|
|
616
812
|
return apiUrl.replace(/\/$/, "");
|
|
617
813
|
}
|
|
618
814
|
async function pairStart(apiUrl, input, fetchImpl) {
|
|
619
815
|
const body = {
|
|
620
816
|
machine_label: input.machineLabel,
|
|
621
817
|
...input.os !== void 0 ? { os: input.os } : {},
|
|
622
|
-
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {}
|
|
818
|
+
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {},
|
|
819
|
+
...input.codeChallenge !== void 0 ? { code_challenge: input.codeChallenge } : {}
|
|
623
820
|
};
|
|
624
|
-
const res = await fetchImpl(`${
|
|
821
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/start`, {
|
|
625
822
|
method: "POST",
|
|
626
823
|
headers: { "content-type": "application/json" },
|
|
627
824
|
body: JSON.stringify(body)
|
|
@@ -639,12 +836,13 @@ var TERMINAL_TOKEN_ERRORS = /* @__PURE__ */ new Set([
|
|
|
639
836
|
"not_found",
|
|
640
837
|
"payload_too_large"
|
|
641
838
|
]);
|
|
642
|
-
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint) {
|
|
839
|
+
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint, codeVerifier) {
|
|
643
840
|
const body = {
|
|
644
841
|
device_code: deviceCode,
|
|
645
|
-
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {}
|
|
842
|
+
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {},
|
|
843
|
+
...codeVerifier !== void 0 ? { code_verifier: codeVerifier } : {}
|
|
646
844
|
};
|
|
647
|
-
const res = await fetchImpl(`${
|
|
845
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/token`, {
|
|
648
846
|
method: "POST",
|
|
649
847
|
headers: { "content-type": "application/json" },
|
|
650
848
|
body: JSON.stringify(body)
|
|
@@ -655,7 +853,10 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
|
|
|
655
853
|
return {
|
|
656
854
|
status: "paired",
|
|
657
855
|
machineToken: parsed.data.machine_token,
|
|
658
|
-
machineId: parsed.data.machine_id
|
|
856
|
+
machineId: parsed.data.machine_id,
|
|
857
|
+
// Only surface the key when the server reported it (exactOptionalPropertyTypes: no explicit
|
|
858
|
+
// undefined). Older servers omit approved_by_email; newer ones (dgxd) include it.
|
|
859
|
+
...parsed.data.approved_by_email !== void 0 ? { approvedByEmail: parsed.data.approved_by_email } : {}
|
|
659
860
|
};
|
|
660
861
|
}
|
|
661
862
|
let errBody = null;
|
|
@@ -676,7 +877,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
|
|
|
676
877
|
}
|
|
677
878
|
|
|
678
879
|
// src/version.ts
|
|
679
|
-
var CLI_VERSION = "0.
|
|
880
|
+
var CLI_VERSION = "0.4.0".length > 0 ? "0.4.0" : "0.0.0";
|
|
680
881
|
|
|
681
882
|
// src/commands/pair.ts
|
|
682
883
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -684,22 +885,172 @@ var HEARTBEAT_MS = 15e3;
|
|
|
684
885
|
function renderQrMatrix(qrPayload) {
|
|
685
886
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
686
887
|
}
|
|
888
|
+
function parsePairFlags(args) {
|
|
889
|
+
const flags = { yes: false };
|
|
890
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
891
|
+
const token = args[i] ?? "";
|
|
892
|
+
if (token === "--yes" || token === "-y") {
|
|
893
|
+
flags.yes = true;
|
|
894
|
+
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
895
|
+
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
896
|
+
const value = inline ?? args[++i];
|
|
897
|
+
if (value === void 0 || value.length === 0 || value.startsWith("-")) {
|
|
898
|
+
return { ...flags, error: "--expect-email requires an email address" };
|
|
899
|
+
}
|
|
900
|
+
flags.expectEmail = value;
|
|
901
|
+
} else {
|
|
902
|
+
return { ...flags, error: `unexpected argument "${token}"` };
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return flags;
|
|
906
|
+
}
|
|
907
|
+
function sameEmail(a, b) {
|
|
908
|
+
const fold = (v) => v.trim().toLowerCase();
|
|
909
|
+
const rawEqual = fold(a) === fold(b);
|
|
910
|
+
const nfkcEqual = fold(a.normalize("NFKC")) === fold(b.normalize("NFKC"));
|
|
911
|
+
return rawEqual && nfkcEqual;
|
|
912
|
+
}
|
|
913
|
+
function decidePairConfirmation(input) {
|
|
914
|
+
const { approvedByEmail, expectEmail } = input;
|
|
915
|
+
const platform = input.platform ?? process.platform;
|
|
916
|
+
if (expectEmail !== void 0) {
|
|
917
|
+
if (approvedByEmail === void 0) {
|
|
918
|
+
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.";
|
|
919
|
+
return {
|
|
920
|
+
action: "reject",
|
|
921
|
+
reason: "expected_email_unverifiable",
|
|
922
|
+
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}`
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (sameEmail(approvedByEmail, expectEmail)) {
|
|
926
|
+
return { action: "approve", reason: "expected_email_match" };
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
action: "reject",
|
|
930
|
+
reason: "expected_email_mismatch",
|
|
931
|
+
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\`.`
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
if (input.yes) return { action: "approve", reason: "yes_flag" };
|
|
935
|
+
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] ";
|
|
936
|
+
if (!input.nonInteractive) {
|
|
937
|
+
if (input.stdinIsTTY) return { action: "prompt", question, on: "stdin" };
|
|
938
|
+
if (input.controllingTerminalAvailable) {
|
|
939
|
+
return { action: "prompt", question, on: "controlling-terminal" };
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const who = approvedByEmail !== void 0 ? ` (approved by ${approvedByEmail})` : "";
|
|
943
|
+
const winptyHint = platform === "win32" && !input.nonInteractive ? " In Git Bash / MSYS, `winpty birdybeep pair` attaches a real console so the prompt can appear." : "";
|
|
944
|
+
return {
|
|
945
|
+
action: "reject",
|
|
946
|
+
reason: "non_interactive",
|
|
947
|
+
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
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function isAffirmative(answer) {
|
|
951
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
952
|
+
}
|
|
953
|
+
function controllingTerminalPath() {
|
|
954
|
+
return "/dev/tty";
|
|
955
|
+
}
|
|
956
|
+
function canOpenControllingTerminal(path = controllingTerminalPath(), platform = process.platform) {
|
|
957
|
+
if (platform === "win32") return false;
|
|
958
|
+
let fd;
|
|
959
|
+
try {
|
|
960
|
+
fd = (0, import_node_fs4.openSync)(path, "r");
|
|
961
|
+
return true;
|
|
962
|
+
} catch {
|
|
963
|
+
return false;
|
|
964
|
+
} finally {
|
|
965
|
+
if (fd !== void 0) {
|
|
966
|
+
try {
|
|
967
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
async function promptForAnswer(question, on) {
|
|
974
|
+
const { createInterface } = await import("readline/promises");
|
|
975
|
+
let ttyFd;
|
|
976
|
+
let input;
|
|
977
|
+
if (on === "stdin") {
|
|
978
|
+
input = process.stdin;
|
|
979
|
+
} else {
|
|
980
|
+
const { ReadStream } = await import("tty");
|
|
981
|
+
ttyFd = (0, import_node_fs4.openSync)(controllingTerminalPath(), "r");
|
|
982
|
+
input = new ReadStream(ttyFd);
|
|
983
|
+
}
|
|
984
|
+
return new Promise((resolve) => {
|
|
985
|
+
const rl = createInterface({ input, output: process.stderr });
|
|
986
|
+
let settled = false;
|
|
987
|
+
const done = (value) => {
|
|
988
|
+
if (settled) return;
|
|
989
|
+
settled = true;
|
|
990
|
+
rl.close();
|
|
991
|
+
if (on === "stdin") {
|
|
992
|
+
process.stdin.unref?.();
|
|
993
|
+
} else {
|
|
994
|
+
try {
|
|
995
|
+
input.unref?.();
|
|
996
|
+
input.destroy?.();
|
|
997
|
+
} catch {
|
|
998
|
+
}
|
|
999
|
+
if (ttyFd !== void 0) {
|
|
1000
|
+
try {
|
|
1001
|
+
(0, import_node_fs4.closeSync)(ttyFd);
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
resolve(value);
|
|
1007
|
+
};
|
|
1008
|
+
rl.question(question).then(done, () => done(""));
|
|
1009
|
+
rl.once("close", () => done(""));
|
|
1010
|
+
input.once?.("error", () => done(""));
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
687
1013
|
function createPairCommand(deps = {}) {
|
|
688
1014
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
689
1015
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
690
1016
|
const clock = deps.now ?? (() => Date.now());
|
|
691
1017
|
const renderQr = deps.renderQr ?? renderQrMatrix;
|
|
692
1018
|
const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1019
|
+
const promptLine = deps.promptLine ?? promptForAnswer;
|
|
1020
|
+
const hasControllingTerminal = deps.hasControllingTerminal ?? (() => canOpenControllingTerminal());
|
|
1021
|
+
const configuredExpectEmail = deps.configuredExpectEmail ?? (() => {
|
|
1022
|
+
const pinned = readCliConfig().expectEmail;
|
|
1023
|
+
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
1024
|
+
});
|
|
693
1025
|
return {
|
|
694
1026
|
name: "pair",
|
|
695
1027
|
summary: "Pair this machine with your BirdyBeep account (QR or manual)",
|
|
696
|
-
usage: "birdybeep pair [--json]",
|
|
1028
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--json]",
|
|
1029
|
+
options: [
|
|
1030
|
+
{
|
|
1031
|
+
flag: "--yes",
|
|
1032
|
+
aliases: ["-y"],
|
|
1033
|
+
summary: "Skip the approving-account confirmation (headless/CI)"
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
flag: "--expect-email",
|
|
1037
|
+
value: "<addr>",
|
|
1038
|
+
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
697
1041
|
run: async (ctx) => {
|
|
1042
|
+
const pairFlags = parsePairFlags(ctx.args);
|
|
1043
|
+
if (pairFlags.error !== void 0) {
|
|
1044
|
+
ctx.io.errline(`birdybeep pair: ${pairFlags.error}.`);
|
|
1045
|
+
return EXIT.USAGE;
|
|
1046
|
+
}
|
|
698
1047
|
const apiUrl = resolveApiUrl();
|
|
699
1048
|
const identity = (0, import_agent_core8.getMachineIdentity)();
|
|
1049
|
+
const codeVerifier = (0, import_agent_core8.generateCodeVerifier)();
|
|
1050
|
+
const codeChallenge = (0, import_agent_core8.deriveCodeChallengeS256)(codeVerifier);
|
|
700
1051
|
const start = await pairStart(
|
|
701
1052
|
apiUrl,
|
|
702
|
-
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION },
|
|
1053
|
+
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
703
1054
|
fetchImpl
|
|
704
1055
|
);
|
|
705
1056
|
if (ctx.flags.json) {
|
|
@@ -711,12 +1062,14 @@ function createPairCommand(deps = {}) {
|
|
|
711
1062
|
});
|
|
712
1063
|
} else {
|
|
713
1064
|
ctx.io.line(
|
|
714
|
-
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR
|
|
1065
|
+
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR or open the complete link:"
|
|
715
1066
|
);
|
|
716
1067
|
const isTTY = deps.isTTY ?? process.stdout.isTTY === true;
|
|
717
1068
|
if (isTTY) ctx.io.line(renderQr(start.qr_payload));
|
|
718
1069
|
ctx.io.line(` Scan or open: ${start.qr_payload}`);
|
|
719
|
-
ctx.io.line(
|
|
1070
|
+
ctx.io.line(
|
|
1071
|
+
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1072
|
+
);
|
|
720
1073
|
ctx.io.line("Waiting for you to approve this machine in the app\u2026");
|
|
721
1074
|
}
|
|
722
1075
|
const deadline = Date.parse(start.expires_at);
|
|
@@ -732,7 +1085,9 @@ function createPairCommand(deps = {}) {
|
|
|
732
1085
|
apiUrl,
|
|
733
1086
|
start.device_code,
|
|
734
1087
|
fetchImpl,
|
|
735
|
-
identity.fingerprintHash
|
|
1088
|
+
identity.fingerprintHash,
|
|
1089
|
+
codeVerifier
|
|
1090
|
+
// PKCE proof-of-possession (dgxd) — sent on every poll
|
|
736
1091
|
);
|
|
737
1092
|
if (poll.status === "paired") {
|
|
738
1093
|
paired = poll;
|
|
@@ -757,15 +1112,44 @@ function createPairCommand(deps = {}) {
|
|
|
757
1112
|
if (paired === void 0 || paired.status !== "paired") {
|
|
758
1113
|
ctx.io.result({ paired: false, reason: "timeout" });
|
|
759
1114
|
ctx.io.errline(
|
|
760
|
-
"Pairing timed out before you approved it. In the BirdyBeep app, tap \u201Cpair a machine\u201D, scan
|
|
1115
|
+
"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."
|
|
1116
|
+
);
|
|
1117
|
+
return EXIT.ERROR;
|
|
1118
|
+
}
|
|
1119
|
+
const approvedBy = paired.approvedByEmail;
|
|
1120
|
+
const expectEmail = pairFlags.expectEmail ?? configuredExpectEmail();
|
|
1121
|
+
const stdinIsTTY = deps.isStdinTTY ?? process.stdin.isTTY === true;
|
|
1122
|
+
const decision = decidePairConfirmation({
|
|
1123
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1124
|
+
...expectEmail !== void 0 ? { expectEmail } : {},
|
|
1125
|
+
...expectEmail !== void 0 ? { expectEmailSource: pairFlags.expectEmail !== void 0 ? "flag" : "config" } : {},
|
|
1126
|
+
yes: pairFlags.yes,
|
|
1127
|
+
nonInteractive: ctx.flags.nonInteractive,
|
|
1128
|
+
stdinIsTTY,
|
|
1129
|
+
// Probed ONLY when stdin can't answer — opening /dev/tty is a syscall, and when stdin is
|
|
1130
|
+
// already a terminal the answer is irrelevant.
|
|
1131
|
+
controllingTerminalAvailable: stdinIsTTY ? false : hasControllingTerminal(),
|
|
1132
|
+
configPath: cliConfigPath()
|
|
1133
|
+
});
|
|
1134
|
+
if (decision.action === "reject") {
|
|
1135
|
+
ctx.io.result({ paired: false, reason: decision.reason });
|
|
1136
|
+
ctx.io.errline(decision.message);
|
|
1137
|
+
return EXIT.ERROR;
|
|
1138
|
+
}
|
|
1139
|
+
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1140
|
+
ctx.io.result({ paired: false, reason: "declined" });
|
|
1141
|
+
ctx.io.errline(
|
|
1142
|
+
"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."
|
|
761
1143
|
);
|
|
762
1144
|
return EXIT.ERROR;
|
|
763
1145
|
}
|
|
764
1146
|
await (0, import_agent_core8.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
765
1147
|
writeCliConfig({ apiUrl });
|
|
766
|
-
|
|
1148
|
+
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1149
|
+
ctx.io.emit(`\u2713 Paired${humanSuffix}. Run \`birdybeep test\` to send a test Beep.`, {
|
|
767
1150
|
paired: true,
|
|
768
|
-
machineId: paired.machineId
|
|
1151
|
+
machineId: paired.machineId,
|
|
1152
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {}
|
|
769
1153
|
});
|
|
770
1154
|
return EXIT.OK;
|
|
771
1155
|
}
|
|
@@ -798,14 +1182,24 @@ function createQueueCommand() {
|
|
|
798
1182
|
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
799
1183
|
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
800
1184
|
var import_codex4 = require("@birdybeep/codex");
|
|
1185
|
+
var import_copilot4 = require("@birdybeep/copilot");
|
|
1186
|
+
var import_cursor4 = require("@birdybeep/cursor");
|
|
801
1187
|
var import_opencode4 = require("@birdybeep/opencode");
|
|
802
|
-
var DEFAULT_ADAPTERS3 = [
|
|
1188
|
+
var DEFAULT_ADAPTERS3 = [
|
|
1189
|
+
import_claude_code4.claudeCodeAdapter,
|
|
1190
|
+
import_codex4.codexAdapter,
|
|
1191
|
+
import_opencode4.opencodeAdapter,
|
|
1192
|
+
import_cursor4.cursorAdapter,
|
|
1193
|
+
import_copilot4.copilotAdapter
|
|
1194
|
+
];
|
|
803
1195
|
var ADAPTER_VERSIONS = {
|
|
804
1196
|
claude_code: import_claude_code4.CLAUDE_CODE_ADAPTER_VERSION,
|
|
805
1197
|
codex: import_codex4.CODEX_ADAPTER_VERSION,
|
|
806
|
-
opencode: import_opencode4.OPENCODE_ADAPTER_VERSION
|
|
1198
|
+
opencode: import_opencode4.OPENCODE_ADAPTER_VERSION,
|
|
1199
|
+
cursor: import_cursor4.CURSOR_ADAPTER_VERSION,
|
|
1200
|
+
copilot: import_copilot4.COPILOT_ADAPTER_VERSION
|
|
807
1201
|
};
|
|
808
|
-
var
|
|
1202
|
+
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
809
1203
|
async function gatherItems(adapters) {
|
|
810
1204
|
return Promise.all(
|
|
811
1205
|
adapters.map(async (a) => {
|
|
@@ -840,7 +1234,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
840
1234
|
let outcome = "deferred";
|
|
841
1235
|
let errorCode;
|
|
842
1236
|
try {
|
|
843
|
-
const res = await fetchImpl(`${
|
|
1237
|
+
const res = await fetchImpl(`${base3(resolveApiUrl())}/v1/integrations/status`, {
|
|
844
1238
|
method: "POST",
|
|
845
1239
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
846
1240
|
body: JSON.stringify({ integrations: items })
|
|
@@ -891,8 +1285,16 @@ function createReportStatusCommand(deps = {}) {
|
|
|
891
1285
|
var import_agent_core11 = require("@birdybeep/agent-core");
|
|
892
1286
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
893
1287
|
var import_codex5 = require("@birdybeep/codex");
|
|
1288
|
+
var import_copilot5 = require("@birdybeep/copilot");
|
|
1289
|
+
var import_cursor5 = require("@birdybeep/cursor");
|
|
894
1290
|
var import_opencode5 = require("@birdybeep/opencode");
|
|
895
|
-
var DEFAULT_ADAPTERS4 = [
|
|
1291
|
+
var DEFAULT_ADAPTERS4 = [
|
|
1292
|
+
import_claude_code5.claudeCodeAdapter,
|
|
1293
|
+
import_codex5.codexAdapter,
|
|
1294
|
+
import_opencode5.opencodeAdapter,
|
|
1295
|
+
import_cursor5.cursorAdapter,
|
|
1296
|
+
import_copilot5.copilotAdapter
|
|
1297
|
+
];
|
|
896
1298
|
function createStatusCommand(deps = {}) {
|
|
897
1299
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
898
1300
|
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core11.createSender)(
|
|
@@ -932,7 +1334,7 @@ function createStatusCommand(deps = {}) {
|
|
|
932
1334
|
}
|
|
933
1335
|
|
|
934
1336
|
// src/commands/test.ts
|
|
935
|
-
var
|
|
1337
|
+
var import_node_crypto2 = require("crypto");
|
|
936
1338
|
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
937
1339
|
function buildTestEvent(opts = {}) {
|
|
938
1340
|
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
@@ -944,7 +1346,7 @@ function buildTestEvent(opts = {}) {
|
|
|
944
1346
|
// schema requires a harness; the "test" type distinguishes it
|
|
945
1347
|
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
946
1348
|
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
947
|
-
source_session_id: `birdybeep-cli-test-${(0,
|
|
1349
|
+
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
948
1350
|
machine: { label: machine.label, os: machine.os },
|
|
949
1351
|
workspace: { cwd: process.cwd() },
|
|
950
1352
|
title: "BirdyBeep test event",
|
|
@@ -1014,8 +1416,8 @@ function buildCommands() {
|
|
|
1014
1416
|
}
|
|
1015
1417
|
|
|
1016
1418
|
// src/update-check.ts
|
|
1017
|
-
var
|
|
1018
|
-
var
|
|
1419
|
+
var import_node_fs5 = require("fs");
|
|
1420
|
+
var import_node_path3 = require("path");
|
|
1019
1421
|
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1020
1422
|
var PACKAGE_NAME = "@birdybeep/cli";
|
|
1021
1423
|
var PACKAGE_PATH = "@birdybeep%2Fcli";
|
|
@@ -1070,11 +1472,11 @@ function isNewer(current, latest) {
|
|
|
1070
1472
|
return cur !== null && lat !== null && compareSemver(cur, lat) < 0;
|
|
1071
1473
|
}
|
|
1072
1474
|
function updateCachePath() {
|
|
1073
|
-
return (0,
|
|
1475
|
+
return (0, import_node_path3.join)((0, import_agent_core13.birdyBeepConfigDir)(), UPDATE_CACHE_FILE);
|
|
1074
1476
|
}
|
|
1075
1477
|
function readUpdateCache() {
|
|
1076
1478
|
try {
|
|
1077
|
-
const parsed = JSON.parse((0,
|
|
1479
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(updateCachePath(), "utf8"));
|
|
1078
1480
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1079
1481
|
const { checkedAt, latest } = parsed;
|
|
1080
1482
|
if (typeof checkedAt !== "number") return null;
|
|
@@ -1085,8 +1487,8 @@ function readUpdateCache() {
|
|
|
1085
1487
|
}
|
|
1086
1488
|
}
|
|
1087
1489
|
function writeUpdateCache(cache) {
|
|
1088
|
-
(0,
|
|
1089
|
-
(0,
|
|
1490
|
+
(0, import_node_fs5.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
1491
|
+
(0, import_node_fs5.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
|
|
1090
1492
|
`, { mode: 384 });
|
|
1091
1493
|
}
|
|
1092
1494
|
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|