@binaryandthread/cli 0.1.1 → 0.1.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/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-7FH7BZ3I.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.3";
77
77
 
78
78
  // src/eggs.ts
79
79
  function teapot() {
@@ -224,6 +224,20 @@ function formatFatal(e, json) {
224
224
  return { out: null, err: `\u2717 ${info.message}`, code: info.code };
225
225
  }
226
226
 
227
+ // src/is-main.ts
228
+ import { realpathSync } from "fs";
229
+ import { fileURLToPath } from "url";
230
+ function isMainModule(argv1, moduleUrl, realpath = realpathSync) {
231
+ if (typeof argv1 !== "string" || argv1.length === 0) return false;
232
+ try {
233
+ const invoked = realpath(argv1);
234
+ const self = realpath(fileURLToPath(moduleUrl));
235
+ return invoked === self;
236
+ } catch {
237
+ return false;
238
+ }
239
+ }
240
+
227
241
  // src/keychain.ts
228
242
  import * as fs from "fs";
229
243
  import * as os from "os";
@@ -366,7 +380,7 @@ function registerBrowse(program, makeClient2, loadConfig2) {
366
380
  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
381
  const { json, brand } = resolveOutput(program);
368
382
  if (!json && !opts.plain && brand.color && process.stdout.isTTY) {
369
- const { runInteractiveBrowse } = await import("./interactive-HNU7Y3KG.js");
383
+ const { runInteractiveBrowse } = await import("./interactive-DZ67P6UX.js");
370
384
  const handled = await runInteractiveBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, brand });
371
385
  if (handled) return;
372
386
  }
@@ -529,8 +543,30 @@ async function runCheckout(client, config, args, deps) {
529
543
  if (!prompts) {
530
544
  throw new Error("checkout requires a prompter");
531
545
  }
546
+ let shippedCart;
547
+ try {
548
+ shippedCart = await client.ensureCartShippingMethod(cartId);
549
+ } catch (e) {
550
+ if (e instanceof RecoverableCartError) {
551
+ return {
552
+ lines: [
553
+ warn("that cart is no longer valid (expired or already committed)."),
554
+ " re-stage \u2192 bt browse then bt stage <slug>"
555
+ ]
556
+ };
557
+ }
558
+ return {
559
+ lines: [
560
+ fail({
561
+ code: 1,
562
+ what: "couldn't attach a shipping method to your cart.",
563
+ fix: "this region may not have shipping configured yet \u2014 try again shortly, or re-stage."
564
+ })
565
+ ]
566
+ };
567
+ }
532
568
  const itemCount = cart.items.reduce((n, li) => n + (li.quantity ?? 1), 0);
533
- const totalLine = fmtPrice(cart.total, cart.currency_code ?? region.currency_code);
569
+ const totalLine = fmtPrice(shippedCart?.total ?? cart.total, cart.currency_code ?? region.currency_code);
534
570
  const reviewLines = [header(style, "bt checkout \u2014 staged for commit")];
535
571
  for (const li of cart.items) {
536
572
  const colour = lineColour(li);
@@ -557,34 +593,33 @@ async function runCheckout(client, config, args, deps) {
557
593
  }
558
594
  const allowed = supportedCountries(region);
559
595
  const shipsTo = (allowed[0] ?? "").toUpperCase();
596
+ const email = (config.email ?? "").trim();
597
+ const { checkout_url, session_id } = await client.mintCheckout(cartId, email || void 0, "cli");
598
+ const save = email ? { email } : {};
599
+ const opened = await deps.open(checkout_url);
600
+ for (const line of opened.lines) {
601
+ print(line);
602
+ }
603
+ print(" enter your details on the page \u2014 address + card. cards never touch the terminal.");
604
+ let outcome;
560
605
  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
- }
606
+ outcome = await deps.poll(session_id);
607
+ } catch {
571
608
  return {
572
609
  lines: [
610
+ "",
573
611
  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."
612
+ code: 75,
613
+ what: "lost the connection while waiting for your order to complete.",
614
+ money: "if you completed payment, your order is safe \u2014 we just couldn't confirm it here. check your email, or run bt order.",
615
+ fix: "bt checkout --resume (or open the link below manually)",
616
+ link: checkout_url
577
617
  })
578
- ]
618
+ ],
619
+ save
579
620
  };
580
621
  }
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];
622
+ const lines = [];
588
623
  if (outcome.status === "placed") {
589
624
  const displayId = await resolveDisplayId(client, outcome.order_id, email);
590
625
  lines.push("");
@@ -1703,14 +1738,7 @@ function registerEggs(program) {
1703
1738
  });
1704
1739
  program.command("man", { hidden: true }).description("the bt man page (also installed for the system man(1)).").action(() => print(manPage(VERSION).join("\n")));
1705
1740
  }
1706
- var isMain = (() => {
1707
- try {
1708
- return typeof process.argv[1] === "string" && (process.argv[1].endsWith("bin.js") || process.argv[1].endsWith("bin.ts"));
1709
- } catch {
1710
- return false;
1711
- }
1712
- })();
1713
- if (isMain) {
1741
+ if (isMainModule(process.argv[1], import.meta.url)) {
1714
1742
  buildProgram().parseAsync(process.argv).catch((e) => {
1715
1743
  const json = process.argv.includes("--json");
1716
1744
  const { out, err, code } = formatFatal(e, json);