webcake-landing-mcp 1.0.21 → 1.0.22

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 CHANGED
@@ -221,7 +221,8 @@ lands in logs). Any header that's missing falls back to the matching env var:
221
221
  | `x-webcake-env` | `WEBCAKE_ENV` | named environment (`local`/`staging`/`prod`) |
222
222
  | `x-webcake-org-id` | `WEBCAKE_ORG_ID` | default org |
223
223
  | `x-webcake-api-base` | `WEBCAKE_API_BASE` | overrides the env preset's API base |
224
- | `x-webcake-app-base` | `WEBCAKE_APP_BASE` | overrides the env preset's app base |
224
+ | `x-webcake-app-base` | `WEBCAKE_APP_BASE` | overrides the env preset's SPA base (login connect page) |
225
+ | `x-webcake-builder-base` | `WEBCAKE_BUILDER_BASE` | overrides the builder host used for editor/preview links |
225
226
 
226
227
  > The reference + generation tools (`get_generation_guide`, `list_elements`, `validate_page`, …) need **no
227
228
  > token** — only the persistence tools (`create_page`, `update_page`, …) use it. Without a JWT, those return
@@ -290,7 +291,8 @@ flow can also be done entirely in the SPA, no backend route needed.)
290
291
  | `WEBCAKE_API_BASE` | No* | Backend base URL, e.g. `http://localhost:5800`. Required to persist (or set `WEBCAKE_ENV`). |
291
292
  | `WEBCAKE_JWT` | No* | Account JWT (dashboard auth). Required to persist — expires, refresh when needed. |
292
293
  | `WEBCAKE_ORG_ID` | No | Default organization id for `create_page` (overridden by its `organization_id` arg). Omit → personal page. |
293
- | `WEBCAKE_APP_BASE` | No | Optional base used to build editor/preview URLs in the result. |
294
+ | `WEBCAKE_APP_BASE` | No | Optional SPA base used for the browser `login` connect page. |
295
+ | `WEBCAKE_BUILDER_BASE` | No | Optional builder host for the editor/preview links in the result. Defaults to the env preset, else derived from the API host (`api.x`→`builder.x`). |
294
296
  | `WEBCAKE_CONFIG_DIR` | No | Dir for the saved `auth.json` written by `login` (default `~/.webcake-landing-mcp`). |
295
297
 
296
298
  > \* `WEBCAKE_API_BASE` and `WEBCAKE_JWT` are only needed for the persistence tools. The reference and
@@ -304,11 +306,13 @@ flow can also be done entirely in the SPA, no backend route needed.)
304
306
  Instead of setting both base URLs by hand, pick a named environment — one source of
305
307
  truth for the API + app bases:
306
308
 
307
- | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
308
- |-------------------------|-------------------------------|-------------------------------|
309
- | `local` | `http://localhost:5800` | `http://localhost:5173` |
310
- | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
311
- | `prod` | `https://api.webcake.io` | `https://webcake.io` |
309
+ | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) | Builder base (`WEBCAKE_BUILDER_BASE`) |
310
+ |-------------------------|-------------------------------|-------------------------------|----------------------------------------|
311
+ | `local` | `http://localhost:5800` | `http://localhost:5173` | `http://builder.localhost:5800` |
312
+ | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` | `https://builder.staging.webcake.io` |
313
+ | `prod` | `https://api.webcake.io` | `https://webcake.io` | `https://builder.webcake.io` |
314
+
315
+ > The **editor/preview link** returned after `create_page`/`update_page` opens on the **builder host** (above), not the API or SPA base.
312
316
 
