@binaryandthread/cli 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -1,26 +1,27 @@
1
- # @binaryandthread/cli — `bt`
1
+ # @binaryandthread/cli, `bt`
2
2
 
3
- Buy a Binary & Thread heavyweight tee **from your terminal**, with the ceremony of a production
4
- deploy. A small, well-tested client over the Store API — the card never touches the terminal.
3
+ Buy a Binary & Thread heavyweight piece **from your terminal**, with the ceremony of a production
4
+ deploy. A small, well-tested client over the Store API, the card never touches the terminal.
5
5
 
6
6
  > `bt` is a dumb client. Every dangerous operation (payment, fulfilment) lives in the backend.
7
7
  > Checkout hands you a URL; you pay on the storefront's Stripe page in your own browser.
8
8
 
9
9
  ## Install
10
10
 
11
- Zero-config — it ships pointed at the live store (the publishable key is public by design):
11
+ Zero-config, it ships pointed at the live store (the publishable key is public by design):
12
12
 
13
13
  ```bash
14
- # soft-launch tarball (friends): install the .tgz you were handed
15
- npm i -g ./binaryandthread-cli-0.1.0.tgz
14
+ # recommended: install globally for a persistent `bt` on your PATH
15
+ npm i -g @binaryandthread/cli
16
16
  bt browse
17
17
 
18
- # or, once published:
18
+ # or run without installing (no persistent command):
19
19
  npx @binaryandthread/cli browse
20
20
  ```
21
21
 
22
- A bare `bt` may collide with another binary on your `PATH`; `npx @binaryandthread/cli …` is the
23
- unambiguous form.
22
+ A bare `bt` may collide with another binary on your `PATH`. If it does, this package also installs
23
+ **`btn`** as an alias, `btn browse` works identically. (`npx @binaryandthread/cli …` is always the
24
+ unambiguous form.)
24
25
 
25
26
  ## Commands
26
27
 
@@ -28,7 +29,7 @@ unambiguous form.
28
29
  | --- | --- |
29
30
  | `bt browse` | the catalog (interactive ↑/↓ picker on a TTY; `--json` for scripts) |
30
31
  | `bt search <q>` | filter the catalog |
31
- | `bt view <handle>` | one tee — specs + mockup (`--colour black` renders the black row) |
32
+ | `bt view <handle>` | one piece, specs + mockup (`--colour black` renders the black row) |
32
33
  | `bt stage <handle>` | add to your cart (`--size`, `--colour`) |
33
34
  | `bt cart` / `bt unstage <ref>` | show / remove a line |
34
35
  | `bt checkout` | mint a checkout, hand off to the browser to pay, then poll |
@@ -37,22 +38,22 @@ unambiguous form.
37
38
  | `bt drops` | terminal-visible drops |
38
39
  | `bt forget` | wipe your local session + config (keychain token, cart, email) |
39
40
 
40
- ## Config (all optional — env overrides the baked-in prod defaults)
41
+ ## Config (all optional, env overrides the baked-in prod defaults)
41
42
 
42
43
  | var | notes |
43
44
  | --- | --- |
44
45
  | `BT_API_URL` | Store API base URL (default: production; set for local dev / staging) |
45
- | `BT_PUBLISHABLE_KEY` | the **publishable** key — public by design (a secret `sk_` key is rejected) |
46
+ | `BT_PUBLISHABLE_KEY` | the **publishable** key, public by design (a secret `sk_` key is rejected) |
46
47
  | `NO_COLOR` / `--no-color` | plain output, no ANSI / inline images |
47
48
  | `--json` | machine-readable output for scripts / CI |
48
49
 
49
- Your auth token is **never** stored in config — it lives in the OS keychain (`bt login`).
50
+ Your auth token is **never** stored in config, it lives in the OS keychain (`bt login`).
50
51
  Local config (cart id, email, defaults) lives at `~/.config/bt/config.json`.
51
52
 
52
53
  ## Scripting: `--json` output & exit codes
53
54
 
