@birdybeep/cli 0.8.2 → 0.8.3

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 CHANGED
@@ -224,10 +224,164 @@ function surfaceRemedy(state, group) {
224
224
  // src/framework.ts
225
225
  var import_node_fs2 = require("fs");
226
226
  var import_agent_core2 = require("@birdybeep/agent-core");
227
+
228
+ // src/presentation.ts
229
+ var BIRD = [
230
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
231
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
232
+ " \u2588\u2588\u2588\u2588\u2594\u2594\u2594\u2594\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
233
+ " \u2588\u2588\u2588\u2588\u2588\u2588 \u25AA \u2588\u2588\u2588\u2588\u2588\u2588",
234
+ "\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
235
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
236
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
237
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
238
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
239
+ " \u2588\u2588 \u2588\u2588",
240
+ " \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588"
241
+ ].join("\n");
242
+ function presentation(output, env = process.env) {
243
+ const terminal = output.isTTY === true && env.TERM !== "dumb";
244
+ const color = terminal && !env.NO_COLOR;
245
+ const lime = (text) => {
246
+ if (!color) return text;
247
+ const code = /^(truecolor|24bit)$/.test(env.COLORTERM ?? "") ? "38;2;198;242;78" : (env.TERM ?? "").includes("256color") ? "38;5;191" : "92";
248
+ return `\x1B[${code}m${text}\x1B[0m`;
249
+ };
250
+ return {
251
+ lime,
252
+ banner: () => terminal ? `
253
+ ${lime(BIRD)}
254
+
255
+ ` : "",
256
+ help: (text) => text.split("\n").map((line, i) => i === 0 || line.endsWith(":") ? lime(line) : line).join("\n")
257
+ };
258
+ }
259
+
260
+ // src/bird-animation.ts
261
+ function birdFrame(blink = false, lift = 0, pose = "rest") {
262
+ const rows = BIRD.split("\n");
263
+ if (pose === "inhale" || pose === "breathe") {
264
+ rows[8] = pose === "inhale" ? " \u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580" : " \u2580\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2580";
265
+ } else if (pose === "crouch" || pose === "land") {
266
+ rows.splice(7, 1);
267
+ rows.unshift("");
268
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
269
+ rows[8] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
270
+ rows[9] = " \u2588\u2588 \u2588\u2588";
271
+ } else if (pose === "stretch") {
272
+ rows[4] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
273
+ rows[5] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
274
+ rows[6] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
275
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
276
+ rows[9] = " \u2588\u2588 \u2588\u2588";
277
+ rows[10] = " \u2580\u2580 \u2580\u2580";
278
+ } else if (pose === "flight") {
279
+ rows[4] = "\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
280
+ rows[5] = "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2572\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
281
+ rows[6] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2572\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
282
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
283
+ rows[9] = " \u2580\u2580 \u2580\u2580";
284
+ rows[10] = "";
285
+ }
286
+ if (blink)
287
+ rows[pose === "crouch" || pose === "land" ? 4 : 3] = rows[pose === "crouch" || pose === "land" ? 4 : 3].replace(" \u25AA ", " \u2501 ");
288
+ return [...Array(2 - lift).fill(""), ...rows, ...Array(lift).fill("")].join("\n");
289
+ }
290
+ var HOP = [
291
+ { pose: "crouch", lift: 0, ms: 180 },
292
+ { pose: "stretch", lift: 1, ms: 80 },
293
+ { pose: "flight", lift: 2, ms: 130 },
294
+ { pose: "flight", lift: 2, ms: 110 },
295
+ { pose: "stretch", lift: 1, ms: 90 },
296
+ { pose: "land", lift: 0, ms: 140 },
297
+ { pose: "inhale", lift: 0, ms: 120 },
298
+ { pose: "rest", lift: 0, ms: 180 }
299
+ ];
300
+ function createBirdAnimation(output, env = process.env) {
301
+ const allowed = () => output.isTTY === true && env.TERM !== "dumb" && !env.CI && env.BIRDYBEEP_ANIMATION !== "0" && (output.columns ?? 80) >= 26 && (output.rows ?? 40) >= 16;
302
+ const style = presentation(output, env);
303
+ let activeStop;
304
+ const begin = () => {
305
+ activeStop?.();
306
+ if (!allowed()) return void 0;
307
+ let stopped = false;
308
+ let timer;
309
+ const paint = (frame, rewind = false) => {
310
+ output.write(
311
+ (rewind ? "\x1B[13A" : "") + frame.split("\n").map((line) => `\r\x1B[2K${style.lime(line)}
312
+ `).join("")
313
+ );
314
+ };
315
+ const finish = (clear) => {
316
+ if (stopped) return;
317
+ stopped = true;
318
+ if (timer) clearInterval(timer);
319
+ process.off("exit", stop);
320
+ process.stdout.off("resize", resize);
321
+ if (clear) output.write("\x1B[13A" + "\r\x1B[2K\n".repeat(13) + "\x1B[13A\r");
322
+ activeStop = void 0;
323
+ };
324
+ const stop = () => finish(true);
325
+ const resize = () => {
326
+ stopped = true;
327
+ if (timer) clearInterval(timer);
328
+ process.off("exit", stop);
329
+ process.stdout.off("resize", resize);
330
+ activeStop = void 0;
331
+ };
332
+ process.once("exit", stop);
333
+ process.stdout.once("resize", resize);
334
+ activeStop = stop;
335
+ paint(birdFrame());
336
+ return {
337
+ stop,
338
+ settle: () => finish(false),
339
+ draw: (frame) => {
340
+ if (!stopped) {
341
+ paint(frame, true);
342
+ }
343
+ },
344
+ repeat: (callback) => {
345
+ timer = setInterval(callback, 140);
346
+ timer.unref();
347
+ }
348
+ };
349
+ };
350
+ return {
351
+ wait: () => {
352
+ const canvas = begin();
353
+ if (!canvas) return () => {
354
+ };
355
+ let tick = 0;
356
+ canvas.repeat(() => {
357
+ tick += 1;
358
+ const phase = tick % 22;
359
+ const pose = phase >= 8 && phase <= 10 ? "breathe" : phase >= 6 && phase <= 12 ? "inhale" : "rest";
360
+ canvas.draw(birdFrame(phase === 18, 0, pose));
361
+ });
362
+ return canvas.stop;
363
+ },
364
+ celebrate: async () => {
365
+ const canvas = begin();
366
+ if (!canvas) return;
367
+ try {
368
+ for (const { pose, lift, ms } of HOP) {
369
+ canvas.draw(birdFrame(pose === "land", lift, pose));
370
+ await new Promise((resolve) => setTimeout(resolve, ms));
371
+ }
372
+ } finally {
373
+ canvas.settle();
374
+ }
375
+ }
376
+ };
377
+ }
378
+
379
+ // src/framework.ts
227
380
  var EXIT = { OK: 0, ERROR: 1, USAGE: 2 };
228
- function createIo(json, stdout, stderr) {
381
+ function createIo(json, stdout, stderr, animate = false) {
229
382
  return {
230
383
  json,
384
+ ...!json && animate ? { bird: createBirdAnimation(stdout) } : {},
231
385
  line: (text) => {
232
386
  if (!json) stdout.write(`${text}
233
387
  `);
@@ -358,7 +512,8 @@ function renderCommandHelp(path, command) {
358
512
  }
359
513
  async function dispatch(argv, deps) {
360
514
  const { flags, rest } = parseGlobalFlags(argv);
361
- const io = createIo(flags.json, deps.stdout, deps.stderr);
515
+ const io = createIo(flags.json, deps.stdout, deps.stderr, !flags.nonInteractive);
516
+ const style = presentation(deps.stdout);
362
517
  if (deps.ensureConfig !== false) {
363
518
  try {
364
519
  (0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
@@ -386,7 +541,7 @@ async function dispatch(argv, deps) {
386
541
  }
387
542
  }
388
543
  if (rest.length === 0 || flags.help && command === void 0) {
389
- io.emit(renderRootHelp(deps.version, deps.commands), {
544
+ io.emit(style.banner() + style.help(renderRootHelp(deps.version, deps.commands)), {
390
545
  version: deps.version,
391
546
  commands: deps.commands.map((c) => ({
392
547
  name: c.name,
@@ -402,7 +557,7 @@ async function dispatch(argv, deps) {
402
557
  }
403
558
  const path = pathParts.join(" ");
404
559
  if (flags.help) {
405
- io.emit(renderCommandHelp(path, command), {
560
+ io.emit(style.help(renderCommandHelp(path, command)), {
406
561
  name: path,
407
562
  summary: command.summary,
408
563
  usage: command.usage,
@@ -424,6 +579,10 @@ async function dispatch(argv, deps) {
424
579
  }
425
580
  let code;
426
581
  try {
582
+ if (!flags.nonInteractive && (path === "setup" || path === "pair")) {
583
+ const banner = style.banner();
584
+ if (banner) io.line(banner.trimEnd());
585
+ }
427
586
  code = await command.run({ args, flags, io });
428
587
  } catch (err) {
429
588
  if (err instanceof MissingInputError) {
@@ -1214,7 +1373,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
1214
1373
  }
1215
1374
 
1216
1375
  // src/version.ts
1217
- var CLI_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0";
1376
+ var CLI_VERSION = "0.8.3".length > 0 ? "0.8.3" : "0.0.0";
1218
1377
 
1219
1378
  // src/commands/setup.ts
1220
1379
  var import_claude_code5 = require("@birdybeep/claude-code");
@@ -1767,32 +1926,39 @@ function createPairingCommand(verb, deps = {}) {
1767
1926
  let lastBeat = startedAt;
1768
1927
  let paired;
1769
1928
  let terminal;
1770
- for (; ; ) {
1771
- const nowMs = clock();
1772
- if (nowMs >= deadline) break;
1773
- await sleep(intervalMs);
1774
- const poll = await pairTokenPoll(
1775
- apiUrl,
1776
- start.device_code,
1777
- fetchImpl,
1778
- identity.fingerprintHash,
1779
- codeVerifier
1780
- // PKCE proof-of-possession (dgxd) — sent on every poll
1781
- );
1782
- if (poll.status === "paired") {
1783
- paired = poll;
1784
- break;
1785
- }
1786
- if (poll.status === "error" && !poll.retryable) {
1787
- terminal = poll;
1788
- break;
1789
- }
1790
- if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1791
- ctx.io.line(
1792
- poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1929
+ let stopBird = ctx.io.bird?.wait();
1930
+ try {
1931
+ for (; ; ) {
1932
+ const nowMs = clock();
1933
+ if (nowMs >= deadline) break;
1934
+ await sleep(intervalMs);
1935
+ const poll = await pairTokenPoll(
1936
+ apiUrl,
1937
+ start.device_code,
1938
+ fetchImpl,
1939
+ identity.fingerprintHash,
1940
+ codeVerifier
1941
+ // PKCE proof-of-possession (dgxd) — sent on every poll
1793
1942
  );
1794
- lastBeat = nowMs;
1943
+ if (poll.status === "paired") {
1944
+ paired = poll;
1945
+ break;
1946
+ }
1947
+ if (poll.status === "error" && !poll.retryable) {
1948
+ terminal = poll;
1949
+ break;
1950
+ }
1951
+ if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1952
+ stopBird?.();
1953
+ ctx.io.line(
1954
+ poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1955
+ );
1956
+ lastBeat = nowMs;
1957
+ stopBird = ctx.io.bird?.wait();
1958
+ }
1795
1959
  }
1960
+ } finally {
1961
+ stopBird?.();
1796
1962
  }
1797
1963
  if (terminal !== void 0) {
1798
1964
  ctx.io.result({ paired: false, reason: terminal.code });
@@ -1840,6 +2006,7 @@ function createPairingCommand(verb, deps = {}) {
1840
2006
  const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
1841
2007
  const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
1842
2008
  const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
2009
+ await ctx.io.bird?.celebrate();
1843
2010
  ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
1844
2011
  const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1845
2012
  ctx.io.result({