@birdybeep/cli 0.1.0 → 0.3.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 +611 -69
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-HGH5CDKD.js → chunk-U4EIHC5C.js} +602 -73
- package/dist/chunk-U4EIHC5C.js.map +1 -0
- package/dist/index.cjs +599 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +1 -1
- package/package.json +7 -5
- package/dist/chunk-HGH5CDKD.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
3
25
|
|
|
4
26
|
// src/commands/agent.ts
|
|
5
27
|
var import_claude_code = require("@birdybeep/claude-code");
|
|
6
28
|
var import_codex = require("@birdybeep/codex");
|
|
29
|
+
var import_copilot = require("@birdybeep/copilot");
|
|
30
|
+
var import_cursor = require("@birdybeep/cursor");
|
|
7
31
|
var import_opencode = require("@birdybeep/opencode");
|
|
8
32
|
|
|
9
33
|
// src/framework.ts
|
|
@@ -72,8 +96,11 @@ function parseGlobalFlags(argv) {
|
|
|
72
96
|
}
|
|
73
97
|
return { flags, rest };
|
|
74
98
|
}
|
|
75
|
-
function isUnknownFlag(token) {
|
|
76
|
-
|
|
99
|
+
function isUnknownFlag(token, allowed) {
|
|
100
|
+
if (!token.startsWith("-")) return false;
|
|
101
|
+
if (GLOBAL_FLAG_TOKENS.has(token)) return false;
|
|
102
|
+
const eq = token.indexOf("=");
|
|
103
|
+
return !allowed.has(eq >= 0 ? token.slice(0, eq) : token);
|
|
77
104
|
}
|
|
78
105
|
function renderRootHelp(version, commands) {
|
|
79
106
|
const width = Math.max(...commands.map((c) => c.name.length));
|
|
@@ -94,6 +121,16 @@ function renderRootHelp(version, commands) {
|
|
|
94
121
|
" -v, --version Show the CLI version"
|
|
95
122
|
].join("\n");
|
|
96
123
|
}
|
|
124
|
+
function commandFlagTokens(...commands) {
|
|
125
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
126
|
+
for (const command of commands) {
|
|
127
|
+
for (const option of command?.options ?? []) {
|
|
128
|
+
tokens.add(option.flag);
|
|
129
|
+
for (const alias of option.aliases ?? []) tokens.add(alias);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return tokens;
|
|
133
|
+
}
|
|
97
134
|
function renderCommandHelp(path, command) {
|
|
98
135
|
const lines = [
|
|
99
136
|
`birdybeep ${path} \u2014 ${command.summary}`,
|
|
@@ -101,6 +138,17 @@ function renderCommandHelp(path, command) {
|
|
|
101
138
|
"Usage:",
|
|
102
139
|
` ${command.usage ?? `birdybeep ${path} [options]`}`
|
|
103
140
|
];
|
|
141
|
+
if (command.options && command.options.length > 0) {
|
|
142
|
+
const labels = command.options.map(
|
|
143
|
+
(o) => `${[o.flag, ...o.aliases ?? []].join(", ")}${o.value ? ` ${o.value}` : ""}`
|
|
144
|
+
);
|
|
145
|
+
const width = Math.max(...labels.map((l) => l.length));
|
|
146
|
+
lines.push(
|
|
147
|
+
"",
|
|
148
|
+
"Options:",
|
|
149
|
+
...command.options.map((o, i) => ` ${labels[i]?.padEnd(width)} ${o.summary}`)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
104
152
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
105
153
|
const width = Math.max(...command.subcommands.map((c) => c.name.length));
|
|
106
154
|
lines.push(
|
|
@@ -125,6 +173,7 @@ async function dispatch(argv, deps) {
|
|
|
125
173
|
return EXIT.OK;
|
|
126
174
|
}
|
|
127
175
|
let command = deps.commands.find((c) => c.name === rest[0]);
|
|
176
|
+
let parent;
|
|
128
177
|
const pathParts = [];
|
|
129
178
|
let argsStart = 1;
|
|
130
179
|
if (command) {
|
|
@@ -132,6 +181,7 @@ async function dispatch(argv, deps) {
|
|
|
132
181
|
if (command.subcommands && command.subcommands.length > 0) {
|
|
133
182
|
const sub = command.subcommands.find((c) => c.name === rest[1]);
|
|
134
183
|
if (sub) {
|
|
184
|
+
parent = command;
|
|
135
185
|
command = sub;
|
|
136
186
|
pathParts.push(sub.name);
|
|
137
187
|
argsStart = 2;
|
|
@@ -155,6 +205,7 @@ async function dispatch(argv, deps) {
|
|
|
155
205
|
name: path,
|
|
156
206
|
summary: command.summary,
|
|
157
207
|
usage: command.usage,
|
|
208
|
+
options: command.options,
|
|
158
209
|
subcommands: command.subcommands?.map((c) => ({ name: c.name, summary: c.summary }))
|
|
159
210
|
});
|
|
160
211
|
return EXIT.OK;
|
|
@@ -164,13 +215,15 @@ async function dispatch(argv, deps) {
|
|
|
164
215
|
return EXIT.USAGE;
|
|
165
216
|
}
|
|
166
217
|
const args = rest.slice(argsStart);
|
|
167
|
-
const
|
|
218
|
+
const allowed = commandFlagTokens(command, parent);
|
|
219
|
+
const unknown = args.find((token) => isUnknownFlag(token, allowed));
|
|
168
220
|
if (unknown !== void 0) {
|
|
169
221
|
io.errline(`birdybeep ${path}: unknown option "${unknown}".`);
|
|
170
222
|
return EXIT.USAGE;
|
|
171
223
|
}
|
|
224
|
+
let code;
|
|
172
225
|
try {
|
|
173
|
-
|
|
226
|
+
code = await command.run({ args, flags, io });
|
|
174
227
|
} catch (err) {
|
|
175
228
|
if (err instanceof MissingInputError) {
|
|
176
229
|
io.errline(
|
|
@@ -181,16 +234,38 @@ async function dispatch(argv, deps) {
|
|
|
181
234
|
io.errline(`birdybeep ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
182
235
|
return EXIT.ERROR;
|
|
183
236
|
}
|
|
237
|
+
if (deps.notifyUpdate !== void 0) {
|
|
238
|
+
try {
|
|
239
|
+
await deps.notifyUpdate({ command: pathParts[0] ?? "", flags, io });
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return code;
|
|
184
244
|
}
|
|
185
245
|
|
|
186
246
|
// src/commands/agent.ts
|
|
187
|
-
var DEFAULT_ADAPTERS = [
|
|
247
|
+
var DEFAULT_ADAPTERS = [
|
|
248
|
+
import_claude_code.claudeCodeAdapter,
|
|
249
|
+
import_codex.codexAdapter,
|
|
250
|
+
import_opencode.opencodeAdapter,
|
|
251
|
+
import_cursor.cursorAdapter,
|
|
252
|
+
import_copilot.copilotAdapter
|
|
253
|
+
];
|
|
188
254
|
var TARGET_TO_ID = {
|
|
189
255
|
claude: "claude_code",
|
|
190
256
|
codex: "codex",
|
|
191
|
-
opencode: "opencode"
|
|
257
|
+
opencode: "opencode",
|
|
258
|
+
cursor: "cursor",
|
|
259
|
+
copilot: "copilot"
|
|
192
260
|
};
|
|
193
|
-
var AGENT_TARGETS = [
|
|
261
|
+
var AGENT_TARGETS = [
|
|
262
|
+
"all",
|
|
263
|
+
"claude",
|
|
264
|
+
"codex",
|
|
265
|
+
"opencode",
|
|
266
|
+
"cursor",
|
|
267
|
+
"copilot"
|
|
268
|
+
];
|
|
194
269
|
function selectAdapters(target, adapters) {
|
|
195
270
|
if (target === "all") return adapters;
|
|
196
271
|
const id = TARGET_TO_ID[target];
|
|
@@ -281,18 +356,18 @@ function createAgentCommand(deps = {}) {
|
|
|
281
356
|
return {
|
|
282
357
|
name: "agent",
|
|
283
358
|
summary: "Install or uninstall harness adapters",
|
|
284
|
-
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode]",
|
|
359
|
+
usage: "birdybeep agent <install|uninstall> [all|claude|codex|opencode|cursor|copilot]",
|
|
285
360
|
subcommands: [
|
|
286
361
|
{
|
|
287
362
|
name: "install",
|
|
288
|
-
summary: "Install adapters (all | claude | codex | opencode)",
|
|
289
|
-
usage: "birdybeep agent install [all|claude|codex|opencode]",
|
|
363
|
+
summary: "Install adapters (all | claude | codex | opencode | cursor | copilot)",
|
|
364
|
+
usage: "birdybeep agent install [all|claude|codex|opencode|cursor|copilot]",
|
|
290
365
|
run: (ctx) => installSelected(adapters, ctx)
|
|
291
366
|
},
|
|
292
367
|
{
|
|
293
368
|
name: "uninstall",
|
|
294
369
|
summary: "Restore harness config to its pre-install state",
|
|
295
|
-
usage: "birdybeep agent uninstall [all|claude|codex|opencode]",
|
|
370
|
+
usage: "birdybeep agent uninstall [all|claude|codex|opencode|cursor|copilot]",
|
|
296
371
|
run: (ctx) => uninstallSelected(adapters, ctx)
|
|
297
372
|
}
|
|
298
373
|
]
|
|
@@ -303,6 +378,8 @@ function createAgentCommand(deps = {}) {
|
|
|
303
378
|
var import_agent_core4 = require("@birdybeep/agent-core");
|
|
304
379
|
var import_claude_code2 = require("@birdybeep/claude-code");
|
|
305
380
|
var import_codex2 = require("@birdybeep/codex");
|
|
381
|
+
var import_copilot2 = require("@birdybeep/copilot");
|
|
382
|
+
var import_cursor2 = require("@birdybeep/cursor");
|
|
306
383
|
var import_opencode2 = require("@birdybeep/opencode");
|
|
307
384
|
|
|
308
385
|
// src/config.ts
|
|
@@ -327,6 +404,8 @@ function writeCliConfig(patch) {
|
|
|
327
404
|
const merged = {};
|
|
328
405
|
const apiUrl = patch.apiUrl ?? current.apiUrl;
|
|
329
406
|
if (apiUrl !== void 0) merged.apiUrl = apiUrl;
|
|
407
|
+
const expectEmail = patch.expectEmail ?? current.expectEmail;
|
|
408
|
+
if (expectEmail !== void 0) merged.expectEmail = expectEmail;
|
|
330
409
|
(0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
331
410
|
(0, import_node_fs2.writeFileSync)(cliConfigPath(), `${JSON.stringify(merged, null, 2)}
|
|
332
411
|
`, { mode: 384 });
|
|
@@ -336,6 +415,12 @@ function resolveApiUrl() {
|
|
|
336
415
|
if (env !== void 0 && env.length > 0) return env;
|
|
337
416
|
return readCliConfig().apiUrl ?? DEFAULT_API_URL;
|
|
338
417
|
}
|
|
418
|
+
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
419
|
+
function resolveRegistryUrl() {
|
|
420
|
+
const env = process.env["npm_config_registry"];
|
|
421
|
+
if (env !== void 0 && env.length > 0) return env;
|
|
422
|
+
return DEFAULT_REGISTRY_URL;
|
|
423
|
+
}
|
|
339
424
|
|
|
340
425
|
// src/diagnostics.ts
|
|
341
426
|
var import_agent_core3 = require("@birdybeep/agent-core");
|
|
@@ -359,7 +444,13 @@ function machineIdentity() {
|
|
|
359
444
|
}
|
|
360
445
|
|
|
361
446
|
// src/commands/doctor.ts
|
|
362
|
-
var DEFAULT_ADAPTERS2 = [
|
|
447
|
+
var DEFAULT_ADAPTERS2 = [
|
|
448
|
+
import_claude_code2.claudeCodeAdapter,
|
|
449
|
+
import_codex2.codexAdapter,
|
|
450
|
+
import_opencode2.opencodeAdapter,
|
|
451
|
+
import_cursor2.cursorAdapter,
|
|
452
|
+
import_copilot2.copilotAdapter
|
|
453
|
+
];
|
|
363
454
|
async function defaultProbeNetwork(baseUrl) {
|
|
364
455
|
try {
|
|
365
456
|
const controller = new AbortController();
|
|
@@ -442,16 +533,30 @@ function createDoctorCommand(deps = {}) {
|
|
|
442
533
|
}
|
|
443
534
|
|
|
444
535
|
// src/commands/hook.ts
|
|
536
|
+
var import_node_child_process = require("child_process");
|
|
537
|
+
var import_node_crypto = require("crypto");
|
|
538
|
+
var import_node_fs3 = require("fs");
|
|
539
|
+
var import_node_os = require("os");
|
|
540
|
+
var import_node_path2 = require("path");
|
|
445
541
|
var import_agent_core5 = require("@birdybeep/agent-core");
|
|
446
542
|
var import_claude_code3 = require("@birdybeep/claude-code");
|
|
447
543
|
var import_codex3 = require("@birdybeep/codex");
|
|
544
|
+
var import_copilot3 = require("@birdybeep/copilot");
|
|
545
|
+
var import_cursor3 = require("@birdybeep/cursor");
|
|
448
546
|
var import_opencode3 = require("@birdybeep/opencode");
|
|
449
547
|
var RUNNERS = {
|
|
450
548
|
claude: import_claude_code3.runClaudeHook,
|
|
451
549
|
codex: import_codex3.runCodexHook,
|
|
452
|
-
opencode: import_opencode3.runOpenCodeHook
|
|
550
|
+
opencode: import_opencode3.runOpenCodeHook,
|
|
551
|
+
cursor: import_cursor3.runCursorHook
|
|
453
552
|
};
|
|
454
|
-
var HOOK_HARNESSES = [
|
|
553
|
+
var HOOK_HARNESSES = [
|
|
554
|
+
"claude",
|
|
555
|
+
"codex",
|
|
556
|
+
"opencode",
|
|
557
|
+
"cursor",
|
|
558
|
+
"copilot"
|
|
559
|
+
];
|
|
455
560
|
var STDIN_READ_TIMEOUT_MS = 3e3;
|
|
456
561
|
function withTimeout(promise, ms, fallback) {
|
|
457
562
|
return new Promise((resolve) => {
|
|
@@ -468,9 +573,13 @@ function withTimeout(promise, ms, fallback) {
|
|
|
468
573
|
});
|
|
469
574
|
}
|
|
470
575
|
function isHarnessName(value) {
|
|
471
|
-
return value === "claude" || value === "codex" || value === "opencode";
|
|
576
|
+
return value === "claude" || value === "codex" || value === "opencode" || value === "cursor" || value === "copilot";
|
|
472
577
|
}
|
|
473
|
-
function runHookCommand(harness, payload, sender) {
|
|
578
|
+
function runHookCommand(harness, payload, sender, copilotEventName) {
|
|
579
|
+
if (harness === "copilot") {
|
|
580
|
+
if (copilotEventName === void 0) return Promise.resolve({ outcome: "skipped" });
|
|
581
|
+
return (0, import_copilot3.runCopilotHook)(copilotEventName, payload, { sender });
|
|
582
|
+
}
|
|
474
583
|
return RUNNERS[harness](payload, { sender });
|
|
475
584
|
}
|
|
476
585
|
function readStdinDefault() {
|
|
@@ -486,24 +595,90 @@ function readStdinDefault() {
|
|
|
486
595
|
process.stdin.on("error", () => resolve(""));
|
|
487
596
|
});
|
|
488
597
|
}
|
|
489
|
-
async function readHookPayload(args, readStdin) {
|
|
490
|
-
return args[1] ?? await readStdin();
|
|
598
|
+
async function readHookPayload(args, readStdin, stdinOnly = false) {
|
|
599
|
+
return stdinOnly ? readStdin() : args[1] ?? await readStdin();
|
|
600
|
+
}
|
|
601
|
+
var NOTIFY_STDIN_FILE_ENV = "BIRDYBEEP_CODEX_NOTIFY_STDIN_FILE";
|
|
602
|
+
function detachCodexNotifyWorker(payload) {
|
|
603
|
+
if (process.platform === "win32") return false;
|
|
604
|
+
let file;
|
|
605
|
+
let fd;
|
|
606
|
+
try {
|
|
607
|
+
const birdybeep = (0, import_agent_core5.resolveOnPath)("birdybeep");
|
|
608
|
+
if (birdybeep === null) return false;
|
|
609
|
+
const tmpFile = (0, import_node_path2.join)((0, import_node_os.tmpdir)(), `birdybeep-notify-${(0, import_node_crypto.randomBytes)(16).toString("hex")}.json`);
|
|
610
|
+
file = tmpFile;
|
|
611
|
+
(0, import_node_fs3.writeFileSync)(tmpFile, payload, { mode: 384 });
|
|
612
|
+
fd = (0, import_node_fs3.openSync)(tmpFile, "r");
|
|
613
|
+
const child = (0, import_node_child_process.spawn)(birdybeep, ["hook", "codex"], {
|
|
614
|
+
cwd: (0, import_node_path2.dirname)(birdybeep),
|
|
615
|
+
// trusted dir, never the inherited/attacker cwd
|
|
616
|
+
detached: true,
|
|
617
|
+
// new session (setsid) → survives `codex exec` reaping the group
|
|
618
|
+
stdio: [fd, "ignore", "ignore"],
|
|
619
|
+
// stdin = the temp file; this process holds no pipe
|
|
620
|
+
env: { ...process.env, [NOTIFY_STDIN_FILE_ENV]: tmpFile },
|
|
621
|
+
// worker cleans it up post-read
|
|
622
|
+
windowsHide: true
|
|
623
|
+
});
|
|
624
|
+
child.on("error", () => {
|
|
625
|
+
try {
|
|
626
|
+
(0, import_node_fs3.rmSync)(tmpFile, { force: true });
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
child.unref();
|
|
631
|
+
return true;
|
|
632
|
+
} catch {
|
|
633
|
+
if (file !== void 0) {
|
|
634
|
+
try {
|
|
635
|
+
(0, import_node_fs3.rmSync)(file, { force: true });
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
} finally {
|
|
641
|
+
if (fd !== void 0) {
|
|
642
|
+
try {
|
|
643
|
+
(0, import_node_fs3.closeSync)(fd);
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
491
648
|
}
|
|
492
649
|
function createHookCommand(deps = {}) {
|
|
493
650
|
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core5.createSender)({ baseUrl }));
|
|
494
651
|
const readStdin = deps.readStdin ?? readStdinDefault;
|
|
495
652
|
const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;
|
|
653
|
+
const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;
|
|
496
654
|
return {
|
|
497
655
|
name: "hook",
|
|
498
656
|
summary: "Internal: normalize + send an event fired by a harness hook",
|
|
499
|
-
usage: "birdybeep hook <claude|codex|opencode>",
|
|
657
|
+
usage: "birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]",
|
|
500
658
|
run: async (ctx) => {
|
|
501
659
|
const harness = ctx.args[0];
|
|
502
660
|
if (!isHarnessName(harness)) {
|
|
503
661
|
ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join("|")}`);
|
|
504
662
|
return EXIT.USAGE;
|
|
505
663
|
}
|
|
506
|
-
const
|
|
664
|
+
const notifyPayload = ctx.args[1];
|
|
665
|
+
if (harness === "codex" && notifyPayload !== void 0 && notifyPayload.length > 0 && detachCodexNotify(notifyPayload)) {
|
|
666
|
+
ctx.io.result({ harness, outcome: "detached" });
|
|
667
|
+
return EXIT.OK;
|
|
668
|
+
}
|
|
669
|
+
const copilotEventName = harness === "copilot" && (0, import_copilot3.isCopilotHookEventName)(ctx.args[1]) ? ctx.args[1] : void 0;
|
|
670
|
+
const raw = await withTimeout(
|
|
671
|
+
readHookPayload(ctx.args, readStdin, harness === "copilot"),
|
|
672
|
+
stdinTimeoutMs,
|
|
673
|
+
""
|
|
674
|
+
);
|
|
675
|
+
const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];
|
|
676
|
+
if (notifyStdinFile !== void 0 && (0, import_node_path2.dirname)(notifyStdinFile) === (0, import_node_os.tmpdir)() && (0, import_node_path2.basename)(notifyStdinFile).startsWith("birdybeep-notify-")) {
|
|
677
|
+
try {
|
|
678
|
+
(0, import_node_fs3.rmSync)(notifyStdinFile, { force: true });
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
}
|
|
507
682
|
let payload;
|
|
508
683
|
try {
|
|
509
684
|
payload = JSON.parse(raw);
|
|
@@ -512,8 +687,15 @@ function createHookCommand(deps = {}) {
|
|
|
512
687
|
return EXIT.OK;
|
|
513
688
|
}
|
|
514
689
|
const sender = makeSender(resolveApiUrl());
|
|
515
|
-
const result = await runHookCommand(harness, payload, sender);
|
|
516
|
-
ctx.io.result({
|
|
690
|
+
const result = await runHookCommand(harness, payload, sender, copilotEventName);
|
|
691
|
+
ctx.io.result({
|
|
692
|
+
harness,
|
|
693
|
+
...copilotEventName !== void 0 ? { event: copilotEventName } : {},
|
|
694
|
+
outcome: result.outcome,
|
|
695
|
+
eventType: result.eventType,
|
|
696
|
+
...result.send?.decision ? { decision: result.send.decision } : {},
|
|
697
|
+
...result.send?.status !== void 0 ? { status: result.send.status } : {}
|
|
698
|
+
});
|
|
517
699
|
return EXIT.OK;
|
|
518
700
|
}
|
|
519
701
|
};
|
|
@@ -521,57 +703,73 @@ function createHookCommand(deps = {}) {
|
|
|
521
703
|
|
|
522
704
|
// src/commands/logout.ts
|
|
523
705
|
var import_agent_core6 = require("@birdybeep/agent-core");
|
|
524
|
-
|
|
706
|
+
var base = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
707
|
+
function createLogoutCommand(deps = {}) {
|
|
525
708
|
return {
|
|
526
|
-
name:
|
|
527
|
-
summary:
|
|
528
|
-
usage:
|
|
709
|
+
name: "logout",
|
|
710
|
+
summary: "Remove the local machine token (does NOT revoke the machine server-side)",
|
|
711
|
+
usage: "birdybeep logout",
|
|
529
712
|
run: async (ctx) => {
|
|
530
713
|
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
531
|
-
ctx.io.emit(
|
|
714
|
+
ctx.io.emit("Logged out \u2014 the machine token was removed.", { loggedOut: true });
|
|
532
715
|
return EXIT.OK;
|
|
533
716
|
}
|
|
534
717
|
};
|
|
535
718
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
719
|
+
async function revokeSelf(token, fetchImpl, timeoutMs) {
|
|
720
|
+
const controller = new AbortController();
|
|
721
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
722
|
+
try {
|
|
723
|
+
const res = await fetchImpl(`${base(resolveApiUrl())}/v1/machine/revoke-self`, {
|
|
724
|
+
method: "POST",
|
|
725
|
+
headers: { authorization: `Bearer ${token}` },
|
|
726
|
+
signal: controller.signal
|
|
727
|
+
});
|
|
728
|
+
if (res.ok || res.status === 403) return "revoked";
|
|
729
|
+
return "rejected";
|
|
730
|
+
} catch {
|
|
731
|
+
return "unreachable";
|
|
732
|
+
} finally {
|
|
733
|
+
clearTimeout(timer);
|
|
734
|
+
}
|
|
546
735
|
}
|
|
547
736
|
function createUnpairCommand(deps = {}) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
737
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
738
|
+
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
739
|
+
return {
|
|
740
|
+
name: "unpair",
|
|
741
|
+
summary: "Unpair this machine \u2014 revoke it server-side and remove the local token",
|
|
742
|
+
usage: "birdybeep unpair",
|
|
743
|
+
run: async (ctx) => {
|
|
744
|
+
const token = await (0, import_agent_core6.getToken)(deps.tokenOptions ?? {});
|
|
745
|
+
const outcome = token === null ? "no_token" : await revokeSelf(token, fetchImpl, timeoutMs);
|
|
746
|
+
await (0, import_agent_core6.clearToken)(deps.tokenOptions ?? {});
|
|
747
|
+
const serverRevoked = outcome === "revoked";
|
|
748
|
+
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.";
|
|
749
|
+
ctx.io.emit(human, { unpaired: true, serverRevoked });
|
|
750
|
+
return EXIT.OK;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
557
753
|
}
|
|
558
754
|
|
|
559
755
|
// src/commands/pair.ts
|
|
756
|
+
var import_node_fs4 = require("fs");
|
|
560
757
|
var import_agent_core8 = require("@birdybeep/agent-core");
|
|
561
758
|
var import_uqr = require("uqr");
|
|
562
759
|
|
|
563
760
|
// src/pairing.ts
|
|
564
761
|
var import_agent_core7 = require("@birdybeep/agent-core");
|
|
565
|
-
function
|
|
762
|
+
function base2(apiUrl) {
|
|
566
763
|
return apiUrl.replace(/\/$/, "");
|
|
567
764
|
}
|
|
568
765
|
async function pairStart(apiUrl, input, fetchImpl) {
|
|
569
766
|
const body = {
|
|
570
767
|
machine_label: input.machineLabel,
|
|
571
768
|
...input.os !== void 0 ? { os: input.os } : {},
|
|
572
|
-
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {}
|
|
769
|
+
...input.cliVersion !== void 0 ? { cli_version: input.cliVersion } : {},
|
|
770
|
+
...input.codeChallenge !== void 0 ? { code_challenge: input.codeChallenge } : {}
|
|
573
771
|
};
|
|
574
|
-
const res = await fetchImpl(`${
|
|
772
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/start`, {
|
|
575
773
|
method: "POST",
|
|
576
774
|
headers: { "content-type": "application/json" },
|
|
577
775
|
body: JSON.stringify(body)
|
|
@@ -589,12 +787,13 @@ var TERMINAL_TOKEN_ERRORS = /* @__PURE__ */ new Set([
|
|
|
589
787
|
"not_found",
|
|
590
788
|
"payload_too_large"
|
|
591
789
|
]);
|
|
592
|
-
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint) {
|
|
790
|
+
async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint, codeVerifier) {
|
|
593
791
|
const body = {
|
|
594
792
|
device_code: deviceCode,
|
|
595
|
-
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {}
|
|
793
|
+
...machineFingerprint !== void 0 ? { machine_fingerprint: machineFingerprint } : {},
|
|
794
|
+
...codeVerifier !== void 0 ? { code_verifier: codeVerifier } : {}
|
|
596
795
|
};
|
|
597
|
-
const res = await fetchImpl(`${
|
|
796
|
+
const res = await fetchImpl(`${base2(apiUrl)}/v1/pair/token`, {
|
|
598
797
|
method: "POST",
|
|
599
798
|
headers: { "content-type": "application/json" },
|
|
600
799
|
body: JSON.stringify(body)
|
|
@@ -605,7 +804,10 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
|
|
|
605
804
|
return {
|
|
606
805
|
status: "paired",
|
|
607
806
|
machineToken: parsed.data.machine_token,
|
|
608
|
-
machineId: parsed.data.machine_id
|
|
807
|
+
machineId: parsed.data.machine_id,
|
|
808
|
+
// Only surface the key when the server reported it (exactOptionalPropertyTypes: no explicit
|
|
809
|
+
// undefined). Older servers omit approved_by_email; newer ones (dgxd) include it.
|
|
810
|
+
...parsed.data.approved_by_email !== void 0 ? { approvedByEmail: parsed.data.approved_by_email } : {}
|
|
609
811
|
};
|
|
610
812
|
}
|
|
611
813
|
let errBody = null;
|
|
@@ -626,7 +828,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint)
|
|
|
626
828
|
}
|
|
627
829
|
|
|
628
830
|
// src/version.ts
|
|
629
|
-
var CLI_VERSION = "0.
|
|
831
|
+
var CLI_VERSION = "0.3.0".length > 0 ? "0.3.0" : "0.0.0";
|
|
630
832
|
|
|
631
833
|
// src/commands/pair.ts
|
|
632
834
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -634,22 +836,172 @@ var HEARTBEAT_MS = 15e3;
|
|
|
634
836
|
function renderQrMatrix(qrPayload) {
|
|
635
837
|
return (0, import_uqr.renderUnicodeCompact)(qrPayload, { border: 2 });
|
|
636
838
|
}
|
|
839
|
+
function parsePairFlags(args) {
|
|
840
|
+
const flags = { yes: false };
|
|
841
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
842
|
+
const token = args[i] ?? "";
|
|
843
|
+
if (token === "--yes" || token === "-y") {
|
|
844
|
+
flags.yes = true;
|
|
845
|
+
} else if (token === "--expect-email" || token.startsWith("--expect-email=")) {
|
|
846
|
+
const inline = token.startsWith("--expect-email=") ? token.slice("--expect-email=".length) : void 0;
|
|
847
|
+
const value = inline ?? args[++i];
|
|
848
|
+
if (value === void 0 || value.length === 0 || value.startsWith("-")) {
|
|
849
|
+
return { ...flags, error: "--expect-email requires an email address" };
|
|
850
|
+
}
|
|
851
|
+
flags.expectEmail = value;
|
|
852
|
+
} else {
|
|
853
|
+
return { ...flags, error: `unexpected argument "${token}"` };
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return flags;
|
|
857
|
+
}
|
|
858
|
+
function sameEmail(a, b) {
|
|
859
|
+
const fold = (v) => v.trim().toLowerCase();
|
|
860
|
+
const rawEqual = fold(a) === fold(b);
|
|
861
|
+
const nfkcEqual = fold(a.normalize("NFKC")) === fold(b.normalize("NFKC"));
|
|
862
|
+
return rawEqual && nfkcEqual;
|
|
863
|
+
}
|
|
864
|
+
function decidePairConfirmation(input) {
|
|
865
|
+
const { approvedByEmail, expectEmail } = input;
|
|
866
|
+
const platform = input.platform ?? process.platform;
|
|
867
|
+
if (expectEmail !== void 0) {
|
|
868
|
+
if (approvedByEmail === void 0) {
|
|
869
|
+
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.";
|
|
870
|
+
return {
|
|
871
|
+
action: "reject",
|
|
872
|
+
reason: "expected_email_unverifiable",
|
|
873
|
+
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}`
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
if (sameEmail(approvedByEmail, expectEmail)) {
|
|
877
|
+
return { action: "approve", reason: "expected_email_match" };
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
action: "reject",
|
|
881
|
+
reason: "expected_email_mismatch",
|
|
882
|
+
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\`.`
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
if (input.yes) return { action: "approve", reason: "yes_flag" };
|
|
886
|
+
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] ";
|
|
887
|
+
if (!input.nonInteractive) {
|
|
888
|
+
if (input.stdinIsTTY) return { action: "prompt", question, on: "stdin" };
|
|
889
|
+
if (input.controllingTerminalAvailable) {
|
|
890
|
+
return { action: "prompt", question, on: "controlling-terminal" };
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const who = approvedByEmail !== void 0 ? ` (approved by ${approvedByEmail})` : "";
|
|
894
|
+
const winptyHint = platform === "win32" && !input.nonInteractive ? " In Git Bash / MSYS, `winpty birdybeep pair` attaches a real console so the prompt can appear." : "";
|
|
895
|
+
return {
|
|
896
|
+
action: "reject",
|
|
897
|
+
reason: "non_interactive",
|
|
898
|
+
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
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function isAffirmative(answer) {
|
|
902
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
903
|
+
}
|
|
904
|
+
function controllingTerminalPath() {
|
|
905
|
+
return "/dev/tty";
|
|
906
|
+
}
|
|
907
|
+
function canOpenControllingTerminal(path = controllingTerminalPath(), platform = process.platform) {
|
|
908
|
+
if (platform === "win32") return false;
|
|
909
|
+
let fd;
|
|
910
|
+
try {
|
|
911
|
+
fd = (0, import_node_fs4.openSync)(path, "r");
|
|
912
|
+
return true;
|
|
913
|
+
} catch {
|
|
914
|
+
return false;
|
|
915
|
+
} finally {
|
|
916
|
+
if (fd !== void 0) {
|
|
917
|
+
try {
|
|
918
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async function promptForAnswer(question, on) {
|
|
925
|
+
const { createInterface } = await import("readline/promises");
|
|
926
|
+
let ttyFd;
|
|
927
|
+
let input;
|
|
928
|
+
if (on === "stdin") {
|
|
929
|
+
input = process.stdin;
|
|
930
|
+
} else {
|
|
931
|
+
const { ReadStream } = await import("tty");
|
|
932
|
+
ttyFd = (0, import_node_fs4.openSync)(controllingTerminalPath(), "r");
|
|
933
|
+
input = new ReadStream(ttyFd);
|
|
934
|
+
}
|
|
935
|
+
return new Promise((resolve) => {
|
|
936
|
+
const rl = createInterface({ input, output: process.stderr });
|
|
937
|
+
let settled = false;
|
|
938
|
+
const done = (value) => {
|
|
939
|
+
if (settled) return;
|
|
940
|
+
settled = true;
|
|
941
|
+
rl.close();
|
|
942
|
+
if (on === "stdin") {
|
|
943
|
+
process.stdin.unref?.();
|
|
944
|
+
} else {
|
|
945
|
+
try {
|
|
946
|
+
input.unref?.();
|
|
947
|
+
input.destroy?.();
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
if (ttyFd !== void 0) {
|
|
951
|
+
try {
|
|
952
|
+
(0, import_node_fs4.closeSync)(ttyFd);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
resolve(value);
|
|
958
|
+
};
|
|
959
|
+
rl.question(question).then(done, () => done(""));
|
|
960
|
+
rl.once("close", () => done(""));
|
|
961
|
+
input.once?.("error", () => done(""));
|
|
962
|
+
});
|
|
963
|
+
}
|
|
637
964
|
function createPairCommand(deps = {}) {
|
|
638
965
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
639
966
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
640
967
|
const clock = deps.now ?? (() => Date.now());
|
|
641
968
|
const renderQr = deps.renderQr ?? renderQrMatrix;
|
|
642
969
|
const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
970
|
+
const promptLine = deps.promptLine ?? promptForAnswer;
|
|
971
|
+
const hasControllingTerminal = deps.hasControllingTerminal ?? (() => canOpenControllingTerminal());
|
|
972
|
+
const configuredExpectEmail = deps.configuredExpectEmail ?? (() => {
|
|
973
|
+
const pinned = readCliConfig().expectEmail;
|
|
974
|
+
return typeof pinned === "string" && pinned.trim().length > 0 ? pinned : void 0;
|
|
975
|
+
});
|
|
643
976
|
return {
|
|
644
977
|
name: "pair",
|
|
645
978
|
summary: "Pair this machine with your BirdyBeep account (QR or manual)",
|
|
646
|
-
usage: "birdybeep pair [--json]",
|
|
979
|
+
usage: "birdybeep pair [--yes] [--expect-email <addr>] [--json]",
|
|
980
|
+
options: [
|
|
981
|
+
{
|
|
982
|
+
flag: "--yes",
|
|
983
|
+
aliases: ["-y"],
|
|
984
|
+
summary: "Skip the approving-account confirmation (headless/CI)"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
flag: "--expect-email",
|
|
988
|
+
value: "<addr>",
|
|
989
|
+
summary: "Only trust the pairing if this account approved it (else fail)"
|
|
990
|
+
}
|
|
991
|
+
],
|
|
647
992
|
run: async (ctx) => {
|
|
993
|
+
const pairFlags = parsePairFlags(ctx.args);
|
|
994
|
+
if (pairFlags.error !== void 0) {
|
|
995
|
+
ctx.io.errline(`birdybeep pair: ${pairFlags.error}.`);
|
|
996
|
+
return EXIT.USAGE;
|
|
997
|
+
}
|
|
648
998
|
const apiUrl = resolveApiUrl();
|
|
649
999
|
const identity = (0, import_agent_core8.getMachineIdentity)();
|
|
1000
|
+
const codeVerifier = (0, import_agent_core8.generateCodeVerifier)();
|
|
1001
|
+
const codeChallenge = (0, import_agent_core8.deriveCodeChallengeS256)(codeVerifier);
|
|
650
1002
|
const start = await pairStart(
|
|
651
1003
|
apiUrl,
|
|
652
|
-
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION },
|
|
1004
|
+
{ machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },
|
|
653
1005
|
fetchImpl
|
|
654
1006
|
);
|
|
655
1007
|
if (ctx.flags.json) {
|
|
@@ -661,12 +1013,14 @@ function createPairCommand(deps = {}) {
|
|
|
661
1013
|
});
|
|
662
1014
|
} else {
|
|
663
1015
|
ctx.io.line(
|
|
664
|
-
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR
|
|
1016
|
+
"To pair this machine, open the BirdyBeep app, tap \u201Cpair a machine\u201D, and scan this QR or open the complete link:"
|
|
665
1017
|
);
|
|
666
1018
|
const isTTY = deps.isTTY ?? process.stdout.isTTY === true;
|
|
667
1019
|
if (isTTY) ctx.io.line(renderQr(start.qr_payload));
|
|
668
1020
|
ctx.io.line(` Scan or open: ${start.qr_payload}`);
|
|
669
|
-
ctx.io.line(
|
|
1021
|
+
ctx.io.line(
|
|
1022
|
+
` Session code (display only; cannot approve by itself): ${start.user_code}`
|
|
1023
|
+
);
|
|
670
1024
|
ctx.io.line("Waiting for you to approve this machine in the app\u2026");
|
|
671
1025
|
}
|
|
672
1026
|
const deadline = Date.parse(start.expires_at);
|
|
@@ -682,7 +1036,9 @@ function createPairCommand(deps = {}) {
|
|
|
682
1036
|
apiUrl,
|
|
683
1037
|
start.device_code,
|
|
684
1038
|
fetchImpl,
|
|
685
|
-
identity.fingerprintHash
|
|
1039
|
+
identity.fingerprintHash,
|
|
1040
|
+
codeVerifier
|
|
1041
|
+
// PKCE proof-of-possession (dgxd) — sent on every poll
|
|
686
1042
|
);
|
|
687
1043
|
if (poll.status === "paired") {
|
|
688
1044
|
paired = poll;
|
|
@@ -707,15 +1063,44 @@ function createPairCommand(deps = {}) {
|
|
|
707
1063
|
if (paired === void 0 || paired.status !== "paired") {
|
|
708
1064
|
ctx.io.result({ paired: false, reason: "timeout" });
|
|
709
1065
|
ctx.io.errline(
|
|
710
|
-
"Pairing timed out before you approved it. In the BirdyBeep app, tap \u201Cpair a machine\u201D, scan
|
|
1066
|
+
"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."
|
|
1067
|
+
);
|
|
1068
|
+
return EXIT.ERROR;
|
|
1069
|
+
}
|
|
1070
|
+
const approvedBy = paired.approvedByEmail;
|
|
1071
|
+
const expectEmail = pairFlags.expectEmail ?? configuredExpectEmail();
|
|
1072
|
+
const stdinIsTTY = deps.isStdinTTY ?? process.stdin.isTTY === true;
|
|
1073
|
+
const decision = decidePairConfirmation({
|
|
1074
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {},
|
|
1075
|
+
...expectEmail !== void 0 ? { expectEmail } : {},
|
|
1076
|
+
...expectEmail !== void 0 ? { expectEmailSource: pairFlags.expectEmail !== void 0 ? "flag" : "config" } : {},
|
|
1077
|
+
yes: pairFlags.yes,
|
|
1078
|
+
nonInteractive: ctx.flags.nonInteractive,
|
|
1079
|
+
stdinIsTTY,
|
|
1080
|
+
// Probed ONLY when stdin can't answer — opening /dev/tty is a syscall, and when stdin is
|
|
1081
|
+
// already a terminal the answer is irrelevant.
|
|
1082
|
+
controllingTerminalAvailable: stdinIsTTY ? false : hasControllingTerminal(),
|
|
1083
|
+
configPath: cliConfigPath()
|
|
1084
|
+
});
|
|
1085
|
+
if (decision.action === "reject") {
|
|
1086
|
+
ctx.io.result({ paired: false, reason: decision.reason });
|
|
1087
|
+
ctx.io.errline(decision.message);
|
|
1088
|
+
return EXIT.ERROR;
|
|
1089
|
+
}
|
|
1090
|
+
if (decision.action === "prompt" && !isAffirmative(await promptLine(decision.question, decision.on))) {
|
|
1091
|
+
ctx.io.result({ paired: false, reason: "declined" });
|
|
1092
|
+
ctx.io.errline(
|
|
1093
|
+
"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."
|
|
711
1094
|
);
|
|
712
1095
|
return EXIT.ERROR;
|
|
713
1096
|
}
|
|
714
1097
|
await (0, import_agent_core8.setToken)(paired.machineToken, deps.tokenOptions ?? {});
|
|
715
1098
|
writeCliConfig({ apiUrl });
|
|
716
|
-
|
|
1099
|
+
const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
|
|
1100
|
+
ctx.io.emit(`\u2713 Paired${humanSuffix}. Run \`birdybeep test\` to send a test Beep.`, {
|
|
717
1101
|
paired: true,
|
|
718
|
-
machineId: paired.machineId
|
|
1102
|
+
machineId: paired.machineId,
|
|
1103
|
+
...approvedBy !== void 0 ? { approvedByEmail: approvedBy } : {}
|
|
719
1104
|
});
|
|
720
1105
|
return EXIT.OK;
|
|
721
1106
|
}
|
|
@@ -748,14 +1133,24 @@ function createQueueCommand() {
|
|
|
748
1133
|
var import_agent_core10 = require("@birdybeep/agent-core");
|
|
749
1134
|
var import_claude_code4 = require("@birdybeep/claude-code");
|
|
750
1135
|
var import_codex4 = require("@birdybeep/codex");
|
|
1136
|
+
var import_copilot4 = require("@birdybeep/copilot");
|
|
1137
|
+
var import_cursor4 = require("@birdybeep/cursor");
|
|
751
1138
|
var import_opencode4 = require("@birdybeep/opencode");
|
|
752
|
-
var DEFAULT_ADAPTERS3 = [
|
|
1139
|
+
var DEFAULT_ADAPTERS3 = [
|
|
1140
|
+
import_claude_code4.claudeCodeAdapter,
|
|
1141
|
+
import_codex4.codexAdapter,
|
|
1142
|
+
import_opencode4.opencodeAdapter,
|
|
1143
|
+
import_cursor4.cursorAdapter,
|
|
1144
|
+
import_copilot4.copilotAdapter
|
|
1145
|
+
];
|
|
753
1146
|
var ADAPTER_VERSIONS = {
|
|
754
1147
|
claude_code: import_claude_code4.CLAUDE_CODE_ADAPTER_VERSION,
|
|
755
1148
|
codex: import_codex4.CODEX_ADAPTER_VERSION,
|
|
756
|
-
opencode: import_opencode4.OPENCODE_ADAPTER_VERSION
|
|
1149
|
+
opencode: import_opencode4.OPENCODE_ADAPTER_VERSION,
|
|
1150
|
+
cursor: import_cursor4.CURSOR_ADAPTER_VERSION,
|
|
1151
|
+
copilot: import_copilot4.COPILOT_ADAPTER_VERSION
|
|
757
1152
|
};
|
|
758
|
-
var
|
|
1153
|
+
var base3 = (apiUrl) => apiUrl.replace(/\/$/, "");
|
|
759
1154
|
async function gatherItems(adapters) {
|
|
760
1155
|
return Promise.all(
|
|
761
1156
|
adapters.map(async (a) => {
|
|
@@ -790,7 +1185,7 @@ function createReportStatusCommand(deps = {}) {
|
|
|
790
1185
|
let outcome = "deferred";
|
|
791
1186
|
let errorCode;
|
|
792
1187
|
try {
|
|
793
|
-
const res = await fetchImpl(`${
|
|
1188
|
+
const res = await fetchImpl(`${base3(resolveApiUrl())}/v1/integrations/status`, {
|
|
794
1189
|
method: "POST",
|
|
795
1190
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
796
1191
|
body: JSON.stringify({ integrations: items })
|
|
@@ -841,8 +1236,16 @@ function createReportStatusCommand(deps = {}) {
|
|
|
841
1236
|
var import_agent_core11 = require("@birdybeep/agent-core");
|
|
842
1237
|
var import_claude_code5 = require("@birdybeep/claude-code");
|
|
843
1238
|
var import_codex5 = require("@birdybeep/codex");
|
|
1239
|
+
var import_copilot5 = require("@birdybeep/copilot");
|
|
1240
|
+
var import_cursor5 = require("@birdybeep/cursor");
|
|
844
1241
|
var import_opencode5 = require("@birdybeep/opencode");
|
|
845
|
-
var DEFAULT_ADAPTERS4 = [
|
|
1242
|
+
var DEFAULT_ADAPTERS4 = [
|
|
1243
|
+
import_claude_code5.claudeCodeAdapter,
|
|
1244
|
+
import_codex5.codexAdapter,
|
|
1245
|
+
import_opencode5.opencodeAdapter,
|
|
1246
|
+
import_cursor5.cursorAdapter,
|
|
1247
|
+
import_copilot5.copilotAdapter
|
|
1248
|
+
];
|
|
846
1249
|
function createStatusCommand(deps = {}) {
|
|
847
1250
|
const adapters = deps.adapters ?? DEFAULT_ADAPTERS4;
|
|
848
1251
|
const makeSender = deps.createSender ?? ((baseUrl) => (0, import_agent_core11.createSender)(
|
|
@@ -882,7 +1285,7 @@ function createStatusCommand(deps = {}) {
|
|
|
882
1285
|
}
|
|
883
1286
|
|
|
884
1287
|
// src/commands/test.ts
|
|
885
|
-
var
|
|
1288
|
+
var import_node_crypto2 = require("crypto");
|
|
886
1289
|
var import_agent_core12 = require("@birdybeep/agent-core");
|
|
887
1290
|
function buildTestEvent(opts = {}) {
|
|
888
1291
|
const machine = (0, import_agent_core12.getMachineIdentity)();
|
|
@@ -894,7 +1297,7 @@ function buildTestEvent(opts = {}) {
|
|
|
894
1297
|
// schema requires a harness; the "test" type distinguishes it
|
|
895
1298
|
// Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must
|
|
896
1299
|
// still beep — a constant id made the second test silently "deduped" (9fh).
|
|
897
|
-
source_session_id: `birdybeep-cli-test-${(0,
|
|
1300
|
+
source_session_id: `birdybeep-cli-test-${(0, import_node_crypto2.randomUUID)()}`,
|
|
898
1301
|
machine: { label: machine.label, os: machine.os },
|
|
899
1302
|
workspace: { cwd: process.cwd() },
|
|
900
1303
|
title: "BirdyBeep test event",
|
|
@@ -963,13 +1366,152 @@ function buildCommands() {
|
|
|
963
1366
|
];
|
|
964
1367
|
}
|
|
965
1368
|
|
|
1369
|
+
// src/update-check.ts
|
|
1370
|
+
var import_node_fs5 = require("fs");
|
|
1371
|
+
var import_node_path3 = require("path");
|
|
1372
|
+
var import_agent_core13 = require("@birdybeep/agent-core");
|
|
1373
|
+
var PACKAGE_NAME = "@birdybeep/cli";
|
|
1374
|
+
var PACKAGE_PATH = "@birdybeep%2Fcli";
|
|
1375
|
+
var UPDATE_CACHE_FILE = "update-check.json";
|
|
1376
|
+
var DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1377
|
+
var DEFAULT_TIMEOUT_MS = 1500;
|
|
1378
|
+
var SKIP_COMMANDS = /* @__PURE__ */ new Set(["hook", "report-status"]);
|
|
1379
|
+
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
1380
|
+
function parseSemver(input) {
|
|
1381
|
+
const m = SEMVER_RE.exec(input.trim());
|
|
1382
|
+
if (m === null) return null;
|
|
1383
|
+
return {
|
|
1384
|
+
major: Number(m[1]),
|
|
1385
|
+
minor: Number(m[2]),
|
|
1386
|
+
patch: Number(m[3]),
|
|
1387
|
+
prerelease: m[4] !== void 0 ? m[4].split(".") : []
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
function comparePrerelease(a, b) {
|
|
1391
|
+
if (a.length === 0 && b.length === 0) return 0;
|
|
1392
|
+
if (a.length === 0) return 1;
|
|
1393
|
+
if (b.length === 0) return -1;
|
|
1394
|
+
const len = Math.min(a.length, b.length);
|
|
1395
|
+
for (let i = 0; i < len; i++) {
|
|
1396
|
+
const ai = a[i];
|
|
1397
|
+
const bi = b[i];
|
|
1398
|
+
const aNum = /^\d+$/.test(ai);
|
|
1399
|
+
const bNum = /^\d+$/.test(bi);
|
|
1400
|
+
if (aNum && bNum) {
|
|
1401
|
+
const d = Number(ai) - Number(bi);
|
|
1402
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1403
|
+
} else if (aNum) {
|
|
1404
|
+
return -1;
|
|
1405
|
+
} else if (bNum) {
|
|
1406
|
+
return 1;
|
|
1407
|
+
} else if (ai !== bi) {
|
|
1408
|
+
return ai < bi ? -1 : 1;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (a.length === b.length) return 0;
|
|
1412
|
+
return a.length < b.length ? -1 : 1;
|
|
1413
|
+
}
|
|
1414
|
+
function compareSemver(a, b) {
|
|
1415
|
+
if (a.major !== b.major) return a.major < b.major ? -1 : 1;
|
|
1416
|
+
if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1;
|
|
1417
|
+
if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1;
|
|
1418
|
+
return comparePrerelease(a.prerelease, b.prerelease);
|
|
1419
|
+
}
|
|
1420
|
+
function isNewer(current, latest) {
|
|
1421
|
+
const cur = parseSemver(current);
|
|
1422
|
+
const lat = parseSemver(latest);
|
|
1423
|
+
return cur !== null && lat !== null && compareSemver(cur, lat) < 0;
|
|
1424
|
+
}
|
|
1425
|
+
function updateCachePath() {
|
|
1426
|
+
return (0, import_node_path3.join)((0, import_agent_core13.birdyBeepConfigDir)(), UPDATE_CACHE_FILE);
|
|
1427
|
+
}
|
|
1428
|
+
function readUpdateCache() {
|
|
1429
|
+
try {
|
|
1430
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(updateCachePath(), "utf8"));
|
|
1431
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1432
|
+
const { checkedAt, latest } = parsed;
|
|
1433
|
+
if (typeof checkedAt !== "number") return null;
|
|
1434
|
+
if (latest !== null && typeof latest !== "string") return null;
|
|
1435
|
+
return { checkedAt, latest };
|
|
1436
|
+
} catch {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function writeUpdateCache(cache) {
|
|
1441
|
+
(0, import_node_fs5.mkdirSync)((0, import_agent_core13.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
|
|
1442
|
+
(0, import_node_fs5.writeFileSync)(updateCachePath(), `${JSON.stringify(cache)}
|
|
1443
|
+
`, { mode: 384 });
|
|
1444
|
+
}
|
|
1445
|
+
async function fetchLatestVersion(registryUrl, fetchImpl, timeoutMs) {
|
|
1446
|
+
const url = `${registryUrl.replace(/\/+$/, "")}/${PACKAGE_PATH}/latest`;
|
|
1447
|
+
const controller = new AbortController();
|
|
1448
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1449
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1450
|
+
try {
|
|
1451
|
+
const res = await fetchImpl(url, {
|
|
1452
|
+
headers: { accept: "application/json" },
|
|
1453
|
+
signal: controller.signal
|
|
1454
|
+
});
|
|
1455
|
+
if (!res.ok) throw new Error(`registry responded ${res.status}`);
|
|
1456
|
+
const body = await res.json();
|
|
1457
|
+
if (typeof body.version !== "string" || body.version.length === 0) {
|
|
1458
|
+
throw new Error("registry response had no version");
|
|
1459
|
+
}
|
|
1460
|
+
return body.version;
|
|
1461
|
+
} finally {
|
|
1462
|
+
clearTimeout(timer);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
function renderNotice(current, latest) {
|
|
1466
|
+
return `a new version of birdybeep is available: ${current} \u2192 ${latest}
|
|
1467
|
+
upgrade with: npm install -g ${PACKAGE_NAME}@latest`;
|
|
1468
|
+
}
|
|
1469
|
+
async function maybeNotifyUpdate(opts) {
|
|
1470
|
+
try {
|
|
1471
|
+
if (opts.command !== void 0 && SKIP_COMMANDS.has(opts.command)) return;
|
|
1472
|
+
if (opts.flags.json || opts.flags.nonInteractive) return;
|
|
1473
|
+
const env = opts.env ?? process.env;
|
|
1474
|
+
if (env["BIRDYBEEP_NO_UPDATE_NOTIFIER"] || env["NO_UPDATE_NOTIFIER"] || env["CI"]) return;
|
|
1475
|
+
const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
|
|
1476
|
+
if (!isTTY) return;
|
|
1477
|
+
const current = opts.currentVersion ?? CLI_VERSION;
|
|
1478
|
+
const now = opts.now ?? Date.now();
|
|
1479
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
1480
|
+
const readCache = opts.readCache ?? readUpdateCache;
|
|
1481
|
+
const writeCache = opts.writeCache ?? writeUpdateCache;
|
|
1482
|
+
let cache = readCache();
|
|
1483
|
+
if (cache === null || now - cache.checkedAt >= intervalMs) {
|
|
1484
|
+
let latest = cache?.latest ?? null;
|
|
1485
|
+
try {
|
|
1486
|
+
latest = await fetchLatestVersion(
|
|
1487
|
+
opts.registryUrl ?? resolveRegistryUrl(),
|
|
1488
|
+
opts.fetchImpl ?? fetch,
|
|
1489
|
+
opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
1490
|
+
);
|
|
1491
|
+
} catch {
|
|
1492
|
+
}
|
|
1493
|
+
cache = { checkedAt: now, latest };
|
|
1494
|
+
try {
|
|
1495
|
+
writeCache(cache);
|
|
1496
|
+
} catch {
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (cache.latest !== null && isNewer(current, cache.latest)) {
|
|
1500
|
+
opts.io.errline(renderNotice(current, cache.latest));
|
|
1501
|
+
}
|
|
1502
|
+
} catch {
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
966
1506
|
// src/cli.ts
|
|
967
1507
|
function runCli(argv, deps = {}) {
|
|
1508
|
+
const notifyUpdate = deps.updateCheck === false ? void 0 : (ctx) => maybeNotifyUpdate({ ...ctx, ...deps.updateCheck ?? {} });
|
|
968
1509
|
return dispatch(argv, {
|
|
969
1510
|
version: CLI_VERSION,
|
|
970
1511
|
commands: deps.commands ?? buildCommands(),
|
|
971
1512
|
stdout: deps.stdout ?? process.stdout,
|
|
972
1513
|
stderr: deps.stderr ?? process.stderr,
|
|
1514
|
+
...notifyUpdate !== void 0 ? { notifyUpdate } : {},
|
|
973
1515
|
...deps.ensureConfig !== void 0 ? { ensureConfig: deps.ensureConfig } : {}
|
|
974
1516
|
});
|
|
975
1517
|
}
|