54
55
  Every command takes `--json` for stable, machine-readable output. `bt checkout --json` (or `--yes`)
55
- skips the interactive confirm so it never hangs in CI. Field shapes (stable — safe to depend on):
56
+ skips the interactive confirm so it never hangs in CI. Field shapes (stable, safe to depend on):
56
57
 
57
58
  | command | `--json` shape |
58
59
  | --- | --- |
@@ -71,8 +72,8 @@ exits with a distinct code so CI/agents can branch:
71
72
  | `0` | — | success |
72
73
  | `65` | `client_error` / `stale_cart` | bad request / re-stage the cart |
73
74
  | `69` | `not_found` | no such product/order |
74
- | `75` | `rate_limited` / `upstream` | temporary — retry later (a `429` is auto-retried honoring `Retry-After`) |
75
- | `77` | `auth` | not logged in / forbidden — run `bt login` |
75
+ | `75` | `rate_limited` / `upstream` | temporary, retry later (a `429` is auto-retried honoring `Retry-After`) |
76
+ | `77` | `auth` | not logged in / forbidden, run `bt login` |
76
77
  | `1` | `error` | anything else |
77
78
 
78
79
  **Rate limits:** the client auto-retries `429` responses, honoring the server's `Retry-After`
package/dist/bin.js CHANGED
@@ -15,39 +15,36 @@ import {
15
15
  RecoverableCartError,
16
16
  StoreApiError,
17
17
  StoreClient,
18
+ box,
18
19
  brandOf,
20
+ colourMarker,
21
+ contributionBar,
22
+ cyan,
23
+ dim,
19
24
  displayName,
20
25
  empty,
21
26
  fail,
22
27
  firstPrice,
23
28
  fmtPrice,
24
29
  formatCartLine,
30
+ green,
31
+ header,
25
32
  lineColour,
26
33
  ok,
27
34
  printResult,
28
35
  productColours,
29
36
  redact,
30
- registerBrowse,
31
37
  registerStage,
32
38
  registerView,
33
39
  resolveLineItemRef,
40
+ resolveOutput,
41
+ rule,
34
42
  runStage,
35
43
  stageMarker,
36
44
  versionLine,
37
45
  warn,
38
46
  working
39
- } from "./chunk-DWTH7CNI.js";
40
- import {
41
- box,
42
- colourMarker,
43
- contributionBar,
44
- cyan,
45
- dim,
46
- green,
47
- header,
48
- makeBrand,
49
- rule
50
- } from "./chunk-P3LSUCEO.js";
47
+ } from "./chunk-WYCZ6BPV.js";
51
48
  import {
52
49
  __require
53
50
  } from "./chunk-7OCVIDC7.js";
@@ -75,6 +72,9 @@ function makeLogger(debug, write = (s) => process.stderr.write(s)) {
75
72
  };
76
73
  }
77
74
 
75
+ // src/version.ts
76
+ var VERSION = "0.1.2";
77
+
78
78
  // src/eggs.ts
79
79
  function teapot() {
80
80
  return `${GLYPH.fail} 418 i'm a teapot. we make clothing, not coffee. (RFC 2324)`;
@@ -297,6 +297,84 @@ function deleteToken(deps = {}) {
297
297
  }
298
298
  }
299
299
 
