@binaryandthread/cli 0.1.1 → 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
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.
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 piece — 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
@@ -44,7 +44,7 @@ import {
44
44
  versionLine,
45
45
  warn,
46
46
  working
47
- } from "./chunk-4TEWFVRJ.js";
47
+ } from "./chunk-WYCZ6BPV.js";
48
48
  import {
49
49
  __require
50
50
  } from "./chunk-7OCVIDC7.js";
@@ -73,7 +73,7 @@ function makeLogger(debug, write = (s) => process.stderr.write(s)) {
73
73
  }
74
74
 
75
75
  // src/version.ts
76
- var VERSION = "0.1.1";
76
+ var VERSION = "0.1.2";
77
77
 
78
78
  // src/eggs.ts
79
79
  function teapot() {
@@ -366,7 +366,7 @@ function registerBrowse(program, makeClient2, loadConfig2) {
366
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
367
  const { json, brand } = resolveOutput(program);
368
368
  if (!json && !opts.plain && brand.color && process.stdout.isTTY) {
369
- const { runInteractiveBrowse } = await import("./interactive-HNU7Y3KG.js");
369
+ const { runInteractiveBrowse } = await import("./interactive-DYY27KBC.js");
370
370
  const handled = await runInteractiveBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, brand });
371
371
  if (handled) return;
372
372
  }
@@ -529,8 +529,30 @@ async function runCheckout(client, config, args, deps) {
529
529
  if (!prompts) {
530
530
  throw new Error("checkout requires a prompter");
531
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
+ }
532
554
  const itemCount = cart.items.reduce((n, li) => n + (li.quantity ?? 1), 0);
533
- 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);
534
556
  const reviewLines = [header(style, "bt checkout \u2014 staged for commit")];
535
557
  for (const li of cart.items) {
536
558
  const colour = lineColour(li);
@@ -557,34 +579,33 @@ async function runCheckout(client, config, args, deps) {
557
579
  }
558
580
  const allowed = supportedCountries(region);
559
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;
560
591
  try {
561
- await client.ensureCartShippingMethod(cartId);
562
- } catch (e) {
563
- if (e instanceof RecoverableCartError) {
564
- return {
565
- lines: [
566
- warn("that cart is no longer valid (expired or already committed)."),
567
- " re-stage \u2192 bt browse then bt stage <slug>"
568
- ]
569
- };
570
- }
592
+ outcome = await deps.poll(session_id);
593
+ } catch {
571
594
  return {
572
595
  lines: [
596
+ "",
573
597
  fail({
574
- code: 1,
575
- what: "couldn't attach a shipping method to your cart.",
576
- 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
577
603
  })
578
- ]
604
+ ],
605
+ save
579
606
  };
580
607
  }
581
- const email = (config.email ?? "").trim();
582
- const { checkout_url, session_id } = await client.mintCheckout(cartId, email || void 0, "cli");
583
- const save = email ? { email } : {};
584
- const opened = await deps.open(checkout_url);
585
- print(" enter your details on the page \u2014 address + card. cards never touch the terminal.");
586
- const outcome = await deps.poll(session_id);
587
- const lines = [...opened.lines];
608
+ const lines = [];
588
609
  if (outcome.status === "placed") {
589
610
  const displayId = await resolveDisplayId(client, outcome.order_id, email);
590
611
  lines.push("");