313
317
  ```bash
314
318
  npx -y webcake-landing-mcp login --env local # connect against your local SPA + API
package/README.vi.md CHANGED
@@ -221,7 +221,8 @@ Header nào thiếu sẽ fallback về biến env tương ứng:
221
221
  | `x-webcake-env` | `WEBCAKE_ENV` | môi trường có tên (`local`/`staging`/`prod`) |
222
222
  | `x-webcake-org-id` | `WEBCAKE_ORG_ID` | org mặc định |
223
223
  | `x-webcake-api-base` | `WEBCAKE_API_BASE` | ghi đè API base của preset |
224
- | `x-webcake-app-base` | `WEBCAKE_APP_BASE` | ghi đè app base của preset |
224
+ | `x-webcake-app-base` | `WEBCAKE_APP_BASE` | ghi đè SPA base của preset (trang connect khi `login`) |
225
+ | `x-webcake-builder-base` | `WEBCAKE_BUILDER_BASE` | ghi đè host builder dùng cho link editor/preview |
225
226
 
226
227
  > Tool tham chiếu + generation (`get_generation_guide`, `list_elements`, `validate_page`, …) **không cần
227
228
  > token** — chỉ tool lưu trữ (`create_page`, `update_page`, …) mới dùng. Không có JWT thì các tool đó trả
@@ -285,7 +286,8 @@ SPA cũng được, khỏi cần route backend.)
285
286
  | `WEBCAKE_API_BASE` | Không* | Base URL backend, ví dụ `http://localhost:5800`. Cần để lưu trang (hoặc đặt `WEBCAKE_ENV`). |
286
287
  | `WEBCAKE_JWT` | Không* | JWT tài khoản (auth dashboard). Cần để lưu trang — sẽ hết hạn, làm mới khi cần. |
287
288
  | `WEBCAKE_ORG_ID` | Không | Organization mặc định cho `create_page` (bị ghi đè bởi tham số `organization_id`). Bỏ trống → trang cá nhân. |
288
- | `WEBCAKE_APP_BASE` | Không | Base tuỳ chọn để dựng URL editor/preview trong kết quả. |
289
+ | `WEBCAKE_APP_BASE` | Không | SPA base tuỳ chọn dùng cho trang connect khi `login` qua trình duyệt. |
290
+ | `WEBCAKE_BUILDER_BASE` | Không | Host builder tuỳ chọn cho link editor/preview trong kết quả. Mặc định lấy theo preset môi trường, nếu không thì suy ra từ host API (`api.x`→`builder.x`). |
289
291
  | `WEBCAKE_CONFIG_DIR` | Không | Thư mục chứa `auth.json` do `login` ghi (mặc định `~/.webcake-landing-mcp`). |
290
292
 
291
293
  > \* `WEBCAKE_API_BASE` và `WEBCAKE_JWT` chỉ cần cho các tool lưu trữ. Các tool tham chiếu và kiểm tra
@@ -299,11 +301,13 @@ SPA cũng được, khỏi cần route backend.)
299
301
  Thay vì đặt thủ công cả hai base URL, hãy chọn một môi trường có tên — một nguồn sự thật duy nhất
300
302
  cho API + app base (mặc định là `prod`):
301
303
 
302
- | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) |
303
- |-------------------------|-------------------------------|-------------------------------|
304
- | `local` | `http://localhost:5800` | `http://localhost:5173` |
305
- | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` |
306
- | `prod` *(mặc định)* | `https://api.webcake.io` | `https://webcake.io` |
304
+ | `--env` / `WEBCAKE_ENV` | API base (`WEBCAKE_API_BASE`) | App base (`WEBCAKE_APP_BASE`) | Builder base (`WEBCAKE_BUILDER_BASE`) |
305
+ |-------------------------|-------------------------------|-------------------------------|----------------------------------------|
306
+ | `local` | `http://localhost:5800` | `http://localhost:5173` | `http://builder.localhost:5800` |
307
+ | `staging` | `https://api.staging.webcake.io` | `https://staging.webcake.io` | `https://builder.staging.webcake.io` |
308
+ | `prod` *(mặc định)* | `https://api.webcake.io` | `https://webcake.io` | `https://builder.webcake.io` |
309
+
310
+ > **Link editor/preview** trả về sau `create_page`/`update_page` mở trên **host builder** (bảng trên), không phải API hay SPA base.
307
311
 