300
+ // src/commands/browse.ts
301
+ function productRow(p, stage, style) {
302
+ const { amount, currency } = firstPrice(p);
303
+ const { title, handle } = displayName(p);
304
+ const colours = colourMarker(productColours(p));
305
+ const parts = [
306
+ title.padEnd(22),
307
+ cyan(style, fmtPrice(amount, currency).padEnd(11)),
308
+ dim(style, handle.padEnd(34)),
309
+ stageMarker(stage)
310
+ ];
311
+ const row = parts.join(" ").trimEnd();
312
+ return colours ? `${row}
313
+ ${dim(style, colours)}` : row;
314
+ }
315
+ async function runBrowse(client, config, args) {
316
+ const style = brandOf(args);
317
+ const region = await client.resolveRegion(config.region);
318
+ const products = await client.listProducts({ region });
319
+ const rows = products.map((p) => ({
320
+ p,
321
+ stage: p.metadata?.["channel_stage"] ?? "main"
322
+ }));
323
+ let drops = [];
324
+ if (args.branch === "next") {
325
+ drops = await client.listDrops();
326
+ }
327
+ const lines = [];
328
+ if (rows.length === 0 && drops.length === 0) {
329
+ lines.push(empty("nothing in stock on this branch yet. main is the latest."));
330
+ lines.push(" peek at next \u2192 bt browse --branch=next");
331
+ } else {
332
+ lines.push(header(style, "bt browse \u2014 in stock"));
333
+ for (const { p, stage } of rows) {
334
+ lines.push(productRow(p, stage, style));
335
+ }
336
+ for (const d of drops) {
337
+ const dt = (d.title ?? d.handle ?? d.id).toString();
338
+ lines.push(
339
+ [
340
+ dt.padEnd(22),
341
+ cyan(style, fmtPrice(d.price, region.currency_code).padEnd(11)),
342
+ dim(style, (d.handle ?? d.id).padEnd(34)),
343
+ stageMarker(d.channel_stage)
344
+ ].join(" ").trimEnd()
345
+ );
346
+ }
347
+ if (drops.length > 0) {
348
+ lines.push(" on next, before the web \u2192 bt drops");
349
+ }
350
+ lines.push(" view one \u2192 bt view <handle> \xB7 stage one \u2192 bt stage <handle> --size L");
351
+ }
352
+ return {
353
+ lines,
354
+ json: args.json ? products.map((p) => ({
355
+ id: p.id,
356
+ handle: p.handle,
357
+ title: p.title,
358
+ price: firstPrice(p).amount,
359
+ currency: firstPrice(p).currency,
360
+ colours: productColours(p),
361
+ channel_stage: p.metadata?.["channel_stage"] ?? "main"
362
+ })) : void 0
363
+ };
364
+ }
365
+ function registerBrowse(program, makeClient2, loadConfig2) {
366
+ program.command("browse").alias("ls").description("list what's in stock on a branch.").option("--branch <branch>", "main (live) or next (terminal-only drops)").option("--plain", "force the static table (skip the interactive picker)").action(async (opts) => {
367
+ const { json, brand } = resolveOutput(program);
368
+ if (!json && !opts.plain && brand.color && process.stdout.isTTY) {
369
+ const { runInteractiveBrowse } = await import("./interactive-DYY27KBC.js");
370
+ const handled = await runInteractiveBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, brand });
371
+ if (handled) return;
372
+ }
373
+ const res = await runBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, json, brand });
374
+ printResult(res, json);
375
+ });
376
+ }
377
+
300
378
  // src/commands/search.ts
301
379
  function matches(p, q) {
302
380
  const needle = q.toLowerCase();
@@ -343,9 +421,7 @@ async function runSearch(client, config, args) {
343
421
  }
344
422
  function registerSearch(program, makeClient2, loadConfig2) {
345
423
  program.command("search").argument("<query>", "what to look for").description("filter the catalog by a query.").action(async (query) => {
346
- const gOpts = program.opts();
347
- const json = Boolean(gOpts.json);
348
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
424
+ const { json, brand } = resolveOutput(program);
349
425
  const res = await runSearch(makeClient2(), loadConfig2(), { query, json, brand });
350
426
  printResult(res, json);
351
427
  });
@@ -397,9 +473,7 @@ async function runCart(client, config, args) {
397
473
  }
398
474
  function registerCart(program, makeClient2, loadConfig2) {
399
475
  program.command("cart").description("show your current cart \u2014 the authoritative server state.").action(async () => {
400
- const gOpts = program.opts();
401
- const json = Boolean(gOpts.json);
402
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
476
+ const { json, brand } = resolveOutput(program);
403
477
  const res = await runCart(makeClient2(), loadConfig2(), { json, brand });
404
478
  printResult(res, json);
405
479
  });
@@ -455,8 +529,30 @@ async function runCheckout(client, config, args, deps) {
455
529
  if (!prompts) {
456
530
  throw new Error("checkout requires a prompter");
457
531
  }
532
+ let shippedCart;
533
+ try {
534
+ shippedCart = await client.ensureCartShippingMethod(cartId);
535
+ } catch (e) {
536
+ if (e instanceof RecoverableCartError) {
537
+ return {
538
+ lines: [
539
+ warn("that cart is no longer valid (expired or already committed)."),
540
+ " re-stage \u2192 bt browse then bt stage <slug>"
541
+ ]
542
+ };
543
+ }
544
+ return {
545
+ lines: [
546
+ fail({
547
+ code: 1,
548
+ what: "couldn't attach a shipping method to your cart.",
549
+ fix: "this region may not have shipping configured yet \u2014 try again shortly, or re-stage."
550
+ })
551
+ ]
552
+ };
553
+ }
458
554
  const itemCount = cart.items.reduce((n, li) => n + (li.quantity ?? 1), 0);
459
- const totalLine = fmtPrice(cart.total, cart.currency_code ?? region.currency_code);
555
+ const totalLine = fmtPrice(shippedCart?.total ?? cart.total, cart.currency_code ?? region.currency_code);
460
556
  const reviewLines = [header(style, "bt checkout \u2014 staged for commit")];
461
557
  for (const li of cart.items) {
462
558
  const colour = lineColour(li);
@@ -483,34 +579,33 @@ async function runCheckout(client, config, args, deps) {
483
579
  }
484
580
  const allowed = supportedCountries(region);
485
581
  const shipsTo = (allowed[0] ?? "").toUpperCase();
582
+ const email = (config.email ?? "").trim();
583
+ const { checkout_url, session_id } = await client.mintCheckout(cartId, email || void 0, "cli");
584
+ const save = email ? { email } : {};
585
+ const opened = await deps.open(checkout_url);
586
+ for (const line of opened.lines) {
587
+ print(line);
588
+ }
589
+ print(" enter your details on the page \u2014 address + card. cards never touch the terminal.");
590
+ let outcome;
486
591
  try {
487
- await client.ensureCartShippingMethod(cartId);
488
- } catch (e) {
489
- if (e instanceof RecoverableCartError) {
490
- return {
491
- lines: [
492
- warn("that cart is no longer valid (expired or already committed)."),
493
- " re-stage \u2192 bt browse then bt stage <slug>"
494
- ]
495
- };
496
- }
592
+ outcome = await deps.poll(session_id);
593
+ } catch {
497
594
  return {
498
595
  lines: [
596
+ "",
499
597
  fail({
500
- code: 1,
501
- what: "couldn't attach a shipping method to your cart.",
502
- fix: "this region may not have shipping configured yet \u2014 try again shortly, or re-stage."
598
+ code: 75,
599
+ what: "lost the connection while waiting for your order to complete.",
600
+ money: "if you completed payment, your order is safe \u2014 we just couldn't confirm it here. check your email, or run bt order.",
601
+ fix: "bt checkout --resume (or open the link below manually)",
602
+ link: checkout_url
503
603
  })
504
- ]
604
+ ],
605
+ save
505
606
  };
506
607
  }
507
- const email = (config.email ?? "").trim();
508
- const { checkout_url, session_id } = await client.mintCheckout(cartId, email || void 0, "cli");
509
- const save = email ? { email } : {};
510
- const opened = await deps.open(checkout_url);
511
- print(" enter your details on the page \u2014 address + card. cards never touch the terminal.");
512
- const outcome = await deps.poll(session_id);
513
- const lines = [...opened.lines];
608
+ const lines = [];
514
609
  if (outcome.status === "placed") {
515
610
  const displayId = await resolveDisplayId(client, outcome.order_id, email);
516
611
  lines.push("");
@@ -577,9 +672,7 @@ function maskEmail(email) {
577
672
  }
578
673
  function registerCheckout(program, makeClient2, loadConfig2) {
579
674
  const action = async (opts) => {
580
- const gOpts = program.opts();
581
- const json = Boolean(gOpts.json);
582
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
675
+ const { json, brand } = resolveOutput(program);
583
676
  const { makePrompts: makePrompts2 } = await import("./prompts-GTC5VYM6.js");
584
677
  const { openCheckout } = await import("./browser-EO5EQUXQ.js");
585
678
  const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
@@ -666,9 +759,7 @@ async function runOrder(client, config, args) {
666
759
  }
667
760
  function registerOrder(program, makeClient2, loadConfig2) {
668
761
  program.command("order").argument("<id>", "the order number (display id)").description("track one order: staged \u2192 committed \u2192 printed \u2192 shipped \u2192 delivered.").action(async (id) => {
669
- const gOpts = program.opts();
670
- const json = Boolean(gOpts.json);
671
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
762
+ const { json, brand } = resolveOutput(program);
672
763
  const res = await runOrder(makeClient2(), loadConfig2(), { displayId: id, json, brand });
673
764
  printResult(res, json);
674
765
  });
@@ -704,7 +795,7 @@ function resolveBridgeToken(env, keychainToken) {
704
795
  }
705
796
  function registerToken(program) {
706
797
  program.command("token").description("print your keychain JWT for an MCP config (env BT_TOKEN). a bearer credential \u2014 handle with care.").action(() => {
707
- const json = Boolean(program.opts().json);
798
+ const { json } = resolveOutput(program);
708
799
  const token = resolveBridgeToken(process.env, getToken());
709
800
  if (!token) {
710
801
  process.stderr.write(empty("not logged in \u2014 run bt login first.") + "\n");
@@ -797,15 +888,14 @@ function renderFlat(orders) {
797
888
  // src/commands/log.ts
798
889
  var EMPTY_STATE2 = "no commits yet. your wardrobe is a fresh repo.";
799
890
  async function runLog(client, config, args) {
800
- const history = client;
801
891
  const loggedIn = args.loggedIn ?? Boolean(config.email);
802
- if (!loggedIn || typeof history.listOrders !== "function") {
892
+ if (!loggedIn) {
803
893
  return {
804
894
  lines: [empty(EMPTY_STATE2), " start one \u2192 bt browse"],
805
895
  json: args.json ? { commits: [] } : void 0
806
896
  };
807
897
  }
808
- const orders = await history.listOrders();
898
+ const orders = await client.listOrders();
809
899
  if (!orders || orders.length === 0) {
810
900
  return {
811
901
  lines: [empty(EMPTY_STATE2), " start one \u2192 bt browse"],
@@ -821,9 +911,7 @@ async function runLog(client, config, args) {
821
911
  }
822
912
  function registerLog(program, makeClient2, loadConfig2) {
823
913
  program.command("log").option("--oneline", "compact one-line-per-order format").option("--graph", "graph format").description("your order history as a git log.").action(async (opts) => {
824
- const gOpts = program.opts();
825
- const json = Boolean(gOpts.json);
826
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
914
+ const { json, brand } = resolveOutput(program);
827
915
  const loggedIn = Boolean(resolveBridgeToken(process.env, getToken()));
828
916
  const res = await runLog(makeClient2(), loadConfig2(), { oneline: opts.oneline, graph: opts.graph, json, loggedIn, brand });
829
917
  printResult(res, json);
@@ -882,9 +970,7 @@ async function runStatus(client, config, args) {
882
970
  }
883
971
  function registerStatus(program, makeClient2, loadConfig2) {
884
972
  program.command("status").description("working-tree state: what's staged, what's in flight.").action(async () => {
885
- const gOpts = program.opts();
886
- const json = Boolean(gOpts.json);
887
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
973
+ const { json, brand } = resolveOutput(program);
888
974
  const res = await runStatus(makeClient2(), loadConfig2(), { json, brand });
889
975
  printResult(res, json);
890
976
  });
@@ -899,15 +985,6 @@ async function runUnstage(client, config, args) {
899
985
  json: args.json ? { cart_id: null, items: [] } : void 0
900
986
  };
901
987
  }
902
- const remover = client;
903
- if (typeof remover.removeLineItem !== "function") {
904
- return {
905
- lines: [
906
- warn("unstage isn't wired to this client build yet."),
907
- " for now: bt forget clears your local cart, or re-stage to overwrite."
908
- ]
909
- };
910
- }
911
988
  try {
912
989
  const before = await client.getCart(config.cart_id);
913
990
  const beforeItems = before.items ?? [];
@@ -927,7 +1004,7 @@ async function runUnstage(client, config, args) {
927
1004
  json: args.json ? { cart_id: before.id, removed: null, items: beforeItems } : void 0
928
1005
  };
929
1006
  }
930
- await remover.removeLineItem(config.cart_id, target.id);
1007
+ await client.removeLineItem(config.cart_id, target.id);
931
1008
  const after = await client.getCart(config.cart_id);
932
1009
  const afterItems = after.items ?? [];
933
1010
  const stillThere = afterItems.some((li) => li.id === target.id);
@@ -973,7 +1050,7 @@ async function runUnstage(client, config, args) {
973
1050
  }
974
1051
  function registerUnstage(program, makeClient2, loadConfig2) {
975
1052
  program.command("unstage").alias("reset").argument("<ref>", "the staged item: its # (from bt cart), its line-item id, or its name").description("remove a staged item from the cart (git reset).").action(async (ref) => {
976
- const json = Boolean(program.opts().json);
1053
+ const { json } = resolveOutput(program);
977
1054
  const res = await runUnstage(makeClient2(), loadConfig2(), { itemId: ref, json });
978
1055
  printResult(res, json);
979
1056
  });
@@ -1078,9 +1155,7 @@ async function runWhoami(client, config, args, token) {
1078
1155
  }
1079
1156
  function registerWhoami(program, makeClient2, loadConfig2) {
1080
1157
  program.command("whoami").description("your contribution profile + standing.").action(async () => {
1081
- const gOpts = program.opts();
1082
- const json = Boolean(gOpts.json);
1083
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
1158
+ const { json, brand } = resolveOutput(program);
1084
1159
  const res = await runWhoami(makeClient2(), loadConfig2(), { json, brand }, getToken());
1085
1160
  printResult(res, json);
1086
1161
  });
@@ -1132,7 +1207,7 @@ async function runDrops(client, _config, args) {
1132
1207
  }
1133
1208
  function registerDrops(program, makeClient2, loadConfig2) {
1134
1209
  program.command("drops").alias("next").description("what's on next before it hits the web.").action(async () => {
1135
- const json = Boolean(program.opts().json);
1210
+ const { json } = resolveOutput(program);
1136
1211
  const res = await runDrops(makeClient2(), loadConfig2(), { json });
1137
1212
  printResult(res, json);
1138
1213
  });
@@ -1157,7 +1232,7 @@ async function runConnect(args) {
1157
1232
  }
1158
1233
  function registerConnect(program) {
1159
1234
  program.command("connect").description("the contributor on-ramp \u2014 what connecting unlocks. run bt login to connect.").action(async () => {
1160
- const json = Boolean(program.opts().json);
1235
+ const { json } = resolveOutput(program);
1161
1236
  const res = await runConnect({ json });
1162
1237
  printResult(res, json);
1163
1238
  });
@@ -1222,7 +1297,7 @@ async function runLogin(client, prompts, keychain, config, args) {
1222
1297
  }
1223
1298
  function registerLogin(program, makeClient2, loadConfig2, prompts, keychain, persist) {
1224
1299
  program.command("login").description("connect your wardrobe with a passwordless magic-link (no password, ever).").option("--email <email>", "your email (skips the prompt)").action(async (opts) => {
1225
- const json = Boolean(program.opts().json);
1300
+ const { json } = resolveOutput(program);
1226
1301
  const envToken = process.env.BT_MAGIC_TOKEN?.trim() || void 0;
1227
1302
  const res = await runLogin(makeClient2(), prompts, keychain, loadConfig2(), {
1228
1303
  email: opts.email,
@@ -1310,7 +1385,7 @@ async function runDrop(args, exec) {
1310
1385
  }
1311
1386
  function registerDrop(program, exec) {
1312
1387
  program.command("drop", { hidden: true }).argument("<sub>", "create | verify | promote | retire").option("--motif <slug>", "motif slug (create)").option("--drop-id <id>", "the drop id").option("--stage <stage>", "expected stage for verify (next|main)").description("operator: manage a drop's lifecycle (create/verify/promote/retire).").action(async (sub, opts) => {
1313
- const json = Boolean(program.opts().json);
1388
+ const { json } = resolveOutput(program);
1314
1389
  const operator = process.env.BT_OPERATOR === "1";
1315
1390
  process.stdout.write(working(`drop ${sub}\u2026`) + "\n");
1316
1391
  const res = await runDrop(
@@ -1351,10 +1426,7 @@ function runInit(args) {
1351
1426
  }
1352
1427
  function registerInit(program) {
1353
1428
  program.command("init").description("first-run welcome + the 3-command happy path.").action(async () => {
1354
- const gOpts = program.opts();
1355
- const json = Boolean(gOpts.json);
1356
- const { makeBrand: makeBrand2 } = await import("./brand-TMOYK6QE.js");
1357
- const brand = json ? makeBrand2({ noColor: true }) : makeBrand2({ noColor: gOpts.color === false });
1429
+ const { json, brand } = resolveOutput(program);
1358
1430
  printResult(runInit({ json, brand }), json);
1359
1431
  });
1360
1432
  }
@@ -1386,9 +1458,7 @@ async function runBuy(client, config, args, deps) {
1386
1458
  }
1387
1459
  function registerBuy(program, makeClient2, loadConfig2) {
1388
1460
  program.command("buy").argument("<handle>", "the product handle/slug").option("--size <size>", "garment size").option("--colour <colour>", "colour, e.g. white / black").description("stage + checkout in one \u2014 reserve, then open browser checkout. cards never touch the terminal.").action(async (handle, opts) => {
1389
- const gOpts = program.opts();
1390
- const json = Boolean(gOpts.json);
1391
- const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
1461
+ const { json, brand } = resolveOutput(program);
1392
1462
  const { makePrompts: makePrompts2 } = await import("./prompts-GTC5VYM6.js");
1393
1463
  const { openCheckout } = await import("./browser-EO5EQUXQ.js");
1394
1464
  const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
@@ -1446,7 +1516,7 @@ function runConfig(args, deps) {
1446
1516
  }
1447
1517
  function registerConfig(program) {
1448
1518
  program.command("config").argument("[sub]", "get | set", "get").argument("[key]", "config key").argument("[value]", "value (for set)").description("view or set local prefs (email, region, defaults). the auth token is never stored here.").action(async (sub, key, value) => {
1449
- const json = Boolean(program.opts().json);
1519
+ const { json } = resolveOutput(program);
1450
1520
  const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await import("./config-FXGCWC6Y.js");
1451
1521
  const res = runConfig({ sub, key, value, json }, { load: loadConfig2, save: saveConfig2 });
1452
1522
  printResult(res, json);
@@ -1480,7 +1550,7 @@ function gatherDoctorSnapshot(opts = {}) {
1480
1550
  } catch {
1481
1551
  }
1482
1552
  return {
1483
- btVersion: "0.1.0",
1553
+ btVersion: VERSION,
1484
1554
  node: process.versions.node,
1485
1555
  platform: `${process.platform}/${process.arch}`,
1486
1556
  storeUrl: baseUrl,
@@ -1558,7 +1628,6 @@ function registerReport(program, makeClient2) {
1558
1628
  }
1559
1629
 
1560
1630
  // src/bin.ts
1561
- var VERSION = "0.1.0";
1562
1631
  function makeClient() {
1563
1632
  const { baseUrl, publishableKey } = resolveClientConfig();
1564
1633
  const jwt = resolveBridgeToken(process.env, getToken()) ?? void 0;