@binaryandthread/cli 0.4.11 → 0.4.13

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.
@@ -31,7 +31,11 @@ function loadConfig() {
31
31
  }
32
32
  }
33
33
  function saveConfig(patch) {
34
- const merged = { ...loadConfig(), ...sanitize(patch) };
34
+ const raw = patch;
35
+ const merged = { ...loadConfig(), ...sanitize(raw) };
36
+ for (const key of ALLOWED_KEYS) {
37
+ if (raw[key] === null) delete merged[key];
38
+ }
35
39
  const dir = configDir();
36
40
  fs.mkdirSync(dir, { recursive: true });
37
41
  const target = configPath();
@@ -64,4 +68,4 @@ export {
64
68
  saveConfig,
65
69
  forgetConfig
66
70
  };
67
- //# sourceMappingURL=chunk-T7MFWTB7.js.map
71
+ //# sourceMappingURL=chunk-IUN3OOYY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["// Local config — ~/.config/bt/config.json.\n//\n// Holds ONLY non-sensitive fields (SECURITY G15): cart_id, email, region, defaults.\n// The auth TOKEN is NEVER here — it goes in the OS keychain in Wave 5. Persisting a\n// token in plaintext config would be the exact leak G15 forbids.\n//\n// Writes are ATOMIC (F-B1): write to config.json.tmp then fs.renameSync, so a crash\n// mid-write never corrupts the file (rename is atomic on POSIX + Windows same-volume).\n\nimport * as fs from \"node:fs\"\nimport * as os from \"node:os\"\nimport * as path from \"node:path\"\n\nexport interface BtConfig {\n cart_id?: string\n email?: string\n region?: string\n defaultSize?: string\n defaultColour?: string\n /** CAT-31: \"regular\" | \"oversize\", used when a design comes in both fits and no --fit is given. */\n defaultFit?: string\n defaultBranch?: string\n /** RUNTIME-ONLY (ENG-21): where the effective region came from this run — flag, saved config, or\n * a locale guess. Deliberately NOT in ALLOWED_KEYS, so saveConfig always strips it. */\n region_source?: \"flag\" | \"config\" | \"locale\"\n}\n\n// Persisted keys exclude the RUNTIME-ONLY fields (region_source) — typing ALLOWED_KEYS as the full\n// `keyof BtConfig` wrongly widened `key` to include region_source's literal union, so `out[key] = v`\n// (a string) failed to typecheck. PersistedKey narrows it to the string-valued, persistable fields.\nexport type PersistedKey = Exclude<keyof BtConfig, \"region_source\">\n\n/** A config patch: a string sets the key, `null` DELETES it (TB252: a paid cart's id must go), undefined leaves it. */\nexport type ConfigPatch = { [K in PersistedKey]?: string | null }\n\n// Only these keys are ever persisted — anything else (notably `token`) is dropped (G15).\nconst ALLOWED_KEYS: Array<PersistedKey> = [\n \"cart_id\",\n \"email\",\n \"region\",\n \"defaultSize\",\n \"defaultColour\",\n \"defaultFit\",\n \"defaultBranch\",\n]\n\n/** The config directory: $XDG_CONFIG_HOME/bt or ~/.config/bt. */\nexport function configDir(): string {\n const xdg = process.env.XDG_CONFIG_HOME\n const base = xdg && xdg.trim() ? xdg : path.join(os.homedir(), \".config\")\n return path.join(base, \"bt\")\n}\n\nexport function configPath(): string {\n return path.join(configDir(), \"config.json\")\n}\n\n/** Load the config, or {} if none / unreadable. Never throws. */\nexport function loadConfig(): BtConfig {\n try {\n const raw = fs.readFileSync(configPath(), \"utf8\")\n const parsed = JSON.parse(raw) as Record<string, unknown>\n return sanitize(parsed)\n } catch {\n return {}\n }\n}\n\n/**\n * Merge-and-save the given fields. Strips any non-allowlisted key (G15). Atomic (F-B1). A value of\n * exactly `null` deletes that key (TB252: before this no path could clear cart_id, so a paid cart's\n * id outlived its order and `bt cart` listed the paid items as still staged).\n */\nexport function saveConfig(patch: ConfigPatch | Partial<BtConfig>): BtConfig {\n const raw = patch as Record<string, unknown>\n const merged = { ...loadConfig(), ...sanitize(raw) }\n for (const key of ALLOWED_KEYS) {\n if (raw[key] === null) delete merged[key]\n }\n const dir = configDir()\n fs.mkdirSync(dir, { recursive: true })\n const target = configPath()\n const tmp = target + \".tmp\"\n // write to a temp file then atomically rename (F-B1)\n fs.writeFileSync(tmp, JSON.stringify(merged, null, 2), { encoding: \"utf8\", mode: 0o600 })\n fs.renameSync(tmp, target)\n return merged\n}\n\n/** Wipe the local config file (G15 erasure — `bt forget`). Never throws. */\nexport function forgetConfig(): void {\n try {\n fs.rmSync(configPath(), { force: true })\n } catch {\n /* ignore */\n }\n}\n\n/** Keep only allowlisted, non-sensitive keys (drops `token` and anything unexpected). */\nfunction sanitize(obj: Record<string, unknown>): BtConfig {\n const out: BtConfig = {}\n for (const key of ALLOWED_KEYS) {\n const v = obj[key]\n if (typeof v === \"string\" && v.length > 0) {\n out[key] = v\n }\n }\n return out\n}\n"],"mappings":";;;AASA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAyBtB,IAAM,eAAoC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,YAAoB;AAClC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,OAAO,OAAO,IAAI,KAAK,IAAI,MAAW,UAAQ,WAAQ,GAAG,SAAS;AACxE,SAAY,UAAK,MAAM,IAAI;AAC7B;AAEO,SAAS,aAAqB;AACnC,SAAY,UAAK,UAAU,GAAG,aAAa;AAC7C;AAGO,SAAS,aAAuB;AACrC,MAAI;AACF,UAAM,MAAS,gBAAa,WAAW,GAAG,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,SAAS,MAAM;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAOO,SAAS,WAAW,OAAkD;AAC3E,QAAM,MAAM;AACZ,QAAM,SAAS,EAAE,GAAG,WAAW,GAAG,GAAG,SAAS,GAAG,EAAE;AACnD,aAAW,OAAO,cAAc;AAC9B,QAAI,IAAI,GAAG,MAAM,KAAM,QAAO,OAAO,GAAG;AAAA,EAC1C;AACA,QAAM,MAAM,UAAU;AACtB,EAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,SAAS;AAErB,EAAG,iBAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACxF,EAAG,cAAW,KAAK,MAAM;AACzB,SAAO;AACT;AAGO,SAAS,eAAqB;AACnC,MAAI;AACF,IAAG,UAAO,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,SAAS,KAAwC;AACxD,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,GAAG;AACjB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,GAAG;AACzC,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -220,13 +220,19 @@ var StoreClient = class {
220
220
  } catch {
221
221
  parsed = {};
222
222
  }
223
- if (opts.cartRecovery && (res.status === 404 || res.status === 409 || String(parsed.type ?? "").includes("cart.completed") || /cart.*complete/i.test(String(parsed.message ?? "")))) {
223
+ const idempotencyConflict = res.status === 409 && parsed.code === "IDEMPOTENCY_CONFLICT";
224
+ const parsedType = String(parsed.type ?? "");
225
+ if (opts.cartRecovery && (res.status === 404 || parsedType === "cart_completed" || parsedType.includes("cart.completed") || /cart.*complete/i.test(String(parsed.message ?? "")))) {
224
226
  throw new RecoverableCartError(
225
227
  `cart is stale or completed (HTTP ${res.status})`,
226
228
  res.status
227
229
  );
228
230
  }
229
- if (res.status === 429) {
231
+ if (idempotencyConflict) {
232
+ retryAfterMs = parseRetryAfterMs(res);
233
+ lastErr = new StoreApiError(`request in flight (HTTP 409)`, 409, path);
234
+ this.logWarn("store.request.idempotency_conflict", { path, attempt: attempt + 1, retryAfterMs });
235
+ } else if (res.status === 429) {
230
236
  retryAfterMs = parseRetryAfterMs(res);
231
237
  lastErr = new StoreApiError(`rate limited (HTTP 429)`, 429, path);
232
238
  this.logWarn("store.request.rate_limited", {
@@ -626,8 +632,8 @@ function supplierOf(p) {
626
632
  var PRINTFUL_BLANK_COLOURS = {
627
633
  "ascolour-5082": ["black", "white", "coal", "eucalyptus", "bone", "grey", "khaki", "indigo"],
628
634
  // TB124, TB126
629
- "bellacanvas-4711": ["asphalt", "dust", "navy", "maroon", "forest"]
630
- // TB128: the sweatshirt
635
+ "bellacanvas-4711": ["asphalt", "dust", "navy", "maroon", "forest", "jet"]
636
+ // TB128: the sweatshirt; TB129: + jet (its Black)
631
637
  };
632
638
  var PRINTFUL_BLANK_SIZES = {
633
639
  "ascolour-5082": ["s", "m", "l", "xl", "2xl"],
@@ -771,7 +777,7 @@ function resolveVariant(p, opts) {
771
777
  const supplier = supplierOf(p);
772
778
  const pb = printfulBlankOf(p);
773
779
  const colours = coloursFor(p, fit, opts.countryCode);
774
- const colour = colourKey(opts.colour ?? "") || (colours[0] ?? null);
780
+ const colour = colourKey(opts.colour ?? "", productColours(p)) || (colours[0] ?? null);
775
781
  if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode, supplier, pb)) {
776
782
  return { ok: false, reason: "fit_unavailable", fit: gate, colour };
777
783
  }
@@ -809,15 +815,22 @@ var COLOUR_LABELS = {
809
815
  bone: "Faded Bone",
810
816
  grey: "Faded Grey",
811
817
  khaki: "Faded Khaki",
812
- indigo: "Faded Indigo"
818
+ indigo: "Faded Indigo",
819
+ jet: "Black"
813
820
  };
814
821
  function colourLabel(colour) {
815
822
  const c = colour.trim();
816
823
  if (!c) return c;
817
824
  return COLOUR_LABELS[c.toLowerCase()] ?? c.charAt(0).toUpperCase() + c.slice(1).toLowerCase();
818
825
  }
819
- function colourKey(colour) {
826
+ function colourKey(colour, available) {
820
827
  const c = colour.trim().toLowerCase();
828
+ if (available) {
829
+ const keys = available.map((k) => k.trim().toLowerCase());
830
+ if (keys.includes(c)) return c;
831
+ return keys.find((k) => colourLabel(k).toLowerCase() === c) ?? c;
832
+ }
833
+ if (Object.prototype.hasOwnProperty.call(COLOUR_LABELS, c)) return c;
821
834
  const key = Object.entries(COLOUR_LABELS).find(([, label]) => label.toLowerCase() === c)?.[0];
822
835
  return key ?? c;
823
836
  }
@@ -878,7 +891,7 @@ function findVariant(p, opts = {}) {
878
891
  }
879
892
  const variants = (p.variants ?? []).filter((v) => fit === null || variantFit(v) === fit);
880
893
  const size = opts.size?.trim().toLowerCase();
881
- const colour = opts.colour ? colourKey(opts.colour) : void 0;
894
+ const colour = opts.colour ? colourKey(opts.colour, productColours(p)) : void 0;
882
895
  const sizeMatches = (v) => {
883
896
  if (!size) return true;
884
897
  return (sizeOfVariant(v) ?? "").toLowerCase() === size || (v.title ?? "").toLowerCase() === size;
@@ -964,6 +977,31 @@ function firstImageUrl(images) {
964
977
  function escapeRegex(s) {
965
978
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
966
979
  }
980
+ function sizeTokenOf(title) {
981
+ return titleSegments(title).find((s) => SIZE_TOKEN.test(s)) ?? null;
982
+ }
983
+ var SIZE_ORDER = ["xxs", "xs", "s", "m", "l", "xl", "2xl", "xxl", "3xl", "xxxl", "4xl", "5xl"];
984
+ function sortSizes(sizes) {
985
+ const rank = (s) => {
986
+ const i = SIZE_ORDER.indexOf(s.trim().toLowerCase());
987
+ return i === -1 ? SIZE_ORDER.length : i;
988
+ };
989
+ return [...sizes].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
990
+ }
991
+ function lineOption(item, titles) {
992
+ const o = item.variant?.options?.find((x) => titles.includes((x.option?.title ?? "").toLowerCase()));
993
+ const v = typeof o?.value === "string" ? o.value.trim() : "";
994
+ return v ? v : null;
995
+ }
996
+ function lineColour(item) {
997
+ const opt = lineOption(item, ["colour", "color"]);
998
+ if (opt) return opt.toLowerCase();
999
+ const vt = (item.variant_title ?? "").toString();
1000
+ return vt ? variantColour({ id: "x", title: vt }) : null;
1001
+ }
1002
+ function lineSize(item) {
1003
+ return lineOption(item, ["size"]) ?? sizeTokenOf(item.variant_title);
1004
+ }
967
1005
  function orderStage(o) {
968
1006
  const status = (o.status ?? "").trim().toLowerCase();
969
1007
  if (status === "canceled" || status === "refunded") return "reverted";
@@ -1280,7 +1318,7 @@ function contributionBar(style, commits, cap = 20) {
1280
1318
  function colourSwatch(colour) {
1281
1319
  const c = colour.trim().toLowerCase();
1282
1320
  if (c === "white") return "\u2B1C";
1283
- if (c === "black") return "\u2B1B";
1321
+ if (c === "black" || c === "jet") return "\u2B1B";
1284
1322
  if (c === "khaki") return "\u{1F7EB}";
1285
1323
  return "\u25FC";
1286
1324
  }
@@ -1298,14 +1336,6 @@ function regionLine(rr, style, source) {
1298
1336
  const nudge = rr.home ? " \xB7 not you? \u2192 bt region" : source === "locale" ? " \xB7 guessed from your locale \u2014 not you? \u2192 bt region" : "";
1299
1337
  return dim(style, `${GLYPH.gear} region: ${loc}${rr.region.currency_code}${nudge}`);
1300
1338
  }
1301
- var SIZE_ORDER = ["xxs", "xs", "s", "m", "l", "xl", "2xl", "xxl", "3xl", "xxxl", "4xl", "5xl"];
1302
- function sortSizes(sizes) {
1303
- const rank = (s) => {
1304
- const i = SIZE_ORDER.indexOf(s.trim().toLowerCase());
1305
- return i === -1 ? SIZE_ORDER.length : i;
1306
- };
1307
- return [...sizes].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
1308
- }
1309
1339
  function brandOf(args) {
1310
1340
  return args?.brand ?? PLAIN;
1311
1341
  }
@@ -1330,21 +1360,6 @@ function displayName(p) {
1330
1360
  const title = (p.title ?? "").toString().trim() || handle;
1331
1361
  return { title, handle };
1332
1362
  }
1333
- function lineOption(item, titles) {
1334
- const o = item.variant?.options?.find((x) => titles.includes((x.option?.title ?? "").toLowerCase()));
1335
- const v = typeof o?.value === "string" ? o.value.trim() : "";
1336
- return v ? v : null;
1337
- }
1338
- function lineColour(item) {
1339
- const opt = lineOption(item, ["colour", "color"]);
1340
- if (opt) return opt.toLowerCase();
1341
- const vt = (item.variant_title ?? "").toString();
1342
- if (vt) {
1343
- const c = variantColour({ id: "x", title: vt });
1344
- if (c) return c;
1345
- }
1346
- return null;
1347
- }
1348
1363
  function lineFit(item) {
1349
1364
  const opt = parseFit(lineOption(item, ["fit"]));
1350
1365
  if (opt) return opt;
@@ -1365,7 +1380,8 @@ function formatCartLine(item, index, currency, brand) {
1365
1380
  const idTag = item.id ? dimTag(style, ` (#${shortItemId(item.id)})`) : "";
1366
1381
  const colour = lineColour(item);
1367
1382
  const fit = lineFit(item);
1368
- const tags = [colour ? colourLabel(colour).toLowerCase() : null, fit ? fitLabel(fit).toLowerCase() : null].filter(Boolean).join(", ");
1383
+ const size = lineSize(item);
1384
+ const tags = [colour ? colourLabel(colour).toLowerCase() : null, fit ? fitLabel(fit).toLowerCase() : null, size].filter(Boolean).join(", ");
1369
1385
  const colourTag = tags ? ` \xB7 ${tags}` : "";
1370
1386
  const price = item.total != null ? ` ${accentPrice(style, fmtPrice(item.total, currency))}` : "";
1371
1387
  return ` ${ref} ${title}${colourTag}${idTag} \xB7 qty ${item.quantity}${price}`;
@@ -1649,7 +1665,7 @@ async function runView(client, config, args) {
1649
1665
  }
1650
1666
  let imageUrl = product.thumbnail ?? null;
1651
1667
  let blackNote = null;
1652
- const wanted = (args.colour ? colourKey(args.colour) : "") || (fit ? colours[0] : void 0);
1668
+ const wanted = (args.colour ? colourKey(args.colour, [...colours, ...unavailable]) : "") || (fit ? colours[0] : void 0);
1653
1669
  if (wanted) {
1654
1670
  const resolved = imageFor(product, { fit, colour: wanted }) ?? (fit === null ? imageForColour(product, wanted) : null);
1655
1671
  if (resolved) {
@@ -1721,6 +1737,10 @@ function registerView(program, makeClient, loadConfig) {
1721
1737
  }
1722
1738
 
1723
1739
  // src/commands/stage.ts
1740
+ function sizeChoice(sizes) {
1741
+ const sorted = sortSizes(sizes);
1742
+ return sorted.length === 1 ? sorted[0] : `<${sorted.join("|")}>`;
1743
+ }
1724
1744
  async function runStage(client, config, args) {
1725
1745
  const style = brandOf(args);
1726
1746
  const rr = await client.resolveRegionResult(config.region);
@@ -1745,6 +1765,11 @@ async function runStage(client, config, args) {
1745
1765
  cartId = void 0;
1746
1766
  recovered = true;
1747
1767
  }
1768
+ if (existing?.completed_at) {
1769
+ existing = null;
1770
+ cartId = void 0;
1771
+ recovered = true;
1772
+ }
1748
1773
  if (existing) {
1749
1774
  const to = await cartShipTo(existing, () => client.getRegions());
1750
1775
  if (!to) {
@@ -1817,7 +1842,7 @@ async function runStage(client, config, args) {
1817
1842
  }
1818
1843
  const fitFlag = r.fit && productFits(product).length > 1 ? ` --fit ${r.fit}` : "";
1819
1844
  const colourAsked = "colour" in r ? r.colour : null;
1820
- const madeIn = coloursFor(product, r.fit ?? null);
1845
+ const madeIn = coloursFor(product, productFits(product).length > 0 ? r.fit ?? null : null);
1821
1846
  if (colourAsked && (r.reason === "fit_unavailable" || r.reason === "not_found") && !madeIn.includes(colourAsked)) {
1822
1847
  return failResult({
1823
1848
  code: EXIT.DATAERR,
@@ -1839,7 +1864,7 @@ async function runStage(client, config, args) {
1839
1864
  code: EXIT.DATAERR,
1840
1865
  what: `${r.size} is not made in ${r.fit} where you ship (${countryCode ?? "your region"}).`,
1841
1866
  money: "nothing staged, nothing charged.",
1842
- fix: r.sizes.length ? `bt stage ${handleShown} --fit ${r.fit} --size ${r.sizes[0]} (sizes here: ${r.sizes.join(" ")})` : `bt view ${handleShown} --fit ${r.fit} (to see the sizes you can buy)`
1867
+ fix: r.sizes.length ? `${r.sizes.length > 1 ? "ask which size is wanted, don't choose for them, then: " : ""}bt stage ${handleShown} --fit ${r.fit} --size ${sizeChoice(r.sizes)}` : `bt view ${handleShown} --fit ${r.fit} (to see the sizes you can buy)`
1843
1868
  });
1844
1869
  }
1845
1870
  const inColour = r.colour ? ` in ${r.colour}` : "";
@@ -1848,7 +1873,7 @@ async function runStage(client, config, args) {
1848
1873
  code: EXIT.DATAERR,
1849
1874
  what: `${handleShown} doesn't come in size ${size}${inColour}. sizes: ${r.sizes.join(" ")}.`,
1850
1875
  money: "nothing staged, nothing charged.",
1851
- fix: `bt stage ${handleShown}${fitFlag} --size ${r.sizes[0]}${r.colour ? ` --colour ${r.colour}` : ""}`
1876
+ fix: `${r.sizes.length > 1 ? "ask which size is wanted, don't choose for them, then: " : ""}bt stage ${handleShown}${fitFlag} --size ${sizeChoice(r.sizes)}${r.colour ? ` --colour ${r.colour}` : ""}`
1852
1877
  });
1853
1878
  }
1854
1879
  return failResult({
@@ -1861,6 +1886,20 @@ async function runStage(client, config, args) {
1861
1886
  const variant = r.variant;
1862
1887
  const colour = r.colour ?? void 0;
1863
1888
  const fit = r.fit;
1889
+ if (!size) {
1890
+ const sizes = sortSizes(
1891
+ r.colour ? sizesFor(product, r.fit, r.colour, countryCode) : [...new Set((product.variants ?? []).map((v) => sizeOfVariant(v)).filter((s) => !!s))]
1892
+ );
1893
+ if (sizes.length > 1) {
1894
+ const fitFlag = r.fit && productFits(product).length > 1 ? ` --fit ${r.fit}` : "";
1895
+ return failResult({
1896
+ code: EXIT.DATAERR,
1897
+ what: `${handleShown} needs a size. sizes${r.colour ? ` in ${r.colour}` : ""}: ${sizes.join(" ")}.`,
1898
+ money: "nothing staged, nothing charged.",
1899
+ fix: `ask which size is wanted, don't choose for them, then: bt stage ${handleShown}${fitFlag} --size ${sizeChoice(sizes)}${r.colour ? ` --colour ${r.colour}` : ""}`
1900
+ });
1901
+ }
1902
+ }
1864
1903
  const refTags = [colour ? colourLabel(colour).toLowerCase() : null, fit].filter(Boolean).join(", ");
1865
1904
  const colourSuffix = refTags ? ` (${refTags})` : "";
1866
1905
  if (args.dryRun) {
@@ -1931,9 +1970,19 @@ async function runStage(client, config, args) {
1931
1970
  json: args.json ? { cart_id: cartId, recovered, fit: fit ?? null, colour: colour ?? null, items: cart.items ?? [], total: cart.total ?? null } : void 0
1932
1971
  };
1933
1972
  }
1973
+ function oneDesignPerCommand() {
1974
+ return failResult({
1975
+ code: EXIT.DATAERR,
1976
+ what: 'one design per command. a two-word colour is written --colour coal, or in quotes: --colour "faded coal"'
1977
+ });
1978
+ }
1934
1979
  function registerStage(program, makeClient, loadConfig) {
1935
- 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("--fit <fit>", "regular or oversize (required when a design comes in both; default: your defaultFit)").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) => {
1980
+ 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 (required unless bt config set defaultSize)").option("--colour <colour>", "colour, e.g. white / black (default: the product's first colour)").option("--fit <fit>", "regular or oversize (required when a design comes in both; default: your defaultFit)").option("--dry-run", "resolve the variant + show what would be staged, without staging").description("reserve stock \u2014 acquires an inventory lock, reserves nothing permanent.").allowExcessArguments().action(async (handle, opts, cmd) => {
1936
1981
  const { json, brand } = resolveOutput(program);
1982
+ if (cmd.args.length > 1) {
1983
+ printResult(oneDesignPerCommand(), json);
1984
+ return;
1985
+ }
1937
1986
  const res = await runStage(makeClient(), loadConfig(), {
1938
1987
  handle,
1939
1988
  size: opts.size,
@@ -1944,7 +1993,7 @@ function registerStage(program, makeClient, loadConfig) {
1944
1993
  brand
1945
1994
  });
1946
1995
  if (res.save) {
1947
- const { saveConfig } = await import("./config-L4X7G563.js");
1996
+ const { saveConfig } = await import("./config-PWI2G2O5.js");
1948
1997
  saveConfig(res.save);
1949
1998
  }
1950
1999
  printResult(res, json);
@@ -1967,6 +2016,7 @@ export {
1967
2016
  labelVariantTitle,
1968
2017
  productColours,
1969
2018
  findVariant,
2019
+ lineColour,
1970
2020
  orderStage,
1971
2021
  productLore,
1972
2022
  isCartTaxInclusive,
@@ -1996,7 +2046,6 @@ export {
1996
2046
  priceLine,
1997
2047
  stageMarker,
1998
2048
  displayName,
1999
- lineColour,
2000
2049
  formatCartLine,
2001
2050
  resolveLineItemRef,
2002
2051
  resolveProduct,
@@ -2007,6 +2056,7 @@ export {
2007
2056
  runView,
2008
2057
  registerView,
2009
2058
  runStage,
2059
+ oneDesignPerCommand,
2010
2060
  registerStage
2011
2061
  };
2012
- //# sourceMappingURL=chunk-UTSTDWKZ.js.map
2062
+ //# sourceMappingURL=chunk-ZRHSCMBQ.js.map