@binaryandthread/cli 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,8 +33,8 @@ unambiguous form.)
33
33
  | --- | --- |
34
34
  | `bt browse` | the catalog as a numbered table (`bt stage <n>`); `--pick` opts into the ↑/↓ picker; `--json` for scripts |
35
35
  | `bt search <q>` | filter the catalog |
36
- | `bt view <n\|slug>` | one piece, specs + mockup (`--colour black` renders the black row) |
37
- | `bt stage <n\|slug>` | add to your cart (`--size`, `--colour`) |
36
+ | `bt view <n\|slug>` | one piece, specs + mockup (`--colour black` renders the black row; `--fit oversize` shows the other fit of a two-fit design) |
37
+ | `bt stage <n\|slug>` | add to your cart (`--size`, `--colour`, `--fit regular\|oversize`; a design sold in both fits needs `--fit`, or `bt config set defaultFit …`) |
38
38
  | `bt blame <n\|slug>` | the design's lore + sources (git blame, for motifs) |
39
39
  | `bt cart` / `bt unstage <ref>` | show / remove a line |
40
40
  | `bt checkout` | mint a checkout, hand off to the browser to pay, then poll |
@@ -70,8 +70,8 @@ skips the interactive confirm so it never hangs in CI. Field shapes (stable, saf
70
70
 
71
71
  | command | `--json` shape |
72
72
  | --- | --- |
73
- | `bt browse` / `bt search` | `[{ id, handle, title, price, currency, colours[], channel_stage }]` |
74
- | `bt view <handle>` | `{ id, handle, title, price, currency, colours[], sizes[] }` |
73
+ | `bt browse` / `bt search` | `[{ id, handle, title, price, price_from, currency, colours[], fits[], channel_stage }]` (`price` is the cheapest fit when `price_from`) |
74
+ | `bt view <handle>` | `{ id, handle, title, fits[], fit, colours[], unavailable_colours[], specs, variants: [{ id, fit, colour, size, price, currency }] }` |
75
75
  | `bt cart` | `{ cart_id, items: [{ id, title, colour, qty, total }], total, total_excludes_shipping }` |
76
76
  | `bt checkout` | `{ status: "placed"\|"timeout"\|"expired", order_id?, display_id?, total?, currency?, checkout_url? }` |
77
77
  | `bt region` | `{ region, currency, matched, home }` · `--list` → `{ regions: [{ currency, countries[] }] }` |
package/dist/bin.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  forgetConfig,
6
6
  loadConfig,
7
7
  saveConfig
8
- } from "./chunk-ZNDKXHMV.js";
8
+ } from "./chunk-T7MFWTB7.js";
9
9
  import {
10
10
  makePrompts
11
11
  } from "./chunk-AAN4VWP3.js";
