@sudajs/cli 0.10.1 → 0.10.2
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 +792 -0
- package/dist/index.js +215 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/theme/AGENTS.md +25 -0
package/dist/index.js
CHANGED
|
@@ -265,6 +265,123 @@ 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 contactNotificationUpdateSchema = z.object({
|
|
311
|
+
id: z.string().trim().min(1).max(48),
|
|
312
|
+
type: z.enum(["email", "slack", "wecom", "feishu", "dingtalk"]),
|
|
313
|
+
enabled: z.boolean().default(true),
|
|
314
|
+
label: z.string().trim().min(1).max(80),
|
|
315
|
+
recipients: z.array(z.string().trim().email()).default([]),
|
|
316
|
+
url: z.string().trim().url().optional()
|
|
317
|
+
});
|
|
318
|
+
var contactWebhookUpdateSchema = z.object({
|
|
319
|
+
id: z.string().trim().min(1).max(48),
|
|
320
|
+
enabled: z.boolean().default(true),
|
|
321
|
+
label: z.string().trim().min(1).max(80),
|
|
322
|
+
url: z.string().trim().url().optional()
|
|
323
|
+
});
|
|
324
|
+
var updateContactFormInputSchema = z.object({
|
|
325
|
+
projectId: z.string().min(1),
|
|
326
|
+
enabled: z.boolean().optional(),
|
|
327
|
+
title: z.string().trim().min(1).max(120).optional(),
|
|
328
|
+
successMessage: z.string().trim().min(1).max(240).optional(),
|
|
329
|
+
submitLabel: z.string().trim().min(1).max(80).optional(),
|
|
330
|
+
fields: contactFieldsSchema.optional(),
|
|
331
|
+
notifications: z.array(contactNotificationUpdateSchema).optional(),
|
|
332
|
+
webhooks: z.array(contactWebhookUpdateSchema).optional(),
|
|
333
|
+
confirm: z.boolean().optional()
|
|
334
|
+
});
|
|
335
|
+
var contactFormViewSchema = z.object({
|
|
336
|
+
enabled: z.boolean(),
|
|
337
|
+
title: z.string(),
|
|
338
|
+
successMessage: z.string(),
|
|
339
|
+
submitLabel: z.string(),
|
|
340
|
+
fields: z.array(contactFieldSchema),
|
|
341
|
+
notifications: z.array(
|
|
342
|
+
z.object({
|
|
343
|
+
id: z.string(),
|
|
344
|
+
type: z.enum(["email", "slack", "wecom", "feishu", "dingtalk"]),
|
|
345
|
+
enabled: z.boolean(),
|
|
346
|
+
label: z.string(),
|
|
347
|
+
recipients: z.array(z.string()),
|
|
348
|
+
urlMasked: z.string().nullable()
|
|
349
|
+
})
|
|
350
|
+
),
|
|
351
|
+
webhooks: z.array(
|
|
352
|
+
z.object({
|
|
353
|
+
id: z.string(),
|
|
354
|
+
enabled: z.boolean(),
|
|
355
|
+
label: z.string(),
|
|
356
|
+
urlMasked: z.string().nullable()
|
|
357
|
+
})
|
|
358
|
+
),
|
|
359
|
+
limits: z.object({
|
|
360
|
+
maxFields: z.number(),
|
|
361
|
+
maxNotificationChannels: z.number(),
|
|
362
|
+
maxWebhooks: z.number()
|
|
363
|
+
}),
|
|
364
|
+
features: z.object({
|
|
365
|
+
contactNotifications: z.boolean(),
|
|
366
|
+
contactWebhooks: z.boolean()
|
|
367
|
+
})
|
|
368
|
+
});
|
|
369
|
+
var contactFormOutputSchema = z.object({
|
|
370
|
+
contactForm: contactFormViewSchema
|
|
371
|
+
});
|
|
372
|
+
var updateContactFormOutputSchema = z.discriminatedUnion("status", [
|
|
373
|
+
z.object({
|
|
374
|
+
status: z.literal("needs_confirmation"),
|
|
375
|
+
projectId: z.string(),
|
|
376
|
+
impact: z.string(),
|
|
377
|
+
draft: updateContactFormInputSchema.omit({ confirm: true })
|
|
378
|
+
}),
|
|
379
|
+
z.object({
|
|
380
|
+
status: z.literal("updated"),
|
|
381
|
+
projectId: z.string(),
|
|
382
|
+
contactForm: contactFormViewSchema
|
|
383
|
+
})
|
|
384
|
+
]);
|
|
268
385
|
var PROJECT_NAME_MIN_LENGTH = 1;
|
|
269
386
|
var PROJECT_NAME_MAX_LENGTH = 80;
|
|
270
387
|
var PROJECT_DESCRIPTION_MIN_LENGTH = 1;
|
|
@@ -968,6 +1085,7 @@ var __testUtils = {
|
|
|
968
1085
|
initThemeWithKey,
|
|
969
1086
|
performActivateTheme,
|
|
970
1087
|
performConfirmedPageOperation,
|
|
1088
|
+
performUpdateContactForm,
|
|
971
1089
|
renderDevStarterPageHtml,
|
|
972
1090
|
resolveScreenshotOptions,
|
|
973
1091
|
slugifyThemeName,
|
|
@@ -1643,6 +1761,58 @@ async function performActivateTheme(auth, input, fetcher = fetchJson) {
|
|
|
1643
1761
|
themeVersion: response.themeVersion
|
|
1644
1762
|
});
|
|
1645
1763
|
}
|
|
1764
|
+
async function getRemoteContactForm(auth, projectId, fetcher = fetchJson) {
|
|
1765
|
+
const response = await fetcher(
|
|
1766
|
+
`${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(projectId)}/contact-form`,
|
|
1767
|
+
{ token: auth.token }
|
|
1768
|
+
);
|
|
1769
|
+
return contactFormViewSchema.parse(response.contactForm);
|
|
1770
|
+
}
|
|
1771
|
+
async function updateRemoteContactForm(auth, input, fetcher = fetchJson) {
|
|
1772
|
+
const body = {};
|
|
1773
|
+
if (input.enabled !== void 0) body.enabled = input.enabled;
|
|
1774
|
+
if (input.title !== void 0) body.title = input.title;
|
|
1775
|
+
if (input.successMessage !== void 0) body.successMessage = input.successMessage;
|
|
1776
|
+
if (input.submitLabel !== void 0) body.submitLabel = input.submitLabel;
|
|
1777
|
+
if (input.fields !== void 0) body.fields = input.fields;
|
|
1778
|
+
if (input.notifications !== void 0) body.notifications = input.notifications;
|
|
1779
|
+
if (input.webhooks !== void 0) body.webhooks = input.webhooks;
|
|
1780
|
+
const response = await fetcher(
|
|
1781
|
+
`${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(input.projectId)}/contact-form`,
|
|
1782
|
+
{
|
|
1783
|
+
method: "PATCH",
|
|
1784
|
+
token: auth.token,
|
|
1785
|
+
headers: { "Content-Type": "application/json" },
|
|
1786
|
+
body: JSON.stringify(body)
|
|
1787
|
+
}
|
|
1788
|
+
);
|
|
1789
|
+
return contactFormViewSchema.parse(response.contactForm);
|
|
1790
|
+
}
|
|
1791
|
+
async function performUpdateContactForm(auth, input, fetcher = fetchJson) {
|
|
1792
|
+
const draft = updateContactFormInputSchema.omit({ confirm: true }).parse(input);
|
|
1793
|
+
const changes = [
|
|
1794
|
+
input.enabled !== void 0 ? "enabled state" : null,
|
|
1795
|
+
input.title !== void 0 || input.successMessage !== void 0 || input.submitLabel !== void 0 ? "form copy" : null,
|
|
1796
|
+
input.fields !== void 0 ? `${input.fields.length} field(s)` : null,
|
|
1797
|
+
input.notifications !== void 0 ? `${input.notifications.length} notification channel(s)` : null,
|
|
1798
|
+
input.webhooks !== void 0 ? `${input.webhooks.length} webhook(s)` : null
|
|
1799
|
+
].filter((item) => typeof item === "string");
|
|
1800
|
+
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.`;
|
|
1801
|
+
if (input.confirm !== true) {
|
|
1802
|
+
return {
|
|
1803
|
+
status: "needs_confirmation",
|
|
1804
|
+
projectId: input.projectId,
|
|
1805
|
+
impact,
|
|
1806
|
+
draft
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
const contactForm = await updateRemoteContactForm(auth, input, fetcher);
|
|
1810
|
+
return {
|
|
1811
|
+
status: "updated",
|
|
1812
|
+
projectId: input.projectId,
|
|
1813
|
+
contactForm
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1646
1816
|
async function listAgentProjects() {
|
|
1647
1817
|
const auth = await requireCliBaseUrl();
|
|
1648
1818
|
const result = await fetchJson(
|
|
@@ -1696,7 +1866,7 @@ async function startMcpServer() {
|
|
|
1696
1866
|
server.registerTool(
|
|
1697
1867
|
"create_project",
|
|
1698
1868
|
{
|
|
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
|
|
1869
|
+
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
1870
|
inputSchema: {
|
|
1701
1871
|
name: createProjectInputSchema.shape.name.describe(
|
|
1702
1872
|
"Website or brand name confirmed by the user in a dedicated turn. Used as the project name and site title."
|
|
@@ -1769,7 +1939,7 @@ async function startMcpServer() {
|
|
|
1769
1939
|
server.registerTool(
|
|
1770
1940
|
"get_page_schema",
|
|
1771
1941
|
{
|
|
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.",
|
|
1942
|
+
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
1943
|
inputSchema: {
|
|
1774
1944
|
theme: z.string(),
|
|
1775
1945
|
version: z.string().optional(),
|
|
@@ -1790,7 +1960,7 @@ async function startMcpServer() {
|
|
|
1790
1960
|
server.registerTool(
|
|
1791
1961
|
"get_section_schema",
|
|
1792
1962
|
{
|
|
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.",
|
|
1963
|
+
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
1964
|
inputSchema: {
|
|
1795
1965
|
theme: z.string(),
|
|
1796
1966
|
section: z.string(),
|
|
@@ -1827,6 +1997,48 @@ async function startMcpServer() {
|
|
|
1827
1997
|
return mcpStructured("Suda page content validation result.", structuredContent);
|
|
1828
1998
|
}
|
|
1829
1999
|
);
|
|
2000
|
+
server.registerTool(
|
|
2001
|
+
"get_contact_form_settings",
|
|
2002
|
+
{
|
|
2003
|
+
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.",
|
|
2004
|
+
inputSchema: {
|
|
2005
|
+
projectId: z.string()
|
|
2006
|
+
},
|
|
2007
|
+
outputSchema: contactFormOutputSchema.shape
|
|
2008
|
+
},
|
|
2009
|
+
async ({ projectId }) => {
|
|
2010
|
+
const auth = await requireCliBaseUrl();
|
|
2011
|
+
const structuredContent = contactFormOutputSchema.parse({
|
|
2012
|
+
contactForm: await getRemoteContactForm(auth, projectId)
|
|
2013
|
+
});
|
|
2014
|
+
return mcpStructured("Suda contact form settings.", structuredContent);
|
|
2015
|
+
}
|
|
2016
|
+
);
|
|
2017
|
+
server.registerTool(
|
|
2018
|
+
"update_contact_form_settings",
|
|
2019
|
+
{
|
|
2020
|
+
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.",
|
|
2021
|
+
inputSchema: updateContactFormInputSchema.shape,
|
|
2022
|
+
outputSchema: {
|
|
2023
|
+
status: z.enum(["needs_confirmation", "updated"]),
|
|
2024
|
+
projectId: z.string(),
|
|
2025
|
+
impact: z.string().optional(),
|
|
2026
|
+
draft: updateContactFormInputSchema.omit({ confirm: true }).optional(),
|
|
2027
|
+
contactForm: contactFormViewSchema.optional()
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
async (input) => {
|
|
2031
|
+
const parsed = updateContactFormInputSchema.parse(input);
|
|
2032
|
+
const auth = await requireCliBaseUrl();
|
|
2033
|
+
const structuredContent = updateContactFormOutputSchema.parse(
|
|
2034
|
+
await performUpdateContactForm(auth, parsed)
|
|
2035
|
+
);
|
|
2036
|
+
return mcpStructured(
|
|
2037
|
+
structuredContent.status === "needs_confirmation" ? "Updating this contact form requires explicit confirmation." : "Updated Suda contact form settings.",
|
|
2038
|
+
structuredContent
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
);
|
|
1830
2042
|
server.registerTool(
|
|
1831
2043
|
"create_page_draft",
|
|
1832
2044
|
{
|