@binaryandthread/mcp 0.4.12 → 0.4.14
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 +5 -0
- package/dist/bin.js +146 -35
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,3 +117,8 @@ fits (regular and oversize, priced apart), `list_products` marks it (`fits`, `pr
|
|
|
117
117
|
lists sizes and prices per fit (pass `fit` to narrow), and `add_to_cart` **requires** `fit` for it:
|
|
118
118
|
without it the tool returns an error naming both fits, and nothing is added. Colours and sizes follow the ship-to region:
|
|
119
119
|
`get_product` lists the ones you can buy where the cart ships.
|
|
120
|
+
|
|
121
|
+
`add_to_cart` by `handle` also **requires** `size` whenever the colour comes in more than one: without it
|
|
122
|
+
the tool returns the sizes and asks the agent to put the choice to the buyer, and nothing is added. A
|
|
123
|
+
`variant_id` already names its size. `get_cart` and `add_to_cart` list each line's `size` and `colour`,
|
|
124
|
+
and `get_cart` on a cart that was already checked out says so (`completed: true`) instead of listing it.
|
package/dist/bin.js
CHANGED
|
@@ -220,13 +220,19 @@ var StoreClient = class {
|
|
|
220
220
|
} catch {
|
|
221
221
|
parsed = {};
|
|
222
222
|
}
|
|
223
|
-
|
|
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 (
|
|
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", {
|
|
@@ -624,12 +630,20 @@ function supplierOf(p) {
|
|
|
624
630
|
return v === "gelato" || v === "printful" ? v : null;
|
|
625
631
|
}
|
|
626
632
|
var PRINTFUL_BLANK_COLOURS = {
|
|
627
|
-
"ascolour-5082": ["black", "white", "coal", "eucalyptus", "bone", "grey", "khaki", "indigo"]
|
|
633
|
+
"ascolour-5082": ["black", "white", "coal", "eucalyptus", "bone", "grey", "khaki", "indigo"],
|
|
628
634
|
// TB124, TB126
|
|
635
|
+
"bellacanvas-4711": ["asphalt", "dust", "navy", "maroon", "forest", "jet"]
|
|
636
|
+
// TB128: the sweatshirt; TB129: + jet (its Black)
|
|
629
637
|
};
|
|
630
638
|
var PRINTFUL_BLANK_SIZES = {
|
|
631
|
-
"ascolour-5082": ["s", "m", "l", "xl", "2xl"]
|
|
639
|
+
"ascolour-5082": ["s", "m", "l", "xl", "2xl"],
|
|
640
|
+
"bellacanvas-4711": ["s", "m", "l", "xl", "2xl"]
|
|
632
641
|
};
|
|
642
|
+
function printfulBlankOf(p) {
|
|
643
|
+
const v = (p.metadata ?? {})["bt_blank"];
|
|
644
|
+
if (v === void 0 || v === null) return "ascolour-5082";
|
|
645
|
+
return typeof v === "string" && Object.prototype.hasOwnProperty.call(PRINTFUL_BLANK_COLOURS, v) ? v : null;
|
|
646
|
+
}
|
|
633
647
|
var PRINTFUL_SERVED_COUNTRIES = [
|
|
634
648
|
"se",
|
|
635
649
|
"us",
|
|
@@ -660,29 +674,30 @@ var PRINTFUL_SERVED_COUNTRIES = [
|
|
|
660
674
|
"si",
|
|
661
675
|
"es"
|
|
662
676
|
];
|
|
663
|
-
function printfulBlankFor(fit, countryCode) {
|
|
677
|
+
function printfulBlankFor(fit, countryCode, productBlank = "ascolour-5082") {
|
|
664
678
|
if (!PRINTFUL_SERVED_COUNTRIES.includes((countryCode ?? "").trim().toLowerCase())) return null;
|
|
665
|
-
|
|
679
|
+
if (productBlank === null) return null;
|
|
680
|
+
return fit === "regular" ? productBlank : null;
|
|
666
681
|
}
|
|
667
682
|
function sizeKey(size) {
|
|
668
683
|
const s = size.trim().toLowerCase();
|
|
669
684
|
return s === "xxl" ? "2xl" : s === "xxxl" ? "3xl" : s;
|
|
670
685
|
}
|
|
671
|
-
function blankRule(fit, countryCode, supplier) {
|
|
686
|
+
function blankRule(fit, countryCode, supplier, productBlank) {
|
|
672
687
|
if (supplier === null) return null;
|
|
673
688
|
if (supplier === "printful") {
|
|
674
|
-
const pb = printfulBlankFor(fit, countryCode);
|
|
689
|
+
const pb = printfulBlankFor(fit, countryCode, productBlank);
|
|
675
690
|
return pb ? { colours: PRINTFUL_BLANK_COLOURS[pb], sizes: PRINTFUL_BLANK_SIZES[pb] } : null;
|
|
676
691
|
}
|
|
677
692
|
const blank = blankFor(fit, countryCode);
|
|
678
693
|
return blank ? { colours: BLANK_COLOURS[blank], sizes: BLANK_SIZES[blank] } : null;
|
|
679
694
|
}
|
|
680
|
-
function colourBuyable(fit, colour, countryCode, supplier = "gelato") {
|
|
681
|
-
const rule = blankRule(fit, countryCode, supplier);
|
|
695
|
+
function colourBuyable(fit, colour, countryCode, supplier = "gelato", printfulBlank = "ascolour-5082") {
|
|
696
|
+
const rule = blankRule(fit, countryCode, supplier, printfulBlank);
|
|
682
697
|
return !!rule && rule.colours.includes(colour.trim().toLowerCase());
|
|
683
698
|
}
|
|
684
|
-
function sizeBuyable(fit, size, countryCode, supplier = "gelato") {
|
|
685
|
-
const rule = blankRule(fit, countryCode, supplier);
|
|
699
|
+
function sizeBuyable(fit, size, countryCode, supplier = "gelato", printfulBlank = "ascolour-5082") {
|
|
700
|
+
const rule = blankRule(fit, countryCode, supplier, printfulBlank);
|
|
686
701
|
return !!rule && rule.sizes.includes(sizeKey(size));
|
|
687
702
|
}
|
|
688
703
|
function legacyFit(p) {
|
|
@@ -701,11 +716,12 @@ function variantsOfFit(p, fit) {
|
|
|
701
716
|
function coloursFor(p, fit, countryCode) {
|
|
702
717
|
const gate = gateFit(p, fit);
|
|
703
718
|
const supplier = supplierOf(p);
|
|
719
|
+
const pb = printfulBlankOf(p);
|
|
704
720
|
const out = [];
|
|
705
721
|
for (const v of variantsOfFit(p, fit)) {
|
|
706
722
|
const c = variantColour(v);
|
|
707
723
|
if (!c || out.includes(c)) continue;
|
|
708
|
-
if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode, supplier)) continue;
|
|
724
|
+
if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode, supplier, pb)) continue;
|
|
709
725
|
out.push(c);
|
|
710
726
|
}
|
|
711
727
|
return out;
|
|
@@ -713,13 +729,14 @@ function coloursFor(p, fit, countryCode) {
|
|
|
713
729
|
function sizesFor(p, fit, colour, countryCode) {
|
|
714
730
|
const gate = gateFit(p, fit);
|
|
715
731
|
const supplier = supplierOf(p);
|
|
732
|
+
const pb = printfulBlankOf(p);
|
|
716
733
|
const target = colour.trim().toLowerCase();
|
|
717
734
|
const out = [];
|
|
718
735
|
for (const v of variantsOfFit(p, fit)) {
|
|
719
736
|
if (variantColour(v) !== target) continue;
|
|
720
737
|
const s = sizeOfVariant(v);
|
|
721
738
|
if (!s || out.includes(s)) continue;
|
|
722
|
-
if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode, supplier)) continue;
|
|
739
|
+
if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode, supplier, pb)) continue;
|
|
723
740
|
out.push(s);
|
|
724
741
|
}
|
|
725
742
|
return out;
|
|
@@ -758,21 +775,22 @@ function resolveVariant(p, opts) {
|
|
|
758
775
|
const pool = variantsOfFit(p, fit);
|
|
759
776
|
const gate = gateFit(p, fit);
|
|
760
777
|
const supplier = supplierOf(p);
|
|
778
|
+
const pb = printfulBlankOf(p);
|
|
761
779
|
const colours = coloursFor(p, fit, opts.countryCode);
|
|
762
|
-
const colour = colourKey(opts.colour ?? "") || (colours[0] ?? null);
|
|
763
|
-
if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode, supplier)) {
|
|
780
|
+
const colour = colourKey(opts.colour ?? "", productColours(p)) || (colours[0] ?? null);
|
|
781
|
+
if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode, supplier, pb)) {
|
|
764
782
|
return { ok: false, reason: "fit_unavailable", fit: gate, colour };
|
|
765
783
|
}
|
|
766
784
|
const size = (opts.size ?? "").trim() || null;
|
|
767
785
|
const sizeLc = size?.toLowerCase();
|
|
768
786
|
const buyableSizes = colour ? sizesFor(p, fit, colour, opts.countryCode) : [];
|
|
769
787
|
const sizeExists = !!size && pool.some((x) => (sizeOfVariant(x) ?? x.title ?? "").toLowerCase() === size.toLowerCase());
|
|
770
|
-
if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode, supplier)) {
|
|
788
|
+
if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode, supplier, pb)) {
|
|
771
789
|
return { ok: false, reason: "size_unavailable", fit: gate, size, sizes: buyableSizes };
|
|
772
790
|
}
|
|
773
791
|
const sellable = gate && opts.countryCode !== void 0 ? pool.filter((x) => {
|
|
774
792
|
const s = sizeOfVariant(x);
|
|
775
|
-
return !s || sizeBuyable(gate, s, opts.countryCode, supplier);
|
|
793
|
+
return !s || sizeBuyable(gate, s, opts.countryCode, supplier, pb);
|
|
776
794
|
}) : pool;
|
|
777
795
|
const v = sellable.find((x) => {
|
|
778
796
|
const colourOk = !colour || variantColour(x) === colour;
|
|
@@ -797,15 +815,22 @@ var COLOUR_LABELS = {
|
|
|
797
815
|
bone: "Faded Bone",
|
|
798
816
|
grey: "Faded Grey",
|
|
799
817
|
khaki: "Faded Khaki",
|
|
800
|
-
indigo: "Faded Indigo"
|
|
818
|
+
indigo: "Faded Indigo",
|
|
819
|
+
jet: "Black"
|
|
801
820
|
};
|
|
802
821
|
function colourLabel(colour) {
|
|
803
822
|
const c = colour.trim();
|
|
804
823
|
if (!c) return c;
|
|
805
824
|
return COLOUR_LABELS[c.toLowerCase()] ?? c.charAt(0).toUpperCase() + c.slice(1).toLowerCase();
|
|
806
825
|
}
|
|
807
|
-
function colourKey(colour) {
|
|
826
|
+
function colourKey(colour, available) {
|
|
808
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;
|
|
809
834
|
const key = Object.entries(COLOUR_LABELS).find(([, label]) => label.toLowerCase() === c)?.[0];
|
|
810
835
|
return key ?? c;
|
|
811
836
|
}
|
|
@@ -921,6 +946,31 @@ function firstImageUrl(images) {
|
|
|
921
946
|
function escapeRegex(s) {
|
|
922
947
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
923
948
|
}
|
|
949
|
+
function sizeTokenOf(title) {
|
|
950
|
+
return titleSegments(title).find((s) => SIZE_TOKEN.test(s)) ?? null;
|
|
951
|
+
}
|
|
952
|
+
var SIZE_ORDER = ["xxs", "xs", "s", "m", "l", "xl", "2xl", "xxl", "3xl", "xxxl", "4xl", "5xl"];
|
|
953
|
+
function sortSizes(sizes) {
|
|
954
|
+
const rank = (s) => {
|
|
955
|
+
const i = SIZE_ORDER.indexOf(s.trim().toLowerCase());
|
|
956
|
+
return i === -1 ? SIZE_ORDER.length : i;
|
|
957
|
+
};
|
|
958
|
+
return [...sizes].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
959
|
+
}
|
|
960
|
+
function lineOption(item, titles) {
|
|
961
|
+
const o = item.variant?.options?.find((x) => titles.includes((x.option?.title ?? "").toLowerCase()));
|
|
962
|
+
const v = typeof o?.value === "string" ? o.value.trim() : "";
|
|
963
|
+
return v ? v : null;
|
|
964
|
+
}
|
|
965
|
+
function lineColour(item) {
|
|
966
|
+
const opt = lineOption(item, ["colour", "color"]);
|
|
967
|
+
if (opt) return opt.toLowerCase();
|
|
968
|
+
const vt = (item.variant_title ?? "").toString();
|
|
969
|
+
return vt ? variantColour({ id: "x", title: vt }) : null;
|
|
970
|
+
}
|
|
971
|
+
function lineSize(item) {
|
|
972
|
+
return lineOption(item, ["size"]) ?? sizeTokenOf(item.variant_title);
|
|
973
|
+
}
|
|
924
974
|
function orderStage(o) {
|
|
925
975
|
const status = (o.status ?? "").trim().toLowerCase();
|
|
926
976
|
if (status === "canceled" || status === "refunded") return "reverted";
|
|
@@ -1057,7 +1107,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
1057
1107
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1058
1108
|
|
|
1059
1109
|
// src/version.ts
|
|
1060
|
-
var VERSION = "0.4.
|
|
1110
|
+
var VERSION = "0.4.14";
|
|
1061
1111
|
|
|
1062
1112
|
// src/onboarding.ts
|
|
1063
1113
|
var GETTING_STARTED_URI = "bt://getting-started";
|
|
@@ -1275,12 +1325,18 @@ function taxSuffix(isTaxInclusive) {
|
|
|
1275
1325
|
return isTaxInclusive ? " incl. VAT" : "";
|
|
1276
1326
|
}
|
|
1277
1327
|
function cartItems(cart) {
|
|
1278
|
-
return (cart.items ?? []).map((li) =>
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1328
|
+
return (cart.items ?? []).map((li) => {
|
|
1329
|
+
const size = lineSize(li);
|
|
1330
|
+
const colour = lineColour(li);
|
|
1331
|
+
return {
|
|
1332
|
+
variant_id: li.variant_id ?? null,
|
|
1333
|
+
title: sanitize(li.product_title ?? li.title ?? "item", 80),
|
|
1334
|
+
size: size ? sanitize(size, 20) : null,
|
|
1335
|
+
colour: colour ? sanitize(colour, 20) : null,
|
|
1336
|
+
qty: li.quantity ?? 1,
|
|
1337
|
+
total: li.total ?? null
|
|
1338
|
+
};
|
|
1339
|
+
});
|
|
1284
1340
|
}
|
|
1285
1341
|
function cartSummary(cart, region) {
|
|
1286
1342
|
const lines = [];
|
|
@@ -1291,7 +1347,11 @@ function cartSummary(cart, region) {
|
|
|
1291
1347
|
} else {
|
|
1292
1348
|
for (const li of items) {
|
|
1293
1349
|
const title = sanitize(li.product_title ?? li.title ?? "item", 80);
|
|
1294
|
-
|
|
1350
|
+
const rawColour = lineColour(li);
|
|
1351
|
+
const colour = rawColour ? colourLabel(rawColour) : null;
|
|
1352
|
+
const size = lineSize(li);
|
|
1353
|
+
const tags = [colour, size].filter((x) => !!x).map((x) => ` \xB7 ${sanitize(x, 20)}`).join("");
|
|
1354
|
+
lines.push(` ${li.quantity}x ${title}${tags} \u2014 ${fmtPrice(li.total, cart.currency_code ?? region?.currency_code)}`);
|
|
1295
1355
|
}
|
|
1296
1356
|
}
|
|
1297
1357
|
const cur = cart.currency_code ?? region?.currency_code;
|
|
@@ -1459,7 +1519,10 @@ function makeTools(client, opts = {}) {
|
|
|
1459
1519
|
// the map used to hand out ids for colours the ship-to blank can't print, with the caveat buried
|
|
1460
1520
|
// in a separate unavailable_colours field. Same derived answer, on the row an agent actually uses.
|
|
1461
1521
|
// CAT-37 added the SIZE half — the US and EU oversize blanks have different size ranges.
|
|
1462
|
-
buyable: (
|
|
1522
|
+
buyable: (
|
|
1523
|
+
// TB129 review: judged by the product's OWN garment (a 4711 is not a 5082).
|
|
1524
|
+
(!gFit || !vColour || colourBuyable(gFit, vColour, shipCC, supplierOf(product), printfulBlankOf(product))) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC, supplierOf(product), printfulBlankOf(product)))
|
|
1525
|
+
)
|
|
1463
1526
|
};
|
|
1464
1527
|
});
|
|
1465
1528
|
const sizeList = colours.length > 0 ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)])) : (product.variants ?? []).map((v) => v.title ?? "?");
|
|
@@ -1498,12 +1561,12 @@ function makeTools(client, opts = {}) {
|
|
|
1498
1561
|
};
|
|
1499
1562
|
const add_to_cart = {
|
|
1500
1563
|
name: "add_to_cart",
|
|
1501
|
-
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.",
|
|
1564
|
+
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`. size is required when the colour comes in more than one size: ask the buyer, never choose one for them. 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.",
|
|
1502
1565
|
inputSchema: {
|
|
1503
1566
|
handle: z.string().optional().describe("product handle (resolves a variant by size + colour; call get_product for valid values)"),
|
|
1504
1567
|
variant_id: z.string().optional().describe("a specific variant id from get_product (overrides handle/size/colour)"),
|
|
1505
1568
|
fit: z.string().optional().describe("regular or oversize; REQUIRED when the design comes in both (get_product lists them)"),
|
|
1506
|
-
size: z.string().optional().describe("size label from get_product (it lists the sizes you can buy where the cart ships)"),
|
|
1569
|
+
size: z.string().optional().describe("size label from get_product (it lists the sizes you can buy where the cart ships); required when the colour comes in more than one size, ask the buyer"),
|
|
1507
1570
|
colour: z.string().optional().describe("piece colour from get_product, e.g. black / white, or the name shown, e.g. Faded Black (default: the first colour you can buy where the cart ships)"),
|
|
1508
1571
|
qty: z.number().int().positive().max(20).optional().describe("quantity, 1\u201320 (default 1)"),
|
|
1509
1572
|
cart_id: z.string().optional().describe("reuse this cart; omit to create a new one"),
|
|
@@ -1519,6 +1582,7 @@ function makeTools(client, opts = {}) {
|
|
|
1519
1582
|
} catch (e) {
|
|
1520
1583
|
if (!(e instanceof RecoverableCartError)) throw e;
|
|
1521
1584
|
}
|
|
1585
|
+
if (existing?.completed_at) existing = null;
|
|
1522
1586
|
}
|
|
1523
1587
|
let region;
|
|
1524
1588
|
let shipCC;
|
|
@@ -1557,12 +1621,13 @@ function makeTools(client, opts = {}) {
|
|
|
1557
1621
|
const gFit = gateFit(owner, variantFit(v));
|
|
1558
1622
|
const shipCC2 = shipCC;
|
|
1559
1623
|
const supplier = supplierOf(owner);
|
|
1560
|
-
|
|
1624
|
+
const blank = printfulBlankOf(owner);
|
|
1625
|
+
if (gFit && vColour && !colourBuyable(gFit, vColour, shipCC2, supplier, blank)) {
|
|
1561
1626
|
return errorText(
|
|
1562
1627
|
`${sanitize(colourLabel(vColour), 20)} is not available in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1563
1628
|
);
|
|
1564
1629
|
}
|
|
1565
|
-
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2, supplier)) {
|
|
1630
|
+
if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2, supplier, blank)) {
|
|
1566
1631
|
return errorText(
|
|
1567
1632
|
`size ${sanitize(vSize, 20)} is not made in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
|
|
1568
1633
|
);
|
|
@@ -1596,6 +1661,13 @@ function makeTools(client, opts = {}) {
|
|
|
1596
1661
|
`"${sanitize(handle, 60)}" isn't sold in fit "${sanitize(r.requested, 20)}"${r.fits.length ? ` \u2014 it comes in ${r.fits.join(" / ")}` : ""}. nothing was added.`
|
|
1597
1662
|
);
|
|
1598
1663
|
}
|
|
1664
|
+
const colourAsked = "colour" in r ? r.colour : null;
|
|
1665
|
+
const madeIn = coloursFor(product, productFits(product).length > 0 ? r.fit ?? null : null);
|
|
1666
|
+
if (colourAsked && (r.reason === "fit_unavailable" || r.reason === "not_found") && !madeIn.includes(colourAsked)) {
|
|
1667
|
+
return errorText(
|
|
1668
|
+
`"${sanitize(handle, 60)}" isn't made in ${sanitize(colourAsked, 20)}. colours: ${madeIn.map((c) => sanitize(c, 20)).join(" \xB7 ")} (pass one as colour). nothing was added.`
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1599
1671
|
if (r.reason === "fit_unavailable") {
|
|
1600
1672
|
return errorText(
|
|
1601
1673
|
`${sanitize(colourLabel(r.colour ?? ""), 20)} is not available in ${r.fit} for ${sanitize(shipCC ?? "this region", 8)}. nothing was added.`
|
|
@@ -1606,12 +1678,27 @@ function makeTools(client, opts = {}) {
|
|
|
1606
1678
|
`size ${sanitize(r.size, 20)} is not made in ${r.fit} for ${sanitize(shipCC ?? "this region", 8)}${r.sizes.length ? ` \u2014 sizes here: ${r.sizes.map((x) => sanitize(x, 20)).join(" ")}` : ""}. nothing was added.`
|
|
1607
1679
|
);
|
|
1608
1680
|
}
|
|
1681
|
+
if (size && r.colour && r.sizes.length) {
|
|
1682
|
+
return errorText(
|
|
1683
|
+
`"${sanitize(handle, 60)}" doesn't come in size ${sanitize(size, 20)} in ${sanitize(r.colour, 20)}. sizes: ${sortSizes(r.sizes).map((x) => sanitize(x, 20)).join(" ")}. nothing was added.`
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1609
1686
|
const have = r.colours.length ? ` available colours: ${r.colours.map((c) => sanitize(colourLabel(c), 20)).join(", ")}.` : "";
|
|
1610
1687
|
const haveSizes = r.sizes.length ? ` sizes: ${r.sizes.map((x) => sanitize(x, 20)).join(" ")}.` : "";
|
|
1611
1688
|
return errorText(
|
|
1612
1689
|
`no variant for "${sanitize(handle, 60)}"${r.fit ? ` fit ${r.fit}` : ""}${size ? ` size ${sanitize(size, 20)}` : ""}${r.colour ? ` (${sanitize(colourLabel(r.colour), 20)})` : ""}.${have}${haveSizes}`
|
|
1613
1690
|
);
|
|
1614
1691
|
}
|
|
1692
|
+
if (!size?.trim()) {
|
|
1693
|
+
const sizes = sortSizes(
|
|
1694
|
+
r.colour ? sizesFor(product, r.fit, r.colour, shipCC) : [...new Set((product.variants ?? []).map((v) => sizeOfVariant(v)).filter((s) => !!s))]
|
|
1695
|
+
);
|
|
1696
|
+
if (sizes.length > 1) {
|
|
1697
|
+
return errorText(
|
|
1698
|
+
`size required for "${sanitize(handle, 60)}": ${sizes.map((x) => sanitize(x, 20)).join(" ")}${r.colour ? ` in ${sanitize(r.colour, 20)}` : ""}. ask the buyer which size, don't choose for them, then pass size. nothing was added.`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1615
1702
|
variantId = r.variant.id;
|
|
1616
1703
|
}
|
|
1617
1704
|
const addTo = async (cartId) => client.addLineItem(cartId, variantId, qty);
|
|
@@ -1658,6 +1745,14 @@ ${cartSummary(cart, region)}`,
|
|
|
1658
1745
|
const region = await client.resolveRegion(regionCC);
|
|
1659
1746
|
try {
|
|
1660
1747
|
const cart = await client.getCart(cartId);
|
|
1748
|
+
if (cart.completed_at) {
|
|
1749
|
+
return {
|
|
1750
|
+
...errorText(
|
|
1751
|
+
"that cart was already checked out and became an order, so it cannot be edited. omit cart_id on the next add_to_cart to start a new one."
|
|
1752
|
+
),
|
|
1753
|
+
structuredContent: { completed: true }
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1661
1756
|
const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0);
|
|
1662
1757
|
return result(cartSummary(cart, region), {
|
|
1663
1758
|
cart_id: cart.id,
|
|
@@ -1695,12 +1790,28 @@ ${cartSummary(cart, region)}`,
|
|
|
1695
1790
|
}
|
|
1696
1791
|
return errorText("couldn't attach a shipping method to this cart \u2014 checkout isn't available for it right now.");
|
|
1697
1792
|
}
|
|
1698
|
-
const
|
|
1793
|
+
const completedAnswer = () => ({
|
|
1794
|
+
...errorText(
|
|
1795
|
+
"that cart was already checked out and became an order. call check_payment with its session_id, or omit cart_id on add_to_cart to start a new one."
|
|
1796
|
+
),
|
|
1797
|
+
structuredContent: { completed: true }
|
|
1798
|
+
});
|
|
1799
|
+
if (shipped.completed_at) {
|
|
1800
|
+
return completedAnswer();
|
|
1801
|
+
}
|
|
1802
|
+
let mint;
|
|
1803
|
+
try {
|
|
1804
|
+
mint = await client.mintCheckout(cartId, email, "mcp");
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
if (e instanceof StoreApiError && e.status === 409) {
|
|
1807
|
+
return completedAnswer();
|
|
1808
|
+
}
|
|
1809
|
+
throw e;
|
|
1810
|
+
}
|
|
1699
1811
|
const { checkout_url, session_id } = mint;
|
|
1700
1812
|
const finalTotal = mint.total ?? shipped.total ?? null;
|
|
1701
1813
|
const finalCurrency = mint.currency ?? shipped.currency_code ?? null;
|
|
1702
1814
|
const totalStr = `${fmtPrice(finalTotal, finalCurrency)}${taxSuffix(isCartTaxInclusive(shipped))}`;
|
|
1703
|
-
const discountLine = mint.discount && mint.discount.amount > 0 ? [` dev channel: \u2212${fmtPrice(mint.discount.amount, finalCurrency)} (${mint.discount.pct}% off \u2014 for buying from the terminal/agent)`] : [];
|
|
1704
1815
|
const moneyLines = cartMoneyRows({
|
|
1705
1816
|
itemTotal: shipped.item_total,
|
|
1706
1817
|
discountTotal: mint.discount?.amount,
|