@solumflow-app/crm-mcp-server 0.8.0 → 0.9.0
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 +16 -4
- package/dist/index.js +652 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
import { createClient } from "@solumflow-app/crm-client";
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
|
-
var version = "0.
|
|
9
|
+
var version = "0.9.0";
|
|
10
10
|
|
|
11
11
|
// src/catalog-migration-tools.ts
|
|
12
12
|
import { z as z4 } from "zod";
|
|
@@ -506,8 +506,8 @@ var page = {
|
|
|
506
506
|
function more(nextCursor) {
|
|
507
507
|
return nextCursor ? `More on the next page: cursor ${nextCursor}` : "";
|
|
508
508
|
}
|
|
509
|
-
function money(
|
|
510
|
-
return `${(
|
|
509
|
+
function money(cents3, currency2) {
|
|
510
|
+
return `${(cents3 / 100).toFixed(2)} ${currency2.toUpperCase()}`;
|
|
511
511
|
}
|
|
512
512
|
function fieldsNotOnPageNote(slugs) {
|
|
513
513
|
if (!slugs?.length) {
|
|
@@ -1280,7 +1280,11 @@ var CONFIGURATOR_LIMITS = {
|
|
|
1280
1280
|
expandedParts: 200,
|
|
1281
1281
|
repeatCount: 12,
|
|
1282
1282
|
repeatDepth: 2,
|
|
1283
|
-
annotations: 30
|
|
1283
|
+
annotations: 30,
|
|
1284
|
+
roomWallOptions: 12,
|
|
1285
|
+
roomFloorOptions: 8,
|
|
1286
|
+
focus: 60,
|
|
1287
|
+
focusNames: 20
|
|
1284
1288
|
};
|
|
1285
1289
|
function jsonByteLength(value) {
|
|
1286
1290
|
return new TextEncoder().encode(JSON.stringify(value)).length + jsonbSpacing(value);
|
|
@@ -1649,8 +1653,10 @@ var PRIMITIVE_PARAMS = {
|
|
|
1649
1653
|
// A quarter of an elliptical shell in plan view, filling the block's width
|
|
1650
1654
|
// and depth; `mirror` 0 bends it towards the right, 1 towards the left.
|
|
1651
1655
|
arc_panel: { required: ["thickness"], optional: ["mirror"] },
|
|
1652
|
-
// A thin strip that gives light.
|
|
1653
|
-
|
|
1656
|
+
// A thin strip that gives light. `facing` is where the light goes: 0 down
|
|
1657
|
+
// (the default: under a shelf, under a floating cabinet), 1 up, 2 forwards
|
|
1658
|
+
// into the room, 3 back against the wall.
|
|
1659
|
+
led_strip: { required: [], optional: ["facing"] },
|
|
1654
1660
|
// A wall with a rectangular opening, measured from the block's own corner.
|
|
1655
1661
|
niche_panel: {
|
|
1656
1662
|
required: ["niche_x", "niche_y", "niche_width", "niche_height"],
|
|
@@ -1666,13 +1672,24 @@ var PRIMITIVE_PARAMS = {
|
|
|
1666
1672
|
};
|
|
1667
1673
|
var CONFIGURATOR_MODEL_PATH_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.glb$/;
|
|
1668
1674
|
var CONFIGURATOR_FOLLOWS_PRESET = "preset";
|
|
1675
|
+
var MOTION_GROUP_PATTERN = /^[a-z](?:[a-z0-9_]|\{[ij]\}){0,39}$/;
|
|
1676
|
+
var MOTION_KINDS = ["hinge", "slide"];
|
|
1677
|
+
var MOTION_EDGES = ["bottom", "top", "left", "right"];
|
|
1678
|
+
var GeometryMotionSchema = z5.strictObject({
|
|
1679
|
+
group: z5.string().regex(MOTION_GROUP_PATTERN, "configurator.invalidName"),
|
|
1680
|
+
kind: z5.enum(MOTION_KINDS),
|
|
1681
|
+
edge: z5.enum(MOTION_EDGES).optional(),
|
|
1682
|
+
angle: FormulaSchema.optional(),
|
|
1683
|
+
distance: FormulaSchema.optional()
|
|
1684
|
+
});
|
|
1669
1685
|
var PrimitiveNodeFields = {
|
|
1670
1686
|
id: ConfiguratorNameSchema,
|
|
1671
1687
|
slot: ConfiguratorNameSchema,
|
|
1672
1688
|
size: Vector3Schema,
|
|
1673
1689
|
at: Vector3Schema,
|
|
1674
1690
|
visible: FormulaSchema.optional(),
|
|
1675
|
-
params: z5.record(ConfiguratorNameSchema, FormulaSchema).optional()
|
|
1691
|
+
params: z5.record(ConfiguratorNameSchema, FormulaSchema).optional(),
|
|
1692
|
+
motion: GeometryMotionSchema.optional()
|
|
1676
1693
|
};
|
|
1677
1694
|
var PrimitiveNodeSchema = z5.strictObject({
|
|
1678
1695
|
...PrimitiveNodeFields,
|
|
@@ -1762,6 +1779,26 @@ function geometryDocumentSchema(modelFile) {
|
|
|
1762
1779
|
});
|
|
1763
1780
|
}
|
|
1764
1781
|
var GeometryDocumentSchema = geometryDocumentSchema(ModelPathSchema);
|
|
1782
|
+
function motionNames(nodes) {
|
|
1783
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1784
|
+
const groups = /* @__PURE__ */ new Set();
|
|
1785
|
+
const walk = (list) => {
|
|
1786
|
+
for (const node of list) {
|
|
1787
|
+
ids.add(node.id);
|
|
1788
|
+
if (node.type === "repeat") {
|
|
1789
|
+
walk(node.children);
|
|
1790
|
+
} else if (node.motion) {
|
|
1791
|
+
groups.add(node.motion.group);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
walk(nodes);
|
|
1796
|
+
return { ids, groups };
|
|
1797
|
+
}
|
|
1798
|
+
function motionGroupMatches(template, name) {
|
|
1799
|
+
const pattern = template.split(/\{[ij]\}/).join("[1-9][0-9]?");
|
|
1800
|
+
return new RegExp(`^${pattern}$`).test(name);
|
|
1801
|
+
}
|
|
1765
1802
|
function choiceModelBlocks(nodes) {
|
|
1766
1803
|
const found = [];
|
|
1767
1804
|
const walk = (list) => {
|
|
@@ -2001,6 +2038,32 @@ function geometryDocumentProblems(document, context = {}) {
|
|
|
2001
2038
|
}
|
|
2002
2039
|
checkFormula2(formula, [...nodePath, "params", key], scope);
|
|
2003
2040
|
}
|
|
2041
|
+
if (node.motion) {
|
|
2042
|
+
const motion = node.motion;
|
|
2043
|
+
const motionPath = [...nodePath, "motion"];
|
|
2044
|
+
if (motion.angle !== void 0) {
|
|
2045
|
+
checkFormula2(motion.angle, [...motionPath, "angle"], scope);
|
|
2046
|
+
}
|
|
2047
|
+
if (motion.distance !== void 0) {
|
|
2048
|
+
checkFormula2(motion.distance, [...motionPath, "distance"], scope);
|
|
2049
|
+
}
|
|
2050
|
+
for (const placeholder of motion.group.match(/\{([ij])\}/g) ?? []) {
|
|
2051
|
+
if (!scope.has(placeholder.slice(1, 2))) {
|
|
2052
|
+
problems.push(
|
|
2053
|
+
issue([...motionPath, "group"], "configurator.unknownName", {
|
|
2054
|
+
name: placeholder
|
|
2055
|
+
})
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (motion.kind === "hinge" && motion.distance !== void 0 || motion.kind === "slide" && (motion.edge !== void 0 || motion.angle !== void 0)) {
|
|
2060
|
+
problems.push(
|
|
2061
|
+
issue(motionPath, "configurator.motionMixed", {
|
|
2062
|
+
kind: motion.kind
|
|
2063
|
+
})
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2004
2067
|
if (node.follows !== void 0 && context.parameters) {
|
|
2005
2068
|
problems.push(
|
|
2006
2069
|
...followProblems(node.follows, nodePath, {
|
|
@@ -2049,6 +2112,34 @@ function followProblems(follows, path, context) {
|
|
|
2049
2112
|
|
|
2050
2113
|
// ../features/product-configurator/src/shared/configurator-structure.schema.ts
|
|
2051
2114
|
import { z as z6 } from "zod";
|
|
2115
|
+
|
|
2116
|
+
// ../features/product-configurator/src/shared/room-palette.ts
|
|
2117
|
+
var ROOM_WALL_PALETTE = [
|
|
2118
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2119
|
+
{ code: "gebroken-wit", label: "Gebroken wit", color: "#ece8e1" },
|
|
2120
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2121
|
+
{ code: "warm-grijs", label: "Warm grijs", color: "#b9b2a8" },
|
|
2122
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2123
|
+
{ code: "zand", label: "Zand", color: "#d6c5a8" },
|
|
2124
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2125
|
+
{ code: "saliegroen", label: "Saliegroen", color: "#a7b29c" },
|
|
2126
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2127
|
+
{ code: "nachtblauw", label: "Nachtblauw", color: "#2f3a4c" },
|
|
2128
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2129
|
+
{ code: "antraciet", label: "Antraciet", color: "#3d3d3f" }
|
|
2130
|
+
];
|
|
2131
|
+
var ROOM_FLOOR_PALETTE = [
|
|
2132
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2133
|
+
{ code: "licht-eiken", label: "Licht eiken", color: "#c8a77e" },
|
|
2134
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2135
|
+
{ code: "donker-eiken", label: "Donker eiken", color: "#6b4f38" },
|
|
2136
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2137
|
+
{ code: "betongrijs", label: "Betongrijs", color: "#9a9893" },
|
|
2138
|
+
// i18n-exempt: the Dutch name of a colour in a palette that is also served to customers' own sites, where there is no catalogue
|
|
2139
|
+
{ code: "warm-beige", label: "Warm beige tegel", color: "#d9cbb7" }
|
|
2140
|
+
];
|
|
2141
|
+
|
|
2142
|
+
// ../features/product-configurator/src/shared/configurator-structure.schema.ts
|
|
2052
2143
|
var LabelSchema = z6.string().trim().min(1).max(80);
|
|
2053
2144
|
var CONFIGURATOR_TEXTURE_PATH_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(webp|jpg|jpeg)$/;
|
|
2054
2145
|
var TexturePathSchema = z6.string().regex(CONFIGURATOR_TEXTURE_PATH_PATTERN, "configurator.invalidTexturePath");
|
|
@@ -2106,6 +2197,13 @@ var materialSchema = (picture) => z6.strictObject({
|
|
|
2106
2197
|
opacity: z6.number().min(0.05).max(1).optional(),
|
|
2107
2198
|
/** Gives light rather than reflecting it: a flame, a LED. */
|
|
2108
2199
|
emissive: z6.boolean().optional(),
|
|
2200
|
+
/**
|
|
2201
|
+
* The colour temperature of a light, in kelvin: a LED strip sold as
|
|
2202
|
+
* 3000 K glows and lights in that colour rather than in `color`, which
|
|
2203
|
+
* stays the swatch's fallback. A strip that can do several (2700-6500 K)
|
|
2204
|
+
* names the one to show: 4000 K.
|
|
2205
|
+
*/
|
|
2206
|
+
kelvin: z6.number().int().min(1500).max(1e4).optional(),
|
|
2109
2207
|
priceGroup: ConfiguratorCodeSchema.optional()
|
|
2110
2208
|
});
|
|
2111
2209
|
var ConfiguratorChoiceListSchema = choiceListSchema(TexturePathSchema);
|
|
@@ -2186,9 +2284,37 @@ var CONFIGURATOR_SCENE_LIGHTINGS = [
|
|
|
2186
2284
|
"daylight"
|
|
2187
2285
|
];
|
|
2188
2286
|
var CONFIGURATOR_SCENE_EDGES = ["soft", "sharp"];
|
|
2287
|
+
var RoomColorSchema = z6.strictObject({
|
|
2288
|
+
code: ConfiguratorCodeSchema,
|
|
2289
|
+
label: LabelSchema,
|
|
2290
|
+
color: z6.string().regex(/^#[0-9a-f]{6}$/i, "configurator.invalidColor")
|
|
2291
|
+
});
|
|
2292
|
+
var roomSurfaceSchema = (max) => z6.strictObject({
|
|
2293
|
+
/** The code shown first; absent is the first option. */
|
|
2294
|
+
default: ConfiguratorCodeSchema.optional(),
|
|
2295
|
+
/** Absent offers the package's own palette (`room-palette.ts`). */
|
|
2296
|
+
options: z6.array(RoomColorSchema).min(1).max(max).optional()
|
|
2297
|
+
});
|
|
2298
|
+
var ConfiguratorRoomSchema = z6.strictObject({
|
|
2299
|
+
wall: roomSurfaceSchema(CONFIGURATOR_LIMITS.roomWallOptions).optional(),
|
|
2300
|
+
floor: roomSurfaceSchema(CONFIGURATOR_LIMITS.roomFloorOptions).optional(),
|
|
2301
|
+
/** Millimetres of room beside the furniture on each side; 1500 by default. */
|
|
2302
|
+
margin: z6.number().min(0).max(5e3).optional(),
|
|
2303
|
+
/** Millimetres of floor in front of the wall; 3500 by default. */
|
|
2304
|
+
depth: z6.number().min(1e3).max(1e4).optional()
|
|
2305
|
+
});
|
|
2189
2306
|
var ConfiguratorSceneSchema = z6.strictObject({
|
|
2190
2307
|
lighting: z6.enum(CONFIGURATOR_SCENE_LIGHTINGS).optional(),
|
|
2191
|
-
edges: z6.enum(CONFIGURATOR_SCENE_EDGES).optional()
|
|
2308
|
+
edges: z6.enum(CONFIGURATOR_SCENE_EDGES).optional(),
|
|
2309
|
+
room: ConfiguratorRoomSchema.optional()
|
|
2310
|
+
});
|
|
2311
|
+
var FocusNameSchema = z6.string().regex(/^[a-z][a-z0-9_]{0,39}$/, "configurator.invalidName");
|
|
2312
|
+
var ConfiguratorFocusSchema = z6.strictObject({
|
|
2313
|
+
param: ConfiguratorNameSchema,
|
|
2314
|
+
when: ConfiguratorConditionSchema.optional(),
|
|
2315
|
+
open: z6.array(FocusNameSchema).max(CONFIGURATOR_LIMITS.focusNames).optional(),
|
|
2316
|
+
frame: z6.array(FocusNameSchema).min(1).max(CONFIGURATOR_LIMITS.focusNames).optional(),
|
|
2317
|
+
night: z6.boolean().optional()
|
|
2192
2318
|
});
|
|
2193
2319
|
var structureObjectSchema = (files) => z6.strictObject({
|
|
2194
2320
|
schemaVersion: z6.literal(1),
|
|
@@ -2222,6 +2348,8 @@ var structureObjectSchema = (files) => z6.strictObject({
|
|
|
2222
2348
|
required: z6.boolean()
|
|
2223
2349
|
}).optional(),
|
|
2224
2350
|
rules: z6.array(ConfiguratorRuleSchema).max(CONFIGURATOR_LIMITS.rules),
|
|
2351
|
+
/** What the picture opens and looks at when a choice changes. */
|
|
2352
|
+
focus: z6.array(ConfiguratorFocusSchema).max(CONFIGURATOR_LIMITS.focus).optional(),
|
|
2225
2353
|
priceRules: z6.array(ConfiguratorPriceRuleSchema).max(CONFIGURATOR_LIMITS.priceRules)
|
|
2226
2354
|
});
|
|
2227
2355
|
var ConfiguratorStructureObjectSchema = structureObjectSchema({
|
|
@@ -2505,6 +2633,78 @@ function configuratorStructureProblems(structure) {
|
|
|
2505
2633
|
);
|
|
2506
2634
|
}
|
|
2507
2635
|
});
|
|
2636
|
+
const shape = motionNames(structure.geometry.root);
|
|
2637
|
+
const isGroup = (name) => [...shape.groups].some((template) => motionGroupMatches(template, name));
|
|
2638
|
+
(structure.focus ?? []).forEach((focus, index) => {
|
|
2639
|
+
const path = ["focus", index];
|
|
2640
|
+
if (!parameters.has(focus.param)) {
|
|
2641
|
+
problems.push(
|
|
2642
|
+
issue([...path, "param"], "configurator.unknownParam", {
|
|
2643
|
+
key: focus.param
|
|
2644
|
+
})
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
if (focus.when) {
|
|
2648
|
+
const conditionParam = parameters.get(focus.when.param);
|
|
2649
|
+
const valueProblem = conditionParam ? conditionValueProblem(
|
|
2650
|
+
focus.when.op,
|
|
2651
|
+
focus.when.value,
|
|
2652
|
+
conditionParam,
|
|
2653
|
+
lists,
|
|
2654
|
+
materials
|
|
2655
|
+
) : "configurator.unknownParam";
|
|
2656
|
+
if (valueProblem) {
|
|
2657
|
+
problems.push(
|
|
2658
|
+
issue([...path, "when"], valueProblem, { key: focus.when.param })
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
(focus.open ?? []).forEach((name, nameIndex) => {
|
|
2663
|
+
if (!isGroup(name)) {
|
|
2664
|
+
problems.push(
|
|
2665
|
+
issue(
|
|
2666
|
+
[...path, "open", nameIndex],
|
|
2667
|
+
"configurator.unknownMotionGroup",
|
|
2668
|
+
{
|
|
2669
|
+
name
|
|
2670
|
+
}
|
|
2671
|
+
)
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
(focus.frame ?? []).forEach((name, nameIndex) => {
|
|
2676
|
+
if (!shape.ids.has(name) && !isGroup(name)) {
|
|
2677
|
+
problems.push(
|
|
2678
|
+
issue([...path, "frame", nameIndex], "configurator.unknownFrame", {
|
|
2679
|
+
name
|
|
2680
|
+
})
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
});
|
|
2684
|
+
});
|
|
2685
|
+
const room = structure.scene?.room;
|
|
2686
|
+
for (const surface of ["wall", "floor"]) {
|
|
2687
|
+
const own = room?.[surface];
|
|
2688
|
+
const path = ["scene", "room", surface];
|
|
2689
|
+
const codes = own?.options ? uniqueKeys(
|
|
2690
|
+
own.options,
|
|
2691
|
+
(option) => option.code,
|
|
2692
|
+
[...path, "options"],
|
|
2693
|
+
"code",
|
|
2694
|
+
problems
|
|
2695
|
+
) : new Set(
|
|
2696
|
+
(surface === "wall" ? ROOM_WALL_PALETTE : ROOM_FLOOR_PALETTE).map(
|
|
2697
|
+
(option) => option.code
|
|
2698
|
+
)
|
|
2699
|
+
);
|
|
2700
|
+
if (own?.default !== void 0 && !codes.has(own.default)) {
|
|
2701
|
+
problems.push(
|
|
2702
|
+
issue([...path, "default"], "configurator.unknownCode", {
|
|
2703
|
+
code: own.default
|
|
2704
|
+
})
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2508
2708
|
const geometryParameters = new Map(
|
|
2509
2709
|
structure.parameters.map((parameter) => [
|
|
2510
2710
|
parameter.key,
|
|
@@ -2962,6 +3162,13 @@ function onStep(value, step) {
|
|
|
2962
3162
|
}
|
|
2963
3163
|
|
|
2964
3164
|
// ../features/product-configurator/src/shared/expand-geometry.ts
|
|
3165
|
+
function motionGroupName(template, scope) {
|
|
3166
|
+
return template.replace(
|
|
3167
|
+
/\{([ij])\}/g,
|
|
3168
|
+
(_, index) => String((scope[index] ?? 0) + 1)
|
|
3169
|
+
);
|
|
3170
|
+
}
|
|
3171
|
+
var DEFAULT_HINGE_ANGLE = 90;
|
|
2965
3172
|
function expandGeometry(document, inputs, chosen = { choices: {} }) {
|
|
2966
3173
|
const issues = [];
|
|
2967
3174
|
const parts = [];
|
|
@@ -3074,6 +3281,29 @@ function expandGeometry(document, inputs, chosen = { choices: {} }) {
|
|
|
3074
3281
|
}
|
|
3075
3282
|
params[key] = number2;
|
|
3076
3283
|
}
|
|
3284
|
+
let motion;
|
|
3285
|
+
if (node.motion) {
|
|
3286
|
+
const angle = node.motion.angle === void 0 ? DEFAULT_HINGE_ANGLE : evaluate(node.motion.angle, scope, [
|
|
3287
|
+
...nodePath,
|
|
3288
|
+
"motion",
|
|
3289
|
+
"angle"
|
|
3290
|
+
]);
|
|
3291
|
+
const distance = node.motion.distance === void 0 ? null : evaluate(node.motion.distance, scope, [
|
|
3292
|
+
...nodePath,
|
|
3293
|
+
"motion",
|
|
3294
|
+
"distance"
|
|
3295
|
+
]);
|
|
3296
|
+
if (angle === null || node.motion.distance !== void 0 && distance === null) {
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
motion = {
|
|
3300
|
+
group: motionGroupName(node.motion.group, scope),
|
|
3301
|
+
kind: node.motion.kind,
|
|
3302
|
+
edge: node.motion.edge ?? "bottom",
|
|
3303
|
+
angle,
|
|
3304
|
+
distance
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3077
3307
|
parts.push({
|
|
3078
3308
|
key: `${node.id}${keySuffix}`,
|
|
3079
3309
|
id: node.id,
|
|
@@ -3082,7 +3312,8 @@ function expandGeometry(document, inputs, chosen = { choices: {} }) {
|
|
|
3082
3312
|
size,
|
|
3083
3313
|
at,
|
|
3084
3314
|
params,
|
|
3085
|
-
...model === void 0 ? {} : { model }
|
|
3315
|
+
...model === void 0 ? {} : { model },
|
|
3316
|
+
...motion === void 0 ? {} : { motion }
|
|
3086
3317
|
});
|
|
3087
3318
|
if (parts.length > CONFIGURATOR_LIMITS.expandedParts) {
|
|
3088
3319
|
issues.push(
|
|
@@ -3701,15 +3932,422 @@ function registerServerInfoTool(server, info) {
|
|
|
3701
3932
|
);
|
|
3702
3933
|
}
|
|
3703
3934
|
|
|
3704
|
-
// src/
|
|
3935
|
+
// src/shop-setup-tools.ts
|
|
3705
3936
|
import { z as z11 } from "zod";
|
|
3937
|
+
var id = z11.string().uuid();
|
|
3938
|
+
var cents2 = z11.number().int().min(0).max(99999999);
|
|
3939
|
+
var region = z11.object({
|
|
3940
|
+
countryCode: z11.string().regex(/^[A-Z]{2}$/).describe("Two capital letters, ISO 3166 (NL, BE, DE)."),
|
|
3941
|
+
postcodeFrom: z11.string().max(20).nullable().default(null).describe("With postcodeTo: only this range of the country."),
|
|
3942
|
+
postcodeTo: z11.string().max(20).nullable().default(null)
|
|
3943
|
+
});
|
|
3944
|
+
var discount = z11.object({
|
|
3945
|
+
name: z11.string().min(1).max(120).describe("What the team sees it as."),
|
|
3946
|
+
code: z11.string().max(40).nullable().describe(
|
|
3947
|
+
"What a buyer types (upper-cased), or null for one that applies on its own."
|
|
3948
|
+
),
|
|
3949
|
+
effect: z11.enum([
|
|
3950
|
+
"tier_price",
|
|
3951
|
+
"percent_off_lines",
|
|
3952
|
+
"amount_off_lines",
|
|
3953
|
+
"percent_off_order",
|
|
3954
|
+
"amount_off_order",
|
|
3955
|
+
"free_shipping"
|
|
3956
|
+
]).describe(
|
|
3957
|
+
"tier_price: a lower price from tierMinQuantity of one product; *_lines: off each matching line; *_order: once off the whole order; free_shipping: no postage."
|
|
3958
|
+
),
|
|
3959
|
+
percent: z11.number().nullable().describe("For a percentage: above 0, at most 100."),
|
|
3960
|
+
amountCents: z11.number().int().nullable().describe("For an amount, in currency, as the account enters prices."),
|
|
3961
|
+
currency: z11.string().regex(/^[A-Z]{3}$/).nullable().describe("Required beside amountCents (EUR)."),
|
|
3962
|
+
tierMinQuantity: z11.number().int().nullable(),
|
|
3963
|
+
minSubtotalCents: z11.number().int().nullable().describe("Only from this basket value, before discount."),
|
|
3964
|
+
productIds: z11.array(id).max(500).describe("Empty: every product."),
|
|
3965
|
+
categoryIds: z11.array(id).max(100),
|
|
3966
|
+
objectIds: z11.array(id).max(20),
|
|
3967
|
+
startsAt: z11.string().datetime({ offset: true }).nullable(),
|
|
3968
|
+
endsAt: z11.string().datetime({ offset: true }).nullable(),
|
|
3969
|
+
maxUses: z11.number().int().positive().nullable(),
|
|
3970
|
+
maxUsesPerEmail: z11.number().int().positive().nullable(),
|
|
3971
|
+
combinable: z11.boolean().describe(
|
|
3972
|
+
"Whether it may count beside another discount on the same order."
|
|
3973
|
+
)
|
|
3974
|
+
});
|
|
3975
|
+
function registerShopSetupTools(server, client) {
|
|
3976
|
+
server.registerTool(
|
|
3977
|
+
"check_shop_readiness",
|
|
3978
|
+
{
|
|
3979
|
+
title: "What the shop still misses",
|
|
3980
|
+
description: "Everything that stops the shop from selling (blocker) or costs it sales (warning): no product for sale, products without a price, no shipping method, Stripe not connected or still in test mode, no revalidation webhook, no cookie banner, no visit tracking. Each finding names the tool that fixes it, or the screen in the app. Run it first, and again when you think you are done.",
|
|
3981
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
3982
|
+
},
|
|
3983
|
+
async () => {
|
|
3984
|
+
try {
|
|
3985
|
+
const readiness = await client.getShopReadiness();
|
|
3986
|
+
return answer(
|
|
3987
|
+
[
|
|
3988
|
+
readiness.ready ? "Nothing blocks a sale." : "The shop cannot sell yet.",
|
|
3989
|
+
...readiness.findings.map(describeFinding)
|
|
3990
|
+
].join("\n"),
|
|
3991
|
+
readiness
|
|
3992
|
+
);
|
|
3993
|
+
} catch (error) {
|
|
3994
|
+
return refusal(error, "shop:manage");
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
);
|
|
3998
|
+
server.registerTool(
|
|
3999
|
+
"list_shipping_methods",
|
|
4000
|
+
{
|
|
4001
|
+
title: "List shipping zones and methods",
|
|
4002
|
+
description: "Every shipping zone with the countries it covers and the methods under it (what each charges, whether it is on), and the shipping classes a flat rate may charge per class. Amounts in cents, as the account enters prices.",
|
|
4003
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4004
|
+
},
|
|
4005
|
+
async () => {
|
|
4006
|
+
try {
|
|
4007
|
+
const setup = await client.getShippingSetup();
|
|
4008
|
+
return answer(
|
|
4009
|
+
setup.zones.length === 0 ? "No shipping zones yet: make one with upsert_shipping_zone." : setup.zones.map(describeZone).join("\n"),
|
|
4010
|
+
setup
|
|
4011
|
+
);
|
|
4012
|
+
} catch (error) {
|
|
4013
|
+
return refusal(error, "shop:manage");
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
);
|
|
4017
|
+
server.registerTool(
|
|
4018
|
+
"upsert_shipping_zone",
|
|
4019
|
+
{
|
|
4020
|
+
title: "Make or change a shipping zone",
|
|
4021
|
+
description: "A zone is a name and the countries (or postcode ranges) it covers. Without id a new zone; with one, that zone renamed and its places replaced by these. Answers the zone id, for upsert_shipping_method.",
|
|
4022
|
+
inputSchema: {
|
|
4023
|
+
id: id.optional().describe("The zone to change; leave out for a new one."),
|
|
4024
|
+
name: z11.string().min(1).max(80),
|
|
4025
|
+
regions: z11.array(region).max(300).default([])
|
|
4026
|
+
},
|
|
4027
|
+
annotations: { idempotentHint: true }
|
|
4028
|
+
},
|
|
4029
|
+
async (input) => {
|
|
4030
|
+
try {
|
|
4031
|
+
const zone = await client.upsertShippingZone(input);
|
|
4032
|
+
return answer(
|
|
4033
|
+
`${input.id ? "Changed" : "Made"} zone "${input.name}" (id ${zone.id}).`,
|
|
4034
|
+
zone
|
|
4035
|
+
);
|
|
4036
|
+
} catch (error) {
|
|
4037
|
+
return refusal(error, "shop:manage");
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
);
|
|
4041
|
+
server.registerTool(
|
|
4042
|
+
"upsert_shipping_method",
|
|
4043
|
+
{
|
|
4044
|
+
title: "Make or change a shipping method",
|
|
4045
|
+
description: "A way to post under a zone. flat_rate charges amountCents (and per class through classRates, noClassAmountCents for a product without one); free_shipping charges nothing, from freeThresholdCents of goods if given; local_pickup charges nothing. Without id a new method under zoneId; with one, that method changed -- its kind and zone are fixed once made.",
|
|
4046
|
+
inputSchema: {
|
|
4047
|
+
id: id.optional(),
|
|
4048
|
+
zoneId: id,
|
|
4049
|
+
kind: z11.enum(["flat_rate", "free_shipping", "local_pickup"]),
|
|
4050
|
+
name: z11.string().min(1).max(80).describe("What the buyer chooses."),
|
|
4051
|
+
enabled: z11.boolean().default(true),
|
|
4052
|
+
amountCents: cents2.default(0),
|
|
4053
|
+
noClassAmountCents: cents2.default(0),
|
|
4054
|
+
taxRateId: id.nullish(),
|
|
4055
|
+
freeThresholdCents: cents2.nullish(),
|
|
4056
|
+
classRates: z11.array(z11.object({ classId: id, amountCents: cents2 })).max(100).default([])
|
|
4057
|
+
},
|
|
4058
|
+
annotations: { idempotentHint: true }
|
|
4059
|
+
},
|
|
4060
|
+
async (input) => {
|
|
4061
|
+
try {
|
|
4062
|
+
const method = await client.upsertShippingMethod(input);
|
|
4063
|
+
return answer(
|
|
4064
|
+
`${input.id ? "Changed" : "Made"} method "${input.name}" (id ${method.id}).`,
|
|
4065
|
+
method
|
|
4066
|
+
);
|
|
4067
|
+
} catch (error) {
|
|
4068
|
+
return refusal(error, "shop:manage");
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
);
|
|
4072
|
+
server.registerTool(
|
|
4073
|
+
"list_discounts",
|
|
4074
|
+
{
|
|
4075
|
+
title: "List discounts",
|
|
4076
|
+
description: "The account discounts, newest first, at most 200: codes, the ones that apply on their own, and tier prices, with every condition and how many paid orders used each. Archived ones only with archived: true.",
|
|
4077
|
+
inputSchema: { archived: z11.boolean().optional() },
|
|
4078
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4079
|
+
},
|
|
4080
|
+
async ({ archived }) => {
|
|
4081
|
+
try {
|
|
4082
|
+
const discounts = await client.getDiscounts({ archived });
|
|
4083
|
+
return answer(
|
|
4084
|
+
discounts.length === 0 ? "No discounts." : discounts.map(describeDiscount).join("\n"),
|
|
4085
|
+
discounts
|
|
4086
|
+
);
|
|
4087
|
+
} catch (error) {
|
|
4088
|
+
return refusal(error, "shop:manage");
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
);
|
|
4092
|
+
server.registerTool(
|
|
4093
|
+
"upsert_discount",
|
|
4094
|
+
{
|
|
4095
|
+
title: "Make or change a discount",
|
|
4096
|
+
description: "A code, a discount that applies on its own (code null), or a tier price. Every field is sent, null where it does not apply. Without id a new discount; with one, that discount replaced field by field. A code the account already has is refused (conflict). A discount is counted against maxUses when an order with it is paid.",
|
|
4097
|
+
inputSchema: {
|
|
4098
|
+
id: id.optional(),
|
|
4099
|
+
discount
|
|
4100
|
+
},
|
|
4101
|
+
annotations: { idempotentHint: true }
|
|
4102
|
+
},
|
|
4103
|
+
async (input) => {
|
|
4104
|
+
try {
|
|
4105
|
+
const made = await client.upsertDiscount(input);
|
|
4106
|
+
return answer(describeDiscount(made), made);
|
|
4107
|
+
} catch (error) {
|
|
4108
|
+
return refusal(error, "shop:manage");
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
);
|
|
4112
|
+
server.registerTool(
|
|
4113
|
+
"archive_discount",
|
|
4114
|
+
{
|
|
4115
|
+
title: "Archive a discount",
|
|
4116
|
+
description: "Stops a discount from giving anything to a new cart or order; orders that used it keep it. archived: false brings it back. Deleting is left to a person in the app.",
|
|
4117
|
+
inputSchema: {
|
|
4118
|
+
id,
|
|
4119
|
+
archived: z11.boolean().default(true)
|
|
4120
|
+
},
|
|
4121
|
+
annotations: { idempotentHint: true }
|
|
4122
|
+
},
|
|
4123
|
+
async ({ id: discountId, archived }) => {
|
|
4124
|
+
try {
|
|
4125
|
+
const result = await client.archiveDiscount(discountId, archived);
|
|
4126
|
+
return answer(
|
|
4127
|
+
archived ? "Archived." : "Brought back from the archive.",
|
|
4128
|
+
result
|
|
4129
|
+
);
|
|
4130
|
+
} catch (error) {
|
|
4131
|
+
return refusal(error, "shop:manage");
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
);
|
|
4135
|
+
server.registerTool(
|
|
4136
|
+
"get_payment_status",
|
|
4137
|
+
{
|
|
4138
|
+
title: "Can the shop take a payment",
|
|
4139
|
+
description: "Whether a Stripe account is connected and ready for cards, whether Stripe still asks for something, and whether the account is in test mode. Read only: connecting Stripe and switching to live happen in the app, at the screen named in connectIn -- tell the person.",
|
|
4140
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4141
|
+
},
|
|
4142
|
+
async () => {
|
|
4143
|
+
try {
|
|
4144
|
+
const status = await client.getPaymentStatus();
|
|
4145
|
+
const sentence = !status.connected ? `Stripe is not connected (${status.status}). A person connects it in the app: ${status.connectIn}.` : [
|
|
4146
|
+
"Stripe is connected",
|
|
4147
|
+
status.testMode ? "in test mode: orders are test orders and no money moves" : "and live",
|
|
4148
|
+
status.requirementsDue ? "Stripe still asks for details" : ""
|
|
4149
|
+
].filter(Boolean).join(", ") + ".";
|
|
4150
|
+
return answer(sentence, status);
|
|
4151
|
+
} catch (error) {
|
|
4152
|
+
return refusal(error, "shop:manage");
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
);
|
|
4156
|
+
server.registerTool(
|
|
4157
|
+
"list_api_webhooks",
|
|
4158
|
+
{
|
|
4159
|
+
title: "List webhook addresses",
|
|
4160
|
+
description: "The addresses the account tells about changes (products, categories, events, orders), with whether each is on, paused after failures, and how its last delivery went. The signing key is never shown again.",
|
|
4161
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4162
|
+
},
|
|
4163
|
+
async () => {
|
|
4164
|
+
try {
|
|
4165
|
+
const hooks = await client.getApiWebhooks();
|
|
4166
|
+
return answer(
|
|
4167
|
+
hooks.length === 0 ? "No webhook addresses." : hooks.map(
|
|
4168
|
+
(hook) => `- ${hook.name} (id ${hook.id}): ${hook.url}, ${hook.events.join(", ")}` + (hook.paused ? `, paused after ${hook.consecutiveFailures} failures` : hook.active ? "" : ", off")
|
|
4169
|
+
).join("\n"),
|
|
4170
|
+
hooks
|
|
4171
|
+
);
|
|
4172
|
+
} catch (error) {
|
|
4173
|
+
return refusal(error, "shop:manage");
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
);
|
|
4177
|
+
server.registerTool(
|
|
4178
|
+
"upsert_api_webhook",
|
|
4179
|
+
{
|
|
4180
|
+
title: "Make or change a webhook address",
|
|
4181
|
+
description: "Point the account at the site revalidation route (with the SDK: https://<site>/api/solumflow/revalidate) so its cached pages follow a product the moment it changes. Without id a new address: the answer carries its signing key once -- put it in the site environment as SOLUMFLOW_WEBHOOK_KEY, it cannot be read back. https only, never a private network.",
|
|
4182
|
+
inputSchema: {
|
|
4183
|
+
id: id.optional(),
|
|
4184
|
+
name: z11.string().min(1).max(80),
|
|
4185
|
+
url: z11.string().url().max(2048),
|
|
4186
|
+
events: z11.array(z11.string().min(1).max(60)).min(1).max(20).describe(
|
|
4187
|
+
"For a shop: product.changed, product.deleted, category.changed."
|
|
4188
|
+
)
|
|
4189
|
+
},
|
|
4190
|
+
annotations: { idempotentHint: false }
|
|
4191
|
+
},
|
|
4192
|
+
async (input) => {
|
|
4193
|
+
try {
|
|
4194
|
+
const result = await client.upsertApiWebhook(input);
|
|
4195
|
+
return answer(
|
|
4196
|
+
result.signingSecret ? `Made address ${result.id}. Its signing key is in this answer and nowhere else: set it as SOLUMFLOW_WEBHOOK_KEY in the site environment now.` : `Changed address ${result.id}.`,
|
|
4197
|
+
result
|
|
4198
|
+
);
|
|
4199
|
+
} catch (error) {
|
|
4200
|
+
return refusal(error, "shop:manage");
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
function describeFinding(finding) {
|
|
4206
|
+
const examples = finding.examples?.length ? ` (${finding.examples.join(", ")})` : "";
|
|
4207
|
+
return `- ${finding.severity}: ${finding.message}${examples} Fixed by ${finding.fixedBy}.`;
|
|
4208
|
+
}
|
|
4209
|
+
function describeZone(zone) {
|
|
4210
|
+
const where = zone.isFallback ? "everywhere else" : zone.regions.map((place) => place.countryCode).join(", ") || "no places";
|
|
4211
|
+
const methods = zone.methods.map(
|
|
4212
|
+
(method) => ` - ${method.name} (${method.kind}, id ${method.id})${method.enabled ? "" : ", off"}: ` + (method.kind === "flat_rate" ? `${(method.amountCents / 100).toFixed(2)}` : method.freeThresholdCents !== null ? `free from ${(method.freeThresholdCents / 100).toFixed(2)}` : "free")
|
|
4213
|
+
);
|
|
4214
|
+
return [`- ${zone.name} (id ${zone.id}): ${where}`, ...methods].join("\n");
|
|
4215
|
+
}
|
|
4216
|
+
function describeDiscount(entry) {
|
|
4217
|
+
const what = entry.percent !== null ? `${entry.percent}%` : entry.amountCents !== null && entry.currency ? money(entry.amountCents, entry.currency) : "";
|
|
4218
|
+
return `- ${entry.name} (id ${entry.id}): ${entry.code ?? "no code"}, ${entry.effect}${what ? ` ${what}` : ""}, used ${entry.timesUsed}x` + (entry.archived ? ", archived" : "");
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
// src/shop-test-order-tool.ts
|
|
4222
|
+
import { z as z12 } from "zod";
|
|
4223
|
+
var PAID_POLLS = 10;
|
|
4224
|
+
var PAID_POLL_MS = 1500;
|
|
4225
|
+
function registerShopTestOrderTool(server, client, options = {}) {
|
|
4226
|
+
const wait = options.wait ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
4227
|
+
server.registerTool(
|
|
4228
|
+
"run_test_order",
|
|
4229
|
+
{
|
|
4230
|
+
title: "Place and pay a test order",
|
|
4231
|
+
description: "Proves the shop end to end in Stripe test mode: puts one product in a cart, chooses postage, places the order, pays it with Stripe test card 4242, and reads the order back until it is paid (the invoice and the confirmation mail follow from there). Refused when the account is live -- no real money can move through this tool. Needs a key with shop:manage, cart:write, orders:write and orders:read.",
|
|
4232
|
+
inputSchema: {
|
|
4233
|
+
email: z12.string().email().describe(
|
|
4234
|
+
"Where the confirmation goes: the shop owner, never a customer."
|
|
4235
|
+
),
|
|
4236
|
+
productId: z12.string().uuid().optional().describe(
|
|
4237
|
+
"The product to buy; leave out for the first one in stock."
|
|
4238
|
+
),
|
|
4239
|
+
countryCode: z12.string().regex(/^[A-Z]{2}$/).default("NL").describe("Where it is posted, for a product that is posted."),
|
|
4240
|
+
postalCode: z12.string().max(20).optional()
|
|
4241
|
+
},
|
|
4242
|
+
annotations: { idempotentHint: false }
|
|
4243
|
+
},
|
|
4244
|
+
async (input) => {
|
|
4245
|
+
let scope = "shop:manage";
|
|
4246
|
+
try {
|
|
4247
|
+
const status = await client.getPaymentStatus();
|
|
4248
|
+
if (!status.connected) {
|
|
4249
|
+
return answer(
|
|
4250
|
+
`Stripe is not connected, so there is nothing to pay with. A person connects it in the app: ${status.connectIn}.`,
|
|
4251
|
+
status
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
if (!status.testMode) {
|
|
4255
|
+
return answer(
|
|
4256
|
+
`The account is live: a test order would be a real one. Switch the account to Stripe test mode in the app first (${status.connectIn}), or prove the shop with a real purchase by hand.`,
|
|
4257
|
+
status
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
scope = "catalog:read";
|
|
4261
|
+
const productId = input.productId ?? await firstProductForSale();
|
|
4262
|
+
if (!productId) {
|
|
4263
|
+
return answer(
|
|
4264
|
+
"No product is for sale and in stock: there is nothing to order. check_shop_readiness says what is missing.",
|
|
4265
|
+
null
|
|
4266
|
+
);
|
|
4267
|
+
}
|
|
4268
|
+
scope = "cart:write";
|
|
4269
|
+
const handle = await client.createCart({ email: input.email });
|
|
4270
|
+
let cart = await client.addCartLine(handle.token, {
|
|
4271
|
+
productId,
|
|
4272
|
+
quantity: 1
|
|
4273
|
+
});
|
|
4274
|
+
if (cart.shipping.required) {
|
|
4275
|
+
cart = await client.setCartShipping(handle.token, {
|
|
4276
|
+
countryCode: input.countryCode,
|
|
4277
|
+
postalCode: input.postalCode ?? null
|
|
4278
|
+
});
|
|
4279
|
+
const method = cart.shipping.methods[0];
|
|
4280
|
+
if (!method) {
|
|
4281
|
+
return answer(
|
|
4282
|
+
`No shipping method delivers to ${input.countryCode}: add one with upsert_shipping_method.`,
|
|
4283
|
+
cart
|
|
4284
|
+
);
|
|
4285
|
+
}
|
|
4286
|
+
cart = await client.setCartShipping(handle.token, {
|
|
4287
|
+
countryCode: input.countryCode,
|
|
4288
|
+
postalCode: input.postalCode ?? null,
|
|
4289
|
+
methodId: method.id
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
if (cart.problem) {
|
|
4293
|
+
return answer(
|
|
4294
|
+
`The cart cannot be ordered: ${cart.problem.message}`,
|
|
4295
|
+
cart
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
4298
|
+
scope = "orders:write";
|
|
4299
|
+
const order = await client.submitCartOrder({
|
|
4300
|
+
buyer: { email: input.email },
|
|
4301
|
+
cartToken: handle.token,
|
|
4302
|
+
...cart.shipping.required ? {
|
|
4303
|
+
shippingAddress: {
|
|
4304
|
+
countryCode: input.countryCode,
|
|
4305
|
+
...input.postalCode ? { postalCode: input.postalCode } : {}
|
|
4306
|
+
}
|
|
4307
|
+
} : {}
|
|
4308
|
+
});
|
|
4309
|
+
scope = "shop:manage";
|
|
4310
|
+
const payment = await client.payTestOrder(order.id);
|
|
4311
|
+
scope = "orders:read";
|
|
4312
|
+
const settled = await readUntilPaid(order.id);
|
|
4313
|
+
const total = money(order.totalCents, order.currency);
|
|
4314
|
+
return answer(
|
|
4315
|
+
settled?.status === "paid" ? `Test order ${order.number} for ${total} is paid. The invoice and the confirmation mail to ${input.email} follow from here.` : `Test order ${order.number} for ${total}: Stripe says the payment ${payment.paymentStatus}, but the order still reads ${settled?.status ?? "unknown"}. The platform marks it paid when Stripe reaches its webhook; if that never happens, the webhook between Stripe and the platform is the place to look.`,
|
|
4316
|
+
{ order, payment, settled }
|
|
4317
|
+
);
|
|
4318
|
+
} catch (error) {
|
|
4319
|
+
return refusal(error, scope);
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
);
|
|
4323
|
+
async function firstProductForSale() {
|
|
4324
|
+
const list = await client.getProducts({ limit: 50 });
|
|
4325
|
+
return list.data.find(
|
|
4326
|
+
(product) => product.inStock && product.priceFromCents !== null
|
|
4327
|
+
)?.id;
|
|
4328
|
+
}
|
|
4329
|
+
async function readUntilPaid(orderId) {
|
|
4330
|
+
let order = null;
|
|
4331
|
+
for (let poll = 0; poll < PAID_POLLS; poll += 1) {
|
|
4332
|
+
order = await client.getOrder(orderId);
|
|
4333
|
+
if (order?.status === "paid") {
|
|
4334
|
+
return order;
|
|
4335
|
+
}
|
|
4336
|
+
await wait(PAID_POLL_MS);
|
|
4337
|
+
}
|
|
4338
|
+
return order;
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
// src/tool-definitions-version.ts
|
|
4343
|
+
import { z as z13 } from "zod";
|
|
3706
4344
|
import { createHash } from "crypto";
|
|
3707
4345
|
function toolDefinitionsHash(tools) {
|
|
3708
4346
|
const definitions = [...tools].sort((left, right) => left.name.localeCompare(right.name)).map(({ name, config }) => ({
|
|
3709
4347
|
name,
|
|
3710
4348
|
title: config.title ?? null,
|
|
3711
4349
|
description: config.description ?? null,
|
|
3712
|
-
inputSchema: config.inputSchema ?
|
|
4350
|
+
inputSchema: config.inputSchema ? z13.toJSONSchema(z13.object(config.inputSchema), {
|
|
3713
4351
|
io: "input",
|
|
3714
4352
|
unrepresentable: "any"
|
|
3715
4353
|
}) : null,
|
|
@@ -3757,7 +4395,7 @@ function holdToolRegistrations(real) {
|
|
|
3757
4395
|
description: `${config.description ?? ""} Definitions version ${version2}: send it as schemaHash and the answer tells you when these tool definitions are older than the server.`,
|
|
3758
4396
|
inputSchema: {
|
|
3759
4397
|
...config.inputSchema,
|
|
3760
|
-
schemaHash:
|
|
4398
|
+
schemaHash: z13.string().max(64).optional().describe(
|
|
3761
4399
|
`The definitions version named in this tool's description (${version2}).`
|
|
3762
4400
|
)
|
|
3763
4401
|
}
|
|
@@ -3818,6 +4456,8 @@ async function main() {
|
|
|
3818
4456
|
registerCatalogMigrationTools(held.server, client);
|
|
3819
4457
|
registerEventTools(held.server, client);
|
|
3820
4458
|
registerConfiguratorTools(held.server, client);
|
|
4459
|
+
registerShopSetupTools(held.server, client);
|
|
4460
|
+
registerShopTestOrderTool(held.server, client);
|
|
3821
4461
|
held.publish();
|
|
3822
4462
|
await server.connect(new StdioServerTransport());
|
|
3823
4463
|
try {
|