308
312
  ```bash
309
313
  npx -y webcake-landing-mcp login --env local # đăng nhập vào SPA + API local
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "v": "1.0.22",
4
+ "d": "07/06/2026",
5
+ "type": "Added",
6
+ "en": "New WEBCAKE_BUILDER_BASE environment variable, x-webcake-builder-base HTTP header, and ?builder_base= query parameter set the page-builder host used…",
7
+ "vi": "Biến môi trường WEBCAKE_BUILDER_BASE mới, HTTP header x-webcake-builder-base, và tham số truy vấn ?builder_base= cho phép đặt host của page-builder…"
8
+ },
2
9
  {
3
10
  "v": "1.0.21",
4
11
  "d": "07/06/2026",
@@ -33,12 +40,5 @@
33
40
  "type": "Fixed",
34
41
  "en": "get_element and get_generation_guide no longer suggest https://picsum.photos as an alternative image placeholder; agents are now directed to use…",
35
42
  "vi": "get_element và get_generation_guide không còn gợi ý https://picsum.photos làm ảnh placeholder thay thế; agent nay được hướng dẫn chỉ dùng…"
36
- },
37
- {
38
- "v": "1.0.16",
39
- "d": "06/06/2026",
40
- "type": "Changed",
41
- "en": "The server icon (served at /favicon.svg and embedded in serverInfo.icons) has been refined to the official Webcake brand mark: a green-gradient…",
42
- "vi": "Icon server (phục vụ tại /favicon.svg và nhúng trong serverInfo.icons) đã được tinh chỉnh về đúng logo thương hiệu Webcake: ô bo góc gradient xanh…"
43
43
  }
44
44
  ]
package/dist/http.js CHANGED
@@ -45,6 +45,7 @@ const QUERY_AUTH = {
45
45
  api_base: "x-webcake-api-base",
46
46
  org_id: "x-webcake-org-id",
47
47
  app_base: "x-webcake-app-base",
48
+ builder_base: "x-webcake-builder-base",
48
49
  };
49
50
  function applyQueryAuth(req) {
50
51
  const q = (req.url ?? "").indexOf("?");
@@ -18,7 +18,9 @@
18
18
  * WEBCAKE_API_BASE e.g. http://localhost:5800 (required to call the backend)
19
19
  * WEBCAKE_JWT the account JWT (required to call the backend)
20
20
  * WEBCAKE_ORG_ID optional default organization id for create_page
21
- * WEBCAKE_APP_BASE optional base for editor/preview URLs in the result
21
+ * WEBCAKE_APP_BASE optional SPA base (used for the login connect page)
22
+ * WEBCAKE_BUILDER_BASE optional builder host for editor/preview URLs in the result
23
+ * (defaults to the env preset, else derived from the API host)
22
24
  * WEBCAKE_CONFIG_DIR optional dir for the saved auth.json (default ~/.webcake-landing-mcp)
23
25
  */
24
26
  import { homedir } from "node:os";
@@ -29,12 +31,14 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
29
31
  * base URLs. Selecting one (via the `--env` flag, WEBCAKE_ENV, the `x-webcake-env`
30
32
  * header, or `?env=` in the URL) fills in both bases so callers don't repeat them.
31
33
  * Explicit WEBCAKE_API_BASE / WEBCAKE_APP_BASE (or per-request overrides) win over
32
- * the preset. `apiBase` is the backend; `appBase` is the SPA (editor/preview/connect).
34
+ * the preset. `apiBase` is the backend; `appBase` is the SPA (login connect page);
35
+ * `builderBase` is the page builder host that serves the `/editor/v2` URL returned
36
+ * after create/update (a distinct host — NOT the API and NOT the SPA).
33
37
  */
34
38
  export const ENVIRONMENTS = {
35
- local: { apiBase: "http://localhost:5800", appBase: "http://localhost:5173" },
36
- staging: { apiBase: "https://api.staging.webcake.io", appBase: "https://staging.webcake.io" },
37
- prod: { apiBase: "https://api.webcake.io", appBase: "https://webcake.io" },
39
+ local: { apiBase: "http://localhost:5800", appBase: "http://localhost:5173", builderBase: "http://builder.localhost:5800" },
40
+ staging: { apiBase: "https://api.staging.webcake.io", appBase: "https://staging.webcake.io", builderBase: "https://builder.staging.webcake.io" },
41
+ prod: { apiBase: "https://api.webcake.io", appBase: "https://webcake.io", builderBase: "https://builder.webcake.io" },
38
42
  };
39
43
  export const ENV_NAMES = Object.keys(ENVIRONMENTS);
40
44
  /** True when `v` names a known environment (local|staging|prod). */
@@ -45,6 +49,23 @@ export function isEnvName(v) {
45
49
  export function resolveEnv(name) {
46
50
  return isEnvName(name) ? ENVIRONMENTS[name] : undefined;
47
51
  }
52
+ /**
53
+ * Derive the page-builder host from the API base when no preset / explicit value is
54
+ * given: `api.<domain>` → `builder.<domain>`, otherwise `builder.<host>` (so
55
+ * `http://localhost:5800` → `http://builder.localhost:5800`, matching the presets).
56
+ */
57
+ export function deriveBuilderBase(apiBase) {
58
+ if (!apiBase)
59
+ return undefined;
60
+ try {
61
+ const u = new URL(apiBase);
62
+ u.hostname = u.hostname.startsWith("api.") ? `builder.${u.hostname.slice(4)}` : `builder.${u.hostname}`;
63
+ return u.origin;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
48
69
  export function readConfig(overrides = {}) {
49
70
  const saved = readSavedConfig();
50
71
  // A named environment supplies default base URLs; explicit values still win.
@@ -58,12 +79,22 @@ export function readConfig(overrides = {}) {
58
79
  missing.push("WEBCAKE_JWT");
59
80
  if (missing.length)
60
81
  return { config: null, missing };
82
+ const cleanBase = base.replace(/\/+$/, "");
83
+ // The editor/preview URL lives on the builder host (e.g. builder.localhost:5800),
84
+ // not the API base (5800) nor the SPA (5173). Resolve it explicitly so the link
85
+ // returned to the user opens in the page builder.
86
+ const builderBase = (overrides.builderBase ??
87
+ process.env.WEBCAKE_BUILDER_BASE ??
88
+ preset?.builderBase ??
89
+ saved.builderBase ??
90
+ deriveBuilderBase(cleanBase))?.replace(/\/+$/, "");
61
91
  return {
62
92
  config: {
63
- base: base.replace(/\/+$/, ""),
93
+ base: cleanBase,
64
94
  jwt: jwt,
65
95
  orgId: overrides.orgId ?? process.env.WEBCAKE_ORG_ID ?? saved.orgId,
66
96
  appBase: (overrides.appBase ?? process.env.WEBCAKE_APP_BASE ?? preset?.appBase ?? saved.appBase)?.replace(/\/+$/, ""),
97
+ builderBase,
67
98
  },
68
99
  missing: [],
69
100
  };
@@ -79,7 +110,8 @@ function header(headers, name) {
79
110
  * x-webcake-org-id organization id
80
111
  * x-webcake-env named environment (local|staging|prod) for the base URLs
81
112
  * x-webcake-api-base backend base URL (overrides the env preset)
82
- * x-webcake-app-base editor/preview URL base (overrides the env preset)
113
+ * x-webcake-app-base SPA base used for the login connect page (overrides the preset)
114
+ * x-webcake-builder-base builder host for editor/preview URLs (overrides the preset)
83
115
  * Any header that is absent falls back to the corresponding env var in readConfig.
84
116
  */
85
117
  export function configFromHeaders(headers) {
@@ -90,6 +122,7 @@ export function configFromHeaders(headers) {
90
122
  jwt: header(headers, "x-webcake-jwt") ?? bearer,
91
123
  orgId: header(headers, "x-webcake-org-id"),
92
124
  appBase: header(headers, "x-webcake-app-base"),
125
+ builderBase: header(headers, "x-webcake-builder-base"),
93
126
  env: header(headers, "x-webcake-env"),
94
127
  };
95
128
  }
@@ -15,6 +15,33 @@ function authHeaders(config, orgId) {
15
15
  headers["x-org-id"] = `${org}`;
16
16
  return headers;
17
17
  }
18
+ /**
19
+ * Resolve the editor/preview link the backend returns onto the page-builder host
20
+ * (config.builderBase, e.g. builder.localhost:5800), NOT the API base. The backend
21
+ * may return either a path (`/editor/v2/<id>`) or an absolute URL on its own host
22
+ * (`http://localhost:5800/editor/v2/<id>`) — in both cases we keep only the
23
+ * path+query and re-root it on the builder host.
24
+ */
25
+ export function toEditorUrl(config, raw) {
26
+ if (!raw)
27
+ return raw;
28
+ const builder = config.builderBase ?? config.appBase;
29
+ if (!builder)
30
+ return raw;
31
+ let pathQuery = raw;
32
+ if (/^https?:\/\//i.test(raw)) {
33
+ try {
34
+ const u = new URL(raw);
35
+ pathQuery = u.pathname + u.search + u.hash;
36
+ }
37
+ catch {
38
+ /* not a parseable URL — use as-is */
39
+ }
40
+ }
41
+ if (!pathQuery.startsWith("/"))
42
+ pathQuery = `/${pathQuery}`;
43
+ return `${builder.replace(/\/+$/, "")}${pathQuery}`;
44
+ }
18
45
  /** Build (but do not send) the create request — used for dry-run previews. */
19
46
  export function buildRequest(config, name, source, orgId) {
20
47
  return {
@@ -88,7 +115,6 @@ export async function createPage(config, name, source, orgId) {
88
115
  const pageId = data?.page_id;
89
116
  const editorPath = data?.editor_url;
90
117
  const previewPath = data?.preview_url;
91
- const app = config.appBase;
92
118
  if (!res.ok || !pageId) {
93
119
  // The backend's failure envelope is { success:false, message } on 422; auth
94
120
  // plugs return plain-text 401/403 (json is null). Surface the real reason
@@ -105,8 +131,8 @@ export async function createPage(config, name, source, orgId) {
105
131
  ok: true,
106
132
  status: res.status,
107
133
  page_id: pageId,
108
- editor_url: app && editorPath ? `${app}${editorPath}` : editorPath,
109
- preview_url: app && previewPath ? `${app}${previewPath}` : previewPath,
134
+ editor_url: toEditorUrl(config, editorPath),
135
+ preview_url: toEditorUrl(config, previewPath),
110
136
  organization_id: (orgId ?? config.orgId) ?? null,
111
137
  raw: data,
112
138
  };
@@ -190,7 +216,6 @@ export async function updatePageSource(config, pageId, source) {
190
216
  }
191
217
  const data = json?.data ?? json;
192
218
  const pageIdOut = data?.page_id;
193
- const app = config.appBase;
194
219
  if (!res.ok || !pageIdOut) {
195
220
  const backendMsg = json?.message ?? json?.reason ?? (json ? undefined : text.slice(0, 200));
196
221
  return {
@@ -204,8 +229,8 @@ export async function updatePageSource(config, pageId, source) {
204
229
  ok: true,
205
230
  status: res.status,
206
231
  page_id: pageIdOut,
207
- editor_url: app && data?.editor_url ? `${app}${data.editor_url}` : data?.editor_url,
208
- preview_url: app && data?.preview_url ? `${app}${data.preview_url}` : data?.preview_url,
232
+ editor_url: toEditorUrl(config, data?.editor_url),
233
+ preview_url: toEditorUrl(config, data?.preview_url),
209
234
  organization_id: data?.organization_id ?? null,
210
235
  raw: data,
211
236
  };
package/dist/smoke.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { createElement, CONTAINER_TYPES, FIELD_TYPES, LIBRARY, ELEMENT_TYPES, ELEMENTS, } from "./domains/landing/elements/index.js";
6
6
  import { validatePage, pageSchema } from "./domains/landing/validate.js";
7
7
  import { readConfig, resolveEnv, ENV_NAMES } from "./persistence/config.js";
8
+ import { toEditorUrl } from "./persistence/webcake-client.js";
8
9
  let failures = 0;
9
10
  const check = (name, cond, extra) => {
10
11
  if (cond) {
@@ -212,7 +213,7 @@ check("clean form has no binding warnings", rbg.warnings.length === 0, rbg.warni
212
213
  console.log("== config: named environment presets (local/staging/prod) ==");
213
214
  {
214
215
  // Deterministic: isolate from any ambient WEBCAKE_* and the saved auth.json on the dev box.
215
- for (const k of ["WEBCAKE_API_BASE", "WEBCAKE_APP_BASE", "WEBCAKE_ENV", "WEBCAKE_JWT", "WEBCAKE_ORG_ID"])
216
+ for (const k of ["WEBCAKE_API_BASE", "WEBCAKE_APP_BASE", "WEBCAKE_BUILDER_BASE", "WEBCAKE_ENV", "WEBCAKE_JWT", "WEBCAKE_ORG_ID"])
216
217
  delete process.env[k];
217
218
  process.env.WEBCAKE_CONFIG_DIR = "/nonexistent/webcake-smoke";
218
219
  check("env names are local/staging/prod", setEq(new Set(ENV_NAMES), ["local", "staging", "prod"]), ENV_NAMES);
@@ -224,6 +225,20 @@ console.log("== config: named environment presets (local/staging/prod) ==");
224
225
  check("readConfig(env=local) fills api+app base", local?.base === "http://localhost:5800" && local?.appBase === "http://localhost:5173", local);
225
226
  check("explicit base overrides the preset", readConfig({ env: "prod", base: "http://x:1", jwt: "t" }).config?.base === "http://x:1");
226
227
  check("unknown env leaves base missing", readConfig({ env: "bogus", jwt: "t" }).missing.includes("WEBCAKE_API_BASE"));
228
+ // builder host for editor/preview URLs (distinct from the api + app bases)
229
+ check("env presets carry builder bases", resolveEnv("prod")?.builderBase === "https://builder.webcake.io" && resolveEnv("local")?.builderBase === "http://builder.localhost:5800");
230
+ check("readConfig(env=prod) sets builderBase", prod?.builderBase === "https://builder.webcake.io", prod);
231
+ check("readConfig(env=local) sets builderBase", local?.builderBase === "http://builder.localhost:5800", local);
232
+ check("readConfig(env=staging) sets builderBase", readConfig({ env: "staging", jwt: "t" }).config?.builderBase === "https://builder.staging.webcake.io");
233
+ check("builderBase derives from a custom api base (api. → builder.)", readConfig({ base: "https://api.example.com", jwt: "t" }).config?.builderBase === "https://builder.example.com");
234
+ check("builderBase derives from a localhost api base", readConfig({ base: "http://localhost:5800", jwt: "t" }).config?.builderBase === "http://builder.localhost:5800");
235
+ check("explicit builderBase overrides the preset", readConfig({ env: "prod", builderBase: "https://b.test", jwt: "t" }).config?.builderBase === "https://b.test");
236
+ // editor/preview link is re-rooted on the builder host, whether the backend
237
+ // returns a path or an absolute URL on its own host.
238
+ const localCfg = readConfig({ env: "local", jwt: "t" }).config;
239
+ check("editor url from a path → builder host", toEditorUrl(localCfg, "/editor/v2/abc") === "http://builder.localhost:5800/editor/v2/abc");
240
+ check("editor url from an absolute api url → builder host", toEditorUrl(localCfg, "http://localhost:5800/editor/v2/abc?x=1") === "http://builder.localhost:5800/editor/v2/abc?x=1");
241
+ check("editor url passthrough when empty", toEditorUrl(localCfg, undefined) === undefined);
227
242
  }
228
243
  console.log(`\n${failures === 0 ? "ALL GOOD" : failures + " FAILURE(S)"}`);
229
244
  process.exit(failures === 0 ? 0 : 1);
package/dist/web-guide.js CHANGED
@@ -58,6 +58,9 @@ const ICONS = {
58
58
  arrow: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
59
59
  clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
60
60
  globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
61
+ bulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
62
+ server: '<rect width="20" height="8" x="2" y="2" rx="2"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 6h.01"/><path d="M6 18h.01"/>',
63
+ window: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 9h20"/><path d="M6 6.5h.01"/><path d="M9 6.5h.01"/>',
61
64
  };
62
65
  function icon(name) {
63
66
  return `<svg class="i" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[name] ?? ""}</svg>`;
@@ -136,6 +139,14 @@ const T = {
136
139
  leadPost: " của bạn. Không kéo thả, không cài server — kết nối một lần là xong.",
137
140
  ctaStart: "Bắt đầu kết nối",
138
141
  ctaStar: "Star trên GitHub",
142
+ flowH2: "Mô hình hoạt động",
143
+ flow: [
144
+ { icon: "bulb", t: "Bạn", s: "ý tưởng" },
145
+ { icon: "brain", t: "Trợ lý AI", s: "Claude · Cursor" },
146
+ { icon: "server", t: "MCP", s: "webcake-landing" },
147
+ { icon: "window", t: "WebCake", s: "trang thật" },
148
+ ],
149
+ flowCap: "Bạn mô tả bằng lời → AI học mô hình thật từ MCP → MCP dựng JSON + kiểm tra → lưu thành trang thật trên WebCake. Bạn nhận link, mở editor, publish.",
139
150
  howH2: "Cách hoạt động",
140
151
  how: [
141
152
  {
@@ -206,6 +217,14 @@ const T = {
206
217
  leadPost: ". No drag-and-drop, no server to host — connect once and you're set.",
207
218
  ctaStart: "Get connected",
208
219
  ctaStar: "Star on GitHub",
220
+ flowH2: "How it flows",
221
+ flow: [
222
+ { icon: "bulb", t: "You", s: "your idea" },
223
+ { icon: "brain", t: "AI assistant", s: "Claude · Cursor" },
224
+ { icon: "server", t: "MCP", s: "webcake-landing" },
225
+ { icon: "window", t: "WebCake", s: "a real page" },
226
+ ],
227
+ flowCap: "You describe it in words → the AI learns the real model from the MCP → the MCP builds the JSON + validates → it's saved as a real WebCake page. You get a link, open the editor, publish.",
209
228
  howH2: "How it works",
210
229
  how: [
211
230
  {
@@ -440,6 +459,27 @@ export function guideHtml(origin, lang = "vi") {
440
459
  .feat li{display:flex;gap:13px;align-items:center;font-size:.97rem;padding:13px 16px}
441
460
  .feat li b{color:var(--ink)}
442
461
  .cta-row{display:flex;gap:12px;flex-wrap:wrap;margin:22px 0 6px}
462
+ /* Flow diagram: nodes connected by wires with a traveling "packet" */
463
+ .flow{display:flex;align-items:flex-start;gap:0;padding:24px 18px 18px;overflow-x:auto}
464
+ .flow .node{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;width:104px}
465
+ .flow .node .ic{width:54px;height:54px;border-radius:16px}
466
+ .flow .node .ic .i{width:27px;height:27px}
467
+ .flow .node b{font-size:.93rem}
468
+ .flow .node span{font-size:.75rem;color:var(--mut)}
469
+ .flow .wire{flex:1 1 auto;min-width:30px;position:relative;height:2px;margin-top:27px;
470
+ background:linear-gradient(90deg,var(--line),rgba(29,185,84,.45),var(--line))}
471
+ .flow .wire .pkt{position:absolute;top:50%;left:0;width:9px;height:9px;margin:-5px 0 0 -4px;border-radius:50%;
472
+ background:var(--g);box-shadow:0 0 9px 1px rgba(29,185,84,.7)}
473
+ .flow .wire::after{content:"";position:absolute;right:-1px;top:50%;width:7px;height:7px;margin-top:-4px;
474
+ border-top:2px solid var(--g7);border-right:2px solid var(--g7);transform:rotate(45deg)}
475
+ .flow-cap{color:var(--mut);font-size:.9rem;margin:2px 2px 0;max-width:68ch}
476
+ @media(prefers-reduced-motion:no-preference){
477
+ .flow .wire .pkt{animation:pkt 2.4s ease-in-out infinite}
478
+ @keyframes pkt{0%{left:0;opacity:0}12%{opacity:1}88%{opacity:1}100%{left:100%;opacity:0}}
479
+ .flow .node .ic{animation:nodepop 2.4s ease-in-out infinite}
480
+ }
481
+ @media(prefers-reduced-motion:reduce){.flow .wire .pkt{display:none}}
482
+ @keyframes nodepop{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 4px rgba(29,185,84,.12)}}
443
483
  .btn{display:inline-flex;align-items:center;gap:9px;padding:11px 19px;border-radius:11px;cursor:pointer;
444
484
  background:var(--g);color:#fff;text-decoration:none;font-weight:700;font-size:.93rem;
445
485
  box-shadow:0 4px 12px -4px rgba(29,185,84,.5);transition:transform .15s ease,background .15s ease}
@@ -540,6 +580,17 @@ export function guideHtml(origin, lang = "vi") {
540
580
  <a class="btn ghost" href="${GITHUB_URL}">${icon("star")} ${t.ctaStar}</a>
541
581
  </div>
542
582
 
583
+ <h2 class="reveal">${t.flowH2}</h2>
584
+ <div class="glass flow reveal">
585
+ ${t.flow
586
+ .map((n, i) => `<div class="node"><span class="ic" style="animation-delay:${(i * 0.8).toFixed(1)}s">${icon(n.icon)}</span><b>${n.t}</b><span>${n.s}</span></div>` +
587
+ (i < t.flow.length - 1
588
+ ? `<div class="wire"><i class="pkt" style="animation-delay:${(i * 0.8).toFixed(1)}s"></i></div>`
589
+ : ""))
590
+ .join("\n ")}
591
+ </div>
592
+ <p class="flow-cap reveal">${t.flowCap}</p>
593
+
543
594
  <h2 class="reveal">${t.howH2}</h2>
544
595
  <div class="grid">
545
596
  ${t.how
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
5
5
  "type": "module",
6
6
  "bin": {