@solumflow-app/crm-mcp-server 0.12.0 → 0.13.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 +78 -0
- package/dist/index.js +836 -576
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,16 +6,203 @@ 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.13.0";
|
|
10
|
+
|
|
11
|
+
// src/calendar-setup-tools.ts
|
|
12
|
+
import { z as z2 } from "zod";
|
|
13
|
+
|
|
14
|
+
// src/tool-answers.ts
|
|
15
|
+
import { CrmApiError } from "@solumflow-app/crm-client";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
function answer(text2, data) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: text2 }],
|
|
20
|
+
structuredContent: { result: data }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function refusal(error, scope) {
|
|
24
|
+
if (!(error instanceof CrmApiError)) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const lines = [`${error.code}: ${error.message}`];
|
|
28
|
+
const next = error.code === "forbidden" ? `The key may not do this. It needs to be a secret key with the scope "${scope}".` : NEXT_STEP[error.code];
|
|
29
|
+
if (next) {
|
|
30
|
+
lines.push(next);
|
|
31
|
+
}
|
|
32
|
+
for (const field of error.fields) {
|
|
33
|
+
lines.push(`- ${field.path}: ${field.message}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
var NEXT_STEP = {
|
|
41
|
+
unauthorized: "The key is not recognised or was revoked. Ask the account owner for a new secret key.",
|
|
42
|
+
feature_unavailable: "The module this needs is switched off for the account. Ask somebody to switch it on in the app.",
|
|
43
|
+
rate_limited: "Too many requests: 120 a minute per key, 600 per account, and 5 MB of bodies a day per key. Wait the seconds the answer names and try again.",
|
|
44
|
+
internal_error: "Something went wrong in the app. Trying again is safe.",
|
|
45
|
+
limit_reached: "The reseller has as many customer accounts as its plan allows. The platform owner raises that, not another try."
|
|
46
|
+
};
|
|
47
|
+
function codeBlock(snippet) {
|
|
48
|
+
return `\`\`\`html
|
|
49
|
+
${snippet}
|
|
50
|
+
\`\`\``;
|
|
51
|
+
}
|
|
52
|
+
var page = {
|
|
53
|
+
cursor: z.string().optional().describe("The nextCursor of the previous page; leave out for the first."),
|
|
54
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
55
|
+
};
|
|
56
|
+
function more(nextCursor) {
|
|
57
|
+
return nextCursor ? `More on the next page: cursor ${nextCursor}` : "";
|
|
58
|
+
}
|
|
59
|
+
function money(cents3, currency2) {
|
|
60
|
+
return `${(cents3 / 100).toFixed(2)} ${currency2.toUpperCase()}`;
|
|
61
|
+
}
|
|
62
|
+
function fieldsNotOnPageNote(slugs) {
|
|
63
|
+
if (!slugs?.length) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
const count = slugs.length === 1 ? "1 public field has" : `${slugs.length} public fields have`;
|
|
67
|
+
return ` ${count} a value but ${slugs.length === 1 ? "is" : "are"} not on this product's page: ${slugs.join(", ")}; send them in publicFields to show them.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/calendar-setup-tools.ts
|
|
71
|
+
var slug = z2.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).describe('Lowercase letters, digits and dashes, e.g. "kennismaking".');
|
|
72
|
+
var time = z2.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).describe("HH:MM on the calendar\u2019s own clock.");
|
|
73
|
+
var NEVER_APPOINTMENTS = "It never makes or moves an appointment: that mails a customer, and stays in the app. list_booking_pages shows the result.";
|
|
74
|
+
function registerCalendarSetupTools(server, client) {
|
|
75
|
+
server.registerTool(
|
|
76
|
+
"upsert_calendar",
|
|
77
|
+
{
|
|
78
|
+
title: "Make or change a calendar",
|
|
79
|
+
description: `A calendar by its address (slug): "event" for meetings with one host, "round_robin" for whoever of the hosts is free, or "services" for the account\u2019s own services calendar, which is changed and never made. A new calendar takes the defaults for what is left out; one that exists keeps it. A new calendar naming no hosts is taken by the account\u2019s owner. ${NEVER_APPOINTMENTS}`,
|
|
80
|
+
inputSchema: {
|
|
81
|
+
slug,
|
|
82
|
+
name: z2.string().min(1).max(120),
|
|
83
|
+
type: z2.enum(["event", "round_robin", "services"]).optional(),
|
|
84
|
+
description: z2.string().max(5e3).nullable().optional(),
|
|
85
|
+
durationMinutes: z2.number().int().min(1).max(1440).optional(),
|
|
86
|
+
isActive: z2.boolean().optional(),
|
|
87
|
+
slotIntervalMinutes: z2.number().int().min(1).max(1440).optional(),
|
|
88
|
+
minNoticeMinutes: z2.number().int().min(0).optional(),
|
|
89
|
+
bookingHorizonDays: z2.number().int().min(1).max(730).optional(),
|
|
90
|
+
bufferBeforeMinutes: z2.number().int().min(0).max(1440).optional(),
|
|
91
|
+
bufferAfterMinutes: z2.number().int().min(0).max(1440).optional(),
|
|
92
|
+
maxPerDay: z2.number().int().min(1).nullable().optional(),
|
|
93
|
+
timezoneMode: z2.enum(["visitor", "fixed"]).optional(),
|
|
94
|
+
timezone: z2.string().optional().describe("An IANA zone, e.g. Europe/Amsterdam."),
|
|
95
|
+
hostUserIds: z2.array(z2.string().uuid()).min(1).max(50).optional()
|
|
96
|
+
},
|
|
97
|
+
annotations: { idempotentHint: true }
|
|
98
|
+
},
|
|
99
|
+
async (input) => {
|
|
100
|
+
try {
|
|
101
|
+
const result = await client.upsertCalendar(input);
|
|
102
|
+
return answer(
|
|
103
|
+
`${result.created ? "Made" : "Changed"}: ${describeCalendar(result.calendar)}`,
|
|
104
|
+
result
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return refusal(error, "calendar:manage");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
server.registerTool(
|
|
112
|
+
"set_calendar_availability",
|
|
113
|
+
{
|
|
114
|
+
title: "Set a calendar\u2019s opening hours",
|
|
115
|
+
description: "The hours a calendar can be booked: weekly rules (weekday 0 is Sunday) and exceptions per date (closed, or other hours). Each list sent replaces the one there; one left out is kept. For the services calendar these are the hours its services can be booked.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
slug,
|
|
118
|
+
rules: z2.array(
|
|
119
|
+
z2.object({
|
|
120
|
+
weekday: z2.number().int().min(0).max(6),
|
|
121
|
+
startsTime: time,
|
|
122
|
+
endsTime: time,
|
|
123
|
+
userId: z2.string().uuid().nullable().optional()
|
|
124
|
+
})
|
|
125
|
+
).max(200).optional(),
|
|
126
|
+
overrides: z2.array(
|
|
127
|
+
z2.object({
|
|
128
|
+
date: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
129
|
+
isUnavailable: z2.boolean().optional(),
|
|
130
|
+
startsTime: time.nullable().optional(),
|
|
131
|
+
endsTime: time.nullable().optional(),
|
|
132
|
+
userId: z2.string().uuid().nullable().optional()
|
|
133
|
+
})
|
|
134
|
+
).max(365).optional()
|
|
135
|
+
},
|
|
136
|
+
annotations: { idempotentHint: true }
|
|
137
|
+
},
|
|
138
|
+
async ({ slug: calendarSlug, rules, overrides }) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await client.setCalendarAvailability(calendarSlug, {
|
|
141
|
+
rules,
|
|
142
|
+
overrides
|
|
143
|
+
});
|
|
144
|
+
const parts = [
|
|
145
|
+
rules ? `${rules.length} weekly rule(s)` : "",
|
|
146
|
+
overrides ? `${overrides.length} exception(s)` : ""
|
|
147
|
+
].filter(Boolean);
|
|
148
|
+
return answer(
|
|
149
|
+
`Hours of "${result.slug}" saved: ${parts.join(" and ")}.`,
|
|
150
|
+
result
|
|
151
|
+
);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return refusal(error, "calendar:manage");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
server.registerTool(
|
|
158
|
+
"upsert_services",
|
|
159
|
+
{
|
|
160
|
+
title: "Make or change bookable services",
|
|
161
|
+
description: `Up to fifty services the services calendar offers, each by its address (slug): name, length in minutes, price in cents, group (category) and whether the booking page lists it. A service nobody performs is given the account\u2019s owner, so it has free times at once. Paid on the spot: paying in advance needs the account\u2019s own Stripe connection, set in the app. ${NEVER_APPOINTMENTS}`,
|
|
162
|
+
inputSchema: {
|
|
163
|
+
services: z2.array(
|
|
164
|
+
z2.object({
|
|
165
|
+
slug: z2.string().min(1).max(120).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
166
|
+
name: z2.string().min(1).max(200),
|
|
167
|
+
durationMinutes: z2.number().int().min(5).max(1440),
|
|
168
|
+
priceCents: z2.number().int().min(0).optional(),
|
|
169
|
+
description: z2.string().max(5e3).nullable().optional(),
|
|
170
|
+
active: z2.boolean().optional(),
|
|
171
|
+
visibility: z2.enum(["public", "private"]).optional(),
|
|
172
|
+
category: z2.string().min(1).max(120).nullable().optional()
|
|
173
|
+
})
|
|
174
|
+
).min(1).max(50)
|
|
175
|
+
},
|
|
176
|
+
annotations: { idempotentHint: true }
|
|
177
|
+
},
|
|
178
|
+
async ({ services }) => {
|
|
179
|
+
try {
|
|
180
|
+
const result = await client.upsertServices(services);
|
|
181
|
+
return answer(result.map(describeService).join("\n"), result);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return refusal(error, "calendar:manage");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function describeCalendar(calendar) {
|
|
189
|
+
const state = calendar.isActive ? "" : ", switched off";
|
|
190
|
+
return `${calendar.name} (${calendar.slug}, ${calendar.type}), ${calendar.durationMinutes} min, ${calendar.timezone}${state}`;
|
|
191
|
+
}
|
|
192
|
+
function describeService(service) {
|
|
193
|
+
const verb = service.created ? "made" : "changed";
|
|
194
|
+
const price = service.priceCents ? `price ${(service.priceCents / 100).toFixed(2)}` : "free";
|
|
195
|
+
return `- ${service.name} (${service.slug}): ${service.durationMinutes} min, ${price}, ${service.visibility}${service.active ? "" : ", inactive"} -- ${verb}`;
|
|
196
|
+
}
|
|
10
197
|
|
|
11
198
|
// src/catalog-migration-tools.ts
|
|
12
|
-
import { z as
|
|
199
|
+
import { z as z9 } from "zod";
|
|
13
200
|
|
|
14
201
|
// ../features/crm-invoicing/src/schema/catalog-api.schema.ts
|
|
15
|
-
import * as
|
|
202
|
+
import * as z7 from "zod";
|
|
16
203
|
|
|
17
204
|
// ../features/crm-invoicing/src/shared/product-surcharge-options-schema.ts
|
|
18
|
-
import { z as
|
|
205
|
+
import { z as z6 } from "zod";
|
|
19
206
|
|
|
20
207
|
// ../features/product-configurator/src/shared/configurator-limits.ts
|
|
21
208
|
var CONFIGURATOR_LIMITS = {
|
|
@@ -385,16 +572,16 @@ function evaluateFormulaSource(source, scope) {
|
|
|
385
572
|
}
|
|
386
573
|
|
|
387
574
|
// ../features/product-configurator/src/shared/geometry-document.schema.ts
|
|
388
|
-
import { z } from "zod";
|
|
575
|
+
import { z as z3 } from "zod";
|
|
389
576
|
var CONFIGURATOR_NAME_PATTERN = /^[a-z][a-z0-9_]{0,39}$/;
|
|
390
577
|
var CONFIGURATOR_CODE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
391
|
-
var ConfiguratorNameSchema =
|
|
392
|
-
var ConfiguratorCodeSchema =
|
|
393
|
-
var FormulaSchema =
|
|
394
|
-
|
|
395
|
-
|
|
578
|
+
var ConfiguratorNameSchema = z3.string().regex(CONFIGURATOR_NAME_PATTERN, "configurator.invalidName");
|
|
579
|
+
var ConfiguratorCodeSchema = z3.string().regex(CONFIGURATOR_CODE_PATTERN, "configurator.invalidCode");
|
|
580
|
+
var FormulaSchema = z3.union([
|
|
581
|
+
z3.number().finite(),
|
|
582
|
+
z3.string().trim().min(1).max(CONFIGURATOR_LIMITS.formulaChars, "configurator.formulaTooLong")
|
|
396
583
|
]);
|
|
397
|
-
var Vector3Schema =
|
|
584
|
+
var Vector3Schema = z3.tuple([FormulaSchema, FormulaSchema, FormulaSchema]);
|
|
398
585
|
var PRIMITIVE_TYPES = [
|
|
399
586
|
"box",
|
|
400
587
|
"rounded_top_box",
|
|
@@ -436,10 +623,10 @@ var CONFIGURATOR_FOLLOWS_PRESET = "preset";
|
|
|
436
623
|
var MOTION_GROUP_PATTERN = /^[a-z](?:[a-z0-9_]|\{[ij]\}){0,39}$/;
|
|
437
624
|
var MOTION_KINDS = ["hinge", "slide"];
|
|
438
625
|
var MOTION_EDGES = ["bottom", "top", "left", "right"];
|
|
439
|
-
var GeometryMotionSchema =
|
|
440
|
-
group:
|
|
441
|
-
kind:
|
|
442
|
-
edge:
|
|
626
|
+
var GeometryMotionSchema = z3.strictObject({
|
|
627
|
+
group: z3.string().regex(MOTION_GROUP_PATTERN, "configurator.invalidName"),
|
|
628
|
+
kind: z3.enum(MOTION_KINDS),
|
|
629
|
+
edge: z3.enum(MOTION_EDGES).optional(),
|
|
443
630
|
angle: FormulaSchema.optional(),
|
|
444
631
|
distance: FormulaSchema.optional()
|
|
445
632
|
});
|
|
@@ -449,94 +636,94 @@ var PrimitiveNodeFields = {
|
|
|
449
636
|
size: Vector3Schema,
|
|
450
637
|
at: Vector3Schema,
|
|
451
638
|
visible: FormulaSchema.optional(),
|
|
452
|
-
params:
|
|
639
|
+
params: z3.record(ConfiguratorNameSchema, FormulaSchema).optional(),
|
|
453
640
|
motion: GeometryMotionSchema.optional()
|
|
454
641
|
};
|
|
455
|
-
var PrimitiveNodeSchema =
|
|
642
|
+
var PrimitiveNodeSchema = z3.strictObject({
|
|
456
643
|
...PrimitiveNodeFields,
|
|
457
|
-
type:
|
|
644
|
+
type: z3.enum(PRIMITIVE_TYPES).exclude(["model"])
|
|
458
645
|
});
|
|
459
|
-
var ModelPathSchema =
|
|
460
|
-
var ServedFileUrlSchema =
|
|
646
|
+
var ModelPathSchema = z3.string().regex(CONFIGURATOR_MODEL_PATH_PATTERN, "configurator.invalidModelPath");
|
|
647
|
+
var ServedFileUrlSchema = z3.url({ protocol: /^https?$/ });
|
|
461
648
|
function geometryNodeSchema(modelFile) {
|
|
462
|
-
const ModelNodeSchema =
|
|
649
|
+
const ModelNodeSchema = z3.strictObject({
|
|
463
650
|
...PrimitiveNodeFields,
|
|
464
|
-
type:
|
|
651
|
+
type: z3.literal("model"),
|
|
465
652
|
model: modelFile
|
|
466
653
|
});
|
|
467
|
-
const ModelPerChoiceNodeSchema =
|
|
654
|
+
const ModelPerChoiceNodeSchema = z3.strictObject({
|
|
468
655
|
...PrimitiveNodeFields,
|
|
469
|
-
type:
|
|
656
|
+
type: z3.literal("model"),
|
|
470
657
|
follows: ConfiguratorNameSchema,
|
|
471
|
-
models:
|
|
658
|
+
models: z3.record(ConfiguratorCodeSchema, modelFile).refine(
|
|
472
659
|
(models) => Object.keys(models).length <= CONFIGURATOR_LIMITS.presets,
|
|
473
660
|
{ message: "configurator.tooMany" }
|
|
474
661
|
)
|
|
475
662
|
});
|
|
476
|
-
const node =
|
|
477
|
-
() =>
|
|
663
|
+
const node = z3.lazy(
|
|
664
|
+
() => z3.union([
|
|
478
665
|
PrimitiveNodeSchema,
|
|
479
666
|
ModelNodeSchema,
|
|
480
667
|
ModelPerChoiceNodeSchema,
|
|
481
|
-
|
|
668
|
+
z3.strictObject({
|
|
482
669
|
id: ConfiguratorNameSchema,
|
|
483
|
-
type:
|
|
670
|
+
type: z3.literal("repeat"),
|
|
484
671
|
count: FormulaSchema,
|
|
485
|
-
index:
|
|
672
|
+
index: z3.enum(["i", "j"]).optional(),
|
|
486
673
|
visible: FormulaSchema.optional(),
|
|
487
|
-
children:
|
|
674
|
+
children: z3.array(node).min(1)
|
|
488
675
|
})
|
|
489
676
|
])
|
|
490
677
|
);
|
|
491
678
|
return node;
|
|
492
679
|
}
|
|
493
680
|
var GeometryNodeSchema = geometryNodeSchema(ModelPathSchema);
|
|
494
|
-
var GeometryBindingSchema =
|
|
681
|
+
var GeometryBindingSchema = z3.strictObject({
|
|
495
682
|
param: ConfiguratorNameSchema,
|
|
496
|
-
map:
|
|
683
|
+
map: z3.record(ConfiguratorCodeSchema, z3.number().finite()).optional()
|
|
497
684
|
});
|
|
498
|
-
var GeometryInputSchema =
|
|
685
|
+
var GeometryInputSchema = z3.strictObject({
|
|
499
686
|
name: ConfiguratorNameSchema,
|
|
500
687
|
binding: GeometryBindingSchema
|
|
501
688
|
});
|
|
502
|
-
var GeometrySlotSchema =
|
|
689
|
+
var GeometrySlotSchema = z3.strictObject({
|
|
503
690
|
name: ConfiguratorNameSchema,
|
|
504
691
|
param: ConfiguratorNameSchema.optional(),
|
|
505
692
|
material: ConfiguratorCodeSchema.optional(),
|
|
506
|
-
color:
|
|
693
|
+
color: z3.string().regex(/^#[0-9a-f]{6}$/i, "configurator.invalidColor").optional(),
|
|
507
694
|
/**
|
|
508
695
|
* Which way the grain of a picture runs on this part: `horizontal` (the
|
|
509
696
|
* default) as the picture is drawn, `vertical` turned a quarter. It belongs
|
|
510
697
|
* to the part, not the material: the same oak lies on a cabinet and stands
|
|
511
698
|
* on a tall wall panel.
|
|
512
699
|
*/
|
|
513
|
-
grain:
|
|
700
|
+
grain: z3.enum(["horizontal", "vertical"]).optional()
|
|
514
701
|
});
|
|
515
|
-
var GeometryAnnotationSchema =
|
|
702
|
+
var GeometryAnnotationSchema = z3.strictObject({
|
|
516
703
|
id: ConfiguratorNameSchema,
|
|
517
|
-
axis:
|
|
704
|
+
axis: z3.enum(["x", "y"]),
|
|
518
705
|
from: FormulaSchema,
|
|
519
706
|
to: FormulaSchema,
|
|
520
707
|
at: FormulaSchema,
|
|
521
|
-
label:
|
|
708
|
+
label: z3.string().trim().min(1).max(80).optional()
|
|
522
709
|
});
|
|
523
710
|
function geometryDocumentSchema(modelFile) {
|
|
524
|
-
return
|
|
711
|
+
return z3.strictObject({
|
|
525
712
|
/** Rises with every new upload, so a product page can say which one it shows. */
|
|
526
|
-
version:
|
|
713
|
+
version: z3.number().int().min(1),
|
|
527
714
|
/** The starter document this one was copied from, for information only. */
|
|
528
|
-
startedFrom:
|
|
529
|
-
inputs:
|
|
530
|
-
constants:
|
|
531
|
-
slots:
|
|
532
|
-
root:
|
|
533
|
-
camera:
|
|
715
|
+
startedFrom: z3.string().trim().max(80).optional(),
|
|
716
|
+
inputs: z3.array(GeometryInputSchema).max(CONFIGURATOR_LIMITS.geometryInputs),
|
|
717
|
+
constants: z3.record(ConfiguratorNameSchema, z3.number().finite()),
|
|
718
|
+
slots: z3.array(GeometrySlotSchema).min(1).max(CONFIGURATOR_LIMITS.geometrySlots),
|
|
719
|
+
root: z3.array(geometryNodeSchema(modelFile)).min(1),
|
|
720
|
+
camera: z3.strictObject({
|
|
534
721
|
/** Degrees around the vertical axis, 0 is straight on. */
|
|
535
|
-
azimuth:
|
|
722
|
+
azimuth: z3.number().min(-90).max(90),
|
|
536
723
|
/** Degrees above the horizon. */
|
|
537
|
-
elevation:
|
|
724
|
+
elevation: z3.number().min(-10).max(80)
|
|
538
725
|
}).optional(),
|
|
539
|
-
annotations:
|
|
726
|
+
annotations: z3.array(GeometryAnnotationSchema).max(CONFIGURATOR_LIMITS.annotations)
|
|
540
727
|
});
|
|
541
728
|
}
|
|
542
729
|
var GeometryDocumentSchema = geometryDocumentSchema(ModelPathSchema);
|
|
@@ -872,7 +1059,7 @@ function followProblems(follows, path, context) {
|
|
|
872
1059
|
}
|
|
873
1060
|
|
|
874
1061
|
// ../features/product-configurator/src/shared/configurator-structure.schema.ts
|
|
875
|
-
import { z as
|
|
1062
|
+
import { z as z4 } from "zod";
|
|
876
1063
|
|
|
877
1064
|
// ../features/product-configurator/src/shared/room-palette.ts
|
|
878
1065
|
var ROOM_WALL_PALETTE = [
|
|
@@ -901,104 +1088,104 @@ var ROOM_FLOOR_PALETTE = [
|
|
|
901
1088
|
];
|
|
902
1089
|
|
|
903
1090
|
// ../features/product-configurator/src/shared/configurator-structure.schema.ts
|
|
904
|
-
var LabelSchema =
|
|
1091
|
+
var LabelSchema = z4.string().trim().min(1).max(80);
|
|
905
1092
|
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)$/;
|
|
906
|
-
var TexturePathSchema =
|
|
1093
|
+
var TexturePathSchema = z4.string().regex(CONFIGURATOR_TEXTURE_PATH_PATTERN, "configurator.invalidTexturePath");
|
|
907
1094
|
var ParameterBase = {
|
|
908
1095
|
key: ConfiguratorNameSchema,
|
|
909
1096
|
label: LabelSchema,
|
|
910
1097
|
tab: ConfiguratorNameSchema
|
|
911
1098
|
};
|
|
912
|
-
var ConfiguratorParameterSchema =
|
|
913
|
-
|
|
1099
|
+
var ConfiguratorParameterSchema = z4.discriminatedUnion("kind", [
|
|
1100
|
+
z4.strictObject({
|
|
914
1101
|
...ParameterBase,
|
|
915
|
-
kind:
|
|
1102
|
+
kind: z4.literal("number"),
|
|
916
1103
|
/** `mm` is a measure, `count` a number of things (compartments). */
|
|
917
|
-
unit:
|
|
918
|
-
step:
|
|
1104
|
+
unit: z4.enum(["mm", "count"]),
|
|
1105
|
+
step: z4.number().positive().finite(),
|
|
919
1106
|
/** The widest range any model may offer; a model narrows it in its value. */
|
|
920
|
-
min:
|
|
921
|
-
max:
|
|
1107
|
+
min: z4.number().finite(),
|
|
1108
|
+
max: z4.number().finite()
|
|
922
1109
|
}),
|
|
923
|
-
|
|
1110
|
+
z4.strictObject({
|
|
924
1111
|
...ParameterBase,
|
|
925
|
-
kind:
|
|
1112
|
+
kind: z4.literal("choice"),
|
|
926
1113
|
list: ConfiguratorNameSchema
|
|
927
1114
|
}),
|
|
928
|
-
|
|
929
|
-
|
|
1115
|
+
z4.strictObject({ ...ParameterBase, kind: z4.literal("material") }),
|
|
1116
|
+
z4.strictObject({ ...ParameterBase, kind: z4.literal("toggle") })
|
|
930
1117
|
]);
|
|
931
|
-
var choiceListSchema = (picture) =>
|
|
1118
|
+
var choiceListSchema = (picture) => z4.strictObject({
|
|
932
1119
|
key: ConfiguratorNameSchema,
|
|
933
1120
|
label: LabelSchema,
|
|
934
|
-
options:
|
|
935
|
-
|
|
1121
|
+
options: z4.array(
|
|
1122
|
+
z4.strictObject({
|
|
936
1123
|
code: ConfiguratorCodeSchema,
|
|
937
1124
|
label: LabelSchema,
|
|
938
1125
|
image: picture.optional()
|
|
939
1126
|
})
|
|
940
1127
|
).min(1).max(CONFIGURATOR_LIMITS.optionsPerList)
|
|
941
1128
|
});
|
|
942
|
-
var materialSchema = (picture) =>
|
|
1129
|
+
var materialSchema = (picture) => z4.strictObject({
|
|
943
1130
|
code: ConfiguratorCodeSchema,
|
|
944
1131
|
label: LabelSchema,
|
|
945
|
-
color:
|
|
1132
|
+
color: z4.string().regex(/^#[0-9a-f]{6}$/i, "configurator.invalidColor"),
|
|
946
1133
|
texture: picture.optional(),
|
|
947
1134
|
/** Millimetres one repeat of the texture covers; the two maps below share it. */
|
|
948
|
-
textureSize:
|
|
1135
|
+
textureSize: z4.number().positive().max(1e4).optional(),
|
|
949
1136
|
/** Relief that moves with the light: a tangent-space normal map. */
|
|
950
1137
|
normalMap: picture.optional(),
|
|
951
1138
|
/** How deep the relief reads; 1 is the map as drawn. */
|
|
952
|
-
normalScale:
|
|
1139
|
+
normalScale: z4.number().min(0).max(3).optional(),
|
|
953
1140
|
/** Where the surface is matte and where it shines (the green channel), times `roughness`. */
|
|
954
1141
|
roughnessMap: picture.optional(),
|
|
955
|
-
roughness:
|
|
956
|
-
metalness:
|
|
1142
|
+
roughness: z4.number().min(0).max(1).optional(),
|
|
1143
|
+
metalness: z4.number().min(0).max(1).optional(),
|
|
957
1144
|
/** How much of what is behind it shows through: glass in front of a flame. Absent is solid. */
|
|
958
|
-
opacity:
|
|
1145
|
+
opacity: z4.number().min(0.05).max(1).optional(),
|
|
959
1146
|
/** Gives light rather than reflecting it: a flame, a LED. */
|
|
960
|
-
emissive:
|
|
1147
|
+
emissive: z4.boolean().optional(),
|
|
961
1148
|
/**
|
|
962
1149
|
* The colour temperature of a light, in kelvin: a LED strip sold as
|
|
963
1150
|
* 3000 K glows and lights in that colour rather than in `color`, which
|
|
964
1151
|
* stays the swatch's fallback. A strip that can do several (2700-6500 K)
|
|
965
1152
|
* names the one to show: 4000 K.
|
|
966
1153
|
*/
|
|
967
|
-
kelvin:
|
|
1154
|
+
kelvin: z4.number().int().min(1500).max(1e4).optional(),
|
|
968
1155
|
priceGroup: ConfiguratorCodeSchema.optional()
|
|
969
1156
|
});
|
|
970
1157
|
var ConfiguratorChoiceListSchema = choiceListSchema(TexturePathSchema);
|
|
971
1158
|
var ConfiguratorMaterialSchema = materialSchema(TexturePathSchema);
|
|
972
|
-
var ConditionValueSchema =
|
|
973
|
-
|
|
974
|
-
|
|
1159
|
+
var ConditionValueSchema = z4.union([
|
|
1160
|
+
z4.number().finite(),
|
|
1161
|
+
z4.boolean(),
|
|
975
1162
|
ConfiguratorCodeSchema,
|
|
976
|
-
|
|
1163
|
+
z4.array(ConfiguratorCodeSchema).min(1).max(CONFIGURATOR_LIMITS.optionsPerList)
|
|
977
1164
|
]);
|
|
978
|
-
var ConfiguratorConditionSchema =
|
|
1165
|
+
var ConfiguratorConditionSchema = z4.strictObject({
|
|
979
1166
|
param: ConfiguratorNameSchema,
|
|
980
|
-
op:
|
|
1167
|
+
op: z4.enum(["eq", "neq", "in", "gte", "lte"]),
|
|
981
1168
|
value: ConditionValueSchema
|
|
982
1169
|
});
|
|
983
|
-
var ConfiguratorRuleSchema =
|
|
1170
|
+
var ConfiguratorRuleSchema = z4.discriminatedUnion("kind", [
|
|
984
1171
|
/** Show `param` only while `when` holds. */
|
|
985
|
-
|
|
986
|
-
kind:
|
|
1172
|
+
z4.strictObject({
|
|
1173
|
+
kind: z4.literal("visible"),
|
|
987
1174
|
param: ConfiguratorNameSchema,
|
|
988
1175
|
when: ConfiguratorConditionSchema
|
|
989
1176
|
}),
|
|
990
1177
|
/** Narrow a number parameter: `max cabinet_height = total_height - 300`. */
|
|
991
|
-
|
|
992
|
-
kind:
|
|
1178
|
+
z4.strictObject({
|
|
1179
|
+
kind: z4.literal("limit"),
|
|
993
1180
|
param: ConfiguratorNameSchema,
|
|
994
|
-
bound:
|
|
1181
|
+
bound: z4.enum(["min", "max"]),
|
|
995
1182
|
formula: FormulaSchema
|
|
996
1183
|
}),
|
|
997
1184
|
/** While `when` holds, these codes of a choice or material are not offered. */
|
|
998
|
-
|
|
999
|
-
kind:
|
|
1185
|
+
z4.strictObject({
|
|
1186
|
+
kind: z4.literal("exclude"),
|
|
1000
1187
|
param: ConfiguratorNameSchema,
|
|
1001
|
-
codes:
|
|
1188
|
+
codes: z4.array(ConfiguratorCodeSchema).min(1).max(CONFIGURATOR_LIMITS.optionsPerList),
|
|
1002
1189
|
when: ConfiguratorConditionSchema
|
|
1003
1190
|
})
|
|
1004
1191
|
]);
|
|
@@ -1008,23 +1195,23 @@ var PriceRuleBase = {
|
|
|
1008
1195
|
/** How many times the amount counts, e.g. `compartments`. Absent is once. */
|
|
1009
1196
|
multiplier: FormulaSchema.optional()
|
|
1010
1197
|
};
|
|
1011
|
-
var ConfiguratorPriceRuleSchema =
|
|
1198
|
+
var ConfiguratorPriceRuleSchema = z4.discriminatedUnion("kind", [
|
|
1012
1199
|
/** The surcharge the model gives the chosen option. */
|
|
1013
|
-
|
|
1200
|
+
z4.strictObject({
|
|
1014
1201
|
...PriceRuleBase,
|
|
1015
|
-
kind:
|
|
1202
|
+
kind: z4.literal("choice"),
|
|
1016
1203
|
param: ConfiguratorNameSchema
|
|
1017
1204
|
}),
|
|
1018
1205
|
/** The surcharge of the chosen material's price group. */
|
|
1019
|
-
|
|
1206
|
+
z4.strictObject({
|
|
1020
1207
|
...PriceRuleBase,
|
|
1021
|
-
kind:
|
|
1208
|
+
kind: z4.literal("material"),
|
|
1022
1209
|
param: ConfiguratorNameSchema
|
|
1023
1210
|
}),
|
|
1024
1211
|
/** An amount, counted while a switch is on. */
|
|
1025
|
-
|
|
1212
|
+
z4.strictObject({
|
|
1026
1213
|
...PriceRuleBase,
|
|
1027
|
-
kind:
|
|
1214
|
+
kind: z4.literal("toggle"),
|
|
1028
1215
|
param: ConfiguratorNameSchema,
|
|
1029
1216
|
amount: ConfiguratorNameSchema
|
|
1030
1217
|
}),
|
|
@@ -1032,10 +1219,10 @@ var ConfiguratorPriceRuleSchema = z2.discriminatedUnion("kind", [
|
|
|
1032
1219
|
* A price on measure: `max(0, total_width - 1800) / 1000 * meter_price`.
|
|
1033
1220
|
* Reads number parameters (in their unit) and amounts (in cents).
|
|
1034
1221
|
*/
|
|
1035
|
-
|
|
1222
|
+
z4.strictObject({
|
|
1036
1223
|
key: ConfiguratorNameSchema,
|
|
1037
1224
|
label: LabelSchema,
|
|
1038
|
-
kind:
|
|
1225
|
+
kind: z4.literal("formula"),
|
|
1039
1226
|
formula: FormulaSchema
|
|
1040
1227
|
})
|
|
1041
1228
|
]);
|
|
@@ -1045,73 +1232,73 @@ var CONFIGURATOR_SCENE_LIGHTINGS = [
|
|
|
1045
1232
|
"daylight"
|
|
1046
1233
|
];
|
|
1047
1234
|
var CONFIGURATOR_SCENE_EDGES = ["soft", "sharp"];
|
|
1048
|
-
var RoomColorSchema =
|
|
1235
|
+
var RoomColorSchema = z4.strictObject({
|
|
1049
1236
|
code: ConfiguratorCodeSchema,
|
|
1050
1237
|
label: LabelSchema,
|
|
1051
|
-
color:
|
|
1238
|
+
color: z4.string().regex(/^#[0-9a-f]{6}$/i, "configurator.invalidColor")
|
|
1052
1239
|
});
|
|
1053
|
-
var roomSurfaceSchema = (max) =>
|
|
1240
|
+
var roomSurfaceSchema = (max) => z4.strictObject({
|
|
1054
1241
|
/** The code shown first; absent is the first option. */
|
|
1055
1242
|
default: ConfiguratorCodeSchema.optional(),
|
|
1056
1243
|
/** Absent offers the package's own palette (`room-palette.ts`). */
|
|
1057
|
-
options:
|
|
1244
|
+
options: z4.array(RoomColorSchema).min(1).max(max).optional()
|
|
1058
1245
|
});
|
|
1059
|
-
var ConfiguratorRoomSchema =
|
|
1246
|
+
var ConfiguratorRoomSchema = z4.strictObject({
|
|
1060
1247
|
wall: roomSurfaceSchema(CONFIGURATOR_LIMITS.roomWallOptions).optional(),
|
|
1061
1248
|
floor: roomSurfaceSchema(CONFIGURATOR_LIMITS.roomFloorOptions).optional(),
|
|
1062
1249
|
/** Millimetres of room beside the furniture on each side; 1500 by default. */
|
|
1063
|
-
margin:
|
|
1250
|
+
margin: z4.number().min(0).max(5e3).optional(),
|
|
1064
1251
|
/** Millimetres of floor in front of the wall; 3500 by default. */
|
|
1065
|
-
depth:
|
|
1252
|
+
depth: z4.number().min(1e3).max(1e4).optional()
|
|
1066
1253
|
});
|
|
1067
|
-
var ConfiguratorSceneSchema =
|
|
1068
|
-
lighting:
|
|
1069
|
-
edges:
|
|
1254
|
+
var ConfiguratorSceneSchema = z4.strictObject({
|
|
1255
|
+
lighting: z4.enum(CONFIGURATOR_SCENE_LIGHTINGS).optional(),
|
|
1256
|
+
edges: z4.enum(CONFIGURATOR_SCENE_EDGES).optional(),
|
|
1070
1257
|
room: ConfiguratorRoomSchema.optional()
|
|
1071
1258
|
});
|
|
1072
|
-
var FocusNameSchema =
|
|
1073
|
-
var ConfiguratorFocusSchema =
|
|
1259
|
+
var FocusNameSchema = z4.string().regex(/^[a-z][a-z0-9_]{0,39}$/, "configurator.invalidName");
|
|
1260
|
+
var ConfiguratorFocusSchema = z4.strictObject({
|
|
1074
1261
|
param: ConfiguratorNameSchema,
|
|
1075
1262
|
when: ConfiguratorConditionSchema.optional(),
|
|
1076
|
-
open:
|
|
1077
|
-
frame:
|
|
1078
|
-
night:
|
|
1263
|
+
open: z4.array(FocusNameSchema).max(CONFIGURATOR_LIMITS.focusNames).optional(),
|
|
1264
|
+
frame: z4.array(FocusNameSchema).min(1).max(CONFIGURATOR_LIMITS.focusNames).optional(),
|
|
1265
|
+
night: z4.boolean().optional()
|
|
1079
1266
|
});
|
|
1080
|
-
var structureObjectSchema = (files) =>
|
|
1081
|
-
schemaVersion:
|
|
1267
|
+
var structureObjectSchema = (files) => z4.strictObject({
|
|
1268
|
+
schemaVersion: z4.literal(1),
|
|
1082
1269
|
geometry: geometryDocumentSchema(files.model),
|
|
1083
1270
|
/**
|
|
1084
1271
|
* How the scene is presented. Part of the structure and not of the shape
|
|
1085
1272
|
* document, so the owner sets it under `crm.manage` like a material.
|
|
1086
1273
|
*/
|
|
1087
1274
|
scene: ConfiguratorSceneSchema.optional(),
|
|
1088
|
-
tabs:
|
|
1089
|
-
|
|
1275
|
+
tabs: z4.array(
|
|
1276
|
+
z4.strictObject({ key: ConfiguratorNameSchema, label: LabelSchema })
|
|
1090
1277
|
).min(1).max(CONFIGURATOR_LIMITS.tabs),
|
|
1091
|
-
parameters:
|
|
1092
|
-
choiceLists:
|
|
1093
|
-
materials:
|
|
1094
|
-
priceGroups:
|
|
1095
|
-
|
|
1278
|
+
parameters: z4.array(ConfiguratorParameterSchema).max(CONFIGURATOR_LIMITS.parameters),
|
|
1279
|
+
choiceLists: z4.array(choiceListSchema(files.picture)).max(CONFIGURATOR_LIMITS.choiceLists),
|
|
1280
|
+
materials: z4.array(materialSchema(files.picture)).max(CONFIGURATOR_LIMITS.materials),
|
|
1281
|
+
priceGroups: z4.array(
|
|
1282
|
+
z4.strictObject({ code: ConfiguratorCodeSchema, label: LabelSchema })
|
|
1096
1283
|
).max(CONFIGURATOR_LIMITS.priceGroups),
|
|
1097
1284
|
/** Named amounts a model fills in and price rules read (`meter_price`). */
|
|
1098
|
-
amounts:
|
|
1099
|
-
|
|
1285
|
+
amounts: z4.array(
|
|
1286
|
+
z4.strictObject({ key: ConfiguratorNameSchema, label: LabelSchema })
|
|
1100
1287
|
).max(CONFIGURATOR_LIMITS.amounts),
|
|
1101
1288
|
/**
|
|
1102
1289
|
* A list of fixed combinations a model offers, each setting some number
|
|
1103
1290
|
* parameters at once: a fireplace and the niche it needs.
|
|
1104
1291
|
*/
|
|
1105
|
-
presets:
|
|
1292
|
+
presets: z4.strictObject({
|
|
1106
1293
|
label: LabelSchema,
|
|
1107
1294
|
tab: ConfiguratorNameSchema,
|
|
1108
|
-
sets:
|
|
1109
|
-
required:
|
|
1295
|
+
sets: z4.array(ConfiguratorNameSchema).min(1).max(CONFIGURATOR_LIMITS.parameters),
|
|
1296
|
+
required: z4.boolean()
|
|
1110
1297
|
}).optional(),
|
|
1111
|
-
rules:
|
|
1298
|
+
rules: z4.array(ConfiguratorRuleSchema).max(CONFIGURATOR_LIMITS.rules),
|
|
1112
1299
|
/** What the picture opens and looks at when a choice changes. */
|
|
1113
|
-
focus:
|
|
1114
|
-
priceRules:
|
|
1300
|
+
focus: z4.array(ConfiguratorFocusSchema).max(CONFIGURATOR_LIMITS.focus).optional(),
|
|
1301
|
+
priceRules: z4.array(ConfiguratorPriceRuleSchema).max(CONFIGURATOR_LIMITS.priceRules)
|
|
1115
1302
|
});
|
|
1116
1303
|
var ConfiguratorStructureObjectSchema = structureObjectSchema({
|
|
1117
1304
|
picture: TexturePathSchema,
|
|
@@ -1599,55 +1786,55 @@ function parameterResolutionOrder(structure) {
|
|
|
1599
1786
|
}
|
|
1600
1787
|
|
|
1601
1788
|
// ../features/product-configurator/src/shared/configurator-value.schema.ts
|
|
1602
|
-
import { z as
|
|
1603
|
-
var CentsSchema =
|
|
1789
|
+
import { z as z5 } from "zod";
|
|
1790
|
+
var CentsSchema = z5.number().int().min(0).max(1e8);
|
|
1604
1791
|
var PriceSchema = CentsSchema.nullable();
|
|
1605
|
-
var ConfiguratorValueObjectSchema =
|
|
1792
|
+
var ConfiguratorValueObjectSchema = z5.strictObject({
|
|
1606
1793
|
/** Null: the model has no price of its own, every quote is "open". */
|
|
1607
1794
|
basePriceCents: PriceSchema,
|
|
1608
|
-
numbers:
|
|
1795
|
+
numbers: z5.record(
|
|
1609
1796
|
ConfiguratorNameSchema,
|
|
1610
|
-
|
|
1611
|
-
min:
|
|
1612
|
-
max:
|
|
1613
|
-
default:
|
|
1797
|
+
z5.strictObject({
|
|
1798
|
+
min: z5.number().finite(),
|
|
1799
|
+
max: z5.number().finite(),
|
|
1800
|
+
default: z5.number().finite()
|
|
1614
1801
|
})
|
|
1615
1802
|
).default({}),
|
|
1616
1803
|
/** Per choice list: the options this model offers, with their surcharge. */
|
|
1617
|
-
choices:
|
|
1804
|
+
choices: z5.record(
|
|
1618
1805
|
ConfiguratorNameSchema,
|
|
1619
|
-
|
|
1620
|
-
options:
|
|
1806
|
+
z5.strictObject({
|
|
1807
|
+
options: z5.record(
|
|
1621
1808
|
ConfiguratorCodeSchema,
|
|
1622
|
-
|
|
1809
|
+
z5.strictObject({ priceCents: PriceSchema })
|
|
1623
1810
|
),
|
|
1624
1811
|
default: ConfiguratorCodeSchema.optional()
|
|
1625
1812
|
})
|
|
1626
1813
|
).default({}),
|
|
1627
1814
|
/** Per material parameter: the materials this model offers. */
|
|
1628
|
-
materials:
|
|
1815
|
+
materials: z5.record(
|
|
1629
1816
|
ConfiguratorNameSchema,
|
|
1630
|
-
|
|
1631
|
-
offered:
|
|
1817
|
+
z5.strictObject({
|
|
1818
|
+
offered: z5.array(ConfiguratorCodeSchema).max(CONFIGURATOR_LIMITS.materials),
|
|
1632
1819
|
default: ConfiguratorCodeSchema.optional()
|
|
1633
1820
|
})
|
|
1634
1821
|
).default({}),
|
|
1635
1822
|
/** Per switch: whether this model offers it, and how it starts. Absent is offered, off. */
|
|
1636
|
-
toggles:
|
|
1823
|
+
toggles: z5.record(
|
|
1637
1824
|
ConfiguratorNameSchema,
|
|
1638
|
-
|
|
1825
|
+
z5.strictObject({ offered: z5.boolean(), default: z5.boolean() })
|
|
1639
1826
|
).default({}),
|
|
1640
1827
|
/** The surcharge of each material price group. */
|
|
1641
|
-
priceGroups:
|
|
1828
|
+
priceGroups: z5.record(ConfiguratorCodeSchema, PriceSchema).default({}),
|
|
1642
1829
|
/** The amounts the structure names, such as a price per metre. */
|
|
1643
|
-
amounts:
|
|
1644
|
-
presets:
|
|
1645
|
-
|
|
1830
|
+
amounts: z5.record(ConfiguratorNameSchema, PriceSchema).default({}),
|
|
1831
|
+
presets: z5.array(
|
|
1832
|
+
z5.strictObject({
|
|
1646
1833
|
code: ConfiguratorCodeSchema,
|
|
1647
|
-
label:
|
|
1834
|
+
label: z5.string().trim().min(1).max(80),
|
|
1648
1835
|
/** A product of the same account this preset stands for, e.g. the fireplace. */
|
|
1649
|
-
productId:
|
|
1650
|
-
values:
|
|
1836
|
+
productId: z5.string().uuid().nullable(),
|
|
1837
|
+
values: z5.record(ConfiguratorNameSchema, z5.number().finite())
|
|
1651
1838
|
})
|
|
1652
1839
|
).max(CONFIGURATOR_LIMITS.presets).default([])
|
|
1653
1840
|
});
|
|
@@ -2148,13 +2335,13 @@ var PRODUCT_OPTION_LIMITS = {
|
|
|
2148
2335
|
choicesBytes: 8 * 1024,
|
|
2149
2336
|
maxSurchargeCents: 1e8
|
|
2150
2337
|
};
|
|
2151
|
-
var KeySchema =
|
|
2338
|
+
var KeySchema = z6.string().regex(/^[a-z0-9_]{1,40}$/, {
|
|
2152
2339
|
// i18n-exempt: returned verbatim to an integration's own code, never rendered in this app's UI
|
|
2153
2340
|
message: "A key is 1 to 40 lower-case letters, digits or underscores"
|
|
2154
2341
|
});
|
|
2155
|
-
var LabelSchema2 =
|
|
2156
|
-
var SurchargeSchema =
|
|
2157
|
-
var ProductOptionChoiceSchema =
|
|
2342
|
+
var LabelSchema2 = z6.string().trim().min(1).max(PRODUCT_OPTION_LIMITS.labelLength);
|
|
2343
|
+
var SurchargeSchema = z6.number().int().min(0).max(PRODUCT_OPTION_LIMITS.maxSurchargeCents);
|
|
2344
|
+
var ProductOptionChoiceSchema = z6.object({
|
|
2158
2345
|
key: KeySchema,
|
|
2159
2346
|
label: LabelSchema2,
|
|
2160
2347
|
surchargeCents: SurchargeSchema
|
|
@@ -2162,26 +2349,26 @@ var ProductOptionChoiceSchema = z4.object({
|
|
|
2162
2349
|
var OptionFields = {
|
|
2163
2350
|
key: KeySchema,
|
|
2164
2351
|
label: LabelSchema2,
|
|
2165
|
-
helpText:
|
|
2166
|
-
required:
|
|
2167
|
-
surchargePerUnit:
|
|
2352
|
+
helpText: z6.string().trim().max(PRODUCT_OPTION_LIMITS.helpTextLength).nullable().default(null),
|
|
2353
|
+
required: z6.boolean().default(false),
|
|
2354
|
+
surchargePerUnit: z6.boolean().default(true)
|
|
2168
2355
|
};
|
|
2169
|
-
var ProductOptionSchema =
|
|
2170
|
-
|
|
2356
|
+
var ProductOptionSchema = z6.discriminatedUnion("kind", [
|
|
2357
|
+
z6.object({
|
|
2171
2358
|
...OptionFields,
|
|
2172
|
-
kind:
|
|
2173
|
-
maxLength:
|
|
2359
|
+
kind: z6.literal("text"),
|
|
2360
|
+
maxLength: z6.number().int().min(1).max(PRODUCT_OPTION_LIMITS.textLength),
|
|
2174
2361
|
surchargeCents: SurchargeSchema.default(0)
|
|
2175
2362
|
}),
|
|
2176
|
-
|
|
2363
|
+
z6.object({
|
|
2177
2364
|
...OptionFields,
|
|
2178
|
-
kind:
|
|
2365
|
+
kind: z6.literal("checkbox"),
|
|
2179
2366
|
surchargeCents: SurchargeSchema.default(0)
|
|
2180
2367
|
}),
|
|
2181
|
-
|
|
2368
|
+
z6.object({
|
|
2182
2369
|
...OptionFields,
|
|
2183
|
-
kind:
|
|
2184
|
-
choices:
|
|
2370
|
+
kind: z6.literal("choice"),
|
|
2371
|
+
choices: z6.array(ProductOptionChoiceSchema).min(PRODUCT_OPTION_LIMITS.minChoices).max(PRODUCT_OPTION_LIMITS.maxChoices)
|
|
2185
2372
|
})
|
|
2186
2373
|
]).superRefine((option, context) => {
|
|
2187
2374
|
if (option.kind !== "choice") {
|
|
@@ -2205,7 +2392,7 @@ var ProductOptionSchema = z4.discriminatedUnion("kind", [
|
|
|
2205
2392
|
});
|
|
2206
2393
|
}
|
|
2207
2394
|
});
|
|
2208
|
-
var ProductOptionsSchema =
|
|
2395
|
+
var ProductOptionsSchema = z6.array(ProductOptionSchema).max(PRODUCT_OPTION_LIMITS.options).superRefine((options, context) => {
|
|
2209
2396
|
const keys = options.map((option) => option.key);
|
|
2210
2397
|
if (new Set(keys).size !== keys.length) {
|
|
2211
2398
|
context.addIssue({
|
|
@@ -2232,47 +2419,47 @@ var MAX_AMOUNT_CENTS = 99999999;
|
|
|
2232
2419
|
// ../features/crm-invoicing/src/schema/catalog-api.schema.ts
|
|
2233
2420
|
var WEB_SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2234
2421
|
var FIELD_SLUG = /^[a-z][a-z0-9_]*$/;
|
|
2235
|
-
var CatalogSourceSchema =
|
|
2422
|
+
var CatalogSourceSchema = z7.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, {
|
|
2236
2423
|
message: (
|
|
2237
2424
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2238
2425
|
'A source is lower-case letters, digits and hyphens, at most 64 characters (for example "coolflames-payload").'
|
|
2239
2426
|
)
|
|
2240
2427
|
});
|
|
2241
|
-
var CatalogExternalRefSchema =
|
|
2242
|
-
var CatalogWebSlugSchema =
|
|
2428
|
+
var CatalogExternalRefSchema = z7.string().trim().min(1).max(255);
|
|
2429
|
+
var CatalogWebSlugSchema = z7.string().min(2).max(64).regex(WEB_SLUG, {
|
|
2243
2430
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2244
2431
|
message: "An address is lower-case letters and digits joined by hyphens."
|
|
2245
2432
|
});
|
|
2246
|
-
var CatalogFieldSlugSchema =
|
|
2433
|
+
var CatalogFieldSlugSchema = z7.string().max(63).regex(FIELD_SLUG, {
|
|
2247
2434
|
message: (
|
|
2248
2435
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2249
2436
|
"A field slug starts with a letter and holds lower-case letters, digits and underscores."
|
|
2250
2437
|
)
|
|
2251
2438
|
});
|
|
2252
|
-
var cents =
|
|
2253
|
-
var CatalogCategoryPlacementSchema =
|
|
2439
|
+
var cents = z7.number().int().min(0).max(MAX_AMOUNT_CENTS);
|
|
2440
|
+
var CatalogCategoryPlacementSchema = z7.object({
|
|
2254
2441
|
slug: CatalogWebSlugSchema,
|
|
2255
2442
|
/** Lowest first; equal places stand in id order. */
|
|
2256
|
-
position:
|
|
2443
|
+
position: z7.number().int().min(-1e6).max(1e6).optional()
|
|
2257
2444
|
}).strict();
|
|
2258
|
-
var text = (max) =>
|
|
2259
|
-
var CatalogPriceSchema =
|
|
2445
|
+
var text = (max) => z7.string().trim().max(max).nullable().optional();
|
|
2446
|
+
var CatalogPriceSchema = z7.object({
|
|
2260
2447
|
/** The price's own id at the source, so a renamed price stays itself. */
|
|
2261
2448
|
externalRef: CatalogExternalRefSchema.optional(),
|
|
2262
|
-
label:
|
|
2449
|
+
label: z7.string().trim().min(1).max(120),
|
|
2263
2450
|
/**
|
|
2264
2451
|
* ISO 4217. Left out, the price is in the product's own currency. A
|
|
2265
2452
|
* price in another currency is the fixed price for that currency: it
|
|
2266
2453
|
* wins over a converted one and is never the default.
|
|
2267
2454
|
*/
|
|
2268
|
-
currency:
|
|
2455
|
+
currency: z7.string().regex(/^[A-Z]{3}$/).optional(),
|
|
2269
2456
|
amountCents: cents,
|
|
2270
2457
|
compareAtCents: cents.nullable().optional(),
|
|
2271
|
-
kind:
|
|
2272
|
-
recurringInterval:
|
|
2273
|
-
recurringIntervalCount:
|
|
2274
|
-
isDefault:
|
|
2275
|
-
active:
|
|
2458
|
+
kind: z7.enum(["one_off", "recurring"]).optional(),
|
|
2459
|
+
recurringInterval: z7.enum(["day", "week", "month", "year"]).optional(),
|
|
2460
|
+
recurringIntervalCount: z7.number().int().min(1).max(365).optional(),
|
|
2461
|
+
isDefault: z7.boolean().optional(),
|
|
2462
|
+
active: z7.boolean().optional()
|
|
2276
2463
|
}).strict().superRefine((price, ctx) => {
|
|
2277
2464
|
if ((price.kind ?? "one_off") === "recurring" && !price.recurringInterval) {
|
|
2278
2465
|
ctx.addIssue({
|
|
@@ -2283,7 +2470,7 @@ var CatalogPriceSchema = z5.object({
|
|
|
2283
2470
|
});
|
|
2284
2471
|
}
|
|
2285
2472
|
});
|
|
2286
|
-
var pricesSchema =
|
|
2473
|
+
var pricesSchema = z7.array(CatalogPriceSchema).min(1).max(50).superRefine((prices, ctx) => {
|
|
2287
2474
|
if (prices.filter((price) => price.isDefault).length > 1) {
|
|
2288
2475
|
ctx.addIssue({
|
|
2289
2476
|
code: "custom",
|
|
@@ -2312,24 +2499,24 @@ var pricesSchema = z5.array(CatalogPriceSchema).min(1).max(50).superRefine((pric
|
|
|
2312
2499
|
});
|
|
2313
2500
|
}
|
|
2314
2501
|
});
|
|
2315
|
-
var HttpsUrlSchema =
|
|
2502
|
+
var HttpsUrlSchema = z7.string().max(2048).url().refine((url) => url.startsWith("https://"), {
|
|
2316
2503
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2317
2504
|
message: "A file is fetched over https only."
|
|
2318
2505
|
});
|
|
2319
|
-
var UploadPathSchema =
|
|
2320
|
-
var CatalogFileRefSchema =
|
|
2321
|
-
|
|
2322
|
-
|
|
2506
|
+
var UploadPathSchema = z7.string().min(1).max(200);
|
|
2507
|
+
var CatalogFileRefSchema = z7.union([
|
|
2508
|
+
z7.object({ url: HttpsUrlSchema }).strict(),
|
|
2509
|
+
z7.object({ upload: UploadPathSchema }).strict()
|
|
2323
2510
|
]);
|
|
2324
2511
|
var IMAGE_ROLE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
2325
|
-
var CatalogImageDetailsSchema =
|
|
2326
|
-
|
|
2327
|
-
|
|
2512
|
+
var CatalogImageDetailsSchema = z7.record(
|
|
2513
|
+
z7.string().min(1).max(64),
|
|
2514
|
+
z7.union([z7.string().max(500), z7.number().finite(), z7.boolean()])
|
|
2328
2515
|
).refine((details) => JSON.stringify(details).length <= 1800, {
|
|
2329
2516
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2330
2517
|
message: "Details are at most about 2 KB."
|
|
2331
2518
|
});
|
|
2332
|
-
var CatalogImageSchema =
|
|
2519
|
+
var CatalogImageSchema = z7.object({
|
|
2333
2520
|
url: HttpsUrlSchema.optional(),
|
|
2334
2521
|
upload: UploadPathSchema.optional(),
|
|
2335
2522
|
alt: text(300),
|
|
@@ -2337,7 +2524,7 @@ var CatalogImageSchema = z5.object({
|
|
|
2337
2524
|
* What the picture is of, in the account's own words: `work`, `detail`,
|
|
2338
2525
|
* `insitu`. Left out keeps the role a stored picture has; `null` clears it.
|
|
2339
2526
|
*/
|
|
2340
|
-
role:
|
|
2527
|
+
role: z7.string().regex(IMAGE_ROLE, {
|
|
2341
2528
|
message: (
|
|
2342
2529
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2343
2530
|
"A role starts with a letter and holds lower-case letters, digits, hyphens and underscores, at most 32."
|
|
@@ -2351,33 +2538,33 @@ var CatalogImageSchema = z5.object({
|
|
|
2351
2538
|
message: "A picture names either a url or an upload, not both."
|
|
2352
2539
|
}
|
|
2353
2540
|
);
|
|
2354
|
-
var CatalogStockSchema =
|
|
2355
|
-
managed:
|
|
2541
|
+
var CatalogStockSchema = z7.object({
|
|
2542
|
+
managed: z7.boolean(),
|
|
2356
2543
|
/** Left out, the count stays as it is. */
|
|
2357
|
-
quantity:
|
|
2544
|
+
quantity: z7.number().int().min(0).max(1e9).optional(),
|
|
2358
2545
|
/** Below this a product counts as running low; `null` warns never. */
|
|
2359
|
-
lowThreshold:
|
|
2546
|
+
lowThreshold: z7.number().int().min(0).max(1e9).nullable().optional(),
|
|
2360
2547
|
/** Whether it can still be ordered at zero: `deny`, `allow`, or `allow_notify`. */
|
|
2361
|
-
backorders:
|
|
2548
|
+
backorders: z7.enum(["deny", "allow", "allow_notify"]).optional()
|
|
2362
2549
|
}).strict();
|
|
2363
|
-
var CatalogVariantAxisSchema =
|
|
2550
|
+
var CatalogVariantAxisSchema = z7.object({
|
|
2364
2551
|
slug: CatalogFieldSlugSchema,
|
|
2365
|
-
label:
|
|
2552
|
+
label: z7.string().trim().min(1).max(120).optional()
|
|
2366
2553
|
}).strict();
|
|
2367
|
-
var CatalogVariantSchema =
|
|
2554
|
+
var CatalogVariantSchema = z7.object({
|
|
2368
2555
|
externalRef: CatalogExternalRefSchema,
|
|
2369
2556
|
/** The value per axis, keyed by the axis slug. */
|
|
2370
|
-
options:
|
|
2557
|
+
options: z7.record(
|
|
2371
2558
|
CatalogFieldSlugSchema,
|
|
2372
|
-
|
|
2559
|
+
z7.string().trim().min(1).max(120)
|
|
2373
2560
|
),
|
|
2374
2561
|
sku: text(100),
|
|
2375
|
-
gtin:
|
|
2562
|
+
gtin: z7.string().regex(/^[0-9]{8,14}$/).nullable().optional(),
|
|
2376
2563
|
prices: pricesSchema.optional(),
|
|
2377
2564
|
/** The variant's own shelf; a physical product counts per variant. */
|
|
2378
2565
|
stock: CatalogStockSchema.optional(),
|
|
2379
|
-
position:
|
|
2380
|
-
active:
|
|
2566
|
+
position: z7.number().int().min(0).max(1e4).optional(),
|
|
2567
|
+
active: z7.boolean().optional()
|
|
2381
2568
|
}).strict();
|
|
2382
2569
|
var CATALOG_AVAILABILITIES = [
|
|
2383
2570
|
"draft",
|
|
@@ -2385,26 +2572,26 @@ var CATALOG_AVAILABILITIES = [
|
|
|
2385
2572
|
"scheduled",
|
|
2386
2573
|
"public"
|
|
2387
2574
|
];
|
|
2388
|
-
var CatalogProductUpsertSchema =
|
|
2575
|
+
var CatalogProductUpsertSchema = z7.object({
|
|
2389
2576
|
source: CatalogSourceSchema,
|
|
2390
2577
|
/** Required to create a product; optional to update one. */
|
|
2391
|
-
name:
|
|
2578
|
+
name: z7.string().trim().min(1).max(500).optional(),
|
|
2392
2579
|
slug: CatalogWebSlugSchema.nullable().optional(),
|
|
2393
2580
|
description: text(5e3),
|
|
2394
2581
|
/** HTML; cleaned against the same allow-list the app uses. */
|
|
2395
2582
|
longDescription: text(1e5),
|
|
2396
2583
|
sku: text(100),
|
|
2397
|
-
gtin:
|
|
2398
|
-
productType:
|
|
2584
|
+
gtin: z7.string().regex(/^[0-9]{8,14}$/).nullable().optional(),
|
|
2585
|
+
productType: z7.enum(["digital", "physical"]).optional(),
|
|
2399
2586
|
/**
|
|
2400
2587
|
* Where the product stands. A new product is a `draft` unless this says
|
|
2401
2588
|
* otherwise -- an import should not put a hundred products on somebody's
|
|
2402
2589
|
* website because a run finished.
|
|
2403
2590
|
*/
|
|
2404
|
-
availability:
|
|
2591
|
+
availability: z7.enum(CATALOG_AVAILABILITIES).optional(),
|
|
2405
2592
|
/** The moment for `scheduled`; ignored for the other three. */
|
|
2406
|
-
publishedAt:
|
|
2407
|
-
visibility:
|
|
2593
|
+
publishedAt: z7.string().datetime({ offset: true }).nullable().optional(),
|
|
2594
|
+
visibility: z7.enum(["public", "unlisted"]).optional(),
|
|
2408
2595
|
metaTitle: text(300),
|
|
2409
2596
|
metaDescription: text(1e3),
|
|
2410
2597
|
/**
|
|
@@ -2422,21 +2609,21 @@ var CatalogProductUpsertSchema = z5.object({
|
|
|
2422
2609
|
* product stands on that category's page (`?sort=position`, lowest
|
|
2423
2610
|
* first); a plain slug leaves a stored place as it is.
|
|
2424
2611
|
*/
|
|
2425
|
-
categories:
|
|
2612
|
+
categories: z7.array(z7.union([CatalogWebSlugSchema, CatalogCategoryPlacementSchema])).max(50).optional(),
|
|
2426
2613
|
/**
|
|
2427
2614
|
* Make a category that does not exist yet instead of refusing. Off unless
|
|
2428
2615
|
* asked for, so a typo cannot become a category on a customer's website.
|
|
2429
2616
|
*/
|
|
2430
|
-
createMissingCategories:
|
|
2617
|
+
createMissingCategories: z7.boolean().optional(),
|
|
2431
2618
|
/** Field values by field slug; `null` empties one field. */
|
|
2432
|
-
fields:
|
|
2619
|
+
fields: z7.record(CatalogFieldSlugSchema, z7.unknown()).optional(),
|
|
2433
2620
|
/** Which fields the product's own page shows, in that order. */
|
|
2434
|
-
publicFields:
|
|
2621
|
+
publicFields: z7.array(CatalogFieldSlugSchema).max(100).optional(),
|
|
2435
2622
|
/**
|
|
2436
2623
|
* Add a choice a select field does not offer yet instead of refusing.
|
|
2437
2624
|
* Off unless asked for, for the same reason as categories.
|
|
2438
2625
|
*/
|
|
2439
|
-
addMissingOptions:
|
|
2626
|
+
addMissingOptions: z7.boolean().optional(),
|
|
2440
2627
|
/** Replaces the product's prices as a whole. */
|
|
2441
2628
|
prices: pricesSchema.optional(),
|
|
2442
2629
|
/**
|
|
@@ -2445,29 +2632,29 @@ var CatalogProductUpsertSchema = z5.object({
|
|
|
2445
2632
|
*/
|
|
2446
2633
|
stock: CatalogStockSchema.optional(),
|
|
2447
2634
|
/** Replaces the product's pictures, in this order. */
|
|
2448
|
-
images:
|
|
2449
|
-
variantAxes:
|
|
2635
|
+
images: z7.array(CatalogImageSchema).max(20).optional(),
|
|
2636
|
+
variantAxes: z7.array(CatalogVariantAxisSchema).min(1).max(3).optional(),
|
|
2450
2637
|
/** Replaces the product's variants; one missing here is archived. */
|
|
2451
|
-
variants:
|
|
2638
|
+
variants: z7.array(CatalogVariantSchema).max(100).optional(),
|
|
2452
2639
|
/** `false` brings back a product the API archived earlier. */
|
|
2453
|
-
archived:
|
|
2640
|
+
archived: z7.literal(false).optional(),
|
|
2454
2641
|
/**
|
|
2455
2642
|
* Sold only as an extra with another product -- a warranty, assembly --
|
|
2456
2643
|
* and how: at most `maxQuantity` per piece or per line, following the
|
|
2457
2644
|
* quantity of its line (`perPiece`), and whether only the team adds it
|
|
2458
2645
|
* (`teamOnly`). `null` makes it a product of its own again.
|
|
2459
2646
|
*/
|
|
2460
|
-
soldAsExtra:
|
|
2461
|
-
maxQuantity:
|
|
2462
|
-
perPiece:
|
|
2463
|
-
teamOnly:
|
|
2647
|
+
soldAsExtra: z7.object({
|
|
2648
|
+
maxQuantity: z7.number().int().min(1).max(99).default(1),
|
|
2649
|
+
perPiece: z7.boolean().default(false),
|
|
2650
|
+
teamOnly: z7.boolean().default(false)
|
|
2464
2651
|
}).strict().nullable().optional(),
|
|
2465
2652
|
/**
|
|
2466
2653
|
* Replaces what may be taken with this product, in this order: each the
|
|
2467
2654
|
* `externalRef` of another product of the same source and the same
|
|
2468
2655
|
* catalogue. `[]` takes them all off.
|
|
2469
2656
|
*/
|
|
2470
|
-
extras:
|
|
2657
|
+
extras: z7.array(CatalogExternalRefSchema).max(50).optional(),
|
|
2471
2658
|
/**
|
|
2472
2659
|
* Replaces what a buyer chooses or types on this product, with a
|
|
2473
2660
|
* surcharge in the account's own currency (`ProductOptionsSchema`).
|
|
@@ -2546,8 +2733,8 @@ var CatalogProductUpsertSchema = z5.object({
|
|
|
2546
2733
|
});
|
|
2547
2734
|
}
|
|
2548
2735
|
});
|
|
2549
|
-
var CatalogProductArchiveSchema =
|
|
2550
|
-
var CatalogCategoryUpsertSchema =
|
|
2736
|
+
var CatalogProductArchiveSchema = z7.object({ source: CatalogSourceSchema }).strict();
|
|
2737
|
+
var CatalogCategoryUpsertSchema = z7.object({
|
|
2551
2738
|
/**
|
|
2552
2739
|
* The category's id in the system it came from -- a series in a site's
|
|
2553
2740
|
* own CMS -- with `source` naming whose id it is. With both, a category
|
|
@@ -2557,12 +2744,12 @@ var CatalogCategoryUpsertSchema = z5.object({
|
|
|
2557
2744
|
*/
|
|
2558
2745
|
source: CatalogSourceSchema.optional(),
|
|
2559
2746
|
externalRef: CatalogExternalRefSchema.optional(),
|
|
2560
|
-
name:
|
|
2747
|
+
name: z7.string().trim().min(1).max(200).optional(),
|
|
2561
2748
|
description: text(5e3),
|
|
2562
2749
|
/** `null` puts the category at the top of the tree. */
|
|
2563
2750
|
parentSlug: CatalogWebSlugSchema.nullable().optional(),
|
|
2564
|
-
availability:
|
|
2565
|
-
publishedAt:
|
|
2751
|
+
availability: z7.enum(CATALOG_AVAILABILITIES).optional(),
|
|
2752
|
+
publishedAt: z7.string().datetime({ offset: true }).nullable().optional(),
|
|
2566
2753
|
metaTitle: text(300),
|
|
2567
2754
|
metaDescription: text(1e3)
|
|
2568
2755
|
}).strict().refine(
|
|
@@ -2573,19 +2760,19 @@ var CatalogCategoryUpsertSchema = z5.object({
|
|
|
2573
2760
|
message: "A category reference names both source and externalRef."
|
|
2574
2761
|
}
|
|
2575
2762
|
);
|
|
2576
|
-
var CatalogObjectSlugSchema =
|
|
2763
|
+
var CatalogObjectSlugSchema = z7.string().max(64).regex(FIELD_SLUG, {
|
|
2577
2764
|
message: (
|
|
2578
2765
|
// i18n-exempt: a public API's field error, returned verbatim to an integration's own code and never shown in this app
|
|
2579
2766
|
"A catalogue slug starts with a letter and holds lower-case letters, digits and underscores."
|
|
2580
2767
|
)
|
|
2581
2768
|
});
|
|
2582
|
-
var CatalogCatalogueUpsertSchema =
|
|
2583
|
-
nameSingular:
|
|
2584
|
-
namePlural:
|
|
2769
|
+
var CatalogCatalogueUpsertSchema = z7.object({
|
|
2770
|
+
nameSingular: z7.string().trim().min(1).max(120).optional(),
|
|
2771
|
+
namePlural: z7.string().trim().min(1).max(120).optional(),
|
|
2585
2772
|
/** A lucide icon name, as the object settings offer them. */
|
|
2586
|
-
icon:
|
|
2773
|
+
icon: z7.string().trim().max(64).nullable().optional()
|
|
2587
2774
|
}).strict();
|
|
2588
|
-
var CatalogFieldTypeSchema =
|
|
2775
|
+
var CatalogFieldTypeSchema = z7.enum([
|
|
2589
2776
|
"text",
|
|
2590
2777
|
"number",
|
|
2591
2778
|
"select",
|
|
@@ -2599,21 +2786,21 @@ var CatalogFieldTypeSchema = z5.enum([
|
|
|
2599
2786
|
"configurator"
|
|
2600
2787
|
]);
|
|
2601
2788
|
var CATALOG_FIELD_TYPES = CatalogFieldTypeSchema.options;
|
|
2602
|
-
var CatalogFieldOptionSchema =
|
|
2603
|
-
value:
|
|
2604
|
-
label:
|
|
2789
|
+
var CatalogFieldOptionSchema = z7.object({
|
|
2790
|
+
value: z7.string().trim().min(1).max(120),
|
|
2791
|
+
label: z7.string().trim().min(1).max(120).optional()
|
|
2605
2792
|
}).strict();
|
|
2606
|
-
var CatalogAttributeUpsertSchema =
|
|
2607
|
-
label:
|
|
2793
|
+
var CatalogAttributeUpsertSchema = z7.object({
|
|
2794
|
+
label: z7.string().trim().min(1).max(120).optional(),
|
|
2608
2795
|
type: CatalogFieldTypeSchema.optional(),
|
|
2609
2796
|
/** For a select: whether a product may carry more than one choice. */
|
|
2610
|
-
multiple:
|
|
2797
|
+
multiple: z7.boolean().optional(),
|
|
2611
2798
|
/** Choices to make sure exist; never removes one that is not listed. */
|
|
2612
|
-
options:
|
|
2799
|
+
options: z7.array(CatalogFieldOptionSchema).max(500).optional(),
|
|
2613
2800
|
/** Choices to take off the list, by value. */
|
|
2614
|
-
removeOptions:
|
|
2801
|
+
removeOptions: z7.array(z7.string().trim().min(1).max(120)).max(500).optional(),
|
|
2615
2802
|
/** Whether the public API may show this field at all. */
|
|
2616
|
-
public:
|
|
2803
|
+
public: z7.boolean().optional(),
|
|
2617
2804
|
/**
|
|
2618
2805
|
* The field's settings, replaced as a whole. Checked against the same
|
|
2619
2806
|
* schema the object builder saves with, per type: a repeater's `display`,
|
|
@@ -2621,47 +2808,47 @@ var CatalogAttributeUpsertSchema = z5.object({
|
|
|
2621
2808
|
* a configurator's whole structure. An item field keeps its type once it
|
|
2622
2809
|
* exists.
|
|
2623
2810
|
*/
|
|
2624
|
-
config:
|
|
2811
|
+
config: z7.record(z7.string(), z7.unknown()).optional(),
|
|
2625
2812
|
/**
|
|
2626
2813
|
* Say what this body would change -- a new field, a new label, choices
|
|
2627
2814
|
* added, choices taken away and how many products hold each -- and
|
|
2628
2815
|
* write nothing.
|
|
2629
2816
|
*/
|
|
2630
|
-
dryRun:
|
|
2817
|
+
dryRun: z7.boolean().optional(),
|
|
2631
2818
|
/**
|
|
2632
2819
|
* Bring the field back from the trash under its own id, with every value
|
|
2633
2820
|
* the products kept. Nothing else may be sent with it.
|
|
2634
2821
|
*/
|
|
2635
|
-
restore:
|
|
2822
|
+
restore: z7.literal(true).optional(),
|
|
2636
2823
|
/**
|
|
2637
2824
|
* For a new content field (`content_text`, `repeater`, `cta`): put its
|
|
2638
2825
|
* block at the end of the product page. On by default; `false` makes the
|
|
2639
2826
|
* field only. Never moves or restores a block of an existing field.
|
|
2640
2827
|
*/
|
|
2641
|
-
placeOnPage:
|
|
2828
|
+
placeOnPage: z7.boolean().optional()
|
|
2642
2829
|
}).strict();
|
|
2643
2830
|
var CONFIGURATOR_FILE_KINDS = ["model", "texture"];
|
|
2644
2831
|
var CONFIGURATOR_TEXTURE_CONTENT_TYPES = [
|
|
2645
2832
|
"image/webp",
|
|
2646
2833
|
"image/jpeg"
|
|
2647
2834
|
];
|
|
2648
|
-
var ConfiguratorFileUploadSchema =
|
|
2649
|
-
|
|
2650
|
-
step:
|
|
2835
|
+
var ConfiguratorFileUploadSchema = z7.discriminatedUnion("step", [
|
|
2836
|
+
z7.object({
|
|
2837
|
+
step: z7.literal("start"),
|
|
2651
2838
|
/** The slug of the configurator field the file is for. */
|
|
2652
2839
|
field: CatalogFieldSlugSchema,
|
|
2653
|
-
kind:
|
|
2840
|
+
kind: z7.enum(CONFIGURATOR_FILE_KINDS),
|
|
2654
2841
|
/** The bytes the caller is about to send, so a too-large file is refused before it travels. */
|
|
2655
|
-
size:
|
|
2842
|
+
size: z7.number().int().min(1),
|
|
2656
2843
|
/** For a texture: what kind of picture it is. A model is always glTF binary. */
|
|
2657
|
-
contentType:
|
|
2844
|
+
contentType: z7.enum(CONFIGURATOR_TEXTURE_CONTENT_TYPES).optional()
|
|
2658
2845
|
}).strict(),
|
|
2659
|
-
|
|
2660
|
-
step:
|
|
2846
|
+
z7.object({
|
|
2847
|
+
step: z7.literal("finish"),
|
|
2661
2848
|
field: CatalogFieldSlugSchema,
|
|
2662
|
-
kind:
|
|
2849
|
+
kind: z7.enum(CONFIGURATOR_FILE_KINDS),
|
|
2663
2850
|
/** The `uploadPath` `start` answered with. */
|
|
2664
|
-
uploadPath:
|
|
2851
|
+
uploadPath: z7.string().max(200)
|
|
2665
2852
|
}).strict()
|
|
2666
2853
|
]);
|
|
2667
2854
|
var CATALOG_FILE_KINDS = ["image", "file"];
|
|
@@ -2677,97 +2864,40 @@ var CATALOG_FILE_CONTENT_TYPES = [
|
|
|
2677
2864
|
"text/csv",
|
|
2678
2865
|
"application/zip"
|
|
2679
2866
|
];
|
|
2680
|
-
var CatalogFileUploadSchema =
|
|
2681
|
-
|
|
2682
|
-
step:
|
|
2867
|
+
var CatalogFileUploadSchema = z7.discriminatedUnion("step", [
|
|
2868
|
+
z7.object({
|
|
2869
|
+
step: z7.literal("start"),
|
|
2683
2870
|
/** A picture (2 MB, png/jpeg/webp) or a download for a list item (20 MB). */
|
|
2684
|
-
kind:
|
|
2871
|
+
kind: z7.enum(CATALOG_FILE_KINDS),
|
|
2685
2872
|
/** The bytes about to be sent, so a too-large file is refused before it travels. */
|
|
2686
|
-
size:
|
|
2687
|
-
contentType:
|
|
2873
|
+
size: z7.number().int().min(1),
|
|
2874
|
+
contentType: z7.enum(CATALOG_FILE_CONTENT_TYPES)
|
|
2688
2875
|
}).strict(),
|
|
2689
|
-
|
|
2690
|
-
step:
|
|
2691
|
-
kind:
|
|
2876
|
+
z7.object({
|
|
2877
|
+
step: z7.literal("finish"),
|
|
2878
|
+
kind: z7.enum(CATALOG_FILE_KINDS),
|
|
2692
2879
|
/** The `uploadPath` `start` answered with. */
|
|
2693
|
-
uploadPath:
|
|
2880
|
+
uploadPath: z7.string().max(200)
|
|
2694
2881
|
}).strict()
|
|
2695
2882
|
]);
|
|
2696
2883
|
|
|
2697
2884
|
// src/catalog-tools.ts
|
|
2698
|
-
import { z as
|
|
2699
|
-
|
|
2700
|
-
// src/tool-answers.ts
|
|
2701
|
-
import { CrmApiError } from "@solumflow-app/crm-client";
|
|
2702
|
-
import { z as z6 } from "zod";
|
|
2703
|
-
function answer(text2, data) {
|
|
2704
|
-
return {
|
|
2705
|
-
content: [{ type: "text", text: text2 }],
|
|
2706
|
-
structuredContent: { result: data }
|
|
2707
|
-
};
|
|
2708
|
-
}
|
|
2709
|
-
function refusal(error, scope) {
|
|
2710
|
-
if (!(error instanceof CrmApiError)) {
|
|
2711
|
-
throw error;
|
|
2712
|
-
}
|
|
2713
|
-
const lines = [`${error.code}: ${error.message}`];
|
|
2714
|
-
const next = error.code === "forbidden" ? `The key may not do this. It needs to be a secret key with the scope "${scope}".` : NEXT_STEP[error.code];
|
|
2715
|
-
if (next) {
|
|
2716
|
-
lines.push(next);
|
|
2717
|
-
}
|
|
2718
|
-
for (const field of error.fields) {
|
|
2719
|
-
lines.push(`- ${field.path}: ${field.message}`);
|
|
2720
|
-
}
|
|
2721
|
-
return {
|
|
2722
|
-
isError: true,
|
|
2723
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
2724
|
-
};
|
|
2725
|
-
}
|
|
2726
|
-
var NEXT_STEP = {
|
|
2727
|
-
unauthorized: "The key is not recognised or was revoked. Ask the account owner for a new secret key.",
|
|
2728
|
-
feature_unavailable: "The module this needs is switched off for the account. Ask somebody to switch it on in the app.",
|
|
2729
|
-
rate_limited: "Too many requests: 120 a minute per key, 600 per account, and 5 MB of bodies a day per key. Wait the seconds the answer names and try again.",
|
|
2730
|
-
internal_error: "Something went wrong in the app. Trying again is safe."
|
|
2731
|
-
};
|
|
2732
|
-
function codeBlock(snippet) {
|
|
2733
|
-
return `\`\`\`html
|
|
2734
|
-
${snippet}
|
|
2735
|
-
\`\`\``;
|
|
2736
|
-
}
|
|
2737
|
-
var page = {
|
|
2738
|
-
cursor: z6.string().optional().describe("The nextCursor of the previous page; leave out for the first."),
|
|
2739
|
-
limit: z6.number().int().min(1).max(100).optional()
|
|
2740
|
-
};
|
|
2741
|
-
function more(nextCursor) {
|
|
2742
|
-
return nextCursor ? `More on the next page: cursor ${nextCursor}` : "";
|
|
2743
|
-
}
|
|
2744
|
-
function money(cents3, currency2) {
|
|
2745
|
-
return `${(cents3 / 100).toFixed(2)} ${currency2.toUpperCase()}`;
|
|
2746
|
-
}
|
|
2747
|
-
function fieldsNotOnPageNote(slugs) {
|
|
2748
|
-
if (!slugs?.length) {
|
|
2749
|
-
return "";
|
|
2750
|
-
}
|
|
2751
|
-
const count = slugs.length === 1 ? "1 public field has" : `${slugs.length} public fields have`;
|
|
2752
|
-
return ` ${count} a value but ${slugs.length === 1 ? "is" : "are"} not on this product's page: ${slugs.join(", ")}; send them in publicFields to show them.`;
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
// src/catalog-tools.ts
|
|
2885
|
+
import { z as z8 } from "zod";
|
|
2756
2886
|
var objectInput = {
|
|
2757
2887
|
object: CatalogObjectSlugSchema.optional().describe(
|
|
2758
2888
|
"The slug of the catalogue (see list_catalogs). Leave out for the account\u2019s own catalogue."
|
|
2759
2889
|
)
|
|
2760
2890
|
};
|
|
2761
2891
|
var CONTENT_BLOCKS = 'Besides plain values (text, number, choice), a product can carry content blocks, three field types: "content_text" is { html, links? } with already-sanitised html; "cta" is { heading, body, primary: {label,url}, secondary? }, a call to action; "repeater" is { items: [{ id, ...one value per item field }] } and covers lists, accordions (FAQ), downloads and testimonials. A repeater\u2019s field says how to draw it: "display" is "list" or "accordion", "preset" names where it started (list, faq, downloads, testimonials), "itemFields" is the structure of one item (key, label, type: text, richtext, url {label,url}, icon {set,name}, image or file {url,name,size,contentType}) and "titleKey" is the item field that names an item, such as the question of a FAQ. A "configurator" is a 3D product configurator: a listing carries only { configurator: true }, a product\u2019s own page the whole document (see read_configurator). A block that is public but empty is null; a block that is not public is not there at all. A new block field stands at the end of the product page from the moment upsert_field makes it (`placeOnPage: false` skips that).';
|
|
2762
|
-
var currency =
|
|
2892
|
+
var currency = z8.string().regex(/^[A-Z]{3}$/).optional().describe(
|
|
2763
2893
|
"Every price in this currency (see list_currencies), each with its origin: base, fixed or converted. Left out, every price in its own currency."
|
|
2764
2894
|
);
|
|
2765
2895
|
var READ_STATUS_HINT = 'Left out, only published ones. "draft" is what is not on the website yet (concept, internal or scheduled), "archived" what was taken out, "all" everything; each answers with a `status` and `publishedAt` per item. Anything but published needs a key with catalog:write.';
|
|
2766
|
-
var productStatus =
|
|
2767
|
-
var categoryStatus =
|
|
2896
|
+
var productStatus = z8.enum(["published", "draft", "archived", "all"]).optional().describe(`Which products. ${READ_STATUS_HINT}`);
|
|
2897
|
+
var categoryStatus = z8.enum(["published", "draft", "all"]).optional().describe(
|
|
2768
2898
|
`Which categories. ${READ_STATUS_HINT.replace('"archived" what was taken out, ', "")} A category cannot be archived.`
|
|
2769
2899
|
);
|
|
2770
|
-
var slugOrId =
|
|
2900
|
+
var slugOrId = z8.string().min(1).max(200).describe("The address (slug) or the id of the product.");
|
|
2771
2901
|
function registerCatalogTools(server, client) {
|
|
2772
2902
|
server.registerTool(
|
|
2773
2903
|
"list_catalog_fields",
|
|
@@ -2817,7 +2947,7 @@ function registerCatalogTools(server, client) {
|
|
|
2817
2947
|
title: "List the currencies the shop sells in",
|
|
2818
2948
|
description: "The account\u2019s own currency first, then the ones it switched on, with a name and symbol: what a currency menu on a website is built from. Pass one as `currency` to get_product or list_products for prices in it. Which currencies are offered, and at what rate, is set in the app (Settings \u2192 Verkoop \u2192 Valuta), not through this tool.",
|
|
2819
2949
|
inputSchema: {
|
|
2820
|
-
locale:
|
|
2950
|
+
locale: z8.string().min(2).max(35).optional().describe('The language names are written in, such as "nl".')
|
|
2821
2951
|
},
|
|
2822
2952
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
2823
2953
|
},
|
|
@@ -2874,22 +3004,22 @@ function registerCatalogTools(server, client) {
|
|
|
2874
3004
|
description: "One page of the catalogue: name, address (slug), lowest price, whether it is in stock, its first picture and the public fields a listing shows. Narrow it to one category\u2019s whole branch by its id or address, or by the reference upsert_category recorded (`categorySource` + `categoryRef`), to one catalogue by the slug of its object, or to the product carrying an article number (`sku`). Read get_product for everything a product page needs. Published products only, and may be a minute old; `status` lists concepts, scheduled and archived ones too (needs a server key). `sort` orders the published ones: -publishedAt (newest first, the default), publishedAt, name, or position -- the seller\u2019s own order on the page of `category`, which it needs. Each item carries `sku`, `publishedAt` and `stockQuantity` (null when not counted).",
|
|
2875
3005
|
inputSchema: {
|
|
2876
3006
|
...page,
|
|
2877
|
-
category:
|
|
3007
|
+
category: z8.string().regex(
|
|
2878
3008
|
/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[a-z0-9]+(-[a-z0-9]+)*)$/i
|
|
2879
3009
|
).max(64).optional().describe(
|
|
2880
3010
|
"A category id or address (slug); includes its subcategories."
|
|
2881
3011
|
),
|
|
2882
|
-
categorySource:
|
|
2883
|
-
categoryRef:
|
|
2884
|
-
sku:
|
|
3012
|
+
categorySource: z8.string().max(64).optional().describe("With categoryRef: whose id the category reference is."),
|
|
3013
|
+
categoryRef: z8.string().max(255).optional().describe("A category by the id it has in your own system."),
|
|
3014
|
+
sku: z8.string().min(1).max(100).optional().describe(
|
|
2885
3015
|
"The product carrying this article number, or whose variant does."
|
|
2886
3016
|
),
|
|
2887
|
-
object:
|
|
3017
|
+
object: z8.string().min(1).max(63).optional().describe(
|
|
2888
3018
|
'One catalogue, by the slug of its object, such as "product".'
|
|
2889
3019
|
),
|
|
2890
3020
|
currency,
|
|
2891
3021
|
status: productStatus,
|
|
2892
|
-
sort:
|
|
3022
|
+
sort: z8.enum(["-publishedAt", "publishedAt", "name", "position"]).optional().describe(
|
|
2893
3023
|
"The order: newest first by default; position needs `category`."
|
|
2894
3024
|
)
|
|
2895
3025
|
},
|
|
@@ -2922,7 +3052,7 @@ function registerCatalogTools(server, client) {
|
|
|
2922
3052
|
description: 'The categories of the catalogue, each with its path from the top and how many products are in it. Left without `parent`, every category comes back, which is what building a menu in one go needs; "root" asks for the top level, a category id for its children. Published categories only unless you pass `status` (draft or all, needs a server key).',
|
|
2923
3053
|
inputSchema: {
|
|
2924
3054
|
...page,
|
|
2925
|
-
parent:
|
|
3055
|
+
parent: z8.union([z8.literal("root"), z8.string().uuid()]).optional().describe('"root" or the id of a category.'),
|
|
2926
3056
|
status: categoryStatus
|
|
2927
3057
|
},
|
|
2928
3058
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
@@ -2945,7 +3075,7 @@ function registerCatalogTools(server, client) {
|
|
|
2945
3075
|
title: "Read one category page",
|
|
2946
3076
|
description: "One category as its page needs it: name, description, the texts for search engines and social media, and the first page of its products. Pass the nextCursor back to page through the products. A category that is not on the website yet needs `status` (draft or all, a server key); its products are the published ones.",
|
|
2947
3077
|
inputSchema: {
|
|
2948
|
-
slugOrId:
|
|
3078
|
+
slugOrId: z8.string().min(1).max(200).describe("The address (slug) or the id of the category."),
|
|
2949
3079
|
...page,
|
|
2950
3080
|
status: categoryStatus
|
|
2951
3081
|
},
|
|
@@ -3121,8 +3251,8 @@ function registerCatalogMigrationTools(server, client) {
|
|
|
3121
3251
|
title: "Make or change many products",
|
|
3122
3252
|
description: `Up to ${BULK_LIMIT} products, one after another, each exactly as upsert_product takes it (\`externalRef\` beside its body). A refusal is that product\u2019s outcome and the others go on; a refusal for speed is waited out and the product sent again; a key that is refused stops the run and the rest is \`skipped\`. Answers per product \`created\`, \`changed\`, \`refused\` (with the fields) or \`skipped\`. ` + LIMITS,
|
|
3123
3253
|
inputSchema: {
|
|
3124
|
-
products:
|
|
3125
|
-
|
|
3254
|
+
products: z9.array(
|
|
3255
|
+
z9.object({
|
|
3126
3256
|
externalRef: CatalogExternalRefSchema,
|
|
3127
3257
|
product: CatalogProductUpsertSchema
|
|
3128
3258
|
})
|
|
@@ -3166,20 +3296,20 @@ function registerCatalogMigrationTools(server, client) {
|
|
|
3166
3296
|
description: "Moves a field to the trash, as the app does: for thirty days it can come back with restore_field, and every product keeps its value meanwhile. Use `permanent` only when the user explicitly asks to delete for good, and first without `confirm`: that only counts the products that would lose their value. Show the user that count; send `confirm` (the slug again) only after they agree. A field of the app\u2019s own, or one a product varies along, is refused.",
|
|
3167
3297
|
inputSchema: {
|
|
3168
3298
|
slug: CatalogFieldSlugSchema,
|
|
3169
|
-
permanent:
|
|
3170
|
-
confirm:
|
|
3299
|
+
permanent: z9.boolean().optional(),
|
|
3300
|
+
confirm: z9.string().max(63).optional(),
|
|
3171
3301
|
...objectInput
|
|
3172
3302
|
},
|
|
3173
3303
|
annotations: { destructiveHint: true, idempotentHint: true }
|
|
3174
3304
|
},
|
|
3175
|
-
async ({ slug:
|
|
3305
|
+
async ({ slug: slug3, permanent, confirm, object: object2 }) => {
|
|
3176
3306
|
try {
|
|
3177
|
-
const result = await client.removeField(
|
|
3307
|
+
const result = await client.removeField(slug3, {
|
|
3178
3308
|
...target(object2),
|
|
3179
3309
|
...permanent ? { permanent } : {},
|
|
3180
3310
|
...confirm ? { confirm } : {}
|
|
3181
3311
|
});
|
|
3182
|
-
const text2 = result.state === "trashed" ? `${
|
|
3312
|
+
const text2 = result.state === "trashed" ? `${slug3} is in the trash until ${result.restorableUntil ?? "thirty days from now"}; ${result.productsWithValue} products keep their value.` : result.state === "would_delete" ? `Nothing changed. Deleting ${slug3} for good would clear its value on ${result.productsWithValue} products. ${result.confirm}` : `${slug3} is deleted; its value on ${result.productsWithValue} products is being cleared.`;
|
|
3183
3313
|
return answer(text2, result);
|
|
3184
3314
|
} catch (error) {
|
|
3185
3315
|
return refusal(error, "catalog:write");
|
|
@@ -3194,9 +3324,9 @@ function registerCatalogMigrationTools(server, client) {
|
|
|
3194
3324
|
inputSchema: { slug: CatalogFieldSlugSchema, ...objectInput },
|
|
3195
3325
|
annotations: { destructiveHint: false, idempotentHint: true }
|
|
3196
3326
|
},
|
|
3197
|
-
async ({ slug:
|
|
3327
|
+
async ({ slug: slug3, object: object2 }) => {
|
|
3198
3328
|
try {
|
|
3199
|
-
const field = await client.restoreField(
|
|
3329
|
+
const field = await client.restoreField(slug3, target(object2));
|
|
3200
3330
|
return answer(`${field.label} (${field.slug}) is back.`, field);
|
|
3201
3331
|
} catch (error) {
|
|
3202
3332
|
return refusal(error, "catalog:write");
|
|
@@ -3211,7 +3341,7 @@ function registerCatalogMigrationTools(server, client) {
|
|
|
3211
3341
|
inputSchema: {
|
|
3212
3342
|
source: CatalogSourceSchema,
|
|
3213
3343
|
externalRef: CatalogExternalRefSchema,
|
|
3214
|
-
status:
|
|
3344
|
+
status: z9.enum(["published", "draft", "all"]).optional().describe(
|
|
3215
3345
|
"Which categories to look in; anything but published needs catalog:write."
|
|
3216
3346
|
)
|
|
3217
3347
|
},
|
|
@@ -3239,8 +3369,8 @@ function registerCatalogMigrationTools(server, client) {
|
|
|
3239
3369
|
title: "Upload a picture or a file from disk",
|
|
3240
3370
|
description: 'Reads one file from the user\u2019s disk and uploads it, for a product write to name as { "upload": "<uploadPath>" } instead of a url: a picture (png, jpeg, webp, at most 2 MB) for `images`, `socialImage` or a list item\u2019s image, or a "file" (pdf, Office, txt, csv, zip, at most 20 MB) for a list item\u2019s download. An upload is kept a day.',
|
|
3241
3371
|
inputSchema: {
|
|
3242
|
-
kind:
|
|
3243
|
-
filePath:
|
|
3372
|
+
kind: z9.enum(["image", "file"]),
|
|
3373
|
+
filePath: z9.string().min(1).max(1e3).describe("The absolute path of the file on this machine.")
|
|
3244
3374
|
},
|
|
3245
3375
|
annotations: { destructiveHint: false, idempotentHint: false }
|
|
3246
3376
|
},
|
|
@@ -3483,10 +3613,10 @@ Archive it with archive_product instead.`
|
|
|
3483
3613
|
},
|
|
3484
3614
|
annotations: { destructiveHint: false, idempotentHint: true }
|
|
3485
3615
|
},
|
|
3486
|
-
async ({ slug:
|
|
3616
|
+
async ({ slug: slug3, object: object2, ...input }) => {
|
|
3487
3617
|
try {
|
|
3488
3618
|
const category = await client.upsertCategory(
|
|
3489
|
-
|
|
3619
|
+
slug3,
|
|
3490
3620
|
input,
|
|
3491
3621
|
target2(object2)
|
|
3492
3622
|
);
|
|
@@ -3513,14 +3643,14 @@ Archive it with archive_product instead.`
|
|
|
3513
3643
|
},
|
|
3514
3644
|
annotations: { destructiveHint: true, idempotentHint: true }
|
|
3515
3645
|
},
|
|
3516
|
-
async ({ slug:
|
|
3646
|
+
async ({ slug: slug3, object: object2, ...input }) => {
|
|
3517
3647
|
try {
|
|
3518
3648
|
if (input.dryRun) {
|
|
3519
3649
|
const { dryRun: _dryRun, ...rest } = input;
|
|
3520
|
-
const preview = await client.previewField(
|
|
3650
|
+
const preview = await client.previewField(slug3, rest, target2(object2));
|
|
3521
3651
|
return answer(`Nothing written. ${JSON.stringify(preview)}`, preview);
|
|
3522
3652
|
}
|
|
3523
|
-
const field = await client.upsertField(
|
|
3653
|
+
const field = await client.upsertField(slug3, input, target2(object2));
|
|
3524
3654
|
return answer(
|
|
3525
3655
|
`${field.created ? "Made" : "Changed"} ${field.label} (${field.slug}), ${field.type}, ${field.public ? "public" : "not public"}.` + (field.placedOnPage === void 0 ? "" : field.placedOnPage ? " Its block is at the end of the product page." : ` Its block is not on the product page (${field.notPlacedBecause ?? "not placed"}).`),
|
|
3526
3656
|
field
|
|
@@ -3543,9 +3673,9 @@ Archive it with archive_product instead.`
|
|
|
3543
3673
|
},
|
|
3544
3674
|
annotations: { destructiveHint: false, idempotentHint: true }
|
|
3545
3675
|
},
|
|
3546
|
-
async ({ slug:
|
|
3676
|
+
async ({ slug: slug3, ...input }) => {
|
|
3547
3677
|
try {
|
|
3548
|
-
const catalog = await client.upsertCatalog(
|
|
3678
|
+
const catalog = await client.upsertCatalog(slug3, input);
|
|
3549
3679
|
return answer(
|
|
3550
3680
|
`${catalog.created ? "Made" : "Changed"} ${catalog.namePlural} (${catalog.slug}).`,
|
|
3551
3681
|
catalog
|
|
@@ -3557,8 +3687,106 @@ Archive it with archive_product instead.`
|
|
|
3557
3687
|
);
|
|
3558
3688
|
}
|
|
3559
3689
|
|
|
3690
|
+
// src/client-account-tools.ts
|
|
3691
|
+
import { z as z10 } from "zod";
|
|
3692
|
+
var RESELLER_ONLY = "Only on the key of a reseller, with clients:manage; only the customer accounts that reseller sold are reached.";
|
|
3693
|
+
function registerClientAccountTools(server, client) {
|
|
3694
|
+
server.registerTool(
|
|
3695
|
+
"list_client_accounts",
|
|
3696
|
+
{
|
|
3697
|
+
title: "List customer accounts",
|
|
3698
|
+
description: `The customer accounts this reseller sold, paged by id: name, address, owner, kind and the modules switched on. ${RESELLER_ONLY}`,
|
|
3699
|
+
inputSchema: { ...page },
|
|
3700
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
3701
|
+
},
|
|
3702
|
+
async (input) => {
|
|
3703
|
+
try {
|
|
3704
|
+
const result = await client.getClientAccounts(input);
|
|
3705
|
+
return answer(
|
|
3706
|
+
result.data.length === 0 ? "No customer accounts." : [...result.data.map(describeAccount), more(result.nextCursor)].filter(Boolean).join("\n"),
|
|
3707
|
+
result
|
|
3708
|
+
);
|
|
3709
|
+
} catch (error) {
|
|
3710
|
+
return refusal(error, "clients:manage");
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
);
|
|
3714
|
+
server.registerTool(
|
|
3715
|
+
"get_client_account",
|
|
3716
|
+
{
|
|
3717
|
+
title: "Read one customer account",
|
|
3718
|
+
description: `One customer account of this reseller by id. ${RESELLER_ONLY}`,
|
|
3719
|
+
inputSchema: { id: z10.string().uuid() },
|
|
3720
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
3721
|
+
},
|
|
3722
|
+
async ({ id: id7 }) => {
|
|
3723
|
+
try {
|
|
3724
|
+
const account = await client.getClientAccount(id7);
|
|
3725
|
+
return account ? answer(describeAccount(account), account) : answer(`No customer account ${id7} under this reseller.`, null);
|
|
3726
|
+
} catch (error) {
|
|
3727
|
+
return refusal(error, "clients:manage");
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
);
|
|
3731
|
+
server.registerTool(
|
|
3732
|
+
"create_client_account",
|
|
3733
|
+
{
|
|
3734
|
+
title: "Make a customer account",
|
|
3735
|
+
description: `A customer account with a SolumFlow workspace of its own: the owner gets a login and a mail with the way in. Asked again for the same owner it makes nothing and answers with the account that owner already has. It starts with the default modules; set_client_modules sets the ones it is sold with. ${RESELLER_ONLY}`,
|
|
3736
|
+
inputSchema: {
|
|
3737
|
+
name: z10.string().min(1).max(255).describe("What the account is called: the customer\u2019s business."),
|
|
3738
|
+
ownerEmail: z10.string().email().max(320).describe("Who owns it, and who gets the mail with the way in."),
|
|
3739
|
+
slug: z10.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(63).optional().describe(
|
|
3740
|
+
"The address in the app; derived from the name when left out."
|
|
3741
|
+
)
|
|
3742
|
+
},
|
|
3743
|
+
annotations: { idempotentHint: true }
|
|
3744
|
+
},
|
|
3745
|
+
async (input) => {
|
|
3746
|
+
try {
|
|
3747
|
+
const result = await client.createClientAccount(input);
|
|
3748
|
+
const said = result.created ? result.invited ? "Made, and its owner was mailed the way in." : "Made, but the mail to its owner did not go: give them access from the account\u2019s members in the app." : "That owner already had an account here; nothing was made.";
|
|
3749
|
+
return answer(`${said}
|
|
3750
|
+
${describeAccount(result.account)}`, result);
|
|
3751
|
+
} catch (error) {
|
|
3752
|
+
return refusal(error, "clients:manage");
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
);
|
|
3756
|
+
server.registerTool(
|
|
3757
|
+
"set_client_modules",
|
|
3758
|
+
{
|
|
3759
|
+
title: "Set a customer account\u2019s modules",
|
|
3760
|
+
description: `The modules a customer account is sold with: every key named is switched on, every other ordinary module off; never an empty list. A module a paid package switched on and this changes is the reseller\u2019s choice from then on. Only modules this reseller may hand out; hosting, domains, mail hosting and site monitoring stay the platform owner\u2019s. Typical: crm, calendar, forms, chat, consent, payments. ${RESELLER_ONLY}`,
|
|
3761
|
+
inputSchema: {
|
|
3762
|
+
clientAccountId: z10.string().uuid(),
|
|
3763
|
+
moduleKeys: z10.array(z10.string().regex(/^[a-z][a-z0-9_]{1,63}$/)).min(1).max(64)
|
|
3764
|
+
},
|
|
3765
|
+
annotations: { idempotentHint: true }
|
|
3766
|
+
},
|
|
3767
|
+
async ({ clientAccountId, moduleKeys }) => {
|
|
3768
|
+
try {
|
|
3769
|
+
const result = await client.setClientModules(
|
|
3770
|
+
clientAccountId,
|
|
3771
|
+
moduleKeys
|
|
3772
|
+
);
|
|
3773
|
+
const said = result.changed.length ? `Switched: ${result.changed.join(", ")}.` : "Nothing changed: the account already had exactly these.";
|
|
3774
|
+
return answer(`${said}
|
|
3775
|
+
${describeAccount(result.account)}`, result);
|
|
3776
|
+
} catch (error) {
|
|
3777
|
+
return refusal(error, "clients:manage");
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
function describeAccount(account) {
|
|
3783
|
+
const test = account.isTestAccount ? " (test)" : "";
|
|
3784
|
+
const modules = account.modules.length ? account.modules.join(", ") : "no modules";
|
|
3785
|
+
return `- ${account.name}${test} (id ${account.id}, ${account.slug}, ${account.kind}): owner ${account.ownerEmail ?? "unknown"}; ${modules}`;
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3560
3788
|
// src/configurator-tools.ts
|
|
3561
|
-
import { z as
|
|
3789
|
+
import { z as z11 } from "zod";
|
|
3562
3790
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
3563
3791
|
import { extname as extname2 } from "path";
|
|
3564
3792
|
var FILE_RULES2 = {
|
|
@@ -3569,7 +3797,7 @@ var FILE_RULES2 = {
|
|
|
3569
3797
|
}
|
|
3570
3798
|
};
|
|
3571
3799
|
var CONFIGURATOR = 'A "configurator" field holds a 3D product configurator. Its structure has "geometry" (the shape document: "inputs" bound to parameters, "constants", material "slots", a "root" tree of building blocks and "annotations" for dimension lines), "parameters", "choiceLists", "materials", "priceGroups", "amounts", optional "presets", optional "scene" ({ "lighting": "studio" | "warm" | "daylight" }), "rules" and "priceRules". Building blocks: box, rounded_top_box (params.radius), stadium_prism, arc_panel (params.thickness, params.mirror), led_strip, niche_panel (params.niche_x, niche_y, niche_width, niche_height), model (a "model" path to a .glb uploaded with upload_configurator_file, params.fit 0 contain or 1 stretch, params.paint 1 to use the slot material) and repeat (count, children, index i or j). Every measure is millimetres; "at" is the minimum corner, x left to right, y bottom to top, z from the wall into the room; "size" is width, height, depth. A measure is a number or a formula with + - * / ( ) and min, max, floor, ceil, round, abs over the shape\u2019s inputs, constants and the repeat index. Texture paths in materials come from upload_configurator_file too. Making a configurator, or changing its "geometry", needs a key with the configurator:shape scope; everything around the shape needs only catalog:write.';
|
|
3572
|
-
var documentInput =
|
|
3800
|
+
var documentInput = z11.string().min(2).max(2e5).describe(
|
|
3573
3801
|
'The JSON text of a whole configurator structure (it has "schemaVersion"), or of a shape document alone (it has "root").'
|
|
3574
3802
|
);
|
|
3575
3803
|
function registerConfiguratorTools(server, client) {
|
|
@@ -3601,16 +3829,16 @@ function registerConfiguratorTools(server, client) {
|
|
|
3601
3829
|
slug: CatalogFieldSlugSchema.describe(
|
|
3602
3830
|
"The slug of the configurator field."
|
|
3603
3831
|
),
|
|
3604
|
-
label:
|
|
3605
|
-
structure:
|
|
3832
|
+
label: z11.string().trim().min(1).max(120).optional(),
|
|
3833
|
+
structure: z11.string().min(2).max(2e5).describe(
|
|
3606
3834
|
'The JSON text of the whole configurator structure (it has "schemaVersion").'
|
|
3607
3835
|
),
|
|
3608
|
-
public:
|
|
3836
|
+
public: z11.boolean().optional().describe("Whether product pages may show the configurator."),
|
|
3609
3837
|
...objectInput
|
|
3610
3838
|
},
|
|
3611
3839
|
annotations: { destructiveHint: true, idempotentHint: true }
|
|
3612
3840
|
},
|
|
3613
|
-
async ({ slug:
|
|
3841
|
+
async ({ slug: slug3, label, structure, public: isPublic, object: object2 }) => {
|
|
3614
3842
|
const problems = checkDocument(structure);
|
|
3615
3843
|
if (problems.length > 0 || !structure.includes('"schemaVersion"')) {
|
|
3616
3844
|
return {
|
|
@@ -3628,7 +3856,7 @@ function registerConfiguratorTools(server, client) {
|
|
|
3628
3856
|
}
|
|
3629
3857
|
try {
|
|
3630
3858
|
const field = await client.upsertField(
|
|
3631
|
-
|
|
3859
|
+
slug3,
|
|
3632
3860
|
{
|
|
3633
3861
|
...label ? { label } : {},
|
|
3634
3862
|
type: "configurator",
|
|
@@ -3655,8 +3883,8 @@ function registerConfiguratorTools(server, client) {
|
|
|
3655
3883
|
fieldSlug: CatalogFieldSlugSchema.describe(
|
|
3656
3884
|
"The slug of the configurator field."
|
|
3657
3885
|
),
|
|
3658
|
-
kind:
|
|
3659
|
-
filePath:
|
|
3886
|
+
kind: z11.enum(CONFIGURATOR_FILE_KINDS),
|
|
3887
|
+
filePath: z11.string().min(1).max(1e3).describe("The absolute path of the file on this machine."),
|
|
3660
3888
|
...objectInput
|
|
3661
3889
|
},
|
|
3662
3890
|
annotations: { destructiveHint: false, idempotentHint: false }
|
|
@@ -3709,8 +3937,8 @@ function registerConfiguratorTools(server, client) {
|
|
|
3709
3937
|
title: "Read the configurator of a product",
|
|
3710
3938
|
description: "The configurator field of one product as its page receives it: the structure (texture paths already URLs), the model\u2019s value (ranges, offered options, amounts, presets), the tax a quote uses and the two hashes a request must send back. Useful to draft a new shape from an existing one. " + CONFIGURATOR,
|
|
3711
3939
|
inputSchema: {
|
|
3712
|
-
slugOrId:
|
|
3713
|
-
field:
|
|
3940
|
+
slugOrId: z11.string().min(1).max(200).describe("The address (slug) or the id of the product."),
|
|
3941
|
+
field: z11.string().min(1).max(64).describe("The slug of the configurator field.")
|
|
3714
3942
|
},
|
|
3715
3943
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
3716
3944
|
},
|
|
@@ -3792,7 +4020,7 @@ function isRecord(value) {
|
|
|
3792
4020
|
}
|
|
3793
4021
|
|
|
3794
4022
|
// src/consent-banner-tools.ts
|
|
3795
|
-
import { z as
|
|
4023
|
+
import { z as z12 } from "zod";
|
|
3796
4024
|
function registerConsentBannerTools(server, client) {
|
|
3797
4025
|
server.registerTool(
|
|
3798
4026
|
"get_consent_banner",
|
|
@@ -3819,16 +4047,16 @@ function registerConsentBannerTools(server, client) {
|
|
|
3819
4047
|
title: "Set the cookie banner",
|
|
3820
4048
|
description: "All of the banner\u2019s settings at once, as the screen in the app saves them. It changes the live site at once. `necessary` is always on and not sent.",
|
|
3821
4049
|
inputSchema: {
|
|
3822
|
-
enabled:
|
|
3823
|
-
gtmContainerId:
|
|
3824
|
-
privacyPolicyUrl:
|
|
3825
|
-
categories:
|
|
3826
|
-
|
|
4050
|
+
enabled: z12.boolean(),
|
|
4051
|
+
gtmContainerId: z12.string().max(20).describe("GTM-\u2026, or an empty string for none."),
|
|
4052
|
+
privacyPolicyUrl: z12.string().max(2048).describe("https only, or an empty string for none."),
|
|
4053
|
+
categories: z12.array(
|
|
4054
|
+
z12.enum(["functionality", "measurement", "marketing", "experience"])
|
|
3827
4055
|
).max(4),
|
|
3828
|
-
defaultLanguage:
|
|
3829
|
-
layout:
|
|
3830
|
-
showLogo:
|
|
3831
|
-
sendHashedUserData:
|
|
4056
|
+
defaultLanguage: z12.enum(["nl", "en"]),
|
|
4057
|
+
layout: z12.enum(["banner", "dialog"]),
|
|
4058
|
+
showLogo: z12.boolean(),
|
|
4059
|
+
sendHashedUserData: z12.boolean()
|
|
3832
4060
|
},
|
|
3833
4061
|
annotations: { idempotentHint: true }
|
|
3834
4062
|
},
|
|
@@ -3844,17 +4072,17 @@ function registerConsentBannerTools(server, client) {
|
|
|
3844
4072
|
}
|
|
3845
4073
|
|
|
3846
4074
|
// src/deal-tools.ts
|
|
3847
|
-
import { z as
|
|
3848
|
-
var dealId =
|
|
4075
|
+
import { z as z13 } from "zod";
|
|
4076
|
+
var dealId = z13.string().uuid().describe("The id of the deal.");
|
|
3849
4077
|
var dealFields = {
|
|
3850
|
-
name:
|
|
3851
|
-
value:
|
|
3852
|
-
currency:
|
|
3853
|
-
contactRecordId:
|
|
3854
|
-
companyRecordId:
|
|
3855
|
-
ownerId:
|
|
3856
|
-
expectedCloseDate:
|
|
3857
|
-
values:
|
|
4078
|
+
name: z13.string().min(1).max(200),
|
|
4079
|
+
value: z13.number().nonnegative().nullable().optional().describe("In whole units of the currency, such as 1500 for \u20AC1,500."),
|
|
4080
|
+
currency: z13.string().length(3).optional().describe("Three letters; EUR when left out."),
|
|
4081
|
+
contactRecordId: z13.string().uuid().nullable().optional(),
|
|
4082
|
+
companyRecordId: z13.string().uuid().nullable().optional(),
|
|
4083
|
+
ownerId: z13.string().uuid().nullable().optional().describe("A member of the account."),
|
|
4084
|
+
expectedCloseDate: z13.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional().describe("YYYY-MM-DD."),
|
|
4085
|
+
values: z13.record(z13.string(), z13.unknown()).optional().describe("Field values by slug. A field marked sensitive is refused.")
|
|
3858
4086
|
};
|
|
3859
4087
|
function registerDealTools(server, client) {
|
|
3860
4088
|
server.registerTool(
|
|
@@ -3886,9 +4114,9 @@ function registerDealTools(server, client) {
|
|
|
3886
4114
|
title: "List deals",
|
|
3887
4115
|
description: "Deals paged by id, open ones unless status says otherwise; pipeline narrows to one pipeline, q searches the name.",
|
|
3888
4116
|
inputSchema: {
|
|
3889
|
-
pipeline:
|
|
3890
|
-
status:
|
|
3891
|
-
q:
|
|
4117
|
+
pipeline: z13.string().uuid().optional(),
|
|
4118
|
+
status: z13.enum(["open", "won", "lost", "all"]).optional(),
|
|
4119
|
+
q: z13.string().max(200).optional(),
|
|
3892
4120
|
...page
|
|
3893
4121
|
},
|
|
3894
4122
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
@@ -3928,7 +4156,7 @@ function registerDealTools(server, client) {
|
|
|
3928
4156
|
title: "Make a deal",
|
|
3929
4157
|
description: "A new deal in the stage named (list_pipelines gives the ids). A won or lost stage closes it at once; a lost one cannot be made directly. Automations that start on a new deal run as in the app.",
|
|
3930
4158
|
inputSchema: {
|
|
3931
|
-
stageId:
|
|
4159
|
+
stageId: z13.string().uuid(),
|
|
3932
4160
|
...dealFields
|
|
3933
4161
|
}
|
|
3934
4162
|
},
|
|
@@ -3948,10 +4176,10 @@ function registerDealTools(server, client) {
|
|
|
3948
4176
|
description: "Changes only the fields sent. A new stageId moves the deal, also to another pipeline of the same deals list; moving into a lost stage needs lostReason. Deleting a deal is done in the app.",
|
|
3949
4177
|
inputSchema: {
|
|
3950
4178
|
id: dealId,
|
|
3951
|
-
stageId:
|
|
4179
|
+
stageId: z13.string().uuid().optional(),
|
|
3952
4180
|
...dealFields,
|
|
3953
4181
|
name: dealFields.name.optional(),
|
|
3954
|
-
lostReason:
|
|
4182
|
+
lostReason: z13.string().min(1).max(500).nullable().optional()
|
|
3955
4183
|
},
|
|
3956
4184
|
annotations: { idempotentHint: true }
|
|
3957
4185
|
},
|
|
@@ -3989,7 +4217,7 @@ function registerDealTools(server, client) {
|
|
|
3989
4217
|
{
|
|
3990
4218
|
title: "Write a note on a deal",
|
|
3991
4219
|
description: "A note on a deal, with no author: it was written through the API.",
|
|
3992
|
-
inputSchema: { dealId, body:
|
|
4220
|
+
inputSchema: { dealId, body: z13.string().min(1).max(2e4) }
|
|
3993
4221
|
},
|
|
3994
4222
|
async ({ dealId: id7, body }) => {
|
|
3995
4223
|
try {
|
|
@@ -4007,32 +4235,32 @@ function describeDeal(deal) {
|
|
|
4007
4235
|
}
|
|
4008
4236
|
|
|
4009
4237
|
// src/document-tools.ts
|
|
4010
|
-
import { z as
|
|
4011
|
-
var id =
|
|
4012
|
-
var date =
|
|
4013
|
-
var line =
|
|
4014
|
-
name:
|
|
4015
|
-
description:
|
|
4016
|
-
quantity:
|
|
4017
|
-
unitPriceCents:
|
|
4238
|
+
import { z as z14 } from "zod";
|
|
4239
|
+
var id = z14.string().uuid();
|
|
4240
|
+
var date = z14.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional().describe("YYYY-MM-DD.");
|
|
4241
|
+
var line = z14.object({
|
|
4242
|
+
name: z14.string().min(1).max(500),
|
|
4243
|
+
description: z14.string().max(5e3).nullable().optional(),
|
|
4244
|
+
quantity: z14.number().positive(),
|
|
4245
|
+
unitPriceCents: z14.number().int().describe(
|
|
4018
4246
|
"In cents: 12500 is 125.00. Negative for a line that takes something off."
|
|
4019
4247
|
),
|
|
4020
|
-
taxRateId:
|
|
4021
|
-
productId:
|
|
4022
|
-
lineGroup:
|
|
4248
|
+
taxRateId: z14.string().uuid().nullable().optional().describe("One of list_tax_rates; left out, the account\u2019s default rate."),
|
|
4249
|
+
productId: z14.string().uuid().nullable().optional(),
|
|
4250
|
+
lineGroup: z14.string().min(1).max(200).nullable().optional()
|
|
4023
4251
|
});
|
|
4024
4252
|
var header = {
|
|
4025
4253
|
contactRecordId: id.nullable().optional(),
|
|
4026
4254
|
companyRecordId: id.nullable().optional(),
|
|
4027
4255
|
dealId: id.nullable().optional(),
|
|
4028
|
-
notes:
|
|
4029
|
-
discountType:
|
|
4030
|
-
discountValue:
|
|
4256
|
+
notes: z14.string().max(2e4).nullable().optional(),
|
|
4257
|
+
discountType: z14.enum(["flat", "percent"]).optional(),
|
|
4258
|
+
discountValue: z14.number().nonnegative().optional().describe(
|
|
4031
4259
|
"Cents for flat, a percentage (at most 100) for percent; sent together with discountType."
|
|
4032
4260
|
)
|
|
4033
4261
|
};
|
|
4034
4262
|
var listInput = {
|
|
4035
|
-
status:
|
|
4263
|
+
status: z14.string().max(40).optional(),
|
|
4036
4264
|
contactRecordId: id.optional(),
|
|
4037
4265
|
...page
|
|
4038
4266
|
};
|
|
@@ -4115,9 +4343,9 @@ function registerDocumentTools(server, client) {
|
|
|
4115
4343
|
description: `A draft estimate with its lines, in EUR unless currency says otherwise. Nobody receives it. ${NOT_HERE}`,
|
|
4116
4344
|
inputSchema: {
|
|
4117
4345
|
...header,
|
|
4118
|
-
currency:
|
|
4346
|
+
currency: z14.string().regex(/^[A-Z]{3}$/).optional(),
|
|
4119
4347
|
expiryDate: date,
|
|
4120
|
-
lines:
|
|
4348
|
+
lines: z14.array(line).min(1).max(200)
|
|
4121
4349
|
}
|
|
4122
4350
|
},
|
|
4123
4351
|
async (input) => {
|
|
@@ -4138,7 +4366,7 @@ function registerDocumentTools(server, client) {
|
|
|
4138
4366
|
id,
|
|
4139
4367
|
...header,
|
|
4140
4368
|
expiryDate: date,
|
|
4141
|
-
lines:
|
|
4369
|
+
lines: z14.array(line).min(1).max(200).optional()
|
|
4142
4370
|
},
|
|
4143
4371
|
annotations: { idempotentHint: true }
|
|
4144
4372
|
},
|
|
@@ -4158,10 +4386,10 @@ function registerDocumentTools(server, client) {
|
|
|
4158
4386
|
description: `A draft invoice with its lines, in EUR unless currency says otherwise; the due date follows the account\u2019s payment terms when left out. Nobody receives it. ${NOT_HERE}`,
|
|
4159
4387
|
inputSchema: {
|
|
4160
4388
|
...header,
|
|
4161
|
-
currency:
|
|
4389
|
+
currency: z14.string().regex(/^[A-Z]{3}$/).optional(),
|
|
4162
4390
|
dueDate: date,
|
|
4163
4391
|
deliveryDate: date,
|
|
4164
|
-
lines:
|
|
4392
|
+
lines: z14.array(line).min(1).max(200)
|
|
4165
4393
|
}
|
|
4166
4394
|
},
|
|
4167
4395
|
async (input) => {
|
|
@@ -4183,7 +4411,7 @@ function registerDocumentTools(server, client) {
|
|
|
4183
4411
|
...header,
|
|
4184
4412
|
dueDate: date,
|
|
4185
4413
|
deliveryDate: date,
|
|
4186
|
-
lines:
|
|
4414
|
+
lines: z14.array(line).min(1).max(200).optional()
|
|
4187
4415
|
},
|
|
4188
4416
|
annotations: { idempotentHint: true }
|
|
4189
4417
|
},
|
|
@@ -4203,8 +4431,8 @@ function describeDocument(document) {
|
|
|
4203
4431
|
}
|
|
4204
4432
|
|
|
4205
4433
|
// src/email-template-tools.ts
|
|
4206
|
-
import { z as
|
|
4207
|
-
var id2 =
|
|
4434
|
+
import { z as z15 } from "zod";
|
|
4435
|
+
var id2 = z15.string().uuid();
|
|
4208
4436
|
function registerEmailTemplateTools(server, client) {
|
|
4209
4437
|
server.registerTool(
|
|
4210
4438
|
"list_email_templates",
|
|
@@ -4212,7 +4440,7 @@ function registerEmailTemplateTools(server, client) {
|
|
|
4212
4440
|
title: "List e-mail templates",
|
|
4213
4441
|
description: "The account\u2019s own e-mail templates, paged by id; kind narrows to transactional (one-to-one) or marketing. The built-in system mails (sign-in, password, receipts) are not among them.",
|
|
4214
4442
|
inputSchema: {
|
|
4215
|
-
kind:
|
|
4443
|
+
kind: z15.enum(["transactional", "marketing"]).optional(),
|
|
4216
4444
|
...page
|
|
4217
4445
|
},
|
|
4218
4446
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
@@ -4246,7 +4474,7 @@ function registerEmailTemplateTools(server, client) {
|
|
|
4246
4474
|
}
|
|
4247
4475
|
}
|
|
4248
4476
|
);
|
|
4249
|
-
const design =
|
|
4477
|
+
const design = z15.record(z15.string(), z15.unknown()).describe(
|
|
4250
4478
|
"The e-mail designer\u2019s document, in the shape get_email_template shows."
|
|
4251
4479
|
);
|
|
4252
4480
|
server.registerTool(
|
|
@@ -4255,14 +4483,14 @@ function registerEmailTemplateTools(server, client) {
|
|
|
4255
4483
|
title: "Save a new e-mail template",
|
|
4256
4484
|
description: "Saves a template; nobody receives anything. The app turns the design into the mail and cleans it.",
|
|
4257
4485
|
inputSchema: {
|
|
4258
|
-
name:
|
|
4259
|
-
subject:
|
|
4486
|
+
name: z15.string().min(1).max(120),
|
|
4487
|
+
subject: z15.string().min(1).max(300),
|
|
4260
4488
|
design,
|
|
4261
|
-
kind:
|
|
4262
|
-
language:
|
|
4263
|
-
category:
|
|
4489
|
+
kind: z15.enum(["transactional", "marketing"]).optional(),
|
|
4490
|
+
language: z15.string().min(2).max(10).describe("nl or en."),
|
|
4491
|
+
category: z15.string().min(1).max(80).nullable().optional(),
|
|
4264
4492
|
layoutId: id2.nullable().optional(),
|
|
4265
|
-
compositionMode:
|
|
4493
|
+
compositionMode: z15.enum(["layout", "standalone"]).optional()
|
|
4266
4494
|
}
|
|
4267
4495
|
},
|
|
4268
4496
|
async (input) => {
|
|
@@ -4281,12 +4509,12 @@ function registerEmailTemplateTools(server, client) {
|
|
|
4281
4509
|
description: "Only what is sent changes; a design sent replaces the design. Nobody receives anything.",
|
|
4282
4510
|
inputSchema: {
|
|
4283
4511
|
id: id2,
|
|
4284
|
-
name:
|
|
4285
|
-
subject:
|
|
4512
|
+
name: z15.string().min(1).max(120).optional(),
|
|
4513
|
+
subject: z15.string().min(1).max(300).optional(),
|
|
4286
4514
|
design: design.optional(),
|
|
4287
|
-
category:
|
|
4515
|
+
category: z15.string().min(1).max(80).nullable().optional(),
|
|
4288
4516
|
layoutId: id2.nullable().optional(),
|
|
4289
|
-
compositionMode:
|
|
4517
|
+
compositionMode: z15.enum(["layout", "standalone"]).optional()
|
|
4290
4518
|
},
|
|
4291
4519
|
annotations: { idempotentHint: true }
|
|
4292
4520
|
},
|
|
@@ -4305,8 +4533,8 @@ function describeTemplate(template) {
|
|
|
4305
4533
|
}
|
|
4306
4534
|
|
|
4307
4535
|
// src/event-tools.ts
|
|
4308
|
-
import { z as
|
|
4309
|
-
var moment =
|
|
4536
|
+
import { z as z16 } from "zod";
|
|
4537
|
+
var moment = z16.string().datetime({ offset: true }).optional().describe("An ISO timestamp with its offset, such as 2026-11-01T00:00:00Z.");
|
|
4310
4538
|
function registerEventTools(server, client) {
|
|
4311
4539
|
server.registerTool(
|
|
4312
4540
|
"list_events",
|
|
@@ -4334,7 +4562,7 @@ function registerEventTools(server, client) {
|
|
|
4334
4562
|
title: "Read one event",
|
|
4335
4563
|
description: "One event as its page needs it: description, doors and sales times, address, ticket types with their prices, and the texts for search engines. May be a minute old; ask get_event_availability for seats that must be current.",
|
|
4336
4564
|
inputSchema: {
|
|
4337
|
-
idOrSlug:
|
|
4565
|
+
idOrSlug: z16.string().min(1).max(200).describe("The id or the address (slug) of the event.")
|
|
4338
4566
|
},
|
|
4339
4567
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4340
4568
|
},
|
|
@@ -4359,7 +4587,7 @@ function registerEventTools(server, client) {
|
|
|
4359
4587
|
title: "Seats left for an event",
|
|
4360
4588
|
description: "Live, not cached: how many seats are left for the event and for each ticket type, and whether it is sold out. A remaining of null means there is no ceiling.",
|
|
4361
4589
|
inputSchema: {
|
|
4362
|
-
eventId:
|
|
4590
|
+
eventId: z16.string().uuid().describe("The id of the event.")
|
|
4363
4591
|
},
|
|
4364
4592
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4365
4593
|
},
|
|
@@ -4387,8 +4615,8 @@ function describeEvent(event) {
|
|
|
4387
4615
|
}
|
|
4388
4616
|
|
|
4389
4617
|
// src/form-definition-tools.ts
|
|
4390
|
-
import { z as
|
|
4391
|
-
var id3 =
|
|
4618
|
+
import { z as z17 } from "zod";
|
|
4619
|
+
var id3 = z17.string().uuid();
|
|
4392
4620
|
function registerFormDefinitionTools(server, client) {
|
|
4393
4621
|
server.registerTool(
|
|
4394
4622
|
"list_form_definitions",
|
|
@@ -4433,9 +4661,9 @@ function registerFormDefinitionTools(server, client) {
|
|
|
4433
4661
|
title: "Make a form",
|
|
4434
4662
|
description: "A new, empty draft form; its address follows from the name. Add fields with update_form.",
|
|
4435
4663
|
inputSchema: {
|
|
4436
|
-
name:
|
|
4437
|
-
submitLabel:
|
|
4438
|
-
successMessage:
|
|
4664
|
+
name: z17.string().min(1).max(120),
|
|
4665
|
+
submitLabel: z17.string().min(1).max(60).optional(),
|
|
4666
|
+
successMessage: z17.string().min(1).max(500).optional()
|
|
4439
4667
|
}
|
|
4440
4668
|
},
|
|
4441
4669
|
async (input) => {
|
|
@@ -4454,11 +4682,11 @@ function registerFormDefinitionTools(server, client) {
|
|
|
4454
4682
|
description: "Only what is sent changes; fields sent replace every field. status published puts the form live at once on every site it is placed on, and needs an e-mail field. What happens after a submission (deals, tasks, tags, mails) is set up in the app.",
|
|
4455
4683
|
inputSchema: {
|
|
4456
4684
|
id: id3,
|
|
4457
|
-
name:
|
|
4458
|
-
fields:
|
|
4459
|
-
settings:
|
|
4460
|
-
spamSettings:
|
|
4461
|
-
status:
|
|
4685
|
+
name: z17.string().min(1).max(120).optional(),
|
|
4686
|
+
fields: z17.array(z17.unknown()).max(500).optional(),
|
|
4687
|
+
settings: z17.record(z17.string(), z17.unknown()).optional(),
|
|
4688
|
+
spamSettings: z17.record(z17.string(), z17.unknown()).optional(),
|
|
4689
|
+
status: z17.enum(["draft", "published", "archived"]).optional()
|
|
4462
4690
|
},
|
|
4463
4691
|
annotations: { idempotentHint: true }
|
|
4464
4692
|
},
|
|
@@ -4477,7 +4705,7 @@ function describeForm(form) {
|
|
|
4477
4705
|
}
|
|
4478
4706
|
|
|
4479
4707
|
// src/hosting-order-page-tools.ts
|
|
4480
|
-
import { z as
|
|
4708
|
+
import { z as z18 } from "zod";
|
|
4481
4709
|
function registerHostingOrderPageTools(server, client) {
|
|
4482
4710
|
server.registerTool(
|
|
4483
4711
|
"get_hosting_order_page",
|
|
@@ -4501,19 +4729,19 @@ function registerHostingOrderPageTools(server, client) {
|
|
|
4501
4729
|
title: "Set up the hosting order page",
|
|
4502
4730
|
description: "Changes the hosting order page; only what is sent changes. `packages` is the whole list in the order the page shows it (product ids from get_hosting_order_page candidates), `allowedOrigins` the whole list of sites the snippet may stand on -- add the site before placing the snippet. `enabled: true` opens real payments. It is refused while the page cannot deliver what it sells; the refusal names what the account owner has to fix in the app themselves (a registrar login, the renewal round still in shadow mode) -- pass that on, do not retry.",
|
|
4503
4731
|
inputSchema: {
|
|
4504
|
-
enabled:
|
|
4505
|
-
quickTlds:
|
|
4506
|
-
defaultPeriod:
|
|
4507
|
-
packages:
|
|
4508
|
-
|
|
4509
|
-
productId:
|
|
4510
|
-
service:
|
|
4511
|
-
featured:
|
|
4512
|
-
badge:
|
|
4513
|
-
highlights:
|
|
4732
|
+
enabled: z18.boolean().optional(),
|
|
4733
|
+
quickTlds: z18.array(z18.string().max(30)).max(12).optional().describe("Extensions without a dot, such as nl, com, be."),
|
|
4734
|
+
defaultPeriod: z18.enum(["month", "year"]).optional(),
|
|
4735
|
+
packages: z18.array(
|
|
4736
|
+
z18.object({
|
|
4737
|
+
productId: z18.string().uuid(),
|
|
4738
|
+
service: z18.enum(["hosting", "email"]),
|
|
4739
|
+
featured: z18.boolean().optional(),
|
|
4740
|
+
badge: z18.string().max(40).nullable().optional(),
|
|
4741
|
+
highlights: z18.array(z18.string().max(80)).max(4).optional()
|
|
4514
4742
|
})
|
|
4515
4743
|
).max(24).optional(),
|
|
4516
|
-
allowedOrigins:
|
|
4744
|
+
allowedOrigins: z18.array(z18.string().max(255)).max(20).optional().describe(
|
|
4517
4745
|
"Origins such as https://example.com: no path, lower case."
|
|
4518
4746
|
)
|
|
4519
4747
|
},
|
|
@@ -4546,7 +4774,7 @@ function describe(page2) {
|
|
|
4546
4774
|
}
|
|
4547
4775
|
|
|
4548
4776
|
// src/order-tools.ts
|
|
4549
|
-
import { z as
|
|
4777
|
+
import { z as z19 } from "zod";
|
|
4550
4778
|
function registerOrderTools(server, client) {
|
|
4551
4779
|
server.registerTool(
|
|
4552
4780
|
"get_order",
|
|
@@ -4554,7 +4782,7 @@ function registerOrderTools(server, client) {
|
|
|
4554
4782
|
title: "Read one order",
|
|
4555
4783
|
description: "One order by its number, its id or its Stripe PaymentIntent (pi_\u2026): payment status, whether it was sent, the lines and the totals. Live, never cached. It does not say who bought it or where it went, and it cannot change, ship or refund an order: that is done in the app.",
|
|
4556
4784
|
inputSchema: {
|
|
4557
|
-
ref:
|
|
4785
|
+
ref: z19.string().min(1).max(200).describe("The order number, its id, or a pi_\u2026 PaymentIntent id.")
|
|
4558
4786
|
},
|
|
4559
4787
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4560
4788
|
},
|
|
@@ -4570,29 +4798,59 @@ function registerOrderTools(server, client) {
|
|
|
4570
4798
|
}
|
|
4571
4799
|
}
|
|
4572
4800
|
);
|
|
4801
|
+
server.registerTool(
|
|
4802
|
+
"list_orders",
|
|
4803
|
+
{
|
|
4804
|
+
title: "List orders",
|
|
4805
|
+
description: "The account\u2019s orders paged by id, in the shape get_order reads one. sellerRef narrows them to orders paid through a payment link that carried ?ref=<sellerRef> -- how whoever sent the link learns it was paid, and which customer account the order made (clientAccountId). Never who bought it.",
|
|
4806
|
+
inputSchema: {
|
|
4807
|
+
sellerRef: z19.string().regex(/^[A-Za-z0-9_-]{1,64}$/).optional().describe("The ?ref= the payment link carried."),
|
|
4808
|
+
...page
|
|
4809
|
+
},
|
|
4810
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4811
|
+
},
|
|
4812
|
+
async (input) => {
|
|
4813
|
+
try {
|
|
4814
|
+
const result = await client.getOrders(input);
|
|
4815
|
+
return answer(
|
|
4816
|
+
result.data.length === 0 ? input.sellerRef ? `No order carries the mark "${input.sellerRef}".` : "No orders." : [
|
|
4817
|
+
...result.data.map((order) => describeOrder(order)),
|
|
4818
|
+
more(result.nextCursor)
|
|
4819
|
+
].filter(Boolean).join("\n\n"),
|
|
4820
|
+
result
|
|
4821
|
+
);
|
|
4822
|
+
} catch (error) {
|
|
4823
|
+
return refusal(error, "orders:read");
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
);
|
|
4573
4827
|
}
|
|
4574
4828
|
function describeOrder(order) {
|
|
4575
4829
|
const test = order.livemode ? "" : " (test)";
|
|
4576
4830
|
const lines = order.lines.map(
|
|
4577
4831
|
(line2) => `- ${line2.quantity} \xD7 ${line2.name}: ${money(line2.totalCents, order.currency)}`
|
|
4578
4832
|
);
|
|
4833
|
+
const made = order.clientAccountId ? `Customer account: ${order.clientAccountId}.` : "";
|
|
4834
|
+
const mark = order.sellerRef ? `Mark: ${order.sellerRef}.` : "";
|
|
4579
4835
|
return [
|
|
4580
4836
|
`Order ${order.number}${test}: ${order.status}, ${order.fulfilmentStatus}, ${money(order.totalCents, order.currency)}.`,
|
|
4581
|
-
...lines
|
|
4582
|
-
|
|
4837
|
+
...lines,
|
|
4838
|
+
mark,
|
|
4839
|
+
made
|
|
4840
|
+
].filter(Boolean).join("\n");
|
|
4583
4841
|
}
|
|
4584
4842
|
|
|
4585
4843
|
// src/payment-page-tools.ts
|
|
4586
|
-
import { z as
|
|
4587
|
-
var id4 =
|
|
4588
|
-
var
|
|
4844
|
+
import { z as z20 } from "zod";
|
|
4845
|
+
var id4 = z20.string().uuid();
|
|
4846
|
+
var slug2 = z20.string().min(2).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).describe("Lowercase letters, digits and hyphens.");
|
|
4589
4847
|
function registerPaymentPageTools(server, client) {
|
|
4590
4848
|
server.registerTool(
|
|
4591
4849
|
"list_payment_pages",
|
|
4592
4850
|
{
|
|
4593
4851
|
title: "List payment pages",
|
|
4594
4852
|
description: "The account\u2019s payment pages, paged by id; archived ones only with archived: true.",
|
|
4595
|
-
inputSchema: { archived:
|
|
4853
|
+
inputSchema: { archived: z20.boolean().optional(), ...page },
|
|
4596
4854
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
4597
4855
|
},
|
|
4598
4856
|
async (input) => {
|
|
@@ -4635,8 +4893,8 @@ ${blockers}`, page2);
|
|
|
4635
4893
|
title: "Make a payment page",
|
|
4636
4894
|
description: "A draft payment page for one of the account\u2019s products. Nothing is live until a person publishes it in the app. Without priceId the buyer picks from the product\u2019s active price options.",
|
|
4637
4895
|
inputSchema: {
|
|
4638
|
-
name:
|
|
4639
|
-
slug:
|
|
4896
|
+
name: z20.string().min(1).max(120),
|
|
4897
|
+
slug: slug2.optional(),
|
|
4640
4898
|
productId: id4,
|
|
4641
4899
|
priceId: id4.optional()
|
|
4642
4900
|
}
|
|
@@ -4657,13 +4915,13 @@ ${blockers}`, page2);
|
|
|
4657
4915
|
description: "Only what is sent changes. content takes whole blocks: hero {headline, subheadline}, bullets {title, items}, quote {text, author, role}, guarantee {title, body}, faq {items: [{question, answer}]}, trust {items}, paymentOptions {recommendedPriceId, note}; a block left out stays. settings takes termsUrl, requireFields, excludedPaymentMethods (what is switched OFF), thankYouMode, redirectUrl, createInvoice, maxQuantity, customFields, tagOnPurchase. status goes back to draft (off the web) or archived; publishing is done in the app. A new slug breaks links already sent.",
|
|
4658
4916
|
inputSchema: {
|
|
4659
4917
|
id: id4,
|
|
4660
|
-
name:
|
|
4661
|
-
slug:
|
|
4918
|
+
name: z20.string().min(1).max(120).optional(),
|
|
4919
|
+
slug: slug2.optional(),
|
|
4662
4920
|
productId: id4.optional(),
|
|
4663
4921
|
priceId: id4.nullable().optional(),
|
|
4664
|
-
status:
|
|
4665
|
-
content:
|
|
4666
|
-
settings:
|
|
4922
|
+
status: z20.enum(["draft", "archived"]).optional(),
|
|
4923
|
+
content: z20.record(z20.string(), z20.unknown()).optional(),
|
|
4924
|
+
settings: z20.record(z20.string(), z20.unknown()).optional()
|
|
4667
4925
|
},
|
|
4668
4926
|
annotations: { idempotentHint: true }
|
|
4669
4927
|
},
|
|
@@ -4682,9 +4940,9 @@ function describePage(page2) {
|
|
|
4682
4940
|
}
|
|
4683
4941
|
|
|
4684
4942
|
// src/placement-tools.ts
|
|
4685
|
-
import { z as
|
|
4943
|
+
import { z as z21 } from "zod";
|
|
4686
4944
|
var REACT_HINT = "On a React or Next.js site, prefer the components in `@solumflow-app/crm-client/react` (CrmForm, CrmBooking, CrmChatWidget, CrmSiteTracking) over pasting HTML.";
|
|
4687
|
-
var origin =
|
|
4945
|
+
var origin = z21.string().regex(/^https?:\/\/[a-zA-Z0-9.-]+(:[0-9]{1,5})?$/).describe("Scheme, host and optional port; no path: https://www.example.com");
|
|
4688
4946
|
function registerPlacementTools(server, client) {
|
|
4689
4947
|
server.registerTool(
|
|
4690
4948
|
"list_forms",
|
|
@@ -4757,13 +5015,13 @@ function registerPlacementTools(server, client) {
|
|
|
4757
5015
|
title: "Make or change a chat widget",
|
|
4758
5016
|
description: "Makes the chat widget at this address when there is none, or changes it. Sets its name, whether it is live, and which sites it may run on. How it looks and what it says is done in the app\u2019s builder. Sites are added and removed, never replaced. Careful: an empty list means ANY site, so adding the first origin narrows the widget to that site only, and removing an origin takes the chat off a site that may be live.",
|
|
4759
5017
|
inputSchema: {
|
|
4760
|
-
slug:
|
|
5018
|
+
slug: z21.string().regex(/^[a-z][a-z0-9-]*$/).max(100).describe(
|
|
4761
5019
|
'The widget\u2019s address on this account, such as "website-chat".'
|
|
4762
5020
|
),
|
|
4763
|
-
name:
|
|
4764
|
-
status:
|
|
4765
|
-
addAllowedOrigins:
|
|
4766
|
-
removeAllowedOrigins:
|
|
5021
|
+
name: z21.string().min(1).max(200).optional().describe("Required when the widget does not exist yet."),
|
|
5022
|
+
status: z21.enum(["draft", "published"]).optional().describe("Only a published widget answers on a site."),
|
|
5023
|
+
addAllowedOrigins: z21.array(origin).max(20).optional(),
|
|
5024
|
+
removeAllowedOrigins: z21.array(origin).max(20).optional()
|
|
4767
5025
|
},
|
|
4768
5026
|
annotations: {
|
|
4769
5027
|
readOnlyHint: false,
|
|
@@ -4771,9 +5029,9 @@ function registerPlacementTools(server, client) {
|
|
|
4771
5029
|
idempotentHint: true
|
|
4772
5030
|
}
|
|
4773
5031
|
},
|
|
4774
|
-
async ({ slug:
|
|
5032
|
+
async ({ slug: slug3, ...input }) => {
|
|
4775
5033
|
try {
|
|
4776
|
-
const widget = await client.upsertChatWidget(
|
|
5034
|
+
const widget = await client.upsertChatWidget(slug3, input);
|
|
4777
5035
|
return answer(
|
|
4778
5036
|
`${widget.created ? "Made" : "Updated"} the widget.
|
|
4779
5037
|
|
|
@@ -4811,9 +5069,9 @@ ${describeChatWidget(widget)}`,
|
|
|
4811
5069
|
title: "Set up or change the tracking script",
|
|
4812
5070
|
description: "Sets website tracking up when it is not there, adds or removes the hosts it counts visits from, and changes the consent it waits for. An empty list of hosts counts nothing, so add the host of every site the script is placed on. Hosts are added and removed, never replaced.",
|
|
4813
5071
|
inputSchema: {
|
|
4814
|
-
addAllowedHosts:
|
|
4815
|
-
removeAllowedHosts:
|
|
4816
|
-
consentSignal:
|
|
5072
|
+
addAllowedHosts: z21.array(z21.string()).max(20).optional().describe('Hosts such as "www.example.com" or "*.example.com".'),
|
|
5073
|
+
removeAllowedHosts: z21.array(z21.string()).max(20).optional(),
|
|
5074
|
+
consentSignal: z21.enum([
|
|
4817
5075
|
"ad_storage",
|
|
4818
5076
|
"analytics_storage",
|
|
4819
5077
|
"personalization_storage",
|
|
@@ -4874,14 +5132,14 @@ function describeSiteTracking(site) {
|
|
|
4874
5132
|
}
|
|
4875
5133
|
|
|
4876
5134
|
// src/record-tools.ts
|
|
4877
|
-
import { z as
|
|
4878
|
-
var recordId =
|
|
4879
|
-
var values =
|
|
5135
|
+
import { z as z22 } from "zod";
|
|
5136
|
+
var recordId = z22.string().uuid().describe("The id of the record.");
|
|
5137
|
+
var values = z22.record(z22.string(), z22.unknown()).describe(
|
|
4880
5138
|
"Field values by field slug, as list_record_lists names them. A field marked sensitive is refused."
|
|
4881
5139
|
);
|
|
4882
|
-
var tags =
|
|
4883
|
-
add:
|
|
4884
|
-
remove:
|
|
5140
|
+
var tags = z22.object({
|
|
5141
|
+
add: z22.array(z22.string().uuid()).max(50).optional(),
|
|
5142
|
+
remove: z22.array(z22.string().uuid()).max(50).optional()
|
|
4885
5143
|
}).optional().describe("Tag ids to put on or take off; list_tags names them.");
|
|
4886
5144
|
function registerRecordTools(server, client) {
|
|
4887
5145
|
server.registerTool(
|
|
@@ -4906,8 +5164,8 @@ function registerRecordTools(server, client) {
|
|
|
4906
5164
|
title: "Search the records of one list",
|
|
4907
5165
|
description: "The records of one list, by its slug, paged. q searches the name and every value but a sensitive one. Records whose processing is restricted are left out.",
|
|
4908
5166
|
inputSchema: {
|
|
4909
|
-
list:
|
|
4910
|
-
q:
|
|
5167
|
+
list: z22.string().min(1).max(64).describe("The slug of the list."),
|
|
5168
|
+
q: z22.string().max(200).optional(),
|
|
4911
5169
|
...page
|
|
4912
5170
|
},
|
|
4913
5171
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
@@ -4947,7 +5205,7 @@ function registerRecordTools(server, client) {
|
|
|
4947
5205
|
title: "Make a record",
|
|
4948
5206
|
description: "A new record in a list (left out: a person, where the account puts new contacts). A person whose e-mail or phone is already known is not made again: the answer gives only their id (matched), nothing on them changes, and get_record reads them; use update_record to change them. Automations that start on a new record run as they do for one made in the app.",
|
|
4949
5207
|
inputSchema: {
|
|
4950
|
-
list:
|
|
5208
|
+
list: z22.string().min(1).max(64).optional(),
|
|
4951
5209
|
values,
|
|
4952
5210
|
tags
|
|
4953
5211
|
}
|
|
@@ -4973,7 +5231,7 @@ function registerRecordTools(server, client) {
|
|
|
4973
5231
|
id: recordId,
|
|
4974
5232
|
values: values.optional(),
|
|
4975
5233
|
tags,
|
|
4976
|
-
ownerId:
|
|
5234
|
+
ownerId: z22.string().uuid().nullable().optional()
|
|
4977
5235
|
},
|
|
4978
5236
|
annotations: { idempotentHint: true }
|
|
4979
5237
|
},
|
|
@@ -5037,7 +5295,7 @@ function registerRecordTools(server, client) {
|
|
|
5037
5295
|
description: "A note on a record\u2019s timeline, shown as written through the API with the name of this key.",
|
|
5038
5296
|
inputSchema: {
|
|
5039
5297
|
recordId,
|
|
5040
|
-
body:
|
|
5298
|
+
body: z22.string().min(1).max(2e4)
|
|
5041
5299
|
}
|
|
5042
5300
|
},
|
|
5043
5301
|
async ({ recordId: id7, body }) => {
|
|
@@ -5055,12 +5313,12 @@ function registerRecordTools(server, client) {
|
|
|
5055
5313
|
title: "Add a contact, or find the one already known",
|
|
5056
5314
|
description: "A person by e-mail address. Somebody already known by that address is matched rather than made twice (created: false), and the answer is their id. A field left out is never cleared on a contact already known.",
|
|
5057
5315
|
inputSchema: {
|
|
5058
|
-
email:
|
|
5059
|
-
firstName:
|
|
5060
|
-
lastName:
|
|
5061
|
-
phone:
|
|
5062
|
-
company:
|
|
5063
|
-
object:
|
|
5316
|
+
email: z22.string().email().max(320),
|
|
5317
|
+
firstName: z22.string().trim().min(1).max(200).optional(),
|
|
5318
|
+
lastName: z22.string().trim().min(1).max(200).optional(),
|
|
5319
|
+
phone: z22.string().trim().min(1).max(50).optional(),
|
|
5320
|
+
company: z22.string().trim().min(1).max(200).optional(),
|
|
5321
|
+
object: z22.string().trim().min(1).max(64).optional().describe(
|
|
5064
5322
|
"The slug of the list for people a new contact goes into; left out, the account\u2019s own choice."
|
|
5065
5323
|
)
|
|
5066
5324
|
},
|
|
@@ -5142,20 +5400,20 @@ function registerServerInfoTool(server, info) {
|
|
|
5142
5400
|
}
|
|
5143
5401
|
|
|
5144
5402
|
// src/shop-setup-tools.ts
|
|
5145
|
-
import { z as
|
|
5146
|
-
var id5 =
|
|
5147
|
-
var cents2 =
|
|
5148
|
-
var region =
|
|
5149
|
-
countryCode:
|
|
5150
|
-
postcodeFrom:
|
|
5151
|
-
postcodeTo:
|
|
5403
|
+
import { z as z23 } from "zod";
|
|
5404
|
+
var id5 = z23.string().uuid();
|
|
5405
|
+
var cents2 = z23.number().int().min(0).max(99999999);
|
|
5406
|
+
var region = z23.object({
|
|
5407
|
+
countryCode: z23.string().regex(/^[A-Z]{2}$/).describe("Two capital letters, ISO 3166 (NL, BE, DE)."),
|
|
5408
|
+
postcodeFrom: z23.string().max(20).nullable().default(null).describe("With postcodeTo: only this range of the country."),
|
|
5409
|
+
postcodeTo: z23.string().max(20).nullable().default(null)
|
|
5152
5410
|
});
|
|
5153
|
-
var discount =
|
|
5154
|
-
name:
|
|
5155
|
-
code:
|
|
5411
|
+
var discount = z23.object({
|
|
5412
|
+
name: z23.string().min(1).max(120).describe("What the team sees it as."),
|
|
5413
|
+
code: z23.string().max(40).nullable().describe(
|
|
5156
5414
|
"What a buyer types (upper-cased), or null for one that applies on its own."
|
|
5157
5415
|
),
|
|
5158
|
-
effect:
|
|
5416
|
+
effect: z23.enum([
|
|
5159
5417
|
"tier_price",
|
|
5160
5418
|
"percent_off_lines",
|
|
5161
5419
|
"amount_off_lines",
|
|
@@ -5165,19 +5423,19 @@ var discount = z21.object({
|
|
|
5165
5423
|
]).describe(
|
|
5166
5424
|
"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."
|
|
5167
5425
|
),
|
|
5168
|
-
percent:
|
|
5169
|
-
amountCents:
|
|
5170
|
-
currency:
|
|
5171
|
-
tierMinQuantity:
|
|
5172
|
-
minSubtotalCents:
|
|
5173
|
-
productIds:
|
|
5174
|
-
categoryIds:
|
|
5175
|
-
objectIds:
|
|
5176
|
-
startsAt:
|
|
5177
|
-
endsAt:
|
|
5178
|
-
maxUses:
|
|
5179
|
-
maxUsesPerEmail:
|
|
5180
|
-
combinable:
|
|
5426
|
+
percent: z23.number().nullable().describe("For a percentage: above 0, at most 100."),
|
|
5427
|
+
amountCents: z23.number().int().nullable().describe("For an amount, in currency, as the account enters prices."),
|
|
5428
|
+
currency: z23.string().regex(/^[A-Z]{3}$/).nullable().describe("Required beside amountCents (EUR)."),
|
|
5429
|
+
tierMinQuantity: z23.number().int().nullable(),
|
|
5430
|
+
minSubtotalCents: z23.number().int().nullable().describe("Only from this basket value, before discount."),
|
|
5431
|
+
productIds: z23.array(id5).max(500).describe("Empty: every product."),
|
|
5432
|
+
categoryIds: z23.array(id5).max(100),
|
|
5433
|
+
objectIds: z23.array(id5).max(20),
|
|
5434
|
+
startsAt: z23.string().datetime({ offset: true }).nullable(),
|
|
5435
|
+
endsAt: z23.string().datetime({ offset: true }).nullable(),
|
|
5436
|
+
maxUses: z23.number().int().positive().nullable(),
|
|
5437
|
+
maxUsesPerEmail: z23.number().int().positive().nullable(),
|
|
5438
|
+
combinable: z23.boolean().describe(
|
|
5181
5439
|
"Whether it may count beside another discount on the same order."
|
|
5182
5440
|
)
|
|
5183
5441
|
});
|
|
@@ -5230,8 +5488,8 @@ function registerShopSetupTools(server, client) {
|
|
|
5230
5488
|
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.",
|
|
5231
5489
|
inputSchema: {
|
|
5232
5490
|
id: id5.optional().describe("The zone to change; leave out for a new one."),
|
|
5233
|
-
name:
|
|
5234
|
-
regions:
|
|
5491
|
+
name: z23.string().min(1).max(80),
|
|
5492
|
+
regions: z23.array(region).max(300).default([])
|
|
5235
5493
|
},
|
|
5236
5494
|
annotations: { idempotentHint: true }
|
|
5237
5495
|
},
|
|
@@ -5255,14 +5513,14 @@ function registerShopSetupTools(server, client) {
|
|
|
5255
5513
|
inputSchema: {
|
|
5256
5514
|
id: id5.optional(),
|
|
5257
5515
|
zoneId: id5,
|
|
5258
|
-
kind:
|
|
5259
|
-
name:
|
|
5260
|
-
enabled:
|
|
5516
|
+
kind: z23.enum(["flat_rate", "free_shipping", "local_pickup"]),
|
|
5517
|
+
name: z23.string().min(1).max(80).describe("What the buyer chooses."),
|
|
5518
|
+
enabled: z23.boolean().default(true),
|
|
5261
5519
|
amountCents: cents2.default(0),
|
|
5262
5520
|
noClassAmountCents: cents2.default(0),
|
|
5263
5521
|
taxRateId: id5.nullish(),
|
|
5264
5522
|
freeThresholdCents: cents2.nullish(),
|
|
5265
|
-
classRates:
|
|
5523
|
+
classRates: z23.array(z23.object({ classId: id5, amountCents: cents2 })).max(100).default([])
|
|
5266
5524
|
},
|
|
5267
5525
|
annotations: { idempotentHint: true }
|
|
5268
5526
|
},
|
|
@@ -5283,7 +5541,7 @@ function registerShopSetupTools(server, client) {
|
|
|
5283
5541
|
{
|
|
5284
5542
|
title: "List discounts",
|
|
5285
5543
|
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.",
|
|
5286
|
-
inputSchema: { archived:
|
|
5544
|
+
inputSchema: { archived: z23.boolean().optional() },
|
|
5287
5545
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
5288
5546
|
},
|
|
5289
5547
|
async ({ archived }) => {
|
|
@@ -5325,7 +5583,7 @@ function registerShopSetupTools(server, client) {
|
|
|
5325
5583
|
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.",
|
|
5326
5584
|
inputSchema: {
|
|
5327
5585
|
id: id5,
|
|
5328
|
-
archived:
|
|
5586
|
+
archived: z23.boolean().default(true)
|
|
5329
5587
|
},
|
|
5330
5588
|
annotations: { idempotentHint: true }
|
|
5331
5589
|
},
|
|
@@ -5390,9 +5648,9 @@ function registerShopSetupTools(server, client) {
|
|
|
5390
5648
|
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.",
|
|
5391
5649
|
inputSchema: {
|
|
5392
5650
|
id: id5.optional(),
|
|
5393
|
-
name:
|
|
5394
|
-
url:
|
|
5395
|
-
events:
|
|
5651
|
+
name: z23.string().min(1).max(80),
|
|
5652
|
+
url: z23.string().url().max(2048),
|
|
5653
|
+
events: z23.array(z23.string().min(1).max(60)).min(1).max(20).describe(
|
|
5396
5654
|
"For a shop: product.changed, product.deleted, category.changed."
|
|
5397
5655
|
)
|
|
5398
5656
|
},
|
|
@@ -5428,7 +5686,7 @@ function describeDiscount(entry) {
|
|
|
5428
5686
|
}
|
|
5429
5687
|
|
|
5430
5688
|
// src/shop-test-order-tool.ts
|
|
5431
|
-
import { z as
|
|
5689
|
+
import { z as z24 } from "zod";
|
|
5432
5690
|
var PAID_POLLS = 10;
|
|
5433
5691
|
var PAID_POLL_MS = 1500;
|
|
5434
5692
|
function registerShopTestOrderTool(server, client, options = {}) {
|
|
@@ -5439,25 +5697,25 @@ function registerShopTestOrderTool(server, client, options = {}) {
|
|
|
5439
5697
|
title: "Place and pay a test order",
|
|
5440
5698
|
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. Pass `extras` (products from the product\u2019s own `extras` list) and `options` (by option key: a text, true, or a choice key) to prove a product with an extra or a surcharge option; the cart must show them, priced by the server, before the order is placed. Never send an amount.",
|
|
5441
5699
|
inputSchema: {
|
|
5442
|
-
email:
|
|
5700
|
+
email: z24.string().email().describe(
|
|
5443
5701
|
"Where the confirmation goes: the shop owner, never a customer."
|
|
5444
5702
|
),
|
|
5445
|
-
productId:
|
|
5703
|
+
productId: z24.string().uuid().optional().describe(
|
|
5446
5704
|
"The product to buy; leave out for the first one in stock."
|
|
5447
5705
|
),
|
|
5448
|
-
countryCode:
|
|
5449
|
-
postalCode:
|
|
5450
|
-
extras:
|
|
5451
|
-
|
|
5452
|
-
productId:
|
|
5453
|
-
quantity:
|
|
5706
|
+
countryCode: z24.string().regex(/^[A-Z]{2}$/).default("NL").describe("Where it is posted, for a product that is posted."),
|
|
5707
|
+
postalCode: z24.string().max(20).optional(),
|
|
5708
|
+
extras: z24.array(
|
|
5709
|
+
z24.object({
|
|
5710
|
+
productId: z24.string().uuid(),
|
|
5711
|
+
quantity: z24.number().int().min(1).max(99).default(1)
|
|
5454
5712
|
})
|
|
5455
5713
|
).max(10).optional().describe(
|
|
5456
5714
|
"Extras to take with the product, each a product from its `extras`; for one that follows its line the quantity is per piece."
|
|
5457
5715
|
),
|
|
5458
|
-
options:
|
|
5459
|
-
|
|
5460
|
-
|
|
5716
|
+
options: z24.record(
|
|
5717
|
+
z24.string().regex(/^[a-z0-9_]{1,40}$/),
|
|
5718
|
+
z24.union([z24.string().max(2e3), z24.boolean()])
|
|
5461
5719
|
).optional().describe(
|
|
5462
5720
|
"The choices for the product\u2019s options, by option key: a text, true for a ticked box, or the key of a choice."
|
|
5463
5721
|
)
|
|
@@ -5584,9 +5842,9 @@ function registerShopTestOrderTool(server, client, options = {}) {
|
|
|
5584
5842
|
}
|
|
5585
5843
|
|
|
5586
5844
|
// src/task-tools.ts
|
|
5587
|
-
import { z as
|
|
5588
|
-
var id6 =
|
|
5589
|
-
var moment2 =
|
|
5845
|
+
import { z as z25 } from "zod";
|
|
5846
|
+
var id6 = z25.string().uuid();
|
|
5847
|
+
var moment2 = z25.string().datetime({ offset: true }).describe(
|
|
5590
5848
|
"An ISO moment with its offset, such as 2026-11-01T09:00:00+01:00."
|
|
5591
5849
|
);
|
|
5592
5850
|
var OUTSIDE_PROJECTS = "Only tasks outside projects: a project\u2019s tasks are seen differently per member and are worked on in the app.";
|
|
@@ -5597,7 +5855,7 @@ function registerTaskTools(server, client) {
|
|
|
5597
5855
|
title: "List tasks",
|
|
5598
5856
|
description: `Tasks paged by id: open and in progress unless status says otherwise; recordId, dealId and assigneeId narrow them. ${OUTSIDE_PROJECTS}`,
|
|
5599
5857
|
inputSchema: {
|
|
5600
|
-
status:
|
|
5858
|
+
status: z25.enum(["open", "in_progress", "done", "all"]).optional(),
|
|
5601
5859
|
recordId: id6.optional(),
|
|
5602
5860
|
dealId: id6.optional(),
|
|
5603
5861
|
assigneeId: id6.optional(),
|
|
@@ -5643,14 +5901,14 @@ function registerTaskTools(server, client) {
|
|
|
5643
5901
|
title: "Make a task",
|
|
5644
5902
|
description: `A task on a contact (recordId), on a deal (dealId; with recordId one of that contact\u2019s deals), or on nothing, optionally due, prioritised and assigned to members. ${OUTSIDE_PROJECTS}`,
|
|
5645
5903
|
inputSchema: {
|
|
5646
|
-
title:
|
|
5647
|
-
description:
|
|
5904
|
+
title: z25.string().min(1).max(500),
|
|
5905
|
+
description: z25.string().max(2e4).nullable().optional(),
|
|
5648
5906
|
dueAt: moment2.nullable().optional(),
|
|
5649
|
-
priority:
|
|
5907
|
+
priority: z25.enum(["low", "normal", "high"]).optional(),
|
|
5650
5908
|
recordId: id6.nullable().optional(),
|
|
5651
5909
|
dealId: id6.nullable().optional(),
|
|
5652
5910
|
parentTaskId: id6.nullable().optional(),
|
|
5653
|
-
assigneeIds:
|
|
5911
|
+
assigneeIds: z25.array(id6).max(20).optional()
|
|
5654
5912
|
}
|
|
5655
5913
|
},
|
|
5656
5914
|
async (input) => {
|
|
@@ -5669,12 +5927,12 @@ function registerTaskTools(server, client) {
|
|
|
5669
5927
|
description: `Changes only what is sent; status done completes it. assigneeIds replaces who works on it. ${OUTSIDE_PROJECTS}`,
|
|
5670
5928
|
inputSchema: {
|
|
5671
5929
|
id: id6,
|
|
5672
|
-
title:
|
|
5673
|
-
description:
|
|
5930
|
+
title: z25.string().min(1).max(500).optional(),
|
|
5931
|
+
description: z25.string().max(2e4).nullable().optional(),
|
|
5674
5932
|
dueAt: moment2.nullable().optional(),
|
|
5675
|
-
priority:
|
|
5676
|
-
status:
|
|
5677
|
-
assigneeIds:
|
|
5933
|
+
priority: z25.enum(["low", "normal", "high"]).optional(),
|
|
5934
|
+
status: z25.enum(["open", "in_progress", "done"]).optional(),
|
|
5935
|
+
assigneeIds: z25.array(id6).max(20).optional()
|
|
5678
5936
|
},
|
|
5679
5937
|
annotations: { idempotentHint: true }
|
|
5680
5938
|
},
|
|
@@ -5695,7 +5953,7 @@ function registerTaskTools(server, client) {
|
|
|
5695
5953
|
inputSchema: {
|
|
5696
5954
|
from: moment2,
|
|
5697
5955
|
to: moment2,
|
|
5698
|
-
kind:
|
|
5956
|
+
kind: z25.enum(["appointment", "block", "all"]).optional(),
|
|
5699
5957
|
contactRecordId: id6.optional(),
|
|
5700
5958
|
...page
|
|
5701
5959
|
},
|
|
@@ -5744,14 +6002,14 @@ function describeAppointment(appointment) {
|
|
|
5744
6002
|
}
|
|
5745
6003
|
|
|
5746
6004
|
// src/tool-definitions-version.ts
|
|
5747
|
-
import { z as
|
|
6005
|
+
import { z as z26 } from "zod";
|
|
5748
6006
|
import { createHash } from "crypto";
|
|
5749
6007
|
function toolDefinitionsHash(tools) {
|
|
5750
6008
|
const definitions = [...tools].sort((left, right) => left.name.localeCompare(right.name)).map(({ name, config }) => ({
|
|
5751
6009
|
name,
|
|
5752
6010
|
title: config.title ?? null,
|
|
5753
6011
|
description: config.description ?? null,
|
|
5754
|
-
inputSchema: config.inputSchema ?
|
|
6012
|
+
inputSchema: config.inputSchema ? z26.toJSONSchema(z26.object(config.inputSchema), {
|
|
5755
6013
|
io: "input",
|
|
5756
6014
|
unrepresentable: "any"
|
|
5757
6015
|
}) : null,
|
|
@@ -5799,7 +6057,7 @@ function holdToolRegistrations(real) {
|
|
|
5799
6057
|
description: `${config.description ?? ""} Definitions version ${version2}: send it as schemaHash and the answer tells you when these tool definitions are older than the server.`,
|
|
5800
6058
|
inputSchema: {
|
|
5801
6059
|
...config.inputSchema,
|
|
5802
|
-
schemaHash:
|
|
6060
|
+
schemaHash: z26.string().max(64).optional().describe(
|
|
5803
6061
|
`The definitions version named in this tool's description (${version2}).`
|
|
5804
6062
|
)
|
|
5805
6063
|
}
|
|
@@ -5872,6 +6130,8 @@ async function main() {
|
|
|
5872
6130
|
registerConsentBannerTools(held.server, client);
|
|
5873
6131
|
registerHostingOrderPageTools(held.server, client);
|
|
5874
6132
|
registerTaskTools(held.server, client);
|
|
6133
|
+
registerClientAccountTools(held.server, client);
|
|
6134
|
+
registerCalendarSetupTools(held.server, client);
|
|
5875
6135
|
held.publish();
|
|
5876
6136
|
await server.connect(new StdioServerTransport());
|
|
5877
6137
|
try {
|