@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 +16 -15
- package/dist/bin.js +61 -33
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-4TEWFVRJ.js → chunk-7FH7BZ3I.js} +19 -3
- package/dist/chunk-7FH7BZ3I.js.map +1 -0
- package/dist/{interactive-HNU7Y3KG.js → interactive-DZ67P6UX.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-4TEWFVRJ.js.map +0 -1
- /package/dist/{interactive-HNU7Y3KG.js.map → interactive-DZ67P6UX.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
# @binaryandthread/cli
|
|
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
|
|
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
|
|
11
|
+
Zero-config, it ships pointed at the live store (the publishable key is public by design):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
15
|
-
npm i -g
|
|
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
|
|
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
|
|
23
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
75
|
-
| `77` | `auth` | not logged in / forbidden
|
|
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-
|
|
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.
|
|
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-
|
|
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
|
|
562
|
-
} catch
|
|
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:
|
|
575
|
-
what: "
|
|
576
|
-
|
|
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
|
|
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
|
-
|
|
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);
|