webcake-storefront-mcp 1.0.1

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 ADDED
@@ -0,0 +1,346 @@
1
+ const DEFAULT_TIMEOUT = 15000;
2
+ export class WebcakeCmsApi {
3
+ baseUrl;
4
+ token;
5
+ siteId;
6
+ sessionId;
7
+ _adminToken;
8
+ _cmsApiKey;
9
+ constructor({ baseUrl, token, siteId, sessionId }) {
10
+ this.baseUrl = (baseUrl || "").replace(/\/$/, "");
11
+ this.token = token;
12
+ this.siteId = siteId;
13
+ this.sessionId = sessionId || "";
14
+ this._adminToken = null;
15
+ this._cmsApiKey = null;
16
+ }
17
+ // CMS file / HTTP-function endpoints require an extra admin token + CMS API key
18
+ // bundled into the body. Fetched lazily and cached; reset on token/site switch.
19
+ async fetchCmsTokens() {
20
+ if (this._adminToken && this._cmsApiKey)
21
+ return;
22
+ const [adminRes, apiKeyRes] = await Promise.all([
23
+ this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/token`),
24
+ this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/api_key`),
25
+ ]);
26
+ this._adminToken = (adminRes && adminRes.data && adminRes.data.key) || null;
27
+ this._cmsApiKey = (apiKeyRes && apiKeyRes.data && apiKeyRes.data.key) || null;
28
+ }
29
+ getBundleParams() {
30
+ return { token: this._adminToken, x_cms_api_key: this._cmsApiKey };
31
+ }
32
+ async request(method, path, { body, query, timeout } = {}) {
33
+ const url = new URL(`${this.baseUrl}${path}`);
34
+ if (query) {
35
+ for (const [k, v] of Object.entries(query)) {
36
+ if (v != null)
37
+ url.searchParams.set(k, String(v));
38
+ }
39
+ }
40
+ const headers = {
41
+ "Content-Type": "application/json",
42
+ Authorization: `Bearer ${this.token}`,
43
+ ...(this.sessionId && { "x-session-id": this.sessionId }),
44
+ };
45
+ const controller = new AbortController();
46
+ const timer = setTimeout(() => controller.abort(), timeout || DEFAULT_TIMEOUT);
47
+ let res;
48
+ try {
49
+ res = await fetch(url, {
50
+ method,
51
+ headers,
52
+ body: body ? JSON.stringify(body) : undefined,
53
+ signal: controller.signal,
54
+ });
55
+ }
56
+ finally {
57
+ clearTimeout(timer);
58
+ }
59
+ const json = await res.json().catch(() => null);
60
+ if (!res.ok) {
61
+ const msg = (json && json.message) || (json && json.error) || res.statusText;
62
+ throw new Error(`API ${res.status}: ${msg}`);
63
+ }
64
+ return json;
65
+ }
66
+ // ── Account & Sites ──
67
+ getMe() {
68
+ return this.request("GET", `/api/v1/@me`);
69
+ }
70
+ listMySites(query) {
71
+ return this.request("GET", `/api/v1/dashboard/site/all`, { query });
72
+ }
73
+ getSiteInfo() {
74
+ return this.request("GET", `/api/v1/site/${this.siteId}/`);
75
+ }
76
+ /** Switch to a different site (in-memory, no restart needed) */
77
+ switchSite(siteId) {
78
+ this.siteId = siteId;
79
+ this._adminToken = null;
80
+ this._cmsApiKey = null;
81
+ }
82
+ /** Update auth token (in-memory) */
83
+ switchToken(token) {
84
+ this.token = token;
85
+ this._adminToken = null;
86
+ this._cmsApiKey = null;
87
+ }
88
+ /** Update session ID */
89
+ switchSession(sessionId) {
90
+ this.sessionId = sessionId;
91
+ }
92
+ // ── CMS Files ──
93
+ listCmsFiles() {
94
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/cms_files`);
95
+ }
96
+ async createCmsFile(params) {
97
+ await this.fetchCmsTokens();
98
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/cms_files`, {
99
+ body: { ...params, ...this.getBundleParams() },
100
+ });
101
+ }
102
+ async updateCmsFile(id, params) {
103
+ await this.fetchCmsTokens();
104
+ return this.request("PATCH", `/api/v1/dashboard/site/${this.siteId}/cms_files/${id}`, {
105
+ body: { ...params, ...this.getBundleParams() },
106
+ });
107
+ }
108
+ getHttpFunction() {
109
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/cms_files/http_function`);
110
+ }
111
+ async createOrUpdateHttpFunction(params) {
112
+ await this.fetchCmsTokens();
113
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/cms_files/http_function`, {
114
+ body: { ...params, ...this.getBundleParams() },
115
+ });
116
+ }
117
+ debugFunction(params) {
118
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/cms_files/debug`, { body: params });
119
+ }
120
+ runFunction(functionName, method, params) {
121
+ return this.request(method, `/api/v1/${this.siteId}/_functions/${functionName}`, { body: params });
122
+ }
123
+ saveFileVersion(params) {
124
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/cms_files/save_version_file`, { body: params });
125
+ }
126
+ getFileVersions(query) {
127
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/cms_files/version_file`, { query });
128
+ }
129
+ toggleDebugRender(params) {
130
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/cms_files/toggle_is_debug_render`, { body: params });
131
+ }
132
+ // ── Pages ──
133
+ listPages() {
134
+ return this.request("GET", `/api/v1/site/${this.siteId}/pages`);
135
+ }
136
+ createPage(params) {
137
+ return this.request("POST", `/api/v1/site/${this.siteId}/page`, { body: params });
138
+ }
139
+ updatePage(pageId, params) {
140
+ return this.request("POST", `/api/v1/site/${this.siteId}/${pageId}/update_page`, { body: params });
141
+ }
142
+ updatePageSource(pageId, params) {
143
+ return this.request("POST", `/api/v1/site/${this.siteId}/${pageId}/update_page_source`, { body: params });
144
+ }
145
+ deletePage(params) {
146
+ return this.request("POST", `/api/v1/site/${this.siteId}/delete_page`, { body: params });
147
+ }
148
+ getPageVersions(pageId) {
149
+ return this.request("GET", `/api/v1/site/${this.siteId}/page_versions/${pageId}`);
150
+ }
151
+ listPageContents(query) {
152
+ return this.request("GET", `/api/v1/site/${this.siteId}/page_contents`, { query });
153
+ }
154
+ updatePageContent(params) {
155
+ return this.request("POST", `/api/v1/site/${this.siteId}/page_contents/page_content`, { body: params });
156
+ }
157
+ listGlobalSections() {
158
+ return this.request("GET", `/api/v1/site/${this.siteId}/global_sections`);
159
+ }
160
+ getSite() {
161
+ return this.request("GET", `/api/v1/site/${this.siteId}/`);
162
+ }
163
+ saveSite(params = {}) {
164
+ return this.request("POST", `/api/v1/site/${this.siteId}/save`, { body: params, timeout: 60000 });
165
+ }
166
+ publishSite(params = {}) {
167
+ return this.request("POST", `/api/v1/site/${this.siteId}/publish`, { body: params, timeout: 60000 });
168
+ }
169
+ uploadImageBase64({ base64, content_type } = {}) {
170
+ return this.request("POST", `/api/v1/site/${this.siteId}/media/content/b64`, {
171
+ body: { base64, content_type },
172
+ timeout: 60000,
173
+ });
174
+ }
175
+ async getSiteSettingField(field) {
176
+ const siteRes = await this.request("GET", `/api/v1/site/${this.siteId}/`, { timeout: 60000 });
177
+ const raw = (siteRes && siteRes.data && siteRes.data.settings) || "";
178
+ if (typeof raw === "object")
179
+ return raw[field] || "";
180
+ const needle = `"${field}":`;
181
+ const idx = raw.indexOf(needle);
182
+ if (idx === -1)
183
+ return "";
184
+ let vStart = idx + needle.length;
185
+ while (vStart < raw.length && raw[vStart] === " ")
186
+ vStart++;
187
+ if (raw[vStart] !== '"')
188
+ return "";
189
+ let vEnd = vStart + 1;
190
+ while (vEnd < raw.length) {
191
+ if (raw[vEnd] === "\\") {
192
+ vEnd += 2;
193
+ continue;
194
+ }
195
+ if (raw[vEnd] === '"') {
196
+ vEnd++;
197
+ break;
198
+ }
199
+ vEnd++;
200
+ }
201
+ try {
202
+ return JSON.parse(raw.slice(vStart, vEnd));
203
+ }
204
+ catch {
205
+ return "";
206
+ }
207
+ }
208
+ async updateSiteSettings(newSettings) {
209
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/update_site`, { body: { settings: newSettings }, timeout: 60000 });
210
+ }
211
+ // ── Collections ──
212
+ listCollections(query) {
213
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections`, { query });
214
+ }
215
+ getCollection(id) {
216
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/${id}`);
217
+ }
218
+ queryCollectionRecords(tableName, query) {
219
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records`, { query });
220
+ }
221
+ // ── Blog Articles ──
222
+ listArticles(query) {
223
+ return this.request("GET", `/api/v1/cms_function/${this.siteId}/blog/article/all`, { query });
224
+ }
225
+ getArticle(id) {
226
+ return this.request("GET", `/api/v1/cms_function/${this.siteId}/blog/article/${id}`);
227
+ }
228
+ createArticle(params) {
229
+ return this.request("POST", `/api/v1/cms_function/${this.siteId}/blog/article`, { body: params });
230
+ }
231
+ updateArticle(id, params) {
232
+ return this.request("PATCH", `/api/v1/cms_function/${this.siteId}/blog/article/${id}`, { body: params });
233
+ }
234
+ deleteArticle(id) {
235
+ return this.request("DELETE", `/api/v1/cms_function/${this.siteId}/blog/article/${id}`);
236
+ }
237
+ // ── Products ──
238
+ listProducts(query) {
239
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/products/all`, { query });
240
+ }
241
+ getProduct(id) {
242
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/products/${id}`);
243
+ }
244
+ searchProducts(query) {
245
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/products/search`, { query });
246
+ }
247
+ listCategories() {
248
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/categories/all`);
249
+ }
250
+ getProductsByCategory(categoryId) {
251
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/categories/products`, { query: { category_id: categoryId } });
252
+ }
253
+ // ── Orders ──
254
+ listOrders(query) {
255
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/orders/all`, { query });
256
+ }
257
+ getOrder(orderId) {
258
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/orders/${orderId}`);
259
+ }
260
+ countOrdersByStatus() {
261
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/orders/count_by_status`);
262
+ }
263
+ // ── Site Style / Themes ──
264
+ listThemes() {
265
+ return this.request("GET", `/api/v1/site/${this.siteId}/themes`);
266
+ }
267
+ /**
268
+ * Semantic search over the theme marketplace embeddings (bge-m3, cosine similarity).
269
+ * Returns { results: [[theme_embedding_record, score], ...] }.
270
+ */
271
+ semanticSearchThemes(query) {
272
+ return this.request("POST", `/api/v1/ai/semantic_search`, { body: { query }, timeout: 45000 });
273
+ }
274
+ // ── Applications ──
275
+ listApps() {
276
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/applications/subcriptions/all`);
277
+ }
278
+ getApp(type) {
279
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/applications/subcriptions/get_app`, { query: { type } });
280
+ }
281
+ // ── Promotions ──
282
+ listPromotions(query) {
283
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/all`, { query });
284
+ }
285
+ getPromotion(id) {
286
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/get_promotion`, { query: { id } });
287
+ }
288
+ getPromotionItems(id, query) {
289
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/get_items`, { query: { id, ...query } });
290
+ }
291
+ getActivePromotions() {
292
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/get_promotions_actived`);
293
+ }
294
+ searchPromotions(query) {
295
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/get_promotions_advance`, { query });
296
+ }
297
+ // ── Combos ──
298
+ listCombos(query) {
299
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/combo_product/all`, { query });
300
+ }
301
+ getComboItems(comboProductId, query) {
302
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/combo_product/items`, { query: { combo_product_id: comboProductId, ...query } });
303
+ }
304
+ // ── Customers ──
305
+ findCustomerById(id) {
306
+ return this.request("GET", `/api/v1/cms_function/${this.siteId}/customer/identity/${id}`);
307
+ }
308
+ findCustomerByPhone(phone) {
309
+ return this.request("GET", `/api/v1/cms_function/${this.siteId}/customer/phone/${phone}`);
310
+ }
311
+ findCustomerByEmail(email) {
312
+ return this.request("GET", `/api/v1/cms_function/${this.siteId}/customer/email/${email}`);
313
+ }
314
+ // ── Global Sources (cart, popup, overview, etc.) ──
315
+ getSourceCart() {
316
+ return this.request("GET", `/api/v1/site/${this.siteId}/cart/get_source_cart`);
317
+ }
318
+ createSourceCart(params) {
319
+ return this.request("POST", `/api/v1/site/${this.siteId}/cart/create_source_cart`, { body: params });
320
+ }
321
+ updateSourceCart(params) {
322
+ return this.request("POST", `/api/v1/site/${this.siteId}/cart/update_source_cart`, { body: params });
323
+ }
324
+ getGlobalSources(query) {
325
+ return this.request("GET", `/api/v1/site/${this.siteId}/global_source/`, { query });
326
+ }
327
+ createGlobalSource(params) {
328
+ return this.request("POST", `/api/v1/site/${this.siteId}/global_source/create`, { body: params });
329
+ }
330
+ updateGlobalSource(params) {
331
+ return this.request("POST", `/api/v1/site/${this.siteId}/global_source/update`, { body: params });
332
+ }
333
+ deleteGlobalSource(params) {
334
+ return this.request("POST", `/api/v1/site/${this.siteId}/global_source/delete`, { body: params });
335
+ }
336
+ getGlobalSourceContents(query) {
337
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/multilingual/global_source_contents`, { query });
338
+ }
339
+ updateGlobalSourceContents(params) {
340
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/multilingual/update_global_source_contents`, { body: params });
341
+ }
342
+ // ── Automation ──
343
+ sendMail(params) {
344
+ return this.request("POST", `/api/v1/cms_function/${this.siteId}/application/automation/send_mail`, { body: params });
345
+ }
346
+ }
@@ -0,0 +1,87 @@
1
+ // Browser login: spins up a loopback server, opens the builder app's /mcp-connect
2
+ // page, and receives the user's token back on the local callback — then saves it to
3
+ // the local config db so the stdio server picks it up automatically.
4
+ import { createServer } from "node:http";
5
+ import { randomBytes } from "node:crypto";
6
+ import { spawn } from "node:child_process";
7
+ import { resolveSettings } from "../config.js";
8
+ import { setConfig } from "../db.js";
9
+ function parseArgs(argv) {
10
+ const opts = { open: true };
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const a = argv[i];
13
+ if (a === "--no-open")
14
+ opts.open = false;
15
+ else if (a === "--port")
16
+ opts.port = Number(argv[++i]);
17
+ else if (a === "--api-base" || a === "--api-url")
18
+ opts.apiUrl = argv[++i];
19
+ else if (a === "--app-base" || a === "--app-url")
20
+ opts.appUrl = argv[++i];
21
+ else if (a === "--site" || a === "--site-id")
22
+ opts.siteId = argv[++i];
23
+ }
24
+ return opts;
25
+ }
26
+ function openBrowser(url) {
27
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
28
+ try {
29
+ spawn(cmd, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
30
+ }
31
+ catch {
32
+ /* user can open the URL manually */
33
+ }
34
+ }
35
+ const SUCCESS_HTML = "<!doctype html><meta charset=utf-8><title>Connected</title><body style='font:16px system-ui;padding:3rem;text-align:center'><h2>✓ Connected to WebCake</h2><p>You can close this tab and return to your terminal.</p></body>";
36
+ function readQuery(url) {
37
+ const q = (url ?? "").indexOf("?");
38
+ return new URLSearchParams(q === -1 ? "" : (url ?? "").slice(q + 1));
39
+ }
40
+ export async function runLogin(argv) {
41
+ const opts = parseArgs(argv);
42
+ const settings = resolveSettings({ apiUrl: opts.apiUrl, appUrl: opts.appUrl });
43
+ const appUrl = settings.appUrl.replace(/\/$/, "");
44
+ const apiUrl = settings.apiUrl.replace(/\/$/, "");
45
+ const state = randomBytes(16).toString("hex");
46
+ await new Promise((resolve, reject) => {
47
+ const server = createServer((req, res) => {
48
+ const params = readQuery(req.url);
49
+ const token = params.get("token") || params.get("jwt");
50
+ const wsid = params.get("wsid") || params.get("session_id") || "";
51
+ const returnedState = params.get("state");
52
+ if (!token) {
53
+ res.writeHead(400, { "content-type": "text/html" }).end("<p>Missing token.</p>");
54
+ return;
55
+ }
56
+ if (returnedState && returnedState !== state) {
57
+ res.writeHead(400, { "content-type": "text/html" }).end("<p>State mismatch.</p>");
58
+ return;
59
+ }
60
+ setConfig("token", token);
61
+ if (wsid)
62
+ setConfig("session_id", wsid);
63
+ if (apiUrl)
64
+ setConfig("api_url", apiUrl);
65
+ if (opts.siteId)
66
+ setConfig("site_id", opts.siteId);
67
+ res.writeHead(200, { "content-type": "text/html" }).end(SUCCESS_HTML);
68
+ console.error(`\n✓ Connected. Token${wsid ? " + session" : ""} saved to local config (api ${apiUrl || "<unset>"}).`);
69
+ server.close();
70
+ resolve();
71
+ });
72
+ server.listen(opts.port ?? 0, "127.0.0.1", () => {
73
+ const addr = server.address();
74
+ const port = typeof addr === "object" && addr ? addr.port : opts.port;
75
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
76
+ const full = `${appUrl}/mcp-storefront?redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
77
+ console.error("Opening your browser to connect (log in to WebCake there if prompted):");
78
+ console.error(" " + full + "\n");
79
+ if (opts.open)
80
+ openBrowser(full);
81
+ });
82
+ setTimeout(() => {
83
+ server.close();
84
+ reject(new Error("login timed out after 180s."));
85
+ }, 180_000).unref();
86
+ });
87
+ }
@@ -0,0 +1,186 @@
1
+ // Catalog / registry layer over builder/factory.js.
2
+ //
3
+ // The factory functions are the source of truth for a valid BuilderX component node.
4
+ // Here we (1) build a type -> factory map automatically by probing every exported
5
+ // create* function, (2) attach curated, human-readable metadata (category + summary)
6
+ // so an AI agent can understand the palette, and (3) expose helpers the MCP tools use.
7
+ import * as F from "./factory.js";
8
+ // Probe every factory once with safe default opts to learn the type string it produces
9
+ // and whether it is a container (has a children array). Calling with {children:[]} is
10
+ // safe for all 132 factories (verified) — none throw.
11
+ const FACTORY_BY_TYPE = {};
12
+ const CONTAINER_TYPES = new Set();
13
+ const ALL_TYPES = [];
14
+ for (const [name, fn] of Object.entries(F)) {
15
+ if (typeof fn !== "function" || !name.startsWith("create"))
16
+ continue;
17
+ let node;
18
+ try {
19
+ node = fn({ children: [] });
20
+ }
21
+ catch {
22
+ continue;
23
+ }
24
+ if (!node || !node.type)
25
+ continue;
26
+ if (!FACTORY_BY_TYPE[node.type]) {
27
+ FACTORY_BY_TYPE[node.type] = fn;
28
+ ALL_TYPES.push(node.type);
29
+ if (Array.isArray(node.children))
30
+ CONTAINER_TYPES.add(node.type);
31
+ }
32
+ }
33
+ ALL_TYPES.sort();
34
+ // ── Curated categorisation ───────────────────────────────────────────────────
35
+ // Maps each known type to a category. Types not listed fall back to "other".
36
+ const CATEGORY_MEMBERS = {
37
+ layout: [
38
+ "section", "container", "row", "slide", "carousel", "swiper", "collapse",
39
+ "collapse-item", "collapse-content", "tabs", "custom-layout", "question-container",
40
+ "layout-dataset",
41
+ ],
42
+ content: [
43
+ "text", "text-dataset", "image", "image-dataset", "video", "video-dataset",
44
+ "rectangle", "rectangle-dataset", "line", "gallery", "list-ordered", "embed",
45
+ "googlemap", "breadcrumb", "tags", "countdown", "table", "agency", "calendar",
46
+ "calendar-content",
47
+ ],
48
+ button: [
49
+ "button", "submit-button", "paypal-button", "button-login-google",
50
+ "button-login-facebook", "current-location",
51
+ ],
52
+ form: [
53
+ "form", "input", "email", "phone-number", "retype-phone-number", "password",
54
+ "retype-password", "current-password", "text-area", "select", "group-select",
55
+ "checkbox", "checkbox-group", "checkbox-item", "radio", "radio-group", "address",
56
+ "detect-address", "postal-code", "country", "input-file", "input-number",
57
+ "input-date", "input-search", "input-product-note", "otp-input", "rating-input",
58
+ "two-point-range", "identity", "search-form", "search-droppable", "color-group",
59
+ "switch",
60
+ ],
61
+ commerce: [
62
+ "grid-product", "slider-product", "product-gallery", "product-image-carousel",
63
+ "cart-items", "cart-icon", "cart-droppable", "cart-items-empty", "quantity-input",
64
+ "attr", "payment", "delivery-method", "order-items", "order-history", "coupon",
65
+ "product-overlay", "product-review", "masonry-review", "empty-product-layout",
66
+ "bonus-items", "flash-sale", "promotions", "promotions-short", "grid-category",
67
+ "slider-category", "number-step", "warehouse", "warehouse-dataset",
68
+ "customer-address", "reward-point", "referral-code", "lucky-wheel", "tee-form",
69
+ "wishlist", "favorite-icon",
70
+ ],
71
+ navigation: [
72
+ "menu", "menu-item", "menu-anchor-item", "submenu", "menu-droppable", "member-bar",
73
+ "member-dropdown", "dropdown", "dropdown-content", "language-menu",
74
+ ],
75
+ blog: [
76
+ "post-list", "slider-post", "grid-blog", "slider-blog", "post-overlay",
77
+ "blog-overlay",
78
+ ],
79
+ marketing: [
80
+ "notify", "popup", "auto-number", "random-number", "user-point-log",
81
+ ],
82
+ course: [
83
+ "lesson-sidebar", "lesson-items", "next-lesson-droppable", "list-lesson-droppable",
84
+ ],
85
+ };
86
+ const CATEGORY_BY_TYPE = {};
87
+ for (const [cat, members] of Object.entries(CATEGORY_MEMBERS)) {
88
+ for (const t of members)
89
+ CATEGORY_BY_TYPE[t] = cat;
90
+ }
91
+ // Short one-line summaries for the most commonly authored types. Types without an
92
+ // entry get an auto summary. get_element returns a live skeleton anyway, so this only
93
+ // needs to help the agent pick the right type.
94
+ const SUMMARY = {
95
+ section: "Top-level band/row container. Every page is a stack of sections. Holds children in a CSS grid.",
96
+ container: "Generic grid/flex box to group elements and position them together.",
97
+ row: "Simple horizontal row wrapper.",
98
+ text: "Rich text block (headings, paragraphs). Content in specials.text, tag in specials.tag.",
99
+ "text-dataset": "Text bound to a dataset field (product name, price...). Uses bindings[].",
100
+ image: "Static image. URL goes in runtime.config.src.",
101
+ "image-dataset": "Image bound to a dataset field. Uses bindings[].",
102
+ video: "Video player. specials.src + specials.thumbnail.",
103
+ rectangle: "Coloured box / shape, useful as a background or divider.",
104
+ line: "Separator line.",
105
+ button: "Clickable button. Label in specials.text, actions in events[].",
106
+ "submit-button": "Form submit button.",
107
+ gallery: "Image gallery/lightbox. Images in specials.media[].",
108
+ carousel: "Auto/paginated carousel of slides.",
109
+ slide: "A single slide inside a carousel.",
110
+ form: "Form wrapper. Put input fields as children. specials.type selects form behaviour.",
111
+ input: "Text input field. specials.field_name, placeholder, label, required.",
112
+ email: "Email input field.",
113
+ "phone-number": "Phone number input field.",
114
+ "text-area": "Multi-line text input.",
115
+ select: "Dropdown select.",
116
+ checkbox: "Single checkbox.",
117
+ radio: "Radio button.",
118
+ address: "Address input.",
119
+ "grid-product": "Product grid (lists products from the store).",
120
+ "slider-product": "Product slider/carousel.",
121
+ "cart-items": "Shopping cart line items.",
122
+ "cart-icon": "Cart icon with item count.",
123
+ "quantity-input": "Quantity stepper for products.",
124
+ payment: "Payment methods block.",
125
+ "delivery-method": "Shipping/delivery options block.",
126
+ "order-items": "Order summary line items.",
127
+ coupon: "Coupon / discount code input.",
128
+ menu: "Navigation menu container.",
129
+ "menu-item": "A single navigation menu link.",
130
+ countdown: "Countdown timer.",
131
+ embed: "Raw HTML / iframe embed.",
132
+ googlemap: "Google Maps iframe embed.",
133
+ popup: "Modal/overlay popup container.",
134
+ notify: "Notification / alert banner.",
135
+ tabs: "Tabbed content container.",
136
+ collapse: "Accordion / collapsible panel.",
137
+ breadcrumb: "Breadcrumb navigation trail.",
138
+ };
139
+ function describeType(type) {
140
+ return {
141
+ type,
142
+ category: CATEGORY_BY_TYPE[type] || "other",
143
+ container: CONTAINER_TYPES.has(type),
144
+ summary: SUMMARY[type] || `BuilderX "${type}" element.`,
145
+ };
146
+ }
147
+ /** Build a fresh, structurally-valid node of the given type using the real factory. */
148
+ export function buildElement(type, opts = {}) {
149
+ const fn = FACTORY_BY_TYPE[type];
150
+ if (!fn) {
151
+ const err = new Error(`Unknown element type "${type}". Use list_elements to see valid types.`);
152
+ err.code = "UNKNOWN_TYPE";
153
+ throw err;
154
+ }
155
+ // Guarantee children-using factories never throw on a missing children array.
156
+ return fn({ children: [], ...opts });
157
+ }
158
+ export function isKnownType(type) {
159
+ return Boolean(FACTORY_BY_TYPE[type]);
160
+ }
161
+ export function listElements() {
162
+ const categories = {};
163
+ for (const type of ALL_TYPES) {
164
+ const d = describeType(type);
165
+ (categories[d.category] ||= []).push({
166
+ type: d.type,
167
+ container: d.container,
168
+ summary: d.summary,
169
+ });
170
+ }
171
+ return { total: ALL_TYPES.length, categories };
172
+ }
173
+ export function getElement(type) {
174
+ if (!isKnownType(type)) {
175
+ return {
176
+ error: `Unknown type "${type}"`,
177
+ valid_types: ALL_TYPES,
178
+ };
179
+ }
180
+ const d = describeType(type);
181
+ // A live skeleton straight from the factory = authoritative shape for this type.
182
+ const skeleton = buildElement(type, {});
183
+ return { ...d, skeleton };
184
+ }
185
+ export const ELEMENT_TYPES = ALL_TYPES;
186
+ export const CONTAINER_TYPE_SET = CONTAINER_TYPES;