@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/index.cjs CHANGED
@@ -243,10 +243,164 @@ function surfaceRemedy(state, group) {
243
243
  // src/framework.ts
244
244
  var import_node_fs2 = require("fs");
245
245
  var import_agent_core2 = require("@birdybeep/agent-core");
246
+
247
+ // src/presentation.ts
248
+ var BIRD = [
249
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
250
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
251
+ " \u2588\u2588\u2588\u2588\u2594\u2594\u2594\u2594\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
252
+ " \u2588\u2588\u2588\u2588\u2588\u2588 \u25AA \u2588\u2588\u2588\u2588\u2588\u2588",
253
+ "\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
254
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
255
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
256
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
257
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588",
258
+ " \u2588\u2588 \u2588\u2588",
259
+ " \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588"
260
+ ].join("\n");
261
+ function presentation(output, env = process.env) {
262
+ const terminal = output.isTTY === true && env.TERM !== "dumb";
263
+ const color = terminal && !env.NO_COLOR;
264
+ const lime = (text) => {
265
+ if (!color) return text;
266
+ const code = /^(truecolor|24bit)$/.test(env.COLORTERM ?? "") ? "38;2;198;242;78" : (env.TERM ?? "").includes("256color") ? "38;5;191" : "92";
267
+ return `\x1B[${code}m${text}\x1B[0m`;
268
+ };
269
+ return {
270
+ lime,
271
+ banner: () => terminal ? `
272
+ ${lime(BIRD)}
273
+
274
+ ` : "",
275
+ help: (text) => text.split("\n").map((line, i) => i === 0 || line.endsWith(":") ? lime(line) : line).join("\n")
276
+ };
277
+ }
278
+
279
+ // src/bird-animation.ts
280
+ function birdFrame(blink = false, lift = 0, pose = "rest") {
281
+ const rows = BIRD.split("\n");
282
+ if (pose === "inhale" || pose === "breathe") {
283
+ 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";
284
+ } else if (pose === "crouch" || pose === "land") {
285
+ rows.splice(7, 1);
286
+ rows.unshift("");
287
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
288
+ rows[8] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
289
+ rows[9] = " \u2588\u2588 \u2588\u2588";
290
+ } else if (pose === "stretch") {
291
+ rows[4] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
292
+ rows[5] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
293
+ rows[6] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
294
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
295
+ rows[9] = " \u2588\u2588 \u2588\u2588";
296
+ rows[10] = " \u2580\u2580 \u2580\u2580";
297
+ } else if (pose === "flight") {
298
+ rows[4] = "\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
299
+ rows[5] = "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2572\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
300
+ rows[6] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2572\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
301
+ rows[7] = " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588";
302
+ rows[9] = " \u2580\u2580 \u2580\u2580";
303
+ rows[10] = "";
304
+ }
305
+ if (blink)
306
+ rows[pose === "crouch" || pose === "land" ? 4 : 3] = rows[pose === "crouch" || pose === "land" ? 4 : 3].replace(" \u25AA ", " \u2501 ");
307
+ return [...Array(2 - lift).fill(""), ...rows, ...Array(lift).fill("")].join("\n");
308
+ }
309
+ var HOP = [
310
+ { pose: "crouch", lift: 0, ms: 180 },
311
+ { pose: "stretch", lift: 1, ms: 80 },
312
+ { pose: "flight", lift: 2, ms: 130 },
313
+ { pose: "flight", lift: 2, ms: 110 },
314
+ { pose: "stretch", lift: 1, ms: 90 },
315
+ { pose: "land", lift: 0, ms: 140 },
316
+ { pose: "inhale", lift: 0, ms: 120 },
317
+ { pose: "rest", lift: 0, ms: 180 }
318
+ ];
319
+ function createBirdAnimation(output, env = process.env) {
320
+ const allowed = () => output.isTTY === true && env.TERM !== "dumb" && !env.CI && env.BIRDYBEEP_ANIMATION !== "0" && (output.columns ?? 80) >= 26 && (output.rows ?? 40) >= 16;
321
+ const style = presentation(output, env);
322
+ let activeStop;
323
+ const begin = () => {
324
+ activeStop?.();
325
+ if (!allowed()) return void 0;
326
+ let stopped = false;
327
+ let timer;
328
+ const paint = (frame, rewind = false) => {
329
+ output.write(
330
+ (rewind ? "\x1B[13A" : "") + frame.split("\n").map((line) => `\r\x1B[2K${style.lime(line)}
331
+ `).join("")
332
+ );
333
+ };
334
+ const finish = (clear) => {
335
+ if (stopped) return;
336
+ stopped = true;
337
+ if (timer) clearInterval(timer);
338
+ process.off("exit", stop);
339
+ process.stdout.off("resize", resize);
340
+ if (clear) output.write("\x1B[13A" + "\r\x1B[2K\n".repeat(13) + "\x1B[13A\r");
341
+ activeStop = void 0;
342
+ };
343
+ const stop = () => finish(true);
344
+ const resize = () => {
345
+ stopped = true;
346
+ if (timer) clearInterval(timer);
347
+ process.off("exit", stop);
348
+ process.stdout.off("resize", resize);
349
+ activeStop = void 0;
350
+ };
351
+ process.once("exit", stop);
352
+ process.stdout.once("resize", resize);
353
+ activeStop = stop;
354
+ paint(birdFrame());
355
+ return {
356
+ stop,
357
+ settle: () => finish(false),
358
+ draw: (frame) => {
359
+ if (!stopped) {
360
+ paint(frame, true);
361
+ }
362
+ },
363
+ repeat: (callback) => {
364
+ timer = setInterval(callback, 140);
365
+ timer.unref();
366
+ }
367
+ };
368
+ };
369
+ return {
370
+ wait: () => {
371
+ const canvas = begin();
372
+ if (!canvas) return () => {
373
+ };
374
+ let tick = 0;
375
+ canvas.repeat(() => {
376
+ tick += 1;
377
+ const phase = tick % 22;
378
+ const pose = phase >= 8 && phase <= 10 ? "breathe" : phase >= 6 && phase <= 12 ? "inhale" : "rest";
379
+ canvas.draw(birdFrame(phase === 18, 0, pose));
380
+ });
381
+ return canvas.stop;
382
+ },
383
+ celebrate: async () => {
384
+ const canvas = begin();
385
+ if (!canvas) return;
386
+ try {
387
+ for (const { pose, lift, ms } of HOP) {
388
+ canvas.draw(birdFrame(pose === "land", lift, pose));
389
+ await new Promise((resolve) => setTimeout(resolve, ms));
390
+ }
391
+ } finally {
392
+ canvas.settle();
393
+ }
394
+ }
395
+ };
396
+ }
397
+
398
+ // src/framework.ts
246
399
  var EXIT = { OK: 0, ERROR: 1, USAGE: 2 };
247
- function createIo(json, stdout, stderr) {
400
+ function createIo(json, stdout, stderr, animate = false) {
248
401
  return {
249
402
  json,
403
+ ...!json && animate ? { bird: createBirdAnimation(stdout) } : {},
250
404
  line: (text) => {
251
405
  if (!json) stdout.write(`${text}
252
406
  `);
@@ -382,7 +536,8 @@ function renderCommandHelp(path, command) {
382
536
  }
383
537
  async function dispatch(argv, deps) {
384
538
  const { flags, rest } = parseGlobalFlags(argv);
385
- const io = createIo(flags.json, deps.stdout, deps.stderr);
539
+ const io = createIo(flags.json, deps.stdout, deps.stderr, !flags.nonInteractive);
540
+ const style = presentation(deps.stdout);
386
541
  if (deps.ensureConfig !== false) {
387
542
  try {
388
543
  (0, import_node_fs2.mkdirSync)((0, import_agent_core2.birdyBeepConfigDir)(), { recursive: true, mode: 448 });
@@ -410,7 +565,7 @@ async function dispatch(argv, deps) {
410
565
  }
411
566
  }
412
567
  if (rest.length === 0 || flags.help && command === void 0) {
413
- io.emit(renderRootHelp(deps.version, deps.commands), {
568
+ io.emit(style.banner() + style.help(renderRootHelp(deps.version, deps.commands)), {
414
569
  version: deps.version,
415
570
  commands: deps.commands.map((c) => ({
416
571
  name: c.name,
@@ -426,7 +581,7 @@ async function dispatch(argv, deps) {
426
581
  }
427
582
  const path = pathParts.join(" ");
428
583
  if (flags.help) {
429
- io.emit(renderCommandHelp(path, command), {
584
+ io.emit(style.help(renderCommandHelp(path, command)), {
430
585
  name: path,
431
586
  summary: command.summary,
432
587
  usage: command.usage,
@@ -448,6 +603,10 @@ async function dispatch(argv, deps) {
448
603
  }
449
604
  let code;
450
605
  try {
606
+ if (!flags.nonInteractive && (path === "setup" || path === "pair")) {
607
+ const banner = style.banner();
608
+ if (banner) io.line(banner.trimEnd());
609
+ }
451
610
  code = await command.run({ args, flags, io });
452
611
  } catch (err) {
453
612
  if (err instanceof MissingInputError) {
@@ -1238,7 +1397,7 @@ async function pairTokenPoll(apiUrl, deviceCode, fetchImpl, machineFingerprint,
1238
1397
  }
1239
1398
 
1240
1399
  // src/version.ts
1241
- var CLI_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0";
1400
+ var CLI_VERSION = "0.8.3".length > 0 ? "0.8.3" : "0.0.0";
1242
1401
 
1243
1402
  // src/commands/setup.ts
1244
1403
  var import_claude_code5 = require("@birdybeep/claude-code");
@@ -1791,32 +1950,39 @@ function createPairingCommand(verb, deps = {}) {
1791
1950
  let lastBeat = startedAt;
1792
1951
  let paired;
1793
1952
  let terminal;
1794
- for (; ; ) {
1795
- const nowMs = clock();
1796
- if (nowMs >= deadline) break;
1797
- await sleep(intervalMs);
1798
- const poll = await pairTokenPoll(
1799
- apiUrl,
1800
- start.device_code,
1801
- fetchImpl,
1802
- identity.fingerprintHash,
1803
- codeVerifier
1804
- // PKCE proof-of-possession (dgxd) — sent on every poll
1805
- );
1806
- if (poll.status === "paired") {
1807
- paired = poll;
1808
- break;
1809
- }
1810
- if (poll.status === "error" && !poll.retryable) {
1811
- terminal = poll;
1812
- break;
1813
- }
1814
- if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1815
- ctx.io.line(
1816
- poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1953
+ let stopBird = ctx.io.bird?.wait();
1954
+ try {
1955
+ for (; ; ) {
1956
+ const nowMs = clock();
1957
+ if (nowMs >= deadline) break;
1958
+ await sleep(intervalMs);
1959
+ const poll = await pairTokenPoll(
1960
+ apiUrl,
1961
+ start.device_code,
1962
+ fetchImpl,
1963
+ identity.fingerprintHash,
1964
+ codeVerifier
1965
+ // PKCE proof-of-possession (dgxd) — sent on every poll
1817
1966
  );
1818
- lastBeat = nowMs;
1967
+ if (poll.status === "paired") {
1968
+ paired = poll;
1969
+ break;
1970
+ }
1971
+ if (poll.status === "error" && !poll.retryable) {
1972
+ terminal = poll;
1973
+ break;
1974
+ }
1975
+ if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {
1976
+ stopBird?.();
1977
+ ctx.io.line(
1978
+ poll.status === "error" ? ` Backend unavailable (${poll.message}); retrying.` : " Waiting for approval in the BirdyBeep app."
1979
+ );
1980
+ lastBeat = nowMs;
1981
+ stopBird = ctx.io.bird?.wait();
1982
+ }
1819
1983
  }
1984
+ } finally {
1985
+ stopBird?.();
1820
1986
  }
1821
1987
  if (terminal !== void 0) {
1822
1988
  ctx.io.result({ paired: false, reason: terminal.code });
@@ -1864,6 +2030,7 @@ function createPairingCommand(verb, deps = {}) {
1864
2030
  const humanSuffix = approvedBy !== void 0 ? ` to ${approvedBy}` : "";
1865
2031
  const discardedSuffix = discarded > 0 ? ` Discarded ${discarded} event(s) queued before pairing. They will not produce notifications.` : "";
1866
2032
  const nextStep = chain === void 0 ? " Run `birdybeep setup` to install coding-agent hooks." : "";
2033
+ await ctx.io.bird?.celebrate();
1867
2034
  ctx.io.line(`\u2713 Paired${humanSuffix}.${nextStep}${discardedSuffix}`);
1868
2035
  const report = chain !== void 0 ? await runSetupChain(ctx, setupDeps, pairFlags) : void 0;
1869
2036
  ctx.io.result({