@binaryandthread/cli 0.1.3 → 0.1.4
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/dist/bin.js +95 -77
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-IM3HF7NN.js → chunk-AAN4VWP3.js} +2 -2
- package/dist/{chunk-IM3HF7NN.js.map → chunk-AAN4VWP3.js.map} +1 -1
- package/dist/{chunk-7FH7BZ3I.js → chunk-V7YRJ67J.js} +201 -34
- package/dist/chunk-V7YRJ67J.js.map +1 -0
- package/dist/{interactive-DZ67P6UX.js → interactive-GY4AK7D2.js} +2 -2
- package/dist/{prompts-GTC5VYM6.js → prompts-DDVV5RE6.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-7FH7BZ3I.js.map +0 -1
- /package/dist/{interactive-DZ67P6UX.js.map → interactive-GY4AK7D2.js.map} +0 -0
- /package/dist/{prompts-GTC5VYM6.js.map → prompts-DDVV5RE6.js.map} +0 -0
|
@@ -17,7 +17,7 @@ function makePrompts() {
|
|
|
17
17
|
async function confirm(label) {
|
|
18
18
|
const rl = readline.createInterface({ input, output });
|
|
19
19
|
try {
|
|
20
|
-
const answer = (await rl.question(
|
|
20
|
+
const answer = (await rl.question(` ${label} `)).trim().toLowerCase();
|
|
21
21
|
return answer === "y" || answer === "yes";
|
|
22
22
|
} finally {
|
|
23
23
|
rl.close();
|
|
@@ -29,4 +29,4 @@ function makePrompts() {
|
|
|
29
29
|
export {
|
|
30
30
|
makePrompts
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-AAN4VWP3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["// Terminal prompts — the real readline-backed prompter for the interactive flows.\n//\n// Kept tiny + dependency-free (node:readline/promises). The command handlers take a prompts\n// object INJECTED (CheckoutPrompts), so this concrete implementation is only wired by bin.ts;\n// the flows are unit-tested with a fake prompter. NOTE: this prompter NEVER asks for a card —\n// the only PII it collects is an email + shipping address (cards live on the storefront page).\n\nimport * as readline from \"node:readline/promises\"\nimport { stdin as input, stdout as output } from \"node:process\"\n\nexport interface Prompts {\n ask(label: string, opts?: { default?: string }): Promise<string>\n confirm(label: string): Promise<boolean>\n}\n\n/** Build a readline-backed prompter. Each call opens + closes its own interface (simple, safe). */\nexport function makePrompts(): Prompts {\n async function ask(label: string, opts?: { default?: string }): Promise<string> {\n const rl = readline.createInterface({ input, output })\n try {\n const suffix = opts?.default ? ` [${opts.default}]` : \"\"\n const answer = (await rl.question(` ${label}${suffix}: `)).trim()\n return answer || (opts?.default ?? \"\")\n } finally {\n rl.close()\n }\n }\n\n async function confirm(label: string): Promise<boolean> {\n const rl = readline.createInterface({ input, output })\n try {\n const answer = (await rl.question(
|
|
1
|
+
{"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["// Terminal prompts — the real readline-backed prompter for the interactive flows.\n//\n// Kept tiny + dependency-free (node:readline/promises). The command handlers take a prompts\n// object INJECTED (CheckoutPrompts), so this concrete implementation is only wired by bin.ts;\n// the flows are unit-tested with a fake prompter. NOTE: this prompter NEVER asks for a card —\n// the only PII it collects is an email + shipping address (cards live on the storefront page).\n\nimport * as readline from \"node:readline/promises\"\nimport { stdin as input, stdout as output } from \"node:process\"\n\nexport interface Prompts {\n ask(label: string, opts?: { default?: string }): Promise<string>\n confirm(label: string): Promise<boolean>\n}\n\n/** Build a readline-backed prompter. Each call opens + closes its own interface (simple, safe). */\nexport function makePrompts(): Prompts {\n async function ask(label: string, opts?: { default?: string }): Promise<string> {\n const rl = readline.createInterface({ input, output })\n try {\n const suffix = opts?.default ? ` [${opts.default}]` : \"\"\n const answer = (await rl.question(` ${label}${suffix}: `)).trim()\n return answer || (opts?.default ?? \"\")\n } finally {\n rl.close()\n }\n }\n\n async function confirm(label: string): Promise<boolean> {\n const rl = readline.createInterface({ input, output })\n try {\n // A neutral question marker — NOT ⚠. The confirms we ask (proceed to checkout, send a report)\n // are benign: nothing is charged, nothing is destroyed. Reserve ⚠ for real warnings so the\n // voice never cries wolf; a caller that truly needs a warning puts ⚠ in its own label.\n const answer = (await rl.question(` ${label} `)).trim().toLowerCase()\n return answer === \"y\" || answer === \"yes\"\n } finally {\n rl.close()\n }\n }\n\n return { ask, confirm }\n}\n"],"mappings":";;;AAOA,YAAY,cAAc;AAC1B,SAAS,SAAS,OAAO,UAAU,cAAc;AAQ1C,SAAS,cAAuB;AACrC,iBAAe,IAAI,OAAe,MAA8C;AAC9E,UAAM,KAAc,yBAAgB,EAAE,OAAO,OAAO,CAAC;AACrD,QAAI;AACF,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK,OAAO,MAAM;AACtD,YAAM,UAAU,MAAM,GAAG,SAAS,MAAM,KAAK,GAAG,MAAM,IAAI,GAAG,KAAK;AAClE,aAAO,WAAW,MAAM,WAAW;AAAA,IACrC,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AAEA,iBAAe,QAAQ,OAAiC;AACtD,UAAM,KAAc,yBAAgB,EAAE,OAAO,OAAO,CAAC;AACrD,QAAI;AAIF,YAAM,UAAU,MAAM,GAAG,SAAS,MAAM,KAAK,GAAG,GAAG,KAAK,EAAE,YAAY;AACtE,aAAO,WAAW,OAAO,WAAW;AAAA,IACtC,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,QAAQ;AACxB;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// ../store-client/dist/index.js
|
|
4
4
|
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
5
5
|
"authorization",
|
|
6
6
|
"token",
|
|
@@ -44,15 +44,21 @@ function redact(input, _seen) {
|
|
|
44
44
|
if (typeof input !== "object") {
|
|
45
45
|
return input;
|
|
46
46
|
}
|
|
47
|
-
const seen = _seen ?? /* @__PURE__ */ new
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const seen = _seen ?? /* @__PURE__ */ new Map();
|
|
48
|
+
const existing = seen.get(input);
|
|
49
|
+
if (existing !== void 0) {
|
|
50
|
+
return existing;
|
|
50
51
|
}
|
|
51
|
-
seen.add(input);
|
|
52
52
|
if (Array.isArray(input)) {
|
|
53
|
-
|
|
53
|
+
const out2 = [];
|
|
54
|
+
seen.set(input, out2);
|
|
55
|
+
for (const v of input) {
|
|
56
|
+
out2.push(redact(v, seen));
|
|
57
|
+
}
|
|
58
|
+
return out2;
|
|
54
59
|
}
|
|
55
60
|
const out = {};
|
|
61
|
+
seen.set(input, out);
|
|
56
62
|
for (const [key, value] of Object.entries(input)) {
|
|
57
63
|
if (SENSITIVE_KEYS.has(key.toLowerCase())) {
|
|
58
64
|
out[key] = MASK;
|
|
@@ -70,6 +76,7 @@ var DEFAULT_RETRY = {
|
|
|
70
76
|
maxDelayMs: 2e3
|
|
71
77
|
};
|
|
72
78
|
var RETRY_AFTER_MAX_MS = 1e4;
|
|
79
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
73
80
|
function parseRetryAfterMs(res) {
|
|
74
81
|
let raw = null;
|
|
75
82
|
try {
|
|
@@ -134,6 +141,7 @@ var StoreClient = class {
|
|
|
134
141
|
logger;
|
|
135
142
|
retry;
|
|
136
143
|
sleepImpl;
|
|
144
|
+
timeoutMs;
|
|
137
145
|
/** Cached resolved regions (read-only catalog optimisation). */
|
|
138
146
|
regionsCache = null;
|
|
139
147
|
constructor(opts) {
|
|
@@ -153,6 +161,7 @@ var StoreClient = class {
|
|
|
153
161
|
this.logger = opts.logger ?? console;
|
|
154
162
|
this.retry = { ...DEFAULT_RETRY, ...opts.retry ?? {} };
|
|
155
163
|
this.sleepImpl = opts.sleepImpl ?? sleep;
|
|
164
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
156
165
|
}
|
|
157
166
|
// ----- the request engine --------------------------------------------------
|
|
158
167
|
buildUrl(path, query) {
|
|
@@ -195,8 +204,10 @@ var StoreClient = class {
|
|
|
195
204
|
let lastErr;
|
|
196
205
|
for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
|
|
197
206
|
let retryAfterMs = null;
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
198
209
|
try {
|
|
199
|
-
const res = await this.fetchImpl(url, init);
|
|
210
|
+
const res = await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
200
211
|
if (res.ok) {
|
|
201
212
|
const text = await res.text();
|
|
202
213
|
return text ? JSON.parse(text) : {};
|
|
@@ -244,6 +255,8 @@ var StoreClient = class {
|
|
|
244
255
|
}
|
|
245
256
|
lastErr = e;
|
|
246
257
|
this.logWarn("store.request.network_retry", { path, attempt: attempt + 1 });
|
|
258
|
+
} finally {
|
|
259
|
+
clearTimeout(timer);
|
|
247
260
|
}
|
|
248
261
|
if (attempt < this.retry.attempts - 1) {
|
|
249
262
|
await this.sleepImpl(retryAfterMs ?? this.backoffMs(attempt));
|
|
@@ -294,10 +307,20 @@ var StoreClient = class {
|
|
|
294
307
|
// ----- products (region resolved BEFORE pricing) ---------------------------
|
|
295
308
|
async listProducts(opts) {
|
|
296
309
|
const region = opts?.region ?? await this.resolveRegion(opts?.countryCode);
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
310
|
+
const PAGE = 100;
|
|
311
|
+
const HARD_CAP = 1e3;
|
|
312
|
+
const all = [];
|
|
313
|
+
for (let offset = 0; offset < HARD_CAP; offset += PAGE) {
|
|
314
|
+
const data = await this.request("GET", "/store/products", {
|
|
315
|
+
query: { region_id: region.id, limit: PAGE, offset }
|
|
316
|
+
});
|
|
317
|
+
const page = data.products ?? [];
|
|
318
|
+
all.push(...page);
|
|
319
|
+
if (page.length < PAGE) {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return all;
|
|
301
324
|
}
|
|
302
325
|
async getProduct(handle, opts) {
|
|
303
326
|
const region = opts?.region ?? await this.resolveRegion(opts?.countryCode);
|
|
@@ -388,7 +411,9 @@ var StoreClient = class {
|
|
|
388
411
|
return res.cart;
|
|
389
412
|
}
|
|
390
413
|
// ----- headless contract (04.5-01) -----------------------------------------
|
|
391
|
-
|
|
414
|
+
// `channel` is REQUIRED (L11): a default silently mis-attributes analytics for a future caller that
|
|
415
|
+
// forgets it. Both current callers (cli, mcp) pass it explicitly.
|
|
416
|
+
async mintCheckout(cartId, email, channel) {
|
|
392
417
|
return this.request("POST", "/store/headless/checkout", {
|
|
393
418
|
idempotent: true,
|
|
394
419
|
body: { cart_id: cartId, email, channel }
|
|
@@ -461,13 +486,15 @@ var StoreClient = class {
|
|
|
461
486
|
}
|
|
462
487
|
}
|
|
463
488
|
/**
|
|
464
|
-
* Guest order lookup —
|
|
465
|
-
*
|
|
489
|
+
* Guest order lookup (AUD-8) — POST /store/order-lookup with the order id (display id OR internal
|
|
490
|
+
* id) + the EXACT email in the BODY (never the query string, so the email is not access-logged).
|
|
491
|
+
* The backend returns 404 on any mismatch (no enumeration) → the client surfaces null. Rate-limited
|
|
492
|
+
* server-side; returns minimal status only, never PII (F-H1 / F-H3).
|
|
466
493
|
*/
|
|
467
|
-
async getOrder(
|
|
494
|
+
async getOrder(idOrDisplayId, email) {
|
|
468
495
|
try {
|
|
469
|
-
return await this.request("
|
|
470
|
-
|
|
496
|
+
return await this.request("POST", "/store/order-lookup", {
|
|
497
|
+
body: { id: String(idOrDisplayId), email }
|
|
471
498
|
});
|
|
472
499
|
} catch (e) {
|
|
473
500
|
if (e instanceof StoreApiError && e.status === 404) {
|
|
@@ -567,7 +594,7 @@ function findVariant(p, opts = {}) {
|
|
|
567
594
|
return variants.find((v) => colourMatches(v));
|
|
568
595
|
}
|
|
569
596
|
if (size && !colour) {
|
|
570
|
-
return variants.find((v) => sizeMatches(v))
|
|
597
|
+
return variants.find((v) => sizeMatches(v));
|
|
571
598
|
}
|
|
572
599
|
return variants[0];
|
|
573
600
|
}
|
|
@@ -646,15 +673,67 @@ var CODENAMES = [
|
|
|
646
673
|
"tabs-over-spaces",
|
|
647
674
|
"vim-not-emacs",
|
|
648
675
|
"2-space-indent",
|
|
649
|
-
"trailing-comma"
|
|
676
|
+
"trailing-comma",
|
|
677
|
+
"rebase-not-merge",
|
|
678
|
+
"lf-not-crlf",
|
|
679
|
+
"semicolons-optional",
|
|
680
|
+
"dark-mode-only"
|
|
650
681
|
];
|
|
651
|
-
function codename(
|
|
652
|
-
|
|
682
|
+
function codename(version) {
|
|
683
|
+
const minor = Number.parseInt(version.split(".")[1] ?? "", 10);
|
|
684
|
+
const idx = Number.isFinite(minor) ? Math.abs(minor) % CODENAMES.length : 0;
|
|
685
|
+
return CODENAMES[idx];
|
|
653
686
|
}
|
|
654
687
|
function versionLine(version) {
|
|
655
688
|
return `bt ${version} "${codename(version)}"`;
|
|
656
689
|
}
|
|
657
690
|
|
|
691
|
+
// src/errors.ts
|
|
692
|
+
var EXIT = {
|
|
693
|
+
OK: 0,
|
|
694
|
+
GENERIC: 1,
|
|
695
|
+
DATAERR: 65,
|
|
696
|
+
// EX_DATAERR — bad request / unprocessable / stale cart (400/409/422)
|
|
697
|
+
UNAVAILABLE: 69,
|
|
698
|
+
// EX_UNAVAILABLE — not found (404)
|
|
699
|
+
TEMPFAIL: 75,
|
|
700
|
+
// EX_TEMPFAIL — temporary: 5xx, network/exhausted, rate-limited (429)
|
|
701
|
+
NOPERM: 77
|
|
702
|
+
// EX_NOPERM — auth required / forbidden (401/403)
|
|
703
|
+
};
|
|
704
|
+
function messageOf(e) {
|
|
705
|
+
if (e instanceof Error && e.message) {
|
|
706
|
+
return e.message;
|
|
707
|
+
}
|
|
708
|
+
const s = String(e ?? "").trim();
|
|
709
|
+
return s || "unexpected error";
|
|
710
|
+
}
|
|
711
|
+
function classifyError(e) {
|
|
712
|
+
const message = String(redact(messageOf(e)));
|
|
713
|
+
if (e instanceof RecoverableCartError) {
|
|
714
|
+
return { code: EXIT.DATAERR, type: "stale_cart", status: e.status, message };
|
|
715
|
+
}
|
|
716
|
+
if (e instanceof StoreApiError) {
|
|
717
|
+
const s = e.status;
|
|
718
|
+
if (s === 401 || s === 403) return { code: EXIT.NOPERM, type: "auth", status: s, message };
|
|
719
|
+
if (s === 404) return { code: EXIT.UNAVAILABLE, type: "not_found", status: s, message };
|
|
720
|
+
if (s === 429) return { code: EXIT.TEMPFAIL, type: "rate_limited", status: s, message };
|
|
721
|
+
if (s === 0 || s === 408 || s >= 500) return { code: EXIT.TEMPFAIL, type: "upstream", status: s, message };
|
|
722
|
+
if (s >= 400) return { code: EXIT.DATAERR, type: "client_error", status: s, message };
|
|
723
|
+
}
|
|
724
|
+
return { code: EXIT.GENERIC, type: "error", message };
|
|
725
|
+
}
|
|
726
|
+
function formatFatal(e, json) {
|
|
727
|
+
const info = classifyError(e);
|
|
728
|
+
if (json) {
|
|
729
|
+
const envelope = { error: { code: info.code, type: info.type, message: info.message } };
|
|
730
|
+
return { out: JSON.stringify(envelope, null, 2), err: null, code: info.code };
|
|
731
|
+
}
|
|
732
|
+
const reassurance = "nothing was lost. if a checkout was in flight, your order is safe. file it \u2192 bt report";
|
|
733
|
+
return { out: null, err: `\u2717 ${info.message}
|
|
734
|
+
${reassurance}`, code: info.code };
|
|
735
|
+
}
|
|
736
|
+
|
|
658
737
|
// src/brand.ts
|
|
659
738
|
var ANSI = {
|
|
660
739
|
reset: "\x1B[0m",
|
|
@@ -828,6 +907,53 @@ function resolveLineItemRef(items, ref) {
|
|
|
828
907
|
);
|
|
829
908
|
return byTitle ?? null;
|
|
830
909
|
}
|
|
910
|
+
function isSubsequence(needle, hay) {
|
|
911
|
+
let i = 0;
|
|
912
|
+
for (let j = 0; j < hay.length && i < needle.length; j++) {
|
|
913
|
+
if (hay[j] === needle[i]) i++;
|
|
914
|
+
}
|
|
915
|
+
return i === needle.length;
|
|
916
|
+
}
|
|
917
|
+
function resolveProductSelector(products, selector) {
|
|
918
|
+
const needle = (selector ?? "").trim();
|
|
919
|
+
if (!needle || products.length === 0) {
|
|
920
|
+
return { candidates: [], fuzzy: false };
|
|
921
|
+
}
|
|
922
|
+
if (/^\d+$/.test(needle)) {
|
|
923
|
+
const idx = Number(needle) - 1;
|
|
924
|
+
if (idx >= 0 && idx < products.length) {
|
|
925
|
+
return { match: products[idx], candidates: [], fuzzy: true };
|
|
926
|
+
}
|
|
927
|
+
return { candidates: [], fuzzy: true };
|
|
928
|
+
}
|
|
929
|
+
const lower = needle.toLowerCase();
|
|
930
|
+
const exact = products.find((p) => (p.handle ?? "").toLowerCase() === lower || p.id === needle);
|
|
931
|
+
if (exact) {
|
|
932
|
+
return { match: exact, candidates: [], fuzzy: false };
|
|
933
|
+
}
|
|
934
|
+
const hits = products.filter(
|
|
935
|
+
(p) => isSubsequence(lower, (p.handle ?? "").toLowerCase()) || isSubsequence(lower, (p.title ?? "").toLowerCase())
|
|
936
|
+
);
|
|
937
|
+
if (hits.length === 1) {
|
|
938
|
+
return { match: hits[0], candidates: [], fuzzy: true };
|
|
939
|
+
}
|
|
940
|
+
return { candidates: hits, fuzzy: true };
|
|
941
|
+
}
|
|
942
|
+
async function resolveProduct(client, region, selector) {
|
|
943
|
+
const needle = (selector ?? "").trim();
|
|
944
|
+
if (needle && !/^\d+$/.test(needle)) {
|
|
945
|
+
const exact = await client.getProduct(needle, { region });
|
|
946
|
+
if (exact) {
|
|
947
|
+
return { product: exact };
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const products = await client.listProducts({ region });
|
|
951
|
+
const r = resolveProductSelector(products, needle);
|
|
952
|
+
if (r.match) {
|
|
953
|
+
return { product: r.match, fuzzy: r.fuzzy };
|
|
954
|
+
}
|
|
955
|
+
return { candidates: r.candidates, fuzzy: r.fuzzy };
|
|
956
|
+
}
|
|
831
957
|
function resolveOutput(program) {
|
|
832
958
|
const opts = program.opts();
|
|
833
959
|
const json = Boolean(opts.json);
|
|
@@ -835,6 +961,9 @@ function resolveOutput(program) {
|
|
|
835
961
|
return { json, brand };
|
|
836
962
|
}
|
|
837
963
|
function printResult(res, json) {
|
|
964
|
+
if (res.exitCode) {
|
|
965
|
+
process.exitCode = res.exitCode;
|
|
966
|
+
}
|
|
838
967
|
if (json && res.json !== void 0) {
|
|
839
968
|
process.stdout.write(JSON.stringify(res.json, null, 2) + "\n");
|
|
840
969
|
return;
|
|
@@ -912,21 +1041,36 @@ async function defaultRender(url, opts) {
|
|
|
912
1041
|
async function runView(client, config, args) {
|
|
913
1042
|
const style = brandOf(args);
|
|
914
1043
|
const region = await client.resolveRegion(config.region);
|
|
915
|
-
const
|
|
1044
|
+
const picked = await resolveProduct(client, region, args.handle);
|
|
1045
|
+
const product = picked.product;
|
|
916
1046
|
if (!product) {
|
|
1047
|
+
if (picked.candidates && picked.candidates.length > 1) {
|
|
1048
|
+
return {
|
|
1049
|
+
lines: [
|
|
1050
|
+
fail({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${picked.candidates.length} designs \u2014 pick one:` }),
|
|
1051
|
+
...picked.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1052
|
+
" or be exact \u2192 bt view <slug>"
|
|
1053
|
+
],
|
|
1054
|
+
exitCode: EXIT.DATAERR
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
917
1057
|
return {
|
|
918
1058
|
lines: [
|
|
919
1059
|
fail({
|
|
920
|
-
code:
|
|
921
|
-
what: `
|
|
1060
|
+
code: EXIT.UNAVAILABLE,
|
|
1061
|
+
what: `no design here called "${args.handle}".`,
|
|
922
1062
|
fix: "bt browse (to see what's in stock)"
|
|
923
1063
|
})
|
|
924
|
-
]
|
|
1064
|
+
],
|
|
1065
|
+
exitCode: EXIT.UNAVAILABLE
|
|
925
1066
|
};
|
|
926
1067
|
}
|
|
927
1068
|
const { title, handle } = displayName(product);
|
|
928
1069
|
const colours = productColours(product);
|
|
929
1070
|
const lines = [];
|
|
1071
|
+
if (picked.fuzzy && handle !== args.handle.trim()) {
|
|
1072
|
+
lines.push(working(`matched "${handle}" from "${args.handle}"`));
|
|
1073
|
+
}
|
|
930
1074
|
lines.push(header(style, title));
|
|
931
1075
|
lines.push(` ${dim(style, handle)}`);
|
|
932
1076
|
if (product.description) {
|
|
@@ -993,7 +1137,7 @@ async function runView(client, config, args) {
|
|
|
993
1137
|
};
|
|
994
1138
|
}
|
|
995
1139
|
function registerView(program, makeClient, loadConfig) {
|
|
996
|
-
program.command("view").alias("show").argument("<handle>", "the
|
|
1140
|
+
program.command("view").alias("show").argument("<handle>", "the design: a slug, a bt browse number (3), or a fuzzy hint (termnl)").option("--colour <colour>", "render a specific colour's mockup (e.g. black)").description("render a design: specs + image (always with the link as the floor).").action(async (handle, opts) => {
|
|
997
1141
|
const { json, brand } = resolveOutput(program);
|
|
998
1142
|
const noColor = program.opts().color === false;
|
|
999
1143
|
const res = await runView(makeClient(), loadConfig(), {
|
|
@@ -1011,14 +1155,29 @@ function registerView(program, makeClient, loadConfig) {
|
|
|
1011
1155
|
async function runStage(client, config, args) {
|
|
1012
1156
|
const style = brandOf(args);
|
|
1013
1157
|
const region = await client.resolveRegion(config.region);
|
|
1014
|
-
const
|
|
1015
|
-
if (!product) {
|
|
1158
|
+
const resolved = await resolveProduct(client, region, args.handle);
|
|
1159
|
+
if (!resolved.product) {
|
|
1160
|
+
if (resolved.candidates && resolved.candidates.length > 1) {
|
|
1161
|
+
return {
|
|
1162
|
+
lines: [
|
|
1163
|
+
fail({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${resolved.candidates.length} designs \u2014 pick one:` }),
|
|
1164
|
+
...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1165
|
+
" or be exact \u2192 bt stage <slug>"
|
|
1166
|
+
],
|
|
1167
|
+
exitCode: EXIT.DATAERR
|
|
1168
|
+
// ambiguous input → non-zero so `bt stage x && bt checkout` halts (d10)
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1016
1171
|
return {
|
|
1017
1172
|
lines: [
|
|
1018
|
-
fail({ code:
|
|
1019
|
-
]
|
|
1173
|
+
fail({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" })
|
|
1174
|
+
],
|
|
1175
|
+
exitCode: EXIT.UNAVAILABLE
|
|
1176
|
+
// not-found → non-zero (d10)
|
|
1020
1177
|
};
|
|
1021
1178
|
}
|
|
1179
|
+
const product = resolved.product;
|
|
1180
|
+
const matchNote = resolved.fuzzy && (product.handle ?? "") !== args.handle.trim() ? working(`matched "${product.handle}" from "${args.handle}"`) : null;
|
|
1022
1181
|
const size = args.size ?? config.defaultSize;
|
|
1023
1182
|
const colours = productColours(product);
|
|
1024
1183
|
const colour = args.colour ?? config.defaultColour ?? (colours.length > 0 ? colours[0] : void 0);
|
|
@@ -1028,17 +1187,20 @@ async function runStage(client, config, args) {
|
|
|
1028
1187
|
return {
|
|
1029
1188
|
lines: [
|
|
1030
1189
|
fail({
|
|
1031
|
-
code:
|
|
1190
|
+
code: EXIT.DATAERR,
|
|
1032
1191
|
what: `no variant for ${args.handle}${size ? `#size=${size}` : ""}${colour ? ` (${colour})` : ""}.`,
|
|
1033
1192
|
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1034
1193
|
})
|
|
1035
|
-
]
|
|
1194
|
+
],
|
|
1195
|
+
exitCode: EXIT.DATAERR
|
|
1196
|
+
// unprocessable size+colour combo → non-zero (d10)
|
|
1036
1197
|
};
|
|
1037
1198
|
}
|
|
1038
1199
|
if (args.dryRun) {
|
|
1039
1200
|
const colourSuffix2 = colour ? ` (${colour})` : "";
|
|
1040
1201
|
return {
|
|
1041
1202
|
lines: [
|
|
1203
|
+
...matchNote ? [matchNote] : [],
|
|
1042
1204
|
ok(`dry-run \u2014 would stage ${product.handle ?? args.handle}${size ? `#${size}` : ""}${colourSuffix2} (nothing staged).`),
|
|
1043
1205
|
" run without --dry-run to acquire the inventory lock."
|
|
1044
1206
|
],
|
|
@@ -1054,6 +1216,9 @@ async function runStage(client, config, args) {
|
|
|
1054
1216
|
};
|
|
1055
1217
|
}
|
|
1056
1218
|
const lines = [];
|
|
1219
|
+
if (matchNote) {
|
|
1220
|
+
lines.push(matchNote);
|
|
1221
|
+
}
|
|
1057
1222
|
let cartId = config.cart_id;
|
|
1058
1223
|
let recovered = false;
|
|
1059
1224
|
if (!cartId) {
|
|
@@ -1099,7 +1264,7 @@ async function runStage(client, config, args) {
|
|
|
1099
1264
|
};
|
|
1100
1265
|
}
|
|
1101
1266
|
function registerStage(program, makeClient, loadConfig) {
|
|
1102
|
-
program.command("stage").alias("add").argument("<handle>", "the
|
|
1267
|
+
program.command("stage").alias("add").argument("<handle>", "the design: a slug (terminal-velocity), a bt browse number (3), or a fuzzy hint (termnl)").option("--size <size>", "garment size (default: your last commit's size)").option("--colour <colour>", "colour, e.g. white / black (default: the product's first colour)").option("--dry-run", "resolve the variant + show what would be staged, without staging").description("reserve stock \u2014 acquires an inventory lock, reserves nothing permanent.").action(async (handle, opts) => {
|
|
1103
1268
|
const { json, brand } = resolveOutput(program);
|
|
1104
1269
|
const res = await runStage(makeClient(), loadConfig(), {
|
|
1105
1270
|
handle,
|
|
@@ -1134,6 +1299,8 @@ export {
|
|
|
1134
1299
|
empty,
|
|
1135
1300
|
fail,
|
|
1136
1301
|
versionLine,
|
|
1302
|
+
EXIT,
|
|
1303
|
+
formatFatal,
|
|
1137
1304
|
cyan,
|
|
1138
1305
|
dim,
|
|
1139
1306
|
green,
|
|
@@ -1158,4 +1325,4 @@ export {
|
|
|
1158
1325
|
runStage,
|
|
1159
1326
|
registerStage
|
|
1160
1327
|
};
|
|
1161
|
-
//# sourceMappingURL=chunk-
|
|
1328
|
+
//# sourceMappingURL=chunk-V7YRJ67J.js.map
|