webcake-storefront-mcp 1.9.0 → 1.11.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/dist/api.js +6 -0
- package/dist/builder/guide.js +22 -0
- package/dist/builder/page.js +10 -0
- package/dist/changelog.json +14 -14
- package/dist/tools/builder.js +119 -0
- package/dist/tools/site-style.js +75 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -76,6 +76,12 @@ export class WebcakeCmsApi {
|
|
|
76
76
|
createSite(params) {
|
|
77
77
|
return this.request("POST", `/api/v1/dashboard/site/create`, { body: params, timeout: 60000 });
|
|
78
78
|
}
|
|
79
|
+
/** Duplicate an existing site (its settings AND all pages/page-sources) into a NEW site
|
|
80
|
+
* owned by the current account. Used to spin up a site FROM a marketplace template
|
|
81
|
+
* (pass the template's source site id). Body: { site_id (source), name, slug? }. */
|
|
82
|
+
duplicateSite(params) {
|
|
83
|
+
return this.request("POST", `/api/v1/dashboard/site/duplicate`, { body: params, timeout: 90000 });
|
|
84
|
+
}
|
|
79
85
|
getSiteInfo() {
|
|
80
86
|
return this.request("GET", `/api/v1/site/${this.siteId}/`);
|
|
81
87
|
}
|
package/dist/builder/guide.js
CHANGED
|
@@ -53,6 +53,28 @@ already in this shape — see the \`responsive\` field on get_page_element/updat
|
|
|
53
53
|
- Colours: prefer the site THEME variables \`var(--color_00)\`, \`var(--color_01)\`, …
|
|
54
54
|
(the published site themes them); plain hex or rgba() also work.
|
|
55
55
|
|
|
56
|
+
## Make it look DESIGNED (not plain) — do this, every page
|
|
57
|
+
A bare stack of default elements looks unfinished. Apply real styling:
|
|
58
|
+
- SECTIONS: new_section already gives ~56px top/bottom padding + a centred content column.
|
|
59
|
+
Add variety: give some sections a background (\`section_opts:{ style:{ background:"var(--color_01)" } }\`
|
|
60
|
+
or a light tint) and alternate so the page has rhythm. Increase padding (72–96px) for hero/CTA.
|
|
61
|
+
- SPACING: children are stacked with a rowGap; bump it (\`section_opts:{ rowGap: 24 }\`) for airy
|
|
62
|
+
layouts. Don't cram.
|
|
63
|
+
- TYPOGRAPHY hierarchy: h1 40–56px / fontWeight 700, h2 28–34px / 600, body 16–18px with
|
|
64
|
+
lineHeight "1.6", muted color for sub-text. Center hero text (textAlign:"center").
|
|
65
|
+
- BUTTONS HAVE NO DEFAULT COLOUR — you MUST style them or they look like plain text:
|
|
66
|
+
\`{ type:"button", opts:{ text:"Mua ngay", style:{ background:"var(--color_02)", color:"#fff",
|
|
67
|
+
borderRadius:"8px", fontWeight:"600", height:48 } } }\`.
|
|
68
|
+
- HERO: prefer a section with a background image + an overlay heading/sub/button on top
|
|
69
|
+
(set section_opts.style.background or a full-width image, then text centered), rather than a
|
|
70
|
+
small image stacked above text.
|
|
71
|
+
- PRODUCT GRID: style the cards — \`grid-product opts.config\`: { columns:3-4, image_ratio:"1/1",
|
|
72
|
+
img_object_fit:"cover", gap_column:20, gap_row:28, cardBorderRadius:12, cardBoxShadow:"0 6px 24px rgba(0,0,0,.08)",
|
|
73
|
+
productNameColor:"var(--color_00)", productPriceColor:"var(--color_02)" }.
|
|
74
|
+
- BRAND COLOURS: reuse var(--color_00) (text), and an accent (var(--color_02)/var(--color_03))
|
|
75
|
+
for buttons, prices, highlights — consistent accent = looks intentional.
|
|
76
|
+
- IMAGES: must be WebCake-CDN urls (search_images cdn_url / upload_images), else they won't show.
|
|
77
|
+
|
|
56
78
|
## Responsive breakpoints
|
|
57
79
|
The four breakpoints (largest → smallest), keyed bp1..bp4, are:
|
|
58
80
|
- \`bp1\` ≥1320px (desktop, the base) · \`bp2\` 993–1319 (laptop) · \`bp3\` 641–992 (tablet) · \`bp4\` 320–640 (mobile).
|
package/dist/builder/page.js
CHANGED
|
@@ -63,6 +63,7 @@ export function stackChildren(container, children, opts = {}) {
|
|
|
63
63
|
grid: `${gridCols}x${children.length || 1}`,
|
|
64
64
|
columns: opts.columns || [{ unit: "fr", value: 1 }],
|
|
65
65
|
rows: rows.length ? rows : [{ unit: "min/max", min: { unit: "px", absValue: 50 }, max: { unit: "max-c" } }],
|
|
66
|
+
rowGap: opts.rowGap ?? 16, // breathing room between stacked children
|
|
66
67
|
heightUnit: "auto",
|
|
67
68
|
};
|
|
68
69
|
children.forEach((child, i) => {
|
|
@@ -96,7 +97,16 @@ export function buildSection(childSpecs = [], sectionOpts = {}) {
|
|
|
96
97
|
columns: genGridByBp(BREAKPOINTS.bp1[0]).columns,
|
|
97
98
|
contentColStart: SECTION_CONTENT_COL_START,
|
|
98
99
|
contentColEnd: SECTION_CONTENT_COL_END,
|
|
100
|
+
rowGap: sectionOpts.rowGap,
|
|
99
101
|
});
|
|
102
|
+
// Give sections vertical breathing room by default (a plain edge-to-edge stack looks
|
|
103
|
+
// unfinished). Caller can override via sectionOpts.style.
|
|
104
|
+
section.runtime = section.runtime || {};
|
|
105
|
+
section.runtime.style = {
|
|
106
|
+
paddingTop: 56,
|
|
107
|
+
paddingBottom: 56,
|
|
108
|
+
...(sectionOpts.style || {}),
|
|
109
|
+
};
|
|
100
110
|
return section;
|
|
101
111
|
}
|
|
102
112
|
function buildFromSpec(spec) {
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.11.0",
|
|
4
|
+
"d": "24/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New create_site_from_template tool clones a marketplace template (all its pages, page-sources, and settings) into a new account-owned site, switches…",
|
|
7
|
+
"vi": "Tool mới create_site_from_template nhân bản một template marketplace (toàn bộ trang, page-source và cài đặt) thành một site mới thuộc tài khoản hiện…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.10.0",
|
|
11
|
+
"d": "24/06/2026",
|
|
12
|
+
"type": "Added",
|
|
13
|
+
"en": "New scaffold_store_pages tool creates the standard storefront pages (Category, Product, Cart, Checkout, Thank-you) so navigation links resolve…",
|
|
14
|
+
"vi": "Tool mới scaffold_store_pages tạo các trang storefront tiêu chuẩn (Category, Product, Cart, Checkout, Thank-you) để các liên kết điều hướng không bị…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.9.0",
|
|
4
18
|
"d": "24/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "get_element now returns an attributes field with a curated per-element reference covering the meaningful specials, config, events, and bindings keys…",
|
|
28
42
|
"vi": "get_element nay trả về trường attributes chứa tài liệu tham chiếu theo từng loại element, bao gồm các key có ý nghĩa của specials, config, events và…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.5.1",
|
|
32
|
-
"d": "23/06/2026",
|
|
33
|
-
"type": "Fixed",
|
|
34
|
-
"en": "build_page now passes the finalized page source to create_page in the initial create call (required by the backend), then sets slug and is_homepage…",
|
|
35
|
-
"vi": "build_page nay truyền source trang đã hoàn thiện vào lời gọi create_page ban đầu (bắt buộc bởi backend), sau đó đặt slug và is_homepage qua một lời…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.5.0",
|
|
39
|
-
"d": "23/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "New create_product tool creates a storefront product (simple name + price, or advanced with named attributes and per-SKU variations); accepts hosted…",
|
|
42
|
-
"vi": "Tool mới create_product tạo sản phẩm cho storefront (đơn giản với tên + giá, hoặc nâng cao với attributes và variations theo từng SKU); nhận URL ảnh…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/tools/builder.js
CHANGED
|
@@ -165,4 +165,123 @@ Two-step safety: dry_run=true (default) previews; dry_run=false saves.`, {
|
|
|
165
165
|
page_source_id: saved && saved.data && saved.data.id,
|
|
166
166
|
};
|
|
167
167
|
}));
|
|
168
|
+
server.tool("scaffold_store_pages", `Create the standard STOREFRONT pages a shop needs so navigation works — clicking a
|
|
169
|
+
product/category/cart from any page lands on a real page instead of a 404.
|
|
170
|
+
Creates only the ones MISSING (matched by slug): Category (collections), Product (products),
|
|
171
|
+
Cart (cart), Checkout (checkout), Thank-you (complete) — all type 'store' (enables use_store),
|
|
172
|
+
plus optional member (login/register/profile) and blog (blog/post) pages.
|
|
173
|
+
Run this right after you create products/categories. dry_run=true (default) previews.`, {
|
|
174
|
+
include_member: z.boolean().default(false).describe("Also create login/register/profile (type member, use_member)"),
|
|
175
|
+
include_blog: z.boolean().default(false).describe("Also create blog list + post pages (type blog, use_blog)"),
|
|
176
|
+
dry_run: z.boolean().default(true).describe("Preview (true) or actually create the missing pages (false)"),
|
|
177
|
+
}, ({ include_member, include_blog, dry_run }) => handle(async () => {
|
|
178
|
+
const bind = (name, field) => ({
|
|
179
|
+
id: "BINDING" + Math.random().toString(36).slice(2, 8),
|
|
180
|
+
name,
|
|
181
|
+
target: `${name}::${field}`,
|
|
182
|
+
});
|
|
183
|
+
const h1 = (text) => ({ type: "text", opts: { text, specials: { tag: "h1" }, style: { fontSize: "32px", fontWeight: "700" } } });
|
|
184
|
+
const accentBtn = (text, type = "button") => ({ type, opts: { text, style: { background: "var(--color_02)", color: "#fff", borderRadius: "8px", height: 48, fontWeight: "600" } } });
|
|
185
|
+
// Each spec: { name, slug, kind, build() -> { sections } }
|
|
186
|
+
const SPECS = [
|
|
187
|
+
{ name: "Category Page", slug: "collections", kind: "store", flag: "use_store", build: () => ({ sections: [
|
|
188
|
+
buildSection([h1("Danh mục sản phẩm"), { type: "grid-product", opts: { config: { columns: 3, image_ratio: "1/1", gap_column: 24, gap_row: 32 } } }]),
|
|
189
|
+
] }) },
|
|
190
|
+
{ name: "Product Page", slug: "products", kind: "store", flag: "use_store", build: () => ({ sections: [
|
|
191
|
+
buildSection([
|
|
192
|
+
{ type: "product-gallery", opts: {} },
|
|
193
|
+
{ type: "text-dataset", opts: { bindings: [bind("product", "product_name")], style: { fontSize: "28px", fontWeight: "700" } } },
|
|
194
|
+
{ type: "text-dataset", opts: { bindings: [bind("product", "product_price")], style: { fontSize: "22px", fontWeight: "700", color: "var(--color_02)" } } },
|
|
195
|
+
{ type: "quantity-input", opts: {} },
|
|
196
|
+
accentBtn("Thêm vào giỏ"),
|
|
197
|
+
]),
|
|
198
|
+
] }) },
|
|
199
|
+
{ name: "Cart Page", slug: "cart", kind: "store", flag: "use_store", build: () => ({ sections: [
|
|
200
|
+
buildSection([h1("Giỏ hàng"), { type: "cart-items", opts: {} }, accentBtn("Tiến hành thanh toán")]),
|
|
201
|
+
] }) },
|
|
202
|
+
{ name: "Checkout Page", slug: "checkout", kind: "store", flag: "use_store", build: () => ({ sections: [
|
|
203
|
+
buildSection([
|
|
204
|
+
h1("Thanh toán"),
|
|
205
|
+
{ type: "form", opts: { specials: { type: "form_order" } }, children: [
|
|
206
|
+
{ type: "input", opts: { specials: { field_name: "full_name", label: "Họ tên", placeholder: "Họ tên", required: true, show_label: true } } },
|
|
207
|
+
{ type: "phone-number", opts: { specials: { field_name: "phone_number", label: "Số điện thoại", required: true, show_label: true } } },
|
|
208
|
+
{ type: "address", opts: { specials: { field_name: "address", label: "Địa chỉ", show_label: true } } },
|
|
209
|
+
accentBtn("Đặt hàng", "submit-button"),
|
|
210
|
+
] },
|
|
211
|
+
]),
|
|
212
|
+
] }) },
|
|
213
|
+
{ name: "Thank You Page", slug: "complete", kind: "store", flag: "use_store", build: () => ({ sections: [
|
|
214
|
+
buildSection([h1("Cảm ơn bạn đã đặt hàng!"), { type: "order-items", opts: {} }]),
|
|
215
|
+
] }) },
|
|
216
|
+
];
|
|
217
|
+
if (include_member) {
|
|
218
|
+
SPECS.push({ name: "Login Page", slug: "login", kind: "member", flag: "use_member", build: () => ({ sections: [
|
|
219
|
+
buildSection([h1("Đăng nhập"), { type: "form", opts: { specials: { type: "form_login" } }, children: [
|
|
220
|
+
{ type: "identity", opts: { specials: { field_name: "identity", label: "Email / SĐT", required: true, show_label: true } } },
|
|
221
|
+
{ type: "password", opts: { specials: { field_name: "password", label: "Mật khẩu", required: true, show_label: true } } },
|
|
222
|
+
accentBtn("Đăng nhập", "submit-button"),
|
|
223
|
+
] }]),
|
|
224
|
+
] }) }, { name: "Register Page", slug: "register", kind: "member", flag: "use_member", build: () => ({ sections: [
|
|
225
|
+
buildSection([h1("Đăng ký"), { type: "form", opts: { specials: { type: "form_signup" } }, children: [
|
|
226
|
+
{ type: "input", opts: { specials: { field_name: "full_name", label: "Họ tên", required: true, show_label: true } } },
|
|
227
|
+
{ type: "email", opts: { specials: { field_name: "email", label: "Email", required: true, show_label: true } } },
|
|
228
|
+
{ type: "password", opts: { specials: { field_name: "password", label: "Mật khẩu", required: true, show_label: true } } },
|
|
229
|
+
accentBtn("Tạo tài khoản", "submit-button"),
|
|
230
|
+
] }]),
|
|
231
|
+
] }) }, { name: "Profile Page", slug: "profile", kind: "member", flag: "use_member", build: () => ({ sections: [
|
|
232
|
+
buildSection([h1("Tài khoản"), { type: "order-history", opts: {} }, { type: "customer-address", opts: {} }]),
|
|
233
|
+
] }) });
|
|
234
|
+
}
|
|
235
|
+
if (include_blog) {
|
|
236
|
+
SPECS.push({ name: "Blog", slug: "blog", kind: "blog", flag: "use_blog", build: () => ({ sections: [buildSection([h1("Bài viết"), { type: "post-list", opts: {} }])] }) }, { name: "Post", slug: "post", kind: "blog", flag: "use_blog", build: () => ({ sections: [buildSection([{ type: "post-overlay", opts: {} }])] }) });
|
|
237
|
+
}
|
|
238
|
+
// What already exists?
|
|
239
|
+
const pagesRes = await api.listPages();
|
|
240
|
+
const pages = (pagesRes && pagesRes.data) || pagesRes || [];
|
|
241
|
+
const existingSlugs = new Set((Array.isArray(pages) ? pages : []).map((p) => (p.slug || "").replace(/^\//, "")));
|
|
242
|
+
const missing = SPECS.filter((s) => !existingSlugs.has(s.slug));
|
|
243
|
+
const skipped = SPECS.filter((s) => existingSlugs.has(s.slug)).map((s) => s.slug);
|
|
244
|
+
if (dry_run) {
|
|
245
|
+
return {
|
|
246
|
+
dry_run: true,
|
|
247
|
+
will_create: missing.map((s) => ({ name: s.name, slug: s.slug, type: s.kind })),
|
|
248
|
+
already_exist: skipped,
|
|
249
|
+
hint: "Call again with dry_run=false to create the missing pages so product/category/cart links resolve.",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const created = [];
|
|
253
|
+
const errors = [];
|
|
254
|
+
const flagsEnabled = new Set();
|
|
255
|
+
for (const spec of missing) {
|
|
256
|
+
try {
|
|
257
|
+
if (!flagsEnabled.has(spec.flag)) {
|
|
258
|
+
await api.enableSiteFeature(spec.flag).catch(() => { });
|
|
259
|
+
flagsEnabled.add(spec.flag);
|
|
260
|
+
}
|
|
261
|
+
const source = spec.build();
|
|
262
|
+
const validation = validatePage(source);
|
|
263
|
+
if (!validation.valid) {
|
|
264
|
+
errors.push({ slug: spec.slug, validation });
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
finalizeForRender(source);
|
|
268
|
+
const res = await api.createPage({ name: spec.name, source, type: PAGE_TYPE_NUM[spec.kind] });
|
|
269
|
+
const pid = newPageId(res);
|
|
270
|
+
if (pid)
|
|
271
|
+
await api.updatePage(pid, { slug: spec.slug }).catch(() => { });
|
|
272
|
+
created.push({ name: spec.name, slug: spec.slug, type: spec.kind, page_id: pid });
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
errors.push({ slug: spec.slug, error: e?.message ?? String(e) });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
created,
|
|
281
|
+
already_exist: skipped,
|
|
282
|
+
...(errors.length ? { errors } : {}),
|
|
283
|
+
data_sources_enabled: [...flagsEnabled],
|
|
284
|
+
note: "Publish the site (publish_site) to take the new pages live.",
|
|
285
|
+
};
|
|
286
|
+
}));
|
|
168
287
|
}
|
package/dist/tools/site-style.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { setConfig } from "../db.js";
|
|
3
|
+
import { resolvePreviewUrl } from "../config.js";
|
|
2
4
|
const TEMPLATE_THEMES_URL = "https://api.storecake.io/api/v1/templates/list_themes";
|
|
3
5
|
const THEME_CATALOG_TTL_MS = 60 * 60 * 1000;
|
|
4
6
|
const FETCH_TIMEOUT_MS = 15000;
|
|
@@ -50,6 +52,7 @@ async function getThemeCatalog(force = false) {
|
|
|
50
52
|
preview_url: t.preview_url || "",
|
|
51
53
|
thumbnail: t.thumbnail || "",
|
|
52
54
|
categories: (t.categories || []).map((c) => c.name).filter(Boolean),
|
|
55
|
+
site_id: (t.site && t.site.id) || undefined,
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
if (themes.length < limit)
|
|
@@ -143,6 +146,7 @@ export function registerSiteStyleTools(server, api, handle) {
|
|
|
143
146
|
const desc = parseThemeDescription(te && te.description);
|
|
144
147
|
return {
|
|
145
148
|
theme_id: themeId,
|
|
149
|
+
template_site_id: info.site_id || null, // pass to create_site_from_template
|
|
146
150
|
score: typeof score === "number" ? Number(score.toFixed(4)) : null,
|
|
147
151
|
name: info.name || null,
|
|
148
152
|
preview_url: info.preview_url || null,
|
|
@@ -152,6 +156,76 @@ export function registerSiteStyleTools(server, api, handle) {
|
|
|
152
156
|
description_en: desc.description_en,
|
|
153
157
|
};
|
|
154
158
|
});
|
|
155
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
query: q,
|
|
161
|
+
count: matches.length,
|
|
162
|
+
matches,
|
|
163
|
+
hint: "Pick one and call create_site_from_template with its theme_id (or template_site_id) to clone it into a new editable site.",
|
|
164
|
+
};
|
|
165
|
+
}));
|
|
166
|
+
server.tool("create_site_from_template", `Create a NEW site by CLONING a marketplace template (all its pages + settings), so the
|
|
167
|
+
customer starts from a finished design and then edits it. Resolve a template with
|
|
168
|
+
semantic_search_themes / list_template_themes first, then pass its theme_id (or
|
|
169
|
+
template_site_id). After cloning, switch to the new site and edit layout/content with
|
|
170
|
+
update_page_element(s), colours/typography with the site-style tools, and republish.`, {
|
|
171
|
+
name: z.string().describe("Name for the new site"),
|
|
172
|
+
theme_id: z.string().optional().describe("Marketplace theme id (from semantic_search_themes / list_template_themes)"),
|
|
173
|
+
template_site_id: z.string().optional().describe("The template's source site id (alternative to theme_id; semantic_search_themes returns it as template_site_id)"),
|
|
174
|
+
slug: z.string().optional().describe("URL-safe slug for the new site (auto-generated if omitted)"),
|
|
175
|
+
switch_to: z.boolean().default(true).describe("Switch the session to the new site after cloning (saved for next session)"),
|
|
176
|
+
}, ({ name, theme_id, template_site_id, slug, switch_to }) => handle(async () => {
|
|
177
|
+
// Resolve the template's source site id.
|
|
178
|
+
let sourceSiteId = template_site_id;
|
|
179
|
+
if (!sourceSiteId && theme_id) {
|
|
180
|
+
const catalog = await getThemeCatalog().catch(() => new Map());
|
|
181
|
+
sourceSiteId = catalog.get(theme_id)?.site_id;
|
|
182
|
+
}
|
|
183
|
+
if (!sourceSiteId) {
|
|
184
|
+
throw new Error("Could not resolve the template's source site. Pass template_site_id, or a theme_id that exists in the marketplace (list_template_themes / semantic_search_themes).");
|
|
185
|
+
}
|
|
186
|
+
// The duplicate endpoint returns success-only (no id), so snapshot the site list
|
|
187
|
+
// first, clone, then resolve the new site by diff.
|
|
188
|
+
const listIds = async () => {
|
|
189
|
+
const r = await api.listMySites({ page: 1, limit: 100 }).catch(() => null);
|
|
190
|
+
const arr = r?.data?.sites || r?.data || [];
|
|
191
|
+
return Array.isArray(arr) ? arr : [];
|
|
192
|
+
};
|
|
193
|
+
const before = await listIds();
|
|
194
|
+
const beforeIds = new Set(before.map((s) => s.id));
|
|
195
|
+
try {
|
|
196
|
+
await api.duplicateSite({ site_id: sourceSiteId, name, ...(slug ? { slug } : {}) });
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
200
|
+
if (msg.includes("403"))
|
|
201
|
+
throw new Error("Cannot create site: account site quota reached (free plan allows up to 4 sites).");
|
|
202
|
+
throw new Error(`Cloning the template failed: ${msg}`);
|
|
203
|
+
}
|
|
204
|
+
const after = await listIds();
|
|
205
|
+
const fresh = after.filter((s) => !beforeIds.has(s.id));
|
|
206
|
+
const newSite = fresh.find((s) => s.name === name) || fresh[0];
|
|
207
|
+
const newId = newSite?.id;
|
|
208
|
+
if (!newId)
|
|
209
|
+
throw new Error("Template was cloned but the new site could not be located in your site list — check list_my_sites.");
|
|
210
|
+
let switched = false;
|
|
211
|
+
let previewUrl = null;
|
|
212
|
+
const previousSiteId = api.siteId;
|
|
213
|
+
if (switch_to) {
|
|
214
|
+
api.switchSite(newId);
|
|
215
|
+
setConfig("site_id", newId);
|
|
216
|
+
setConfig("site_name", newSite?.name || name);
|
|
217
|
+
switched = true;
|
|
218
|
+
previewUrl = await resolvePreviewUrl(api).catch(() => null);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
site_id: newId,
|
|
223
|
+
name: newSite?.name || name,
|
|
224
|
+
from_template: { theme_id: theme_id || null, source_site_id: sourceSiteId },
|
|
225
|
+
switched,
|
|
226
|
+
...(switched ? { current_site_id: api.siteId, previous_site_id: previousSiteId } : {}),
|
|
227
|
+
preview_url: previewUrl,
|
|
228
|
+
next_step: "Site cloned with all template pages. Edit content with search_page_elements + update_page_element(s), change colours/fonts via list_themes/site-style, then publish_site.",
|
|
229
|
+
};
|
|
156
230
|
}));
|
|
157
231
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "MCP server for the WebCake/StoreCake storefront builder — page CRUD, page authoring, products, orders, and more",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-storefront-mcp",
|
|
6
6
|
"license": "MIT",
|