@binaryandthread/cli 0.1.3 → 0.1.5
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 +135 -78
- 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-VRNJLVHX.js} +230 -36
- package/dist/chunk-VRNJLVHX.js.map +1 -0
- package/dist/{interactive-DZ67P6UX.js → interactive-WFI5K77G.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-WFI5K77G.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
|
}
|
|
@@ -604,6 +631,29 @@ function firstImageUrl(images) {
|
|
|
604
631
|
function escapeRegex(s) {
|
|
605
632
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
606
633
|
}
|
|
634
|
+
function str(v, fallback) {
|
|
635
|
+
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
636
|
+
}
|
|
637
|
+
function productSpecs(p) {
|
|
638
|
+
const md = p.metadata ?? {};
|
|
639
|
+
return [
|
|
640
|
+
{ key: "material", value: str(md.material, "100% cotton") },
|
|
641
|
+
{ key: "weight", value: str(md.weight, "180 g/m\xB2") },
|
|
642
|
+
{ key: "fit", value: str(md.fit, "Unisex heavyweight crewneck") },
|
|
643
|
+
{ key: "print", value: str(md.print, "Front print") },
|
|
644
|
+
{ key: "delivery", value: str(md.delivery, "Ships in 2\u20135 business days + transit") },
|
|
645
|
+
{ key: "care", value: str(md.care, "Cold wash, line dry") }
|
|
646
|
+
];
|
|
647
|
+
}
|
|
648
|
+
function productLore(p) {
|
|
649
|
+
const md = p.metadata ?? {};
|
|
650
|
+
const text = typeof md.lore === "string" ? md.lore : "";
|
|
651
|
+
const raw = Array.isArray(md.lore_sources) ? md.lore_sources : [];
|
|
652
|
+
const sources = raw.filter(
|
|
653
|
+
(s) => !!s && typeof s === "object" && typeof s.url === "string" && typeof s.label === "string"
|
|
654
|
+
).map((s) => ({ url: s.url, label: s.label }));
|
|
655
|
+
return { text, sources };
|
|
656
|
+
}
|
|
607
657
|
|
|
608
658
|
// src/voice.ts
|
|
609
659
|
var GLYPH = {
|
|
@@ -646,15 +696,67 @@ var CODENAMES = [
|
|
|
646
696
|
"tabs-over-spaces",
|
|
647
697
|
"vim-not-emacs",
|
|
648
698
|
"2-space-indent",
|
|
649
|
-
"trailing-comma"
|
|
699
|
+
"trailing-comma",
|
|
700
|
+
"rebase-not-merge",
|
|
701
|
+
"lf-not-crlf",
|
|
702
|
+
"semicolons-optional",
|
|
703
|
+
"dark-mode-only"
|
|
650
704
|
];
|
|
651
|
-
function codename(
|
|
652
|
-
|
|
705
|
+
function codename(version) {
|
|
706
|
+
const minor = Number.parseInt(version.split(".")[1] ?? "", 10);
|
|
707
|
+
const idx = Number.isFinite(minor) ? Math.abs(minor) % CODENAMES.length : 0;
|
|
708
|
+
return CODENAMES[idx];
|
|
653
709
|
}
|
|
654
710
|
function versionLine(version) {
|
|
655
711
|
return `bt ${version} "${codename(version)}"`;
|
|
656
712
|
}
|
|
657
713
|
|
|
714
|
+
// src/errors.ts
|
|
715
|
+
var EXIT = {
|
|
716
|
+
OK: 0,
|
|
717
|
+
GENERIC: 1,
|
|
718
|
+
DATAERR: 65,
|
|
719
|
+
// EX_DATAERR — bad request / unprocessable / stale cart (400/409/422)
|
|
720
|
+
UNAVAILABLE: 69,
|
|
721
|
+
// EX_UNAVAILABLE — not found (404)
|
|
722
|
+
TEMPFAIL: 75,
|
|
723
|
+
// EX_TEMPFAIL — temporary: 5xx, network/exhausted, rate-limited (429)
|
|
724
|
+
NOPERM: 77
|
|
725
|
+
// EX_NOPERM — auth required / forbidden (401/403)
|
|
726
|
+
};
|
|
727
|
+
function messageOf(e) {
|
|
728
|
+
if (e instanceof Error && e.message) {
|
|
729
|
+
return e.message;
|
|
730
|
+
}
|
|
731
|
+
const s = String(e ?? "").trim();
|
|
732
|
+
return s || "unexpected error";
|
|
733
|
+
}
|
|
734
|
+
function classifyError(e) {
|
|
735
|
+
const message = String(redact(messageOf(e)));
|
|
736
|
+
if (e instanceof RecoverableCartError) {
|
|
737
|
+
return { code: EXIT.DATAERR, type: "stale_cart", status: e.status, message };
|
|
738
|
+
}
|
|
739
|
+
if (e instanceof StoreApiError) {
|
|
740
|
+
const s = e.status;
|
|
741
|
+
if (s === 401 || s === 403) return { code: EXIT.NOPERM, type: "auth", status: s, message };
|
|
742
|
+
if (s === 404) return { code: EXIT.UNAVAILABLE, type: "not_found", status: s, message };
|
|
743
|
+
if (s === 429) return { code: EXIT.TEMPFAIL, type: "rate_limited", status: s, message };
|
|
744
|
+
if (s === 0 || s === 408 || s >= 500) return { code: EXIT.TEMPFAIL, type: "upstream", status: s, message };
|
|
745
|
+
if (s >= 400) return { code: EXIT.DATAERR, type: "client_error", status: s, message };
|
|
746
|
+
}
|
|
747
|
+
return { code: EXIT.GENERIC, type: "error", message };
|
|
748
|
+
}
|
|
749
|
+
function formatFatal(e, json) {
|
|
750
|
+
const info = classifyError(e);
|
|
751
|
+
if (json) {
|
|
752
|
+
const envelope = { error: { code: info.code, type: info.type, message: info.message } };
|
|
753
|
+
return { out: JSON.stringify(envelope, null, 2), err: null, code: info.code };
|
|
754
|
+
}
|
|
755
|
+
const reassurance = "nothing was lost. if a checkout was in flight, your order is safe. file it \u2192 bt report";
|
|
756
|
+
return { out: null, err: `\u2717 ${info.message}
|
|
757
|
+
${reassurance}`, code: info.code };
|
|
758
|
+
}
|
|
759
|
+
|
|
658
760
|
// src/brand.ts
|
|
659
761
|
var ANSI = {
|
|
660
762
|
reset: "\x1B[0m",
|
|
@@ -828,6 +930,53 @@ function resolveLineItemRef(items, ref) {
|
|
|
828
930
|
);
|
|
829
931
|
return byTitle ?? null;
|
|
830
932
|
}
|
|
933
|
+
function isSubsequence(needle, hay) {
|
|
934
|
+
let i = 0;
|
|
935
|
+
for (let j = 0; j < hay.length && i < needle.length; j++) {
|
|
936
|
+
if (hay[j] === needle[i]) i++;
|
|
937
|
+
}
|
|
938
|
+
return i === needle.length;
|
|
939
|
+
}
|
|
940
|
+
function resolveProductSelector(products, selector) {
|
|
941
|
+
const needle = (selector ?? "").trim();
|
|
942
|
+
if (!needle || products.length === 0) {
|
|
943
|
+
return { candidates: [], fuzzy: false };
|
|
944
|
+
}
|
|
945
|
+
if (/^\d+$/.test(needle)) {
|
|
946
|
+
const idx = Number(needle) - 1;
|
|
947
|
+
if (idx >= 0 && idx < products.length) {
|
|
948
|
+
return { match: products[idx], candidates: [], fuzzy: true };
|
|
949
|
+
}
|
|
950
|
+
return { candidates: [], fuzzy: true };
|
|
951
|
+
}
|
|
952
|
+
const lower = needle.toLowerCase();
|
|
953
|
+
const exact = products.find((p) => (p.handle ?? "").toLowerCase() === lower || p.id === needle);
|
|
954
|
+
if (exact) {
|
|
955
|
+
return { match: exact, candidates: [], fuzzy: false };
|
|
956
|
+
}
|
|
957
|
+
const hits = products.filter(
|
|
958
|
+
(p) => isSubsequence(lower, (p.handle ?? "").toLowerCase()) || isSubsequence(lower, (p.title ?? "").toLowerCase())
|
|
959
|
+
);
|
|
960
|
+
if (hits.length === 1) {
|
|
961
|
+
return { match: hits[0], candidates: [], fuzzy: true };
|
|
962
|
+
}
|
|
963
|
+
return { candidates: hits, fuzzy: true };
|
|
964
|
+
}
|
|
965
|
+
async function resolveProduct(client, region, selector) {
|
|
966
|
+
const needle = (selector ?? "").trim();
|
|
967
|
+
if (needle && !/^\d+$/.test(needle)) {
|
|
968
|
+
const exact = await client.getProduct(needle, { region });
|
|
969
|
+
if (exact) {
|
|
970
|
+
return { product: exact };
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
const products = await client.listProducts({ region });
|
|
974
|
+
const r = resolveProductSelector(products, needle);
|
|
975
|
+
if (r.match) {
|
|
976
|
+
return { product: r.match, fuzzy: r.fuzzy };
|
|
977
|
+
}
|
|
978
|
+
return { candidates: r.candidates, fuzzy: r.fuzzy };
|
|
979
|
+
}
|
|
831
980
|
function resolveOutput(program) {
|
|
832
981
|
const opts = program.opts();
|
|
833
982
|
const json = Boolean(opts.json);
|
|
@@ -835,6 +984,9 @@ function resolveOutput(program) {
|
|
|
835
984
|
return { json, brand };
|
|
836
985
|
}
|
|
837
986
|
function printResult(res, json) {
|
|
987
|
+
if (res.exitCode) {
|
|
988
|
+
process.exitCode = res.exitCode;
|
|
989
|
+
}
|
|
838
990
|
if (json && res.json !== void 0) {
|
|
839
991
|
process.stdout.write(JSON.stringify(res.json, null, 2) + "\n");
|
|
840
992
|
return;
|
|
@@ -912,29 +1064,45 @@ async function defaultRender(url, opts) {
|
|
|
912
1064
|
async function runView(client, config, args) {
|
|
913
1065
|
const style = brandOf(args);
|
|
914
1066
|
const region = await client.resolveRegion(config.region);
|
|
915
|
-
const
|
|
1067
|
+
const picked = await resolveProduct(client, region, args.handle);
|
|
1068
|
+
const product = picked.product;
|
|
916
1069
|
if (!product) {
|
|
1070
|
+
if (picked.candidates && picked.candidates.length > 1) {
|
|
1071
|
+
return {
|
|
1072
|
+
lines: [
|
|
1073
|
+
fail({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${picked.candidates.length} designs \u2014 pick one:` }),
|
|
1074
|
+
...picked.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1075
|
+
" or be exact \u2192 bt view <slug>"
|
|
1076
|
+
],
|
|
1077
|
+
exitCode: EXIT.DATAERR
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
917
1080
|
return {
|
|
918
1081
|
lines: [
|
|
919
1082
|
fail({
|
|
920
|
-
code:
|
|
921
|
-
what: `
|
|
1083
|
+
code: EXIT.UNAVAILABLE,
|
|
1084
|
+
what: `no design here called "${args.handle}".`,
|
|
922
1085
|
fix: "bt browse (to see what's in stock)"
|
|
923
1086
|
})
|
|
924
|
-
]
|
|
1087
|
+
],
|
|
1088
|
+
exitCode: EXIT.UNAVAILABLE
|
|
925
1089
|
};
|
|
926
1090
|
}
|
|
927
1091
|
const { title, handle } = displayName(product);
|
|
928
1092
|
const colours = productColours(product);
|
|
929
1093
|
const lines = [];
|
|
1094
|
+
if (picked.fuzzy && handle !== args.handle.trim()) {
|
|
1095
|
+
lines.push(working(`matched "${handle}" from "${args.handle}"`));
|
|
1096
|
+
}
|
|
930
1097
|
lines.push(header(style, title));
|
|
931
1098
|
lines.push(` ${dim(style, handle)}`);
|
|
932
1099
|
if (product.description) {
|
|
933
1100
|
lines.push(empty(` ${product.description}`));
|
|
934
1101
|
}
|
|
935
1102
|
const fabric = product.metadata?.["fabric"] ?? null;
|
|
936
|
-
|
|
937
|
-
|
|
1103
|
+
const specs = productSpecs(product);
|
|
1104
|
+
for (const s of specs) {
|
|
1105
|
+
lines.push(` ${s.key.padEnd(9)}${s.value}`);
|
|
938
1106
|
}
|
|
939
1107
|
if (colours.length > 0) {
|
|
940
1108
|
lines.push(` colours ${colours.map((c) => `${colourSwatch(c)} ${c}`).join(" \xB7 ")}`);
|
|
@@ -981,6 +1149,7 @@ async function runView(client, config, args) {
|
|
|
981
1149
|
handle: product.handle,
|
|
982
1150
|
title: product.title,
|
|
983
1151
|
fabric,
|
|
1152
|
+
specs,
|
|
984
1153
|
colours,
|
|
985
1154
|
thumbnail: product.thumbnail,
|
|
986
1155
|
variants: (product.variants ?? []).map((v) => ({
|
|
@@ -993,7 +1162,7 @@ async function runView(client, config, args) {
|
|
|
993
1162
|
};
|
|
994
1163
|
}
|
|
995
1164
|
function registerView(program, makeClient, loadConfig) {
|
|
996
|
-
program.command("view").alias("show").argument("<handle>", "the
|
|
1165
|
+
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
1166
|
const { json, brand } = resolveOutput(program);
|
|
998
1167
|
const noColor = program.opts().color === false;
|
|
999
1168
|
const res = await runView(makeClient(), loadConfig(), {
|
|
@@ -1011,14 +1180,29 @@ function registerView(program, makeClient, loadConfig) {
|
|
|
1011
1180
|
async function runStage(client, config, args) {
|
|
1012
1181
|
const style = brandOf(args);
|
|
1013
1182
|
const region = await client.resolveRegion(config.region);
|
|
1014
|
-
const
|
|
1015
|
-
if (!product) {
|
|
1183
|
+
const resolved = await resolveProduct(client, region, args.handle);
|
|
1184
|
+
if (!resolved.product) {
|
|
1185
|
+
if (resolved.candidates && resolved.candidates.length > 1) {
|
|
1186
|
+
return {
|
|
1187
|
+
lines: [
|
|
1188
|
+
fail({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${resolved.candidates.length} designs \u2014 pick one:` }),
|
|
1189
|
+
...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1190
|
+
" or be exact \u2192 bt stage <slug>"
|
|
1191
|
+
],
|
|
1192
|
+
exitCode: EXIT.DATAERR
|
|
1193
|
+
// ambiguous input → non-zero so `bt stage x && bt checkout` halts (d10)
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1016
1196
|
return {
|
|
1017
1197
|
lines: [
|
|
1018
|
-
fail({ code:
|
|
1019
|
-
]
|
|
1198
|
+
fail({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" })
|
|
1199
|
+
],
|
|
1200
|
+
exitCode: EXIT.UNAVAILABLE
|
|
1201
|
+
// not-found → non-zero (d10)
|
|
1020
1202
|
};
|
|
1021
1203
|
}
|
|
1204
|
+
const product = resolved.product;
|
|
1205
|
+
const matchNote = resolved.fuzzy && (product.handle ?? "") !== args.handle.trim() ? working(`matched "${product.handle}" from "${args.handle}"`) : null;
|
|
1022
1206
|
const size = args.size ?? config.defaultSize;
|
|
1023
1207
|
const colours = productColours(product);
|
|
1024
1208
|
const colour = args.colour ?? config.defaultColour ?? (colours.length > 0 ? colours[0] : void 0);
|
|
@@ -1028,17 +1212,20 @@ async function runStage(client, config, args) {
|
|
|
1028
1212
|
return {
|
|
1029
1213
|
lines: [
|
|
1030
1214
|
fail({
|
|
1031
|
-
code:
|
|
1215
|
+
code: EXIT.DATAERR,
|
|
1032
1216
|
what: `no variant for ${args.handle}${size ? `#size=${size}` : ""}${colour ? ` (${colour})` : ""}.`,
|
|
1033
1217
|
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1034
1218
|
})
|
|
1035
|
-
]
|
|
1219
|
+
],
|
|
1220
|
+
exitCode: EXIT.DATAERR
|
|
1221
|
+
// unprocessable size+colour combo → non-zero (d10)
|
|
1036
1222
|
};
|
|
1037
1223
|
}
|
|
1038
1224
|
if (args.dryRun) {
|
|
1039
1225
|
const colourSuffix2 = colour ? ` (${colour})` : "";
|
|
1040
1226
|
return {
|
|
1041
1227
|
lines: [
|
|
1228
|
+
...matchNote ? [matchNote] : [],
|
|
1042
1229
|
ok(`dry-run \u2014 would stage ${product.handle ?? args.handle}${size ? `#${size}` : ""}${colourSuffix2} (nothing staged).`),
|
|
1043
1230
|
" run without --dry-run to acquire the inventory lock."
|
|
1044
1231
|
],
|
|
@@ -1054,6 +1241,9 @@ async function runStage(client, config, args) {
|
|
|
1054
1241
|
};
|
|
1055
1242
|
}
|
|
1056
1243
|
const lines = [];
|
|
1244
|
+
if (matchNote) {
|
|
1245
|
+
lines.push(matchNote);
|
|
1246
|
+
}
|
|
1057
1247
|
let cartId = config.cart_id;
|
|
1058
1248
|
let recovered = false;
|
|
1059
1249
|
if (!cartId) {
|
|
@@ -1099,7 +1289,7 @@ async function runStage(client, config, args) {
|
|
|
1099
1289
|
};
|
|
1100
1290
|
}
|
|
1101
1291
|
function registerStage(program, makeClient, loadConfig) {
|
|
1102
|
-
program.command("stage").alias("add").argument("<handle>", "the
|
|
1292
|
+
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
1293
|
const { json, brand } = resolveOutput(program);
|
|
1104
1294
|
const res = await runStage(makeClient(), loadConfig(), {
|
|
1105
1295
|
handle,
|
|
@@ -1127,6 +1317,7 @@ export {
|
|
|
1127
1317
|
productColours,
|
|
1128
1318
|
sizesForColour,
|
|
1129
1319
|
findVariant,
|
|
1320
|
+
productLore,
|
|
1130
1321
|
GLYPH,
|
|
1131
1322
|
ok,
|
|
1132
1323
|
warn,
|
|
@@ -1134,6 +1325,8 @@ export {
|
|
|
1134
1325
|
empty,
|
|
1135
1326
|
fail,
|
|
1136
1327
|
versionLine,
|
|
1328
|
+
EXIT,
|
|
1329
|
+
formatFatal,
|
|
1137
1330
|
cyan,
|
|
1138
1331
|
dim,
|
|
1139
1332
|
green,
|
|
@@ -1151,6 +1344,7 @@ export {
|
|
|
1151
1344
|
lineColour,
|
|
1152
1345
|
formatCartLine,
|
|
1153
1346
|
resolveLineItemRef,
|
|
1347
|
+
resolveProduct,
|
|
1154
1348
|
resolveOutput,
|
|
1155
1349
|
printResult,
|
|
1156
1350
|
runView,
|
|
@@ -1158,4 +1352,4 @@ export {
|
|
|
1158
1352
|
runStage,
|
|
1159
1353
|
registerStage
|
|
1160
1354
|
};
|
|
1161
|
-
//# sourceMappingURL=chunk-
|
|
1355
|
+
//# sourceMappingURL=chunk-VRNJLVHX.js.map
|