@@ -27,7 +27,6 @@ import {
27
27
  empty,
28
28
  fail,
29
29
  failResult,
30
- firstPrice,
31
30
  fmtPrice,
32
31
  formatCartLine,
33
32
  formatFatal,
@@ -36,8 +35,10 @@ import {
36
35
  isCartTaxInclusive,
37
36
  lineColour,
38
37
  ok,
38
+ priceLine,
39
39
  printResult,
40
40
  productColours,
41
+ productFits,
41
42
  productLore,
42
43
  redact,
43
44
  regionLine,
@@ -53,7 +54,7 @@ import {
53
54
  versionLine,
54
55
  warn,
55
56
  working
56
- } from "./chunk-VDOFLQ2P.js";
57
+ } from "./chunk-X3RWS3AG.js";
57
58
  import {
58
59
  __require
59
60
  } from "./chunk-7OCVIDC7.js";
@@ -102,7 +103,7 @@ function makeLogger(debug, write = (s) => process.stderr.write(s)) {
102
103
  }
103
104
 
104
105
  // src/version.ts
105
- var VERSION = "0.3.1";
106
+ var VERSION = "0.4.0";
106
107
 
107
108
  // src/eggs.ts
108
109
  function teapot() {
@@ -418,15 +419,18 @@ function truncate(s, w) {
418
419
  }
419
420
  function layoutRows(rows, style) {
420
421
  const cells = rows.map(({ p, stage }, i) => {
421
- const { amount, currency } = firstPrice(p);
422
+ const { amount, currency, from } = priceLine(p);
422
423
  const { title, handle } = displayName(p);
424
+ const fits = productFits(p);
425
+ const fitsMarker = fits.length > 1 ? fits.join(" \xB7 ") : "";
426
+ const colours = colourMarker(productColours(p));
423
427
  return {
424
428
  ref: `[${i + 1}]`,
425
429
  title,
426
- price: fmtPrice(amount, currency),
430
+ price: `${from ? "from " : ""}${fmtPrice(amount, currency)}`,
427
431
  handle,
428
432
  stage,
429
- colours: colourMarker(productColours(p))
433
+ colours: [colours, fitsMarker].filter(Boolean).join(" ")
430
434
  };
431
435
  });
432
436
  const refW = Math.max(...cells.map((c) => c.ref.length));
@@ -491,11 +495,14 @@ async function runBrowse(client, config, args) {
491
495
  if (drops.length > 0) {
492
496
  lines.push(" on next, before the web \u2192 bt drops");
493
497
  }
494
- const taxIncl = rows.length > 0 ? firstPrice(rows[0].p).taxInclusive : null;
498
+ const taxIncl = rows.length > 0 ? priceLine(rows[0].p).taxInclusive : null;
495
499
  if (taxIncl === true) {
496
500
  lines.push(dim(style, " prices incl. VAT"));
497
501
  }
498
502
  lines.push(" view one \u2192 bt view <n|slug> \xB7 stage one \u2192 bt stage <n|slug> --size L");
503
+ if (rows.some(({ p }) => productFits(p).length > 1)) {
504
+ lines.push(dim(style, " two fits? add --fit regular|oversize (or bt config set defaultFit \u2026)"));
505
+ }
499
506
  }
500
507
  return {
501
508
  lines,
@@ -503,9 +510,11 @@ async function runBrowse(client, config, args) {
503
510
  id: p.id,
504
511
  handle: p.handle,
505
512
  title: p.title,
506
- price: firstPrice(p).amount,
507
- currency: firstPrice(p).currency,
513
+ price: priceLine(p).amount,
514
+ price_from: priceLine(p).from,
515
+ currency: priceLine(p).currency,
508
516
  colours: productColours(p),
517
+ fits: productFits(p),
509
518
  channel_stage: p.metadata?.["channel_stage"] ?? "main"
510
519
  })) : void 0
511
520
  };
@@ -514,7 +523,7 @@ function registerBrowse(program, makeClient2, loadConfig2) {
514
523
  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("--pick", "opt into the arrow-key picker (default is the numbered table \u2014 use bt stage <n>)").option("--plain", "force the static table (the default; kept for back-compat)").action(async (opts) => {
515
524
  const { json, brand } = resolveOutput(program);
516
525
  if (opts.pick && !json && brand.color && process.stdout.isTTY && process.stdin.isTTY) {
517
- const { runInteractiveBrowse } = await import("./interactive-JALKLT56.js");
526
+ const { runInteractiveBrowse } = await import("./interactive-VBATIHRD.js");
518
527
  const handled = await runInteractiveBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, brand });
519
528
  if (handled) return;
520
529
  }
@@ -677,13 +686,14 @@ async function runSearch(client, config, args) {
677
686
  } else {
678
687
  lines.push(header(style, `bt search "${args.query}"`));
679
688
  for (const p of filtered) {
680
- const { amount, currency } = firstPrice(p);
689
+ const { amount, currency, from } = priceLine(p);
681
690
  const { title, handle } = displayName(p);
682
691
  const stage = p.metadata?.["channel_stage"] ?? "main";
683
- const colours = colourMarker(productColours(p));
692
+ const fits = productFits(p);
693
+ const colours = [colourMarker(productColours(p)), fits.length > 1 ? fits.join(" \xB7 ") : ""].filter(Boolean).join(" ");
684
694
  const row = [
685
695
  title.padEnd(22),
686
- cyan(style, fmtPrice(amount, currency).padEnd(11)),
696
+ cyan(style, `${from ? "from " : ""}${fmtPrice(amount, currency)}`.padEnd(11)),
687
697
  dim(style, handle.padEnd(34)),
688
698
  stageMarker(stage)
689
699
  ].join(" ").trimEnd();
@@ -698,9 +708,11 @@ async function runSearch(client, config, args) {
698
708
  id: p.id,
699
709
  handle: p.handle,
700
710
  title: p.title,
701
- price: firstPrice(p).amount,
702
- currency: firstPrice(p).currency,
703
- colours: productColours(p)
711
+ price: priceLine(p).amount,
712
+ price_from: priceLine(p).from,
713
+ currency: priceLine(p).currency,
714
+ colours: productColours(p),
715
+ fits: productFits(p)
704
716
  })) : void 0
705
717
  };
706
718
  }
@@ -927,10 +939,16 @@ async function runCheckout(client, config, args, deps) {
927
939
  lines.push(ok("deploy successful."));
928
940
  lines.push(` committed to wardrobe@main`);
929
941
  lines.push(` order #${displayId}`);
930
- if (mint.discount && mint.discount.amount > 0) {
942
+ const settled = outcome.summary;
943
+ const finalTotal = settled?.total ?? mint.total ?? shippedCart?.total ?? cart.total;
944
+ const finalCurrency = settled?.currency_code ?? currency;
945
+ if (settled?.discount_total) {
946
+ const codes = settled.discount_codes.length ? ` (${settled.discount_codes.join(", ")})` : "";
947
+ lines.push(dim(style, ` discount${codes} \u2212${fmtPrice(settled.discount_total, finalCurrency)}`));
948
+ } else if (mint.discount && mint.discount.amount > 0) {
931
949
  lines.push(dim(style, ` dev channel \u2212${fmtPrice(mint.discount.amount, currency)} (${mint.discount.pct}% \u2014 the terminal's cut for you)`));
932
950
  }
933
- lines.push(` total ${cyan(style, totalLine)}${taxIncl ? " (incl. VAT)" : ""}`);
951
+ lines.push(` total ${cyan(style, fmtPrice(finalTotal, finalCurrency))}${taxIncl ? " (incl. VAT)" : ""}`);
934
952
  if (shipsTo) {
935
953
  lines.push(` ships to ${shipsTo} \xB7 ~3\u20136 working days`);
936
954
  } else {
@@ -947,11 +965,13 @@ async function runCheckout(client, config, args, deps) {
947
965
  status: "placed",
948
966
  order_id: outcome.order_id,
949
967
  display_id: displayId,
950
- // A2 — the SHIPPING-INCLUSIVE, dev-channel-discounted total the buyer consented to (same
951
- // figure as the review), so a --json / bt buy consumer has the real amount, not just a status.
952
- total: mint.total ?? shippedCart?.total ?? cart.total ?? null,
953
- currency: mint.currency ?? cart.currency_code ?? region.currency_code ?? null,
954
- discount: mint.discount ?? null
968
+ // A2/ENG-39 — the REAL settled total (every discount applied, incl. at payment); fall back
969
+ // to the mint estimate for an older backend. A --json / bt buy consumer gets the true amount.
970
+ total: settled?.total ?? mint.total ?? shippedCart?.total ?? cart.total ?? null,
971
+ currency: settled?.currency_code ?? mint.currency ?? cart.currency_code ?? region.currency_code ?? null,
972
+ discount: mint.discount ?? null,
973
+ // The full settled summary (subtotal, discount_total, promo codes, line items) when available.
974
+ summary: settled ?? null
955
975
  } : void 0
956
976
  };
957
977
  }
@@ -1008,8 +1028,8 @@ function registerCheckout(program, makeClient2, loadConfig2) {
1008
1028
  const { json, brand } = resolveOutput(program);
1009
1029
  const { makePrompts: makePrompts2 } = await import("./prompts-DDVV5RE6.js");
1010
1030
  const { openCheckout } = await import("./browser-WCNC4AWS.js");
1011
- const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
1012
- const { saveConfig: saveConfig2 } = await import("./config-GVEB4R7I.js");
1031
+ const { pollUntilPlaced } = await import("./poll-II3TVJLK.js");
1032
+ const { saveConfig: saveConfig2 } = await import("./config-L4X7G563.js");
1013
1033
  const client = makeClient2();
1014
1034
  const print = json ? () => {
1015
1035
  } : (line) => process.stdout.write(line + "\n");
@@ -1716,7 +1736,7 @@ function registerDrop(program, exec) {
1716
1736
  }
1717
1737
 
1718
1738
  // src/commands/init.ts
1719
- var HAPPY_PATH = ["bt browse", "bt stage <handle> --size L", "bt checkout"];
1739
+ var HAPPY_PATH = ["bt browse", "bt stage <handle> --size L # add --fit regular|oversize for a two-fit design", "bt checkout"];
1720
1740
  var DOCS_URL = "https://binaryandthread.com/docs";
1721
1741
  function runInit(args) {
1722
1742
  const style = brandOf(args);
@@ -1727,7 +1747,7 @@ function runInit(args) {
1727
1747
  "",
1728
1748
  " the happy path:",
1729
1749
  " bt browse # the catalog, numbered",
1730
- " bt stage <n|slug> --size L # add to your cart (a browse number, or a slug)",
1750
+ " bt stage <n|slug> --size L # add to your cart (a browse number, or a slug; --fit for a two-fit design)",
1731
1751
  " bt checkout # pay in your browser \u2014 cards never touch the terminal (10% applied)",
1732
1752
  "",
1733
1753
  " ships worldwide \u2014 set your country so prices show your currency:",
@@ -1756,6 +1776,7 @@ async function runBuy(client, config, args, deps) {
1756
1776
  handle: args.handle,
1757
1777
  size: args.size,
1758
1778
  colour: args.colour,
1779
+ fit: args.fit,
1759
1780
  json: args.json,
1760
1781
  brand: args.brand
1761
1782
  });
@@ -1779,16 +1800,16 @@ async function runBuy(client, config, args, deps) {
1779
1800
  };
1780
1801
  }
1781
1802
  function registerBuy(program, makeClient2, loadConfig2) {
1782
- program.command("buy").argument("<handle>", "the design: a slug, a bt browse number (3), or a fuzzy hint (termnl)").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) => {
1803
+ program.command("buy").argument("<handle>", "the design: a slug, a bt browse number (3), or a fuzzy hint (termnl)").option("--size <size>", "garment size").option("--colour <colour>", "colour, e.g. white / black").option("--fit <fit>", "regular or oversize (required when a design comes in both)").description("stage + checkout in one \u2014 reserve, then open browser checkout. cards never touch the terminal.").action(async (handle, opts) => {
1783
1804
  const { json, brand } = resolveOutput(program);
1784
1805
  const { makePrompts: makePrompts2 } = await import("./prompts-DDVV5RE6.js");
1785
1806
  const { openCheckout } = await import("./browser-WCNC4AWS.js");
1786
- const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
1787
- const { saveConfig: saveConfig2 } = await import("./config-GVEB4R7I.js");
1807
+ const { pollUntilPlaced } = await import("./poll-II3TVJLK.js");
1808
+ const { saveConfig: saveConfig2 } = await import("./config-L4X7G563.js");
1788
1809
  const client = makeClient2();
1789
1810
  const print = json ? () => {
1790
1811
  } : (line) => process.stdout.write(line + "\n");
1791
- const res = await runBuy(client, loadConfig2(), { handle, size: opts.size, colour: opts.colour, json, brand }, {
1812
+ const res = await runBuy(client, loadConfig2(), { handle, size: opts.size, colour: opts.colour, fit: opts.fit, json, brand }, {
1792
1813
  prompts: makePrompts2(),
1793
1814
  open: (url) => openCheckout(url),
1794
1815
  // Same heartbeat as bt checkout — bt buy's browser-hop wait is the identical 20-min poll.
@@ -1809,7 +1830,7 @@ function registerBuy(program, makeClient2, loadConfig2) {
1809
1830
  }
1810
1831
 
1811
1832
  // src/commands/config.ts
1812
- var SETTABLE = ["email", "region", "defaultSize", "defaultColour", "defaultBranch"];
1833
+ var SETTABLE = ["email", "region", "defaultSize", "defaultColour", "defaultFit", "defaultBranch"];
1813
1834
  function runConfig(args, deps) {
1814
1835
  const cfg = deps.load();
1815
1836
  const sub = (args.sub ?? "get").toLowerCase();
@@ -1844,7 +1865,7 @@ function runConfig(args, deps) {
1844
1865
  function registerConfig(program) {
1845
1866
  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) => {
1846
1867
  const { json } = resolveOutput(program);
1847
- const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await import("./config-GVEB4R7I.js");
1868
+ const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await import("./config-L4X7G563.js");
1848
1869
  const res = runConfig({ sub, key, value, json }, { load: loadConfig2, save: saveConfig2 });
1849
1870
  printResult(res, json);
1850
1871
  });