@binaryandthread/mcp 0.4.6 → 0.4.8
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 +2 -1
- package/dist/bin.js +201 -43
- package/dist/bin.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -115,4 +115,5 @@ floor. The buy is never blocked on rendering.
|
|
|
115
115
|
Some designs are sold in two fits, **regular** and **oversize**, priced apart. `list_products` marks them
|
|
116
116
|
(`fits`, `price_from`), `get_product` lists sizes and prices per fit (pass `fit` to narrow), and
|
|
117
117
|
`add_to_cart` **requires** `fit` for them: without it the tool returns an error naming both fits, and
|
|
118
|
-
nothing is added.
|
|
118
|
+
nothing is added. A design sold in one fit needs no `fit`. Colours and sizes follow the ship-to region:
|
|
119
|
+
`get_product` lists the ones you can buy where the cart ships.
|
package/dist/bin.js
CHANGED
|
@@ -573,24 +573,116 @@ var BLANK_COLOURS = {
|
|
|
573
573
|
"sols-03996": ["black", "white"],
|
|
574
574
|
"shaka-shmhss": ["black", "white", "khaki"]
|
|
575
575
|
};
|
|
576
|
+
var STOCK_REGION_BY_COUNTRY = {
|
|
577
|
+
us: "US-CA",
|
|
578
|
+
se: "EU",
|
|
579
|
+
at: "EU",
|
|
580
|
+
be: "EU",
|
|
581
|
+
bg: "EU",
|
|
582
|
+
hr: "EU",
|
|
583
|
+
cy: "EU",
|
|
584
|
+
cz: "EU",
|
|
585
|
+
dk: "EU",
|
|
586
|
+
ee: "EU",
|
|
587
|
+
fi: "EU",
|
|
588
|
+
fr: "EU",
|
|
589
|
+
de: "EU",
|
|
590
|
+
gr: "EU",
|
|
591
|
+
hu: "EU",
|
|
592
|
+
ie: "EU",
|
|
593
|
+
it: "EU",
|
|
594
|
+
lv: "EU",
|
|
595
|
+
lt: "EU",
|
|
596
|
+
lu: "EU",
|
|
597
|
+
mt: "EU",
|
|
598
|
+
nl: "EU",
|
|
599
|
+
pl: "EU",
|
|
600
|
+
pt: "EU",
|
|
601
|
+
ro: "EU",
|
|
602
|
+
sk: "EU",
|
|
603
|
+
si: "EU",
|
|
604
|
+
es: "EU"
|
|
605
|
+
};
|
|
606
|
+
var BLANK_BY_REGION = {
|
|
607
|
+
regular: { "*": "gildan-5000" },
|
|
608
|
+
oversize: { EU: "sols-03996", "US-CA": "shaka-shmhss" }
|
|
609
|
+
};
|
|
576
610
|
function blankFor(fit, countryCode) {
|
|
577
|
-
|
|
578
|
-
|
|
611
|
+
const region = STOCK_REGION_BY_COUNTRY[(countryCode ?? "").trim().toLowerCase()];
|
|
612
|
+
if (!region) return null;
|
|
613
|
+
const map = BLANK_BY_REGION[fit];
|
|
614
|
+
return map["*"] ?? map[region] ?? null;
|
|
579
615
|
}
|
|
580
616
|
var BLANK_SIZES = {
|
|
581
617
|
"gildan-5000": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
|
|
582
618
|
"shaka-shmhss": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
|
|
583
619
|
"sols-03996": ["xs", "s", "m", "l", "xl", "2xl", "3xl"]
|
|
584
620
|
};
|
|
621
|
+
function supplierOf(p) {
|
|
622
|
+
const v = (p.metadata ?? {})["bt_supplier"];
|
|
623
|
+
if (v === void 0 || v === null) return "gelato";
|
|
624
|
+
return v === "gelato" || v === "printful" ? v : null;
|
|
625
|
+
}
|
|
626
|
+
var PRINTFUL_BLANK_COLOURS = {
|
|
627
|
+
"ascolour-5082": ["black", "white"]
|
|
628
|
+
};
|
|
629
|
+
var PRINTFUL_BLANK_SIZES = {
|
|
630
|
+
"ascolour-5082": ["s", "m", "l", "xl", "2xl"]
|
|
631
|
+
};
|
|
632
|
+
var PRINTFUL_SERVED_COUNTRIES = [
|
|
633
|
+
"se",
|
|
634
|
+
"us",
|
|
635
|
+
"at",
|
|
636
|
+
"be",
|
|
637
|
+
"bg",
|
|
638
|
+
"hr",
|
|
639
|
+
"cy",
|
|
640
|
+
"cz",
|
|
641
|
+
"dk",
|
|
642
|
+
"ee",
|
|
643
|
+
"fi",
|
|
644
|
+
"fr",
|
|
645
|
+
"de",
|
|
646
|
+
"gr",
|
|
647
|
+
"hu",
|
|
648
|
+
"ie",
|
|
649
|
+
"it",
|
|
650
|
+
"lv",
|
|
651
|
+
"lt",
|
|
652
|
+
"lu",
|
|
653
|
+
"mt",
|
|
654
|
+
"nl",
|
|
655
|
+
"pl",
|
|
656
|
+
"pt",
|
|
657
|
+
"ro",
|
|
658
|
+
"sk",
|
|
659
|
+
"si",
|
|
660
|
+
"es"
|
|
661
|
+
];
|
|
662
|
+
function printfulBlankFor(fit, countryCode) {
|
|
663
|
+
if (!PRINTFUL_SERVED_COUNTRIES.includes((countryCode ?? "").trim().toLowerCase())) return null;
|
|
664
|
+
return fit === "regular" ? "ascolour-5082" : null;
|
|
665
|
+
}
|
|
585
666
|
function sizeKey(size) {
|
|
586
667
|
const s = size.trim().toLowerCase();
|
|
587
668
|
return s === "xxl" ? "2xl" : s === "xxxl" ? "3xl" : s;
|
|
588
669
|
}
|
|
589
|
-
function
|
|
590
|
-
|
|
670
|
+
function blankRule(fit, countryCode, supplier) {
|
|
671
|
+
if (supplier === null) return null;
|
|
672
|
+
if (supplier === "printful") {
|
|
673
|
+
const pb = printfulBlankFor(fit, countryCode);
|
|
674
|
+
return pb ? { colours: PRINTFUL_BLANK_COLOURS[pb], sizes: PRINTFUL_BLANK_SIZES[pb] } : null;
|
|
675
|
+
}
|
|
676
|
+
const blank = blankFor(fit, countryCode);
|
|
677
|
+
return blank ? { colours: BLANK_COLOURS[blank], sizes: BLANK_SIZES[blank] } : null;
|
|
678
|
+
}
|
|
679
|
+
function colourBuyable(fit, colour, countryCode, supplier = "gelato") {
|
|
680
|
+
const rule = blankRule(fit, countryCode, supplier);
|
|
681
|
+
return !!rule && rule.colours.includes(colour.trim().toLowerCase());
|
|
591
682
|
}
|
|
592
|
-
function sizeBuyable(fit, size, countryCode) {
|
|
593
|
-
|
|
683
|
+
function sizeBuyable(fit, size, countryCode, supplier = "gelato") {
|
|
684
|
+
const rule = blankRule(fit, countryCode, supplier);
|
|
685
|
+
return !!rule && rule.sizes.includes(sizeKey(size));
|
|
594
686
|
}
|
|
595
687
|
function legacyFit(p) {
|
|
596
688
|
const md = p.metadata ?? {};
|
|
@@ -607,24 +699,26 @@ function variantsOfFit(p, fit) {
|
|
|
607
699
|
}
|
|
608
700
|
function coloursFor(p, fit, countryCode) {
|
|
609
701
|
const gate = gateFit(p, fit);
|
|
702
|
+
const supplier = supplierOf(p);
|
|
610
703
|
const out = [];
|
|
611
704
|
for (const v of variantsOfFit(p, fit)) {
|
|
612
705
|
const c = variantColour(v);
|
|
613
706
|
if (!c || out.includes(c)) continue;
|
|
614
|
-
if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode)) continue;
|
|
707
|
+
if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode, supplier)) continue;
|
|
615
708
|
out.push(c);
|
|
616
709
|
}
|
|
617
710
|
return out;
|
|
618
711
|
}
|
|
619
712
|
function sizesFor(p, fit, colour, countryCode) {
|
|
620
713
|
const gate = gateFit(p, fit);
|
|
714
|
+
const supplier = supplierOf(p);
|
|
621
715
|
const target = colour.trim().toLowerCase();
|
|
622
716
|
const out = [];
|
|
623
717
|
for (const v of variantsOfFit(p, fit)) {
|
|
624
718
|
if (variantColour(v) !== target) continue;
|
|
625
719
|
const s = sizeOfVariant(v);
|
|
626
720
|
if (!s || out.includes(s)) continue;
|
|
627
|
-
if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode)) continue;
|
|
721
|
+
if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode, supplier)) continue;
|
|
628
722
|
out.push(s);
|
|
629
723
|
}
|
|
630
724
|
return out;
|
|
@@ -662,21 +756,22 @@ function resolveVariant(p, opts) {
|
|
|
662
756
|
}
|
|
663
757
|
const pool = variantsOfFit(p, fit);
|
|
664
758
|
const gate = gateFit(p, fit);
|
|
759
|
+
const supplier = supplierOf(p);
|
|
665
760
|
const colours = coloursFor(p, fit, opts.countryCode);
|
|
666
761
|
const colour = (opts.colour ?? "").trim().toLowerCase() || (colours[0] ?? null);
|
|
667
|
-
if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode)) {
|
|
762
|
+
if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode, supplier)) {
|
|
668
763
|
return { ok: false, reason: "fit_unavailable", fit: gate, colour };
|
|
669
764
|
}
|
|
670
765
|
const size = (opts.size ?? "").trim() || null;
|
|
671
766
|
const sizeLc = size?.toLowerCase();
|
|
672
767
|
const buyableSizes = colour ? sizesFor(p, fit, colour, opts.countryCode) : [];
|
|
673
768
|
const sizeExists = !!size && pool.some((x) => (sizeOfVariant(x) ?? x.title ?? "").toLowerCase() === size.toLowerCase());
|
|
674
|
-
if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode)) {
|
|
769
|
+
if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode, supplier)) {
|
|
675
770
|
return { ok: false, reason: "size_unavailable", fit: gate, size, sizes: buyableSizes };
|
|
676
771
|
}
|
|
677
772
|
const sellable = gate && opts.countryCode !== void 0 ? pool.filter((x) => {
|
|
678
773
|
const s = sizeOfVariant(x);
|
|
679
|
-
return !s || sizeBuyable(gate, s, opts.countryCode);
|
|
774
|
+
return !s || sizeBuyable(gate, s, opts.countryCode, supplier);
|
|
680
775
|
}) : pool;
|
|
681
776
|
const v = sellable.find((x) => {
|
|
682
777
|
const colourOk = !colour || variantColour(x) === colour;
|
|
@@ -805,25 +900,58 @@ function firstImageUrl(images) {
|
|
|
805
900
|
function escapeRegex(s) {
|
|
806
901
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
807
902
|
}
|
|
903
|
+
async function cartShipTo(cart, regions) {
|
|
904
|
+
const embedded = cart.region?.countries?.length ? cart.region : null;
|
|
905
|
+
const id = cart.region_id ?? cart.region?.id ?? null;
|
|
906
|
+
const region = embedded ?? (id ? (await regions()).find((r) => r.id === id) ?? null : null);
|
|
907
|
+
if (!region) return null;
|
|
908
|
+
const regionCountries = (region.countries ?? []).map((c) => (c.iso_2 ?? "").trim().toLowerCase()).filter(Boolean);
|
|
909
|
+
const address = (cart.shipping_address?.country_code ?? "").trim().toLowerCase() || null;
|
|
910
|
+
const country = address ?? regionCountries[0] ?? null;
|
|
911
|
+
if (!country) return null;
|
|
912
|
+
return { region, country, fromAddress: address !== null, regionCountries };
|
|
913
|
+
}
|
|
914
|
+
function shipToDisagrees(requested, shipTo) {
|
|
915
|
+
const cc = (requested ?? "").trim().toLowerCase();
|
|
916
|
+
if (!cc || cc === shipTo.country) return false;
|
|
917
|
+
return shipTo.fromAddress || !shipTo.regionCountries.includes(cc);
|
|
918
|
+
}
|
|
808
919
|
function str(v, fallback) {
|
|
809
920
|
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
810
921
|
}
|
|
811
922
|
function productReadme(p) {
|
|
812
923
|
return typeof p.description === "string" ? p.description : "";
|
|
813
924
|
}
|
|
925
|
+
var GELATO_FALLBACK = {
|
|
926
|
+
material: "100% cotton",
|
|
927
|
+
weight: "180 g/m\xB2",
|
|
928
|
+
fit: "Unisex heavyweight crewneck",
|
|
929
|
+
delivery: "Ships in 2\u20135 business days + transit",
|
|
930
|
+
care: "Cold wash, line dry"
|
|
931
|
+
};
|
|
932
|
+
var PRINTFUL_FALLBACK = {
|
|
933
|
+
material: "100% cotton",
|
|
934
|
+
weight: "240 g/m\xB2",
|
|
935
|
+
fit: "Unisex heavyweight crewneck, garment-dyed",
|
|
936
|
+
delivery: "Ships in 2\u20135 business days + transit",
|
|
937
|
+
care: "Machine wash cold, inside out. Do not tumble dry or iron the print. Line dry in shade."
|
|
938
|
+
};
|
|
814
939
|
function productSpecs(p, fit) {
|
|
815
940
|
const md = p.metadata ?? {};
|
|
816
941
|
const byFit = md.specs_by_fit ?? null;
|
|
817
942
|
const fitKey = fit ?? (byFit && Object.keys(byFit).length === 1 ? Object.keys(byFit)[0] : void 0);
|
|
818
943
|
const src = fitKey && byFit?.[fitKey] || md;
|
|
819
944
|
const pick = (k, fallback) => str(src[k] ?? md[k], fallback);
|
|
945
|
+
const d = supplierOf(p) === "printful" ? PRINTFUL_FALLBACK : GELATO_FALLBACK;
|
|
820
946
|
return [
|
|
821
|
-
{ key: "material", value: pick("material",
|
|
822
|
-
{ key: "weight", value: pick("weight",
|
|
823
|
-
{ key: "fit", value: pick("fit",
|
|
947
|
+
{ key: "material", value: pick("material", d.material) },
|
|
948
|
+
{ key: "weight", value: pick("weight", d.weight) },
|
|
949
|
+
{ key: "fit", value: pick("fit", d.fit) },
|
|
824
950
|
{ key: "print", value: pick("print", "Front print") },
|
|
825
|
-
|
|
826
|
-
|
|
951
|
+
// Delivery: the product's own line first (Sam 2026-10-01, as the storefront since U1), else the same
|
|
952
|
+
// fallback wording for both suppliers. No per-region times.
|
|
953
|
+
{ key: "delivery", value: pick("delivery", d.delivery) },
|
|
954
|
+
{ key: "care", value: pick("care", d.care) }
|
|
827
955
|
];
|
|
828
956
|
}
|
|
829
957
|
function productLore(p) {
|
|
@@ -883,7 +1011,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
883
1011
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
884
1012
|
|
|
885
1013
|
// src/version.ts
|
|
886
|
-
var VERSION = "0.4.
|
|
1014
|
+
var VERSION = "0.4.8";
|
|
887
1015
|
|
|
888
1016
|
// src/onboarding.ts
|
|
889
1017
|
var GETTING_STARTED_URI = "bt://getting-started";
|
|
@@ -1285,7 +1413,7 @@ function makeTools(client, opts = {}) {
|
|
|
1285
1413
|
// the map used to hand out ids for colours the ship-to blank can't print, with the caveat buried
|
|
1286
1414
|
// in a separate unavailable_colours field. Same derived answer, on the row an agent actually uses.
|
|
1287
1415
|
// CAT-37 added the SIZE half — the US and EU oversize blanks have different size ranges.
|
|
1288
|
-
buyable: (!gFit || !vColour || colourBuyable(gFit, vColour, shipCC)) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC))
|
|
1416
|
+
buyable: (!gFit || !vColour || colourBuyable(gFit, vColour, shipCC, supplierOf(product))) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC, supplierOf(product)))
|
|
1289
1417
|
};
|
|
1290
1418
|
});
|
|
1291
1419
|
const sizeList = colours.length > 0 ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)])) : (product.variants ?? []).map((v) => v.title ?? "?");
|
|
@@ -1324,24 +1452,50 @@ function makeTools(client, opts = {}) {
|
|
|
1324
1452
|
};
|
|
1325
1453
|
const add_to_cart = {
|
|
1326
1454
|
name: "add_to_cart",
|
|
1327
|
-
description: "Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it to start a fresh one. Call get_product first to get valid sizes/colours (or an exact variant_id); for a design sold in two fits (regular / oversize, priced apart) you MUST pass `fit`. qty is capped at 20. Returns the cart_id and the server-side cart summary (VAT included where applicable). `region` (ISO-2) sets the currency
|
|
1455
|
+
description: "Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it to start a fresh one. Call get_product first to get valid sizes/colours (or an exact variant_id); for a design sold in two fits (regular / oversize, priced apart) you MUST pass `fit`. qty is capped at 20. Returns the cart_id and the server-side cart summary (VAT included where applicable). `region` (ISO-2) sets the ship-to country + currency of a NEW cart. A reused cart ships where it already ships: a `region` that disagrees with it is refused (start a new cart instead). If a reused cart has expired or was already checked out, a fresh one is made in the same region and the reply says cart_replaced: true.",
|
|
1328
1456
|
inputSchema: {
|
|
1329
1457
|
handle: z.string().optional().describe("product handle (resolves a variant by size + colour; call get_product for valid values)"),
|
|
1330
1458
|
variant_id: z.string().optional().describe("a specific variant id from get_product (overrides handle/size/colour)"),
|
|
1331
1459
|
fit: z.string().optional().describe("regular or oversize; REQUIRED when the design comes in both (get_product lists them)"),
|
|
1332
|
-
size: z.string().optional().describe("size label from get_product (
|
|
1333
|
-
colour: z.string().optional().describe("piece colour from get_product, e.g. black / white
|
|
1460
|
+
size: z.string().optional().describe("size label from get_product (it lists the sizes you can buy where the cart ships)"),
|
|
1461
|
+
colour: z.string().optional().describe("piece colour from get_product, e.g. black / white (default: the first colour you can buy where the cart ships)"),
|
|
1334
1462
|
qty: z.number().int().positive().max(20).optional().describe("quantity, 1\u201320 (default 1)"),
|
|
1335
1463
|
cart_id: z.string().optional().describe("reuse this cart; omit to create a new one"),
|
|
1336
|
-
region: z.string().optional().describe("ISO-2 ship-to country for a NEW cart (e.g. se / us / de);
|
|
1464
|
+
region: z.string().optional().describe("ISO-2 ship-to country for a NEW cart (e.g. se / us / de); with a cart_id it must match where that cart ships, or nothing is added")
|
|
1337
1465
|
},
|
|
1338
1466
|
handler: async (args) => {
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1467
|
+
const reuse = typeof args.cart_id === "string" && args.cart_id.trim() ? args.cart_id.trim() : void 0;
|
|
1468
|
+
const explicitCC = typeof args.region === "string" && args.region.trim().toLowerCase() || void 0;
|
|
1469
|
+
let existing = null;
|
|
1470
|
+
if (reuse) {
|
|
1471
|
+
try {
|
|
1472
|
+
existing = await client.getCart(reuse);
|
|
1473
|
+
} catch (e) {
|
|
1474
|
+
if (!(e instanceof RecoverableCartError)) throw e;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
let region;
|
|
1478
|
+
let shipCC;
|
|
1479
|
+
if (existing) {
|
|
1480
|
+
const to = await cartShipTo(existing, () => client.getRegions());
|
|
1481
|
+
if (!to) {
|
|
1482
|
+
return errorText(`can't tell where cart ${sanitize(reuse, 60)} ships, so nothing was added. omit cart_id to start a new cart.`);
|
|
1483
|
+
}
|
|
1484
|
+
if (shipToDisagrees(explicitCC, to)) {
|
|
1485
|
+
return errorText(
|
|
1486
|
+
`cart ${sanitize(reuse, 60)} ships to ${sanitize(to.country, 8)}, not ${sanitize(explicitCC, 8)}. nothing was added. omit region to keep adding to this cart, or omit cart_id to start a new cart for ${sanitize(explicitCC, 8)}.`
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
region = to.region;
|
|
1490
|
+
shipCC = to.country;
|
|
1491
|
+
} else {
|
|
1492
|
+
const rr = await client.resolveRegionResult(explicitCC || regionCC);
|
|
1493
|
+
if (rr.requested && !rr.matched) {
|
|
1494
|
+
return errorText(`we don't ship to ${sanitize(rr.requested, 8)} yet \u2014 nothing was added. (list_products with a supported region to see prices.)`);
|
|
1495
|
+
}
|
|
1496
|
+
region = rr.region;
|
|
1497
|
+
shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1343
1498
|
}
|
|
1344
|
-
const region = rr.region;
|
|
1345
1499
|
const qty = typeof args.qty === "number" ? args.qty : 1;
|
|
1346
1500
|
let variantId = typeof args.variant_id === "string" ? args.variant_id : void 0;
|
|
1347
1501
|
if (variantId) {
|
|
@@ -1355,13 +1509,14 @@ function makeTools(client, opts = {}) {
|
|
|
1355
1509
|
const vColour = variantColour(v);
|
|
1356
1510
|
const vSize = sizeOfVariant(v);
|
|
1357
1511
|
const gFit = gateFit(owner, variantFit(v));
|
|
1358
|
-
const shipCC2 =
|
|
1359
|
-
|
|
1512
|
+
const shipCC2 = shipCC;
|
|
1513
|
+
const supplier = supplierOf(owner);
|
|
1514
|
+
if (gFit && vColour && !colourBuyable(gFit, vColour, shipCC2, supplier)) {
|
|
1360
1515
|
return errorText(
|
|
1361
1516
|
`${sanitize(vColour, 20)} is not available in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1362
1517
|
);
|
|
1363
1518
|
}
|
|
1364
|
-
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2)) {
|
|
1519
|
+
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2, supplier)) {
|
|
1365
1520
|
return errorText(
|
|
1366
1521
|
`size ${sanitize(vSize, 20)} is not made in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1367
1522
|
);
|
|
@@ -1377,7 +1532,6 @@ function makeTools(client, opts = {}) {
|
|
|
1377
1532
|
return errorText(`no product with handle "${sanitize(handle, 60)}".`);
|
|
1378
1533
|
}
|
|
1379
1534
|
const size = typeof args.size === "string" ? args.size : void 0;
|
|
1380
|
-
const shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1381
1535
|
const r = resolveVariant(product, {
|
|
1382
1536
|
fit: typeof args.fit === "string" ? args.fit : void 0,
|
|
1383
1537
|
size,
|
|
@@ -1414,27 +1568,31 @@ function makeTools(client, opts = {}) {
|
|
|
1414
1568
|
}
|
|
1415
1569
|
variantId = r.variant.id;
|
|
1416
1570
|
}
|
|
1417
|
-
const reuse = typeof args.cart_id === "string" ? args.cart_id : void 0;
|
|
1418
1571
|
const addTo = async (cartId) => client.addLineItem(cartId, variantId, qty);
|
|
1419
1572
|
let cart;
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
throw e;
|
|
1573
|
+
let replaced = !!reuse && !existing;
|
|
1574
|
+
if (existing) {
|
|
1575
|
+
try {
|
|
1576
|
+
cart = await addTo(reuse);
|
|
1577
|
+
} catch (e) {
|
|
1578
|
+
if (!(e instanceof RecoverableCartError)) throw e;
|
|
1579
|
+
replaced = true;
|
|
1580
|
+
cart = await addTo((await client.createCart(region)).id);
|
|
1429
1581
|
}
|
|
1582
|
+
} else {
|
|
1583
|
+
cart = await addTo((await client.createCart(region)).id);
|
|
1430
1584
|
}
|
|
1431
1585
|
const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0);
|
|
1586
|
+
const replacedNote = replaced ? `
|
|
1587
|
+
cart ${sanitize(reuse, 60)} had expired or was already checked out, so this is a NEW cart${existing ? " (same region)" : ""}. use the new cart_id from now on.` : "";
|
|
1432
1588
|
return result(
|
|
1433
1589
|
`added.
|
|
1434
|
-
cart_id: ${cart.id} (reuse this handle on the next add / checkout)
|
|
1590
|
+
cart_id: ${cart.id} (reuse this handle on the next add / checkout)${replacedNote}
|
|
1435
1591
|
${cartSummary(cart, region)}`,
|
|
1436
1592
|
{
|
|
1437
1593
|
cart_id: cart.id,
|
|
1594
|
+
// only when a cart_id was passed, so a fresh-cart reply is unchanged
|
|
1595
|
+
...reuse ? { cart_replaced: replaced, ...replaced ? { replaced_cart_id: reuse } : {} } : {},
|
|
1438
1596
|
total: cart.total ?? null,
|
|
1439
1597
|
currency: cart.currency_code ?? region.currency_code ?? null,
|
|
1440
1598
|
item_count: itemCount,
|
|
@@ -1554,7 +1712,7 @@ ${cartSummary(cart, region)}`,
|
|
|
1554
1712
|
const out = redactForOutput({ status: res.status, order_id: res.order_id, summary: s ?? null });
|
|
1555
1713
|
let human;
|
|
1556
1714
|
if (res.status === "placed") {
|
|
1557
|
-
const lines = [`
|
|
1715
|
+
const lines = [`placed \u2014 order ${s?.display_id ? `#${s.display_id}` : "created"}. order_id: ${res.order_id ?? "\u2014"}`];
|
|
1558
1716
|
if (s) {
|
|
1559
1717
|
const cur = s.currency_code;
|
|
1560
1718
|
const vat = taxSuffix(s.is_tax_inclusive === true);
|