@binaryandthread/mcp 0.4.7 → 0.4.9
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 +241 -49
- 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;
|
|
591
678
|
}
|
|
592
|
-
function
|
|
593
|
-
|
|
679
|
+
function colourBuyable(fit, colour, countryCode, supplier = "gelato") {
|
|
680
|
+
const rule = blankRule(fit, countryCode, supplier);
|
|
681
|
+
return !!rule && rule.colours.includes(colour.trim().toLowerCase());
|
|
682
|
+
}
|
|
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,83 @@ function firstImageUrl(images) {
|
|
|
805
900
|
function escapeRegex(s) {
|
|
806
901
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
807
902
|
}
|
|
903
|
+
function orderStage(o) {
|
|
904
|
+
const status = (o.status ?? "").trim().toLowerCase();
|
|
905
|
+
if (status === "canceled" || status === "refunded") return "reverted";
|
|
906
|
+
switch ((o.fulfillment_status ?? "").trim().toLowerCase()) {
|
|
907
|
+
case "canceled":
|
|
908
|
+
return "reverted";
|
|
909
|
+
case "delivered":
|
|
910
|
+
case "partially_delivered":
|
|
911
|
+
return "delivered";
|
|
912
|
+
case "shipped":
|
|
913
|
+
case "partially_shipped":
|
|
914
|
+
return "shipped";
|
|
915
|
+
case "fulfilled":
|
|
916
|
+
// made, not shipped: to the customer it is simply not shipped yet
|
|
917
|
+
case "partially_fulfilled":
|
|
918
|
+
case "not_fulfilled":
|
|
919
|
+
default:
|
|
920
|
+
return "committed";
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function customerFulfillmentStatus(word) {
|
|
924
|
+
const w = (word ?? "").trim().toLowerCase();
|
|
925
|
+
if (!w) return null;
|
|
926
|
+
return w === "fulfilled" || w === "partially_fulfilled" ? "not_fulfilled" : w;
|
|
927
|
+
}
|
|
928
|
+
async function cartShipTo(cart, regions) {
|
|
929
|
+
const embedded = cart.region?.countries?.length ? cart.region : null;
|
|
930
|
+
const id = cart.region_id ?? cart.region?.id ?? null;
|
|
931
|
+
const region = embedded ?? (id ? (await regions()).find((r) => r.id === id) ?? null : null);
|
|
932
|
+
if (!region) return null;
|
|
933
|
+
const regionCountries = (region.countries ?? []).map((c) => (c.iso_2 ?? "").trim().toLowerCase()).filter(Boolean);
|
|
934
|
+
const address = (cart.shipping_address?.country_code ?? "").trim().toLowerCase() || null;
|
|
935
|
+
const country = address ?? regionCountries[0] ?? null;
|
|
936
|
+
if (!country) return null;
|
|
937
|
+
return { region, country, fromAddress: address !== null, regionCountries };
|
|
938
|
+
}
|
|
939
|
+
function shipToDisagrees(requested, shipTo) {
|
|
940
|
+
const cc = (requested ?? "").trim().toLowerCase();
|
|
941
|
+
if (!cc || cc === shipTo.country) return false;
|
|
942
|
+
return shipTo.fromAddress || !shipTo.regionCountries.includes(cc);
|
|
943
|
+
}
|
|
808
944
|
function str(v, fallback) {
|
|
809
945
|
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
810
946
|
}
|
|
811
947
|
function productReadme(p) {
|
|
812
948
|
return typeof p.description === "string" ? p.description : "";
|
|
813
949
|
}
|
|
950
|
+
var GELATO_FALLBACK = {
|
|
951
|
+
material: "100% cotton",
|
|
952
|
+
weight: "180 g/m\xB2",
|
|
953
|
+
fit: "Unisex heavyweight crewneck",
|
|
954
|
+
delivery: "Ships in 2\u20135 business days + transit",
|
|
955
|
+
care: "Cold wash, line dry"
|
|
956
|
+
};
|
|
957
|
+
var PRINTFUL_FALLBACK = {
|
|
958
|
+
material: "100% cotton",
|
|
959
|
+
weight: "240 g/m\xB2",
|
|
960
|
+
fit: "Unisex heavyweight crewneck, garment-dyed",
|
|
961
|
+
delivery: "Ships in 2\u20135 business days + transit",
|
|
962
|
+
care: "Machine wash cold, inside out. Do not tumble dry or iron the print. Line dry in shade."
|
|
963
|
+
};
|
|
814
964
|
function productSpecs(p, fit) {
|
|
815
965
|
const md = p.metadata ?? {};
|
|
816
966
|
const byFit = md.specs_by_fit ?? null;
|
|
817
967
|
const fitKey = fit ?? (byFit && Object.keys(byFit).length === 1 ? Object.keys(byFit)[0] : void 0);
|
|
818
968
|
const src = fitKey && byFit?.[fitKey] || md;
|
|
819
969
|
const pick = (k, fallback) => str(src[k] ?? md[k], fallback);
|
|
970
|
+
const d = supplierOf(p) === "printful" ? PRINTFUL_FALLBACK : GELATO_FALLBACK;
|
|
820
971
|
return [
|
|
821
|
-
{ key: "material", value: pick("material",
|
|
822
|
-
{ key: "weight", value: pick("weight",
|
|
823
|
-
{ key: "fit", value: pick("fit",
|
|
972
|
+
{ key: "material", value: pick("material", d.material) },
|
|
973
|
+
{ key: "weight", value: pick("weight", d.weight) },
|
|
974
|
+
{ key: "fit", value: pick("fit", d.fit) },
|
|
824
975
|
{ key: "print", value: pick("print", "Front print") },
|
|
825
|
-
|
|
826
|
-
|
|
976
|
+
// Delivery: the product's own line first (Sam 2026-10-01, as the storefront since U1), else the same
|
|
977
|
+
// fallback wording for both suppliers. No per-region times.
|
|
978
|
+
{ key: "delivery", value: pick("delivery", d.delivery) },
|
|
979
|
+
{ key: "care", value: pick("care", d.care) }
|
|
827
980
|
];
|
|
828
981
|
}
|
|
829
982
|
function productLore(p) {
|
|
@@ -883,7 +1036,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
883
1036
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
884
1037
|
|
|
885
1038
|
// src/version.ts
|
|
886
|
-
var VERSION = "0.4.
|
|
1039
|
+
var VERSION = "0.4.9";
|
|
887
1040
|
|
|
888
1041
|
// src/onboarding.ts
|
|
889
1042
|
var GETTING_STARTED_URI = "bt://getting-started";
|
|
@@ -1285,7 +1438,7 @@ function makeTools(client, opts = {}) {
|
|
|
1285
1438
|
// the map used to hand out ids for colours the ship-to blank can't print, with the caveat buried
|
|
1286
1439
|
// in a separate unavailable_colours field. Same derived answer, on the row an agent actually uses.
|
|
1287
1440
|
// 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))
|
|
1441
|
+
buyable: (!gFit || !vColour || colourBuyable(gFit, vColour, shipCC, supplierOf(product))) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC, supplierOf(product)))
|
|
1289
1442
|
};
|
|
1290
1443
|
});
|
|
1291
1444
|
const sizeList = colours.length > 0 ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)])) : (product.variants ?? []).map((v) => v.title ?? "?");
|
|
@@ -1324,24 +1477,50 @@ function makeTools(client, opts = {}) {
|
|
|
1324
1477
|
};
|
|
1325
1478
|
const add_to_cart = {
|
|
1326
1479
|
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
|
|
1480
|
+
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
1481
|
inputSchema: {
|
|
1329
1482
|
handle: z.string().optional().describe("product handle (resolves a variant by size + colour; call get_product for valid values)"),
|
|
1330
1483
|
variant_id: z.string().optional().describe("a specific variant id from get_product (overrides handle/size/colour)"),
|
|
1331
1484
|
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
|
|
1485
|
+
size: z.string().optional().describe("size label from get_product (it lists the sizes you can buy where the cart ships)"),
|
|
1486
|
+
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
1487
|
qty: z.number().int().positive().max(20).optional().describe("quantity, 1\u201320 (default 1)"),
|
|
1335
1488
|
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);
|
|
1489
|
+
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
1490
|
},
|
|
1338
1491
|
handler: async (args) => {
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1492
|
+
const reuse = typeof args.cart_id === "string" && args.cart_id.trim() ? args.cart_id.trim() : void 0;
|
|
1493
|
+
const explicitCC = typeof args.region === "string" && args.region.trim().toLowerCase() || void 0;
|
|
1494
|
+
let existing = null;
|
|
1495
|
+
if (reuse) {
|
|
1496
|
+
try {
|
|
1497
|
+
existing = await client.getCart(reuse);
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
if (!(e instanceof RecoverableCartError)) throw e;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
let region;
|
|
1503
|
+
let shipCC;
|
|
1504
|
+
if (existing) {
|
|
1505
|
+
const to = await cartShipTo(existing, () => client.getRegions());
|
|
1506
|
+
if (!to) {
|
|
1507
|
+
return errorText(`can't tell where cart ${sanitize(reuse, 60)} ships, so nothing was added. omit cart_id to start a new cart.`);
|
|
1508
|
+
}
|
|
1509
|
+
if (shipToDisagrees(explicitCC, to)) {
|
|
1510
|
+
return errorText(
|
|
1511
|
+
`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)}.`
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
region = to.region;
|
|
1515
|
+
shipCC = to.country;
|
|
1516
|
+
} else {
|
|
1517
|
+
const rr = await client.resolveRegionResult(explicitCC || regionCC);
|
|
1518
|
+
if (rr.requested && !rr.matched) {
|
|
1519
|
+
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.)`);
|
|
1520
|
+
}
|
|
1521
|
+
region = rr.region;
|
|
1522
|
+
shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1343
1523
|
}
|
|
1344
|
-
const region = rr.region;
|
|
1345
1524
|
const qty = typeof args.qty === "number" ? args.qty : 1;
|
|
1346
1525
|
let variantId = typeof args.variant_id === "string" ? args.variant_id : void 0;
|
|
1347
1526
|
if (variantId) {
|
|
@@ -1355,13 +1534,14 @@ function makeTools(client, opts = {}) {
|
|
|
1355
1534
|
const vColour = variantColour(v);
|
|
1356
1535
|
const vSize = sizeOfVariant(v);
|
|
1357
1536
|
const gFit = gateFit(owner, variantFit(v));
|
|
1358
|
-
const shipCC2 =
|
|
1359
|
-
|
|
1537
|
+
const shipCC2 = shipCC;
|
|
1538
|
+
const supplier = supplierOf(owner);
|
|
1539
|
+
if (gFit && vColour && !colourBuyable(gFit, vColour, shipCC2, supplier)) {
|
|
1360
1540
|
return errorText(
|
|
1361
1541
|
`${sanitize(vColour, 20)} is not available in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1362
1542
|
);
|
|
1363
1543
|
}
|
|
1364
|
-
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2)) {
|
|
1544
|
+
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2, supplier)) {
|
|
1365
1545
|
return errorText(
|
|
1366
1546
|
`size ${sanitize(vSize, 20)} is not made in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1367
1547
|
);
|
|
@@ -1377,7 +1557,6 @@ function makeTools(client, opts = {}) {
|
|
|
1377
1557
|
return errorText(`no product with handle "${sanitize(handle, 60)}".`);
|
|
1378
1558
|
}
|
|
1379
1559
|
const size = typeof args.size === "string" ? args.size : void 0;
|
|
1380
|
-
const shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1381
1560
|
const r = resolveVariant(product, {
|
|
1382
1561
|
fit: typeof args.fit === "string" ? args.fit : void 0,
|
|
1383
1562
|
size,
|
|
@@ -1414,27 +1593,31 @@ function makeTools(client, opts = {}) {
|
|
|
1414
1593
|
}
|
|
1415
1594
|
variantId = r.variant.id;
|
|
1416
1595
|
}
|
|
1417
|
-
const reuse = typeof args.cart_id === "string" ? args.cart_id : void 0;
|
|
1418
1596
|
const addTo = async (cartId) => client.addLineItem(cartId, variantId, qty);
|
|
1419
1597
|
let cart;
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
throw e;
|
|
1598
|
+
let replaced = !!reuse && !existing;
|
|
1599
|
+
if (existing) {
|
|
1600
|
+
try {
|
|
1601
|
+
cart = await addTo(reuse);
|
|
1602
|
+
} catch (e) {
|
|
1603
|
+
if (!(e instanceof RecoverableCartError)) throw e;
|
|
1604
|
+
replaced = true;
|
|
1605
|
+
cart = await addTo((await client.createCart(region)).id);
|
|
1429
1606
|
}
|
|
1607
|
+
} else {
|
|
1608
|
+
cart = await addTo((await client.createCart(region)).id);
|
|
1430
1609
|
}
|
|
1431
1610
|
const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0);
|
|
1611
|
+
const replacedNote = replaced ? `
|
|
1612
|
+
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
1613
|
return result(
|
|
1433
1614
|
`added.
|
|
1434
|
-
cart_id: ${cart.id} (reuse this handle on the next add / checkout)
|
|
1615
|
+
cart_id: ${cart.id} (reuse this handle on the next add / checkout)${replacedNote}
|
|
1435
1616
|
${cartSummary(cart, region)}`,
|
|
1436
1617
|
{
|
|
1437
1618
|
cart_id: cart.id,
|
|
1619
|
+
// only when a cart_id was passed, so a fresh-cart reply is unchanged
|
|
1620
|
+
...reuse ? { cart_replaced: replaced, ...replaced ? { replaced_cart_id: reuse } : {} } : {},
|
|
1438
1621
|
total: cart.total ?? null,
|
|
1439
1622
|
currency: cart.currency_code ?? region.currency_code ?? null,
|
|
1440
1623
|
item_count: itemCount,
|
|
@@ -1554,7 +1737,7 @@ ${cartSummary(cart, region)}`,
|
|
|
1554
1737
|
const out = redactForOutput({ status: res.status, order_id: res.order_id, summary: s ?? null });
|
|
1555
1738
|
let human;
|
|
1556
1739
|
if (res.status === "placed") {
|
|
1557
|
-
const lines = [`
|
|
1740
|
+
const lines = [`placed \u2014 order ${s?.display_id ? `#${s.display_id}` : "created"}. order_id: ${res.order_id ?? "\u2014"}`];
|
|
1558
1741
|
if (s) {
|
|
1559
1742
|
const cur = s.currency_code;
|
|
1560
1743
|
const vat = taxSuffix(s.is_tax_inclusive === true);
|
|
@@ -1573,9 +1756,15 @@ ${cartSummary(cart, region)}`,
|
|
|
1573
1756
|
return { content: [{ type: "text", text: human }], structuredContent: out };
|
|
1574
1757
|
}
|
|
1575
1758
|
};
|
|
1759
|
+
const STAGE_WORDS = {
|
|
1760
|
+
committed: "placed, not shipped yet",
|
|
1761
|
+
shipped: "shipped",
|
|
1762
|
+
delivered: "delivered",
|
|
1763
|
+
reverted: "canceled or refunded"
|
|
1764
|
+
};
|
|
1576
1765
|
const get_order_status = {
|
|
1577
1766
|
name: "get_order_status",
|
|
1578
|
-
description: "Look up an order's status by its display id + the EXACT email on the order. Returns status
|
|
1767
|
+
description: "Look up an order's status by its display id + the EXACT email on the order. Returns status and stage (placed, shipped or delivered; no shipping address). A wrong id/email returns not-found (no enumeration).",
|
|
1579
1768
|
inputSchema: {
|
|
1580
1769
|
display_id: z.string().describe("the order's display id (e.g. 1042)"),
|
|
1581
1770
|
email: z.string().describe("the exact email on the order")
|
|
@@ -1597,17 +1786,20 @@ ${cartSummary(cart, region)}`,
|
|
|
1597
1786
|
return errorText(`no order matches that id + email.`);
|
|
1598
1787
|
}
|
|
1599
1788
|
const order = looked.order ?? looked;
|
|
1600
|
-
const
|
|
1789
|
+
const rawFulfillment = order["fulfillment_status"];
|
|
1790
|
+
const fulfillment = customerFulfillmentStatus(typeof rawFulfillment === "string" ? rawFulfillment : null);
|
|
1791
|
+
const status = sanitize(String(order["status"] ?? fulfillment ?? "unknown"), 60);
|
|
1601
1792
|
const display = sanitize(String(order["display_id"] ?? displayId), 30);
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
if (
|
|
1793
|
+
const stage = STAGE_WORDS[orderStage({ status: String(order["status"] ?? ""), fulfillment_status: fulfillment })];
|
|
1794
|
+
const lines = [`order #${display}`, `status: ${status}`, `stage: ${stage}`];
|
|
1795
|
+
if (fulfillment) {
|
|
1605
1796
|
lines.push(`fulfillment: ${sanitize(fulfillment, 60)}`);
|
|
1606
1797
|
}
|
|
1607
1798
|
return result(redactForOutput(lines.join("\n")), {
|
|
1608
1799
|
display_id: order["display_id"] ?? display,
|
|
1609
1800
|
status,
|
|
1610
|
-
|
|
1801
|
+
stage,
|
|
1802
|
+
fulfillment_status: fulfillment
|
|
1611
1803
|
});
|
|
1612
1804
|
}
|
|
1613
1805
|
};
|