@sudajs/cli 0.10.1 → 0.10.3
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/dist/index.d.ts +908 -0
- package/dist/index.js +289 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/theme/AGENTS.md +32 -0
package/dist/index.js
CHANGED
|
@@ -265,6 +265,143 @@ var activateThemeOutputSchema = z.discriminatedUnion("status", [
|
|
|
265
265
|
themeVersion: z.string().nullable()
|
|
266
266
|
})
|
|
267
267
|
]);
|
|
268
|
+
var contactOptionSchema = z.object({ label: z.string().min(1).max(80), value: z.string().min(1).max(80) });
|
|
269
|
+
var contactFieldSchema = z.object({
|
|
270
|
+
id: z.string().trim().min(1).max(48).regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/),
|
|
271
|
+
type: z.enum(["text", "textarea", "checkbox", "radio", "select"]),
|
|
272
|
+
label: z.string().trim().min(1).max(120),
|
|
273
|
+
placeholder: z.string().trim().max(160).optional(),
|
|
274
|
+
required: z.boolean().default(false),
|
|
275
|
+
options: z.array(contactOptionSchema).default([]),
|
|
276
|
+
validation: z.object({
|
|
277
|
+
format: z.enum(["email"]).optional(),
|
|
278
|
+
maxLength: z.number().int().min(1).max(5e3).optional()
|
|
279
|
+
}).default({})
|
|
280
|
+
});
|
|
281
|
+
function addUniqueContactIssue(items, collectionName, key, ctx, prefix = []) {
|
|
282
|
+
const seen = /* @__PURE__ */ new Set();
|
|
283
|
+
for (const [index, item] of items.entries()) {
|
|
284
|
+
const value = item[key];
|
|
285
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
286
|
+
if (seen.has(value)) {
|
|
287
|
+
ctx.addIssue({
|
|
288
|
+
code: z.ZodIssueCode.custom,
|
|
289
|
+
message: `Duplicate ${String(key)} in ${collectionName}.`,
|
|
290
|
+
path: [...prefix, index, key]
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
seen.add(value);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
var contactFieldsSchema = z.array(contactFieldSchema).superRefine((fields, ctx) => {
|
|
298
|
+
addUniqueContactIssue(fields, "fields", "id", ctx);
|
|
299
|
+
for (const [index, field] of fields.entries()) {
|
|
300
|
+
if ((field.type === "radio" || field.type === "select") && field.options.length === 0) {
|
|
301
|
+
ctx.addIssue({
|
|
302
|
+
code: z.ZodIssueCode.custom,
|
|
303
|
+
message: `${field.type} fields require at least one option.`,
|
|
304
|
+
path: [index, "options"]
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
addUniqueContactIssue(field.options, "options", "value", ctx, [index, "options"]);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
var contactEmailNotificationUpdateSchema = z.object({
|
|
311
|
+
id: z.string().trim().min(1).max(48),
|
|
312
|
+
type: z.literal("email"),
|
|
313
|
+
enabled: z.boolean().default(true),
|
|
314
|
+
label: z.string().trim().min(1).max(80),
|
|
315
|
+
recipients: z.array(z.string().trim().email()).min(1)
|
|
316
|
+
});
|
|
317
|
+
var contactChatNotificationUpdateSchema = z.object({
|
|
318
|
+
id: z.string().trim().min(1).max(48),
|
|
319
|
+
type: z.enum(["slack", "wecom", "feishu", "dingtalk"]),
|
|
320
|
+
enabled: z.boolean().default(true),
|
|
321
|
+
label: z.string().trim().min(1).max(80),
|
|
322
|
+
webhookUrl: z.string().trim().url().optional()
|
|
323
|
+
});
|
|
324
|
+
var contactNotificationUpdateSchema = z.discriminatedUnion("type", [
|
|
325
|
+
contactEmailNotificationUpdateSchema,
|
|
326
|
+
contactChatNotificationUpdateSchema
|
|
327
|
+
]);
|
|
328
|
+
var contactWebhookUpdateSchema = z.object({
|
|
329
|
+
id: z.string().trim().min(1).max(48),
|
|
330
|
+
enabled: z.boolean().default(true),
|
|
331
|
+
label: z.string().trim().min(1).max(80),
|
|
332
|
+
event: z.literal("contact.submitted").default("contact.submitted"),
|
|
333
|
+
url: z.string().trim().url().optional()
|
|
334
|
+
});
|
|
335
|
+
var updateContactFormInputSchema = z.object({
|
|
336
|
+
projectId: z.string().min(1),
|
|
337
|
+
enabled: z.boolean().optional(),
|
|
338
|
+
title: z.string().trim().min(1).max(120).optional(),
|
|
339
|
+
successMessage: z.string().trim().min(1).max(240).optional(),
|
|
340
|
+
submitLabel: z.string().trim().min(1).max(80).optional(),
|
|
341
|
+
fields: contactFieldsSchema.optional(),
|
|
342
|
+
notifications: z.array(contactNotificationUpdateSchema).optional(),
|
|
343
|
+
webhooks: z.array(contactWebhookUpdateSchema).optional(),
|
|
344
|
+
confirm: z.boolean().optional()
|
|
345
|
+
});
|
|
346
|
+
var contactFormViewSchema = z.object({
|
|
347
|
+
enabled: z.boolean(),
|
|
348
|
+
title: z.string(),
|
|
349
|
+
successMessage: z.string(),
|
|
350
|
+
submitLabel: z.string(),
|
|
351
|
+
fields: z.array(contactFieldSchema),
|
|
352
|
+
notifications: z.array(
|
|
353
|
+
z.discriminatedUnion("type", [
|
|
354
|
+
z.object({
|
|
355
|
+
id: z.string(),
|
|
356
|
+
type: z.literal("email"),
|
|
357
|
+
enabled: z.boolean(),
|
|
358
|
+
label: z.string(),
|
|
359
|
+
recipients: z.array(z.string())
|
|
360
|
+
}),
|
|
361
|
+
z.object({
|
|
362
|
+
id: z.string(),
|
|
363
|
+
type: z.enum(["slack", "wecom", "feishu", "dingtalk"]),
|
|
364
|
+
enabled: z.boolean(),
|
|
365
|
+
label: z.string(),
|
|
366
|
+
webhookUrlMasked: z.string().nullable()
|
|
367
|
+
})
|
|
368
|
+
])
|
|
369
|
+
),
|
|
370
|
+
webhooks: z.array(
|
|
371
|
+
z.object({
|
|
372
|
+
id: z.string(),
|
|
373
|
+
enabled: z.boolean(),
|
|
374
|
+
label: z.string(),
|
|
375
|
+
event: z.literal("contact.submitted"),
|
|
376
|
+
urlMasked: z.string().nullable()
|
|
377
|
+
})
|
|
378
|
+
),
|
|
379
|
+
limits: z.object({
|
|
380
|
+
maxFields: z.number(),
|
|
381
|
+
maxNotificationChannels: z.number(),
|
|
382
|
+
maxWebhooks: z.number()
|
|
383
|
+
}),
|
|
384
|
+
features: z.object({
|
|
385
|
+
contactNotifications: z.boolean(),
|
|
386
|
+
contactWebhooks: z.boolean()
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
var contactFormOutputSchema = z.object({
|
|
390
|
+
contactForm: contactFormViewSchema
|
|
391
|
+
});
|
|
392
|
+
var updateContactFormOutputSchema = z.discriminatedUnion("status", [
|
|
393
|
+
z.object({
|
|
394
|
+
status: z.literal("needs_confirmation"),
|
|
395
|
+
projectId: z.string(),
|
|
396
|
+
impact: z.string(),
|
|
397
|
+
draft: updateContactFormInputSchema.omit({ confirm: true })
|
|
398
|
+
}),
|
|
399
|
+
z.object({
|
|
400
|
+
status: z.literal("updated"),
|
|
401
|
+
projectId: z.string(),
|
|
402
|
+
contactForm: contactFormViewSchema
|
|
403
|
+
})
|
|
404
|
+
]);
|
|
268
405
|
var PROJECT_NAME_MIN_LENGTH = 1;
|
|
269
406
|
var PROJECT_NAME_MAX_LENGTH = 80;
|
|
270
407
|
var PROJECT_DESCRIPTION_MIN_LENGTH = 1;
|
|
@@ -883,6 +1020,40 @@ var THEME_CHECK_ICP = {
|
|
|
883
1020
|
text: "\u4EACICP\u590712345678\u53F7",
|
|
884
1021
|
href: "https://beian.miit.gov.cn/"
|
|
885
1022
|
};
|
|
1023
|
+
var PREVIEW_CONTACT_FORM = {
|
|
1024
|
+
enabled: true,
|
|
1025
|
+
endpoint: "/api/contact",
|
|
1026
|
+
honeypotField: "suda_contact_company",
|
|
1027
|
+
title: "Contact us",
|
|
1028
|
+
successMessage: "Thanks, we received your message.",
|
|
1029
|
+
submitLabel: "Submit",
|
|
1030
|
+
fields: [
|
|
1031
|
+
{
|
|
1032
|
+
id: "name",
|
|
1033
|
+
type: "text",
|
|
1034
|
+
label: "Name",
|
|
1035
|
+
required: true,
|
|
1036
|
+
options: [],
|
|
1037
|
+
validation: { maxLength: 120 }
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
id: "email",
|
|
1041
|
+
type: "text",
|
|
1042
|
+
label: "Email",
|
|
1043
|
+
required: true,
|
|
1044
|
+
options: [],
|
|
1045
|
+
validation: { format: "email", maxLength: 200 }
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
id: "message",
|
|
1049
|
+
type: "textarea",
|
|
1050
|
+
label: "Message",
|
|
1051
|
+
required: true,
|
|
1052
|
+
options: [],
|
|
1053
|
+
validation: { maxLength: 2e3 }
|
|
1054
|
+
}
|
|
1055
|
+
]
|
|
1056
|
+
};
|
|
886
1057
|
function stripHtmlTags(value) {
|
|
887
1058
|
return value.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
888
1059
|
}
|
|
@@ -968,7 +1139,9 @@ var __testUtils = {
|
|
|
968
1139
|
initThemeWithKey,
|
|
969
1140
|
performActivateTheme,
|
|
970
1141
|
performConfirmedPageOperation,
|
|
1142
|
+
performUpdateContactForm,
|
|
971
1143
|
renderDevStarterPageHtml,
|
|
1144
|
+
resolveDevStarterSlug,
|
|
972
1145
|
resolveScreenshotOptions,
|
|
973
1146
|
slugifyThemeName,
|
|
974
1147
|
validateThemeKey,
|
|
@@ -1065,24 +1238,25 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1065
1238
|
next();
|
|
1066
1239
|
return;
|
|
1067
1240
|
}
|
|
1068
|
-
|
|
1241
|
+
const slug = resolveDevStarterSlug(url.pathname);
|
|
1242
|
+
if (slug === void 0) {
|
|
1069
1243
|
next();
|
|
1070
1244
|
return;
|
|
1071
1245
|
}
|
|
1072
1246
|
try {
|
|
1073
1247
|
const theme = await loadViteDevTheme(root, server);
|
|
1074
1248
|
const renderer = await loadThemeScopedRenderer(root);
|
|
1075
|
-
if (
|
|
1076
|
-
const
|
|
1077
|
-
if (!
|
|
1249
|
+
if (slug === null) {
|
|
1250
|
+
const homeSlug = pickStarterSlug(theme);
|
|
1251
|
+
if (!homeSlug) {
|
|
1078
1252
|
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1079
1253
|
response.end("No starter pages declared by this theme.");
|
|
1080
1254
|
return;
|
|
1081
1255
|
}
|
|
1082
|
-
const starter2 = findStarterPage(theme,
|
|
1256
|
+
const starter2 = findStarterPage(theme, homeSlug);
|
|
1083
1257
|
if (!starter2) {
|
|
1084
1258
|
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1085
|
-
response.end(`Unknown starter page: ${
|
|
1259
|
+
response.end(`Unknown starter page: ${homeSlug}`);
|
|
1086
1260
|
return;
|
|
1087
1261
|
}
|
|
1088
1262
|
const html2 = await server.transformIndexHtml(
|
|
@@ -1093,7 +1267,6 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1093
1267
|
response.end(html2);
|
|
1094
1268
|
return;
|
|
1095
1269
|
}
|
|
1096
|
-
const slug = decodeURIComponent(url.pathname.slice("/pages/".length));
|
|
1097
1270
|
const starter = findStarterPage(theme, slug);
|
|
1098
1271
|
if (!starter) {
|
|
1099
1272
|
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
@@ -1141,6 +1314,16 @@ function pickStarterSlug(theme) {
|
|
|
1141
1314
|
function findStarterPage(theme, slug) {
|
|
1142
1315
|
return theme.module.starterPages.find((page) => page.slug === slug) ?? null;
|
|
1143
1316
|
}
|
|
1317
|
+
function resolveDevStarterSlug(pathname) {
|
|
1318
|
+
if (pathname === "/") {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
const normalized = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1322
|
+
if (!normalized || normalized.includes("/")) {
|
|
1323
|
+
return void 0;
|
|
1324
|
+
}
|
|
1325
|
+
return decodeURIComponent(normalized);
|
|
1326
|
+
}
|
|
1144
1327
|
function escapeHtml(value) {
|
|
1145
1328
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1146
1329
|
}
|
|
@@ -1177,7 +1360,8 @@ async function loadThemeScopedRenderer(themeRoot) {
|
|
|
1177
1360
|
function renderDevStarterPageHtml(theme, page, renderer) {
|
|
1178
1361
|
const chrome = renderer.extractLayoutChrome(theme.module.defaultLayout);
|
|
1179
1362
|
const metadata = {
|
|
1180
|
-
resolveAssetUrl: createPreviewAssetResolver(renderer.resolveAssetPath)
|
|
1363
|
+
resolveAssetUrl: createPreviewAssetResolver(renderer.resolveAssetPath),
|
|
1364
|
+
contactForm: PREVIEW_CONTACT_FORM
|
|
1181
1365
|
};
|
|
1182
1366
|
const body = renderer.renderToString(
|
|
1183
1367
|
renderer.createElement(renderer.ThemeRender, {
|
|
@@ -1643,6 +1827,58 @@ async function performActivateTheme(auth, input, fetcher = fetchJson) {
|
|
|
1643
1827
|
themeVersion: response.themeVersion
|
|
1644
1828
|
});
|
|
1645
1829
|
}
|
|
1830
|
+
async function getRemoteContactForm(auth, projectId, fetcher = fetchJson) {
|
|
1831
|
+
const response = await fetcher(
|
|
1832
|
+
`${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(projectId)}/contact-form`,
|
|
1833
|
+
{ token: auth.token }
|
|
1834
|
+
);
|
|
1835
|
+
return contactFormViewSchema.parse(response.contactForm);
|
|
1836
|
+
}
|
|
1837
|
+
async function updateRemoteContactForm(auth, input, fetcher = fetchJson) {
|
|
1838
|
+
const body = {};
|
|
1839
|
+
if (input.enabled !== void 0) body.enabled = input.enabled;
|
|
1840
|
+
if (input.title !== void 0) body.title = input.title;
|
|
1841
|
+
if (input.successMessage !== void 0) body.successMessage = input.successMessage;
|
|
1842
|
+
if (input.submitLabel !== void 0) body.submitLabel = input.submitLabel;
|
|
1843
|
+
if (input.fields !== void 0) body.fields = input.fields;
|
|
1844
|
+
if (input.notifications !== void 0) body.notifications = input.notifications;
|
|
1845
|
+
if (input.webhooks !== void 0) body.webhooks = input.webhooks;
|
|
1846
|
+
const response = await fetcher(
|
|
1847
|
+
`${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(input.projectId)}/contact-form`,
|
|
1848
|
+
{
|
|
1849
|
+
method: "PATCH",
|
|
1850
|
+
token: auth.token,
|
|
1851
|
+
headers: { "Content-Type": "application/json" },
|
|
1852
|
+
body: JSON.stringify(body)
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
return contactFormViewSchema.parse(response.contactForm);
|
|
1856
|
+
}
|
|
1857
|
+
async function performUpdateContactForm(auth, input, fetcher = fetchJson) {
|
|
1858
|
+
const draft = updateContactFormInputSchema.omit({ confirm: true }).parse(input);
|
|
1859
|
+
const changes = [
|
|
1860
|
+
input.enabled !== void 0 ? "enabled state" : null,
|
|
1861
|
+
input.title !== void 0 || input.successMessage !== void 0 || input.submitLabel !== void 0 ? "form copy" : null,
|
|
1862
|
+
input.fields !== void 0 ? `${input.fields.length} field(s)` : null,
|
|
1863
|
+
input.notifications !== void 0 ? `${input.notifications.length} notification channel(s)` : null,
|
|
1864
|
+
input.webhooks !== void 0 ? `${input.webhooks.length} webhook(s)` : null
|
|
1865
|
+
].filter((item) => typeof item === "string");
|
|
1866
|
+
const impact = `This will update the project contact form settings for "${input.projectId}": ${changes.join(", ") || "no explicit changes"}. Contact form fields affect what visitors submit publicly; notifications and webhooks may send future submissions to external systems and are subject to plan features and limits.`;
|
|
1867
|
+
if (input.confirm !== true) {
|
|
1868
|
+
return {
|
|
1869
|
+
status: "needs_confirmation",
|
|
1870
|
+
projectId: input.projectId,
|
|
1871
|
+
impact,
|
|
1872
|
+
draft
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
const contactForm = await updateRemoteContactForm(auth, input, fetcher);
|
|
1876
|
+
return {
|
|
1877
|
+
status: "updated",
|
|
1878
|
+
projectId: input.projectId,
|
|
1879
|
+
contactForm
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1646
1882
|
async function listAgentProjects() {
|
|
1647
1883
|
const auth = await requireCliBaseUrl();
|
|
1648
1884
|
const result = await fetchJson(
|
|
@@ -1696,7 +1932,7 @@ async function startMcpServer() {
|
|
|
1696
1932
|
server.registerTool(
|
|
1697
1933
|
"create_project",
|
|
1698
1934
|
{
|
|
1699
|
-
description: "Create a new Suda project for the current user. Only call this after list_projects returned an empty list. Conversation flow before calling: (1) Ask the user for the website / brand name in one turn and capture it as `name`. (2) Ask the user for a business introduction; the user is allowed (and expected) to answer over multiple turns \u2014 keep asking follow-up questions and accumulating their answers until they confirm they are done, then concatenate the accumulated text into a single `siteDescription` (trimmed). Do not invent details the user did not provide. Do not call this tool until both fields are confirmed by the user. The project is created with the default theme
|
|
1935
|
+
description: "Create a new Suda project for the current user. Only call this after list_projects returned an empty list. Conversation flow before calling: (1) Ask the user for the website / brand name in one turn and capture it as `name`. (2) Ask the user for a business introduction; the user is allowed (and expected) to answer over multiple turns \u2014 keep asking follow-up questions and accumulating their answers until they confirm they are done, then concatenate the accumulated text into a single `siteDescription` (trimmed). Do not invent details the user did not provide. Do not call this tool until both fields are confirmed by the user. The project is created with the default theme, starter pages, and a default project contact form. After creation, if the business context suggests better contact fields, call get_contact_form_settings, propose the contact form plan to the user, and only call update_contact_form_settings with confirm:true after they agree. No in-app AI site generation is triggered \u2014 drive page content afterwards via create_page_draft using the returned projectId.",
|
|
1700
1936
|
inputSchema: {
|
|
1701
1937
|
name: createProjectInputSchema.shape.name.describe(
|
|
1702
1938
|
"Website or brand name confirmed by the user in a dedicated turn. Used as the project name and site title."
|
|
@@ -1769,7 +2005,7 @@ async function startMcpServer() {
|
|
|
1769
2005
|
server.registerTool(
|
|
1770
2006
|
"get_page_schema",
|
|
1771
2007
|
{
|
|
1772
|
-
description: "Return the AI-first output schema for generating Suda page content, including all section schemas. Field schemas include dynamic visibility metadata for Suda visibleIf fields.",
|
|
2008
|
+
description: "Return the AI-first output schema for generating Suda page content, including all section schemas. Field schemas include dynamic visibility metadata for Suda visibleIf fields. If the theme has a contact section, generate only the section placement/content props; public form fields and delivery integrations are project contact form settings exposed to themes as metadata.contactForm. Use get_contact_form_settings and update_contact_form_settings for user-approved contact form changes.",
|
|
1773
2009
|
inputSchema: {
|
|
1774
2010
|
theme: z.string(),
|
|
1775
2011
|
version: z.string().optional(),
|
|
@@ -1790,7 +2026,7 @@ async function startMcpServer() {
|
|
|
1790
2026
|
server.registerTool(
|
|
1791
2027
|
"get_section_schema",
|
|
1792
2028
|
{
|
|
1793
|
-
description: "Return the AI-first output schema for one section component in a theme. Field schemas include dynamic visibility metadata for Suda visibleIf fields.",
|
|
2029
|
+
description: "Return the AI-first output schema for one section component in a theme. Field schemas include dynamic visibility metadata for Suda visibleIf fields. For contact sections, use the schema for presentation props only and do not hardcode form field metadata, notification channels, webhook URLs, or recipients into page content. Use contact form tools for project-level contact settings.",
|
|
1794
2030
|
inputSchema: {
|
|
1795
2031
|
theme: z.string(),
|
|
1796
2032
|
section: z.string(),
|
|
@@ -1827,6 +2063,48 @@ async function startMcpServer() {
|
|
|
1827
2063
|
return mcpStructured("Suda page content validation result.", structuredContent);
|
|
1828
2064
|
}
|
|
1829
2065
|
);
|
|
2066
|
+
server.registerTool(
|
|
2067
|
+
"get_contact_form_settings",
|
|
2068
|
+
{
|
|
2069
|
+
description: "Read project contact form settings. Use this before planning contact form changes. The result includes editable field metadata, masked notification/webhook URLs, and plan features/limits. Secrets are never returned.",
|
|
2070
|
+
inputSchema: {
|
|
2071
|
+
projectId: z.string()
|
|
2072
|
+
},
|
|
2073
|
+
outputSchema: contactFormOutputSchema.shape
|
|
2074
|
+
},
|
|
2075
|
+
async ({ projectId }) => {
|
|
2076
|
+
const auth = await requireCliBaseUrl();
|
|
2077
|
+
const structuredContent = contactFormOutputSchema.parse({
|
|
2078
|
+
contactForm: await getRemoteContactForm(auth, projectId)
|
|
2079
|
+
});
|
|
2080
|
+
return mcpStructured("Suda contact form settings.", structuredContent);
|
|
2081
|
+
}
|
|
2082
|
+
);
|
|
2083
|
+
server.registerTool(
|
|
2084
|
+
"update_contact_form_settings",
|
|
2085
|
+
{
|
|
2086
|
+
description: "Plan or update project contact form settings. Use this when the user asks to change contact form fields, labels, validation, notification channels, or webhooks. Supported field types are text, textarea, checkbox, radio, and select. Conversation flow: first call get_contact_form_settings, then propose the exact draft to the user. Call this tool without confirm or with confirm:false to return the impact summary; only call again with confirm:true after the user explicitly agrees. Do not invent webhook URLs, notification recipients, or external delivery secrets. When updating an existing webhook, omit url to keep the stored secret; new webhooks need a user-provided url.",
|
|
2087
|
+
inputSchema: updateContactFormInputSchema.shape,
|
|
2088
|
+
outputSchema: {
|
|
2089
|
+
status: z.enum(["needs_confirmation", "updated"]),
|
|
2090
|
+
projectId: z.string(),
|
|
2091
|
+
impact: z.string().optional(),
|
|
2092
|
+
draft: updateContactFormInputSchema.omit({ confirm: true }).optional(),
|
|
2093
|
+
contactForm: contactFormViewSchema.optional()
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
async (input) => {
|
|
2097
|
+
const parsed = updateContactFormInputSchema.parse(input);
|
|
2098
|
+
const auth = await requireCliBaseUrl();
|
|
2099
|
+
const structuredContent = updateContactFormOutputSchema.parse(
|
|
2100
|
+
await performUpdateContactForm(auth, parsed)
|
|
2101
|
+
);
|
|
2102
|
+
return mcpStructured(
|
|
2103
|
+
structuredContent.status === "needs_confirmation" ? "Updating this contact form requires explicit confirmation." : "Updated Suda contact form settings.",
|
|
2104
|
+
structuredContent
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
1830
2108
|
server.registerTool(
|
|
1831
2109
|
"create_page_draft",
|
|
1832
2110
|
{
|