webcake-storefront-mcp 1.1.2 → 1.1.4

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.
@@ -0,0 +1,44 @@
1
+ [
2
+ {
3
+ "v": "1.1.4",
4
+ "d": "23/06/2026",
5
+ "type": "Added",
6
+ "en": "The remote landing page served by the serve command now includes a \"What's new\" section that renders a version timeline loaded from a build-time…",
7
+ "vi": "Trang landing của lệnh serve nay có thêm mục \"Có gì mới\" hiển thị timeline lịch sử phiên bản được tải từ file changelog.json sinh ra lúc build (từ…"
8
+ },
9
+ {
10
+ "v": "1.1.3",
11
+ "d": "23/06/2026",
12
+ "type": "Changed",
13
+ "en": "The install command's interactive wizard now runs a three-step flow: environment selection (prod / staging / local), authentication, then IDE…",
14
+ "vi": "Trình hướng dẫn tương tác của lệnh install nay chạy theo 3 bước: chọn môi trường (prod / staging / local), xác thực, rồi cấu hình IDE."
15
+ },
16
+ {
17
+ "v": "1.1.2",
18
+ "d": "23/06/2026",
19
+ "type": "Fixed",
20
+ "en": "The server no longer crashes at startup in container environments built with npm ci --ignore-scripts; the better-sqlite3 native SQLite module has…",
21
+ "vi": "Server không còn bị crash khi khởi động trong môi trường container được build bằng npm ci --ignore-scripts; module SQLite native better-sqlite3 đã…"
22
+ },
23
+ {
24
+ "v": "1.1.1",
25
+ "d": "23/06/2026",
26
+ "type": "Added",
27
+ "en": "The serve command's OAuth token store now optionally uses Postgres (via DATABASE_URL) for durable persistence across restarts and shared state…",
28
+ "vi": "Kho lưu trữ token OAuth của lệnh serve nay hỗ trợ tùy chọn sử dụng Postgres (qua DATABASE_URL) để lưu token bền vững qua các lần khởi động lại và…"
29
+ },
30
+ {
31
+ "v": "1.1.0",
32
+ "d": "23/06/2026",
33
+ "type": "Added",
34
+ "en": "The serve (remote Streamable-HTTP) mode now embeds a full OAuth 2.1 Authorization Server at /authorize, /token, /revoke, /register, and…",
35
+ "vi": "Chế độ serve (remote Streamable-HTTP) nay tích hợp sẵn một Authorization Server OAuth 2.1 đầy đủ tại các endpoint /authorize, /token, /revoke,…"
36
+ },
37
+ {
38
+ "v": "1.0.3",
39
+ "d": "23/06/2026",
40
+ "type": "Added",
41
+ "en": "MIT license (Copyright vuluu2k) added to the package.",
42
+ "vi": "Thêm giấy phép MIT (Copyright vuluu2k) vào package."
43
+ }
44
+ ]
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ Usage: npx -y webcake-storefront-mcp [command] [options]
8
8
 
9
9
  Commands:
10
10
  (none) start the stdio MCP server (use this in IDE configs)
11
- install configure the server in your IDE(s) — interactive or via flags
11
+ install configure the server in your IDE(s) — interactive (env + browser login/token) or via flags
12
12
  uninstall remove the server from your IDE configs
13
13
  login grab your token via the browser (saved to the local config db)
14
14
  serve [--port N] run the remote Streamable-HTTP server (default 8787; or PORT env)
package/dist/install.js CHANGED
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { createInterface } from "node:readline/promises";
8
+ import { runLogin } from "./auth/login.js";
8
9
  const SERVER_KEY = "webcake-storefront";
9
10
  function parseArgs(argv) {
10
11
  const o = { uninstall: false, ides: [] };
@@ -100,34 +101,72 @@ function applyToIde(ide, opts, launch, env) {
100
101
  writeJson(cfg.path, json);
101
102
  return `${ide}: configured (${cfg.path})`;
102
103
  }
103
- async function promptMissing(opts) {
104
- if (!process.stdin.isTTY)
105
- return;
104
+ /**
105
+ * Interactive wizard (TTY only): pick environment → authenticate (browser login,
106
+ * recommended, or paste a token) → pick IDEs. Mirrors webcake-landing-mcp's
107
+ * installer. Returns whether a browser login completed (token then lives in the
108
+ * local config db, so it is NOT written into the IDE env block).
109
+ */
110
+ async function promptInteractive(opts) {
111
+ let loggedIn = false;
112
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
113
+ return { loggedIn };
106
114
  const rl = createInterface({ input: process.stdin, output: process.stderr });
107
115
  try {
108
- if (!opts.ides.length) {
109
- const ans = (await rl.question(`IDE(s) [${ALL_IDES.join(", ")}, all]: `)).trim();
110
- opts.ides = ans === "all" || ans === "" ? ALL_IDES : ans.split(",").map((s) => s.trim());
116
+ // 1) Environment — drives which API/app URLs are used (default prod).
117
+ if (!opts.uninstall && !opts.env && !process.env.WEBCAKE_ENV) {
118
+ const e = (await rl.question("Environment [prod] / staging / local: ")).trim().toLowerCase();
119
+ if (e === "staging" || e === "local")
120
+ opts.env = e;
111
121
  }
122
+ if (opts.env)
123
+ process.env.WEBCAKE_ENV = opts.env; // so the login flow opens the right app
124
+ // 2) Authentication — browser login (recommended) or paste a token. An explicit
125
+ // --token/--jwt or an ambient WEBCAKE_TOKEN skips this entirely.
112
126
  if (!opts.uninstall && !opts.token && !process.env.WEBCAKE_TOKEN) {
113
- const t = (await rl.question("Token (paste JWT, or leave blank to set later): ")).trim();
114
- if (t)
115
- opts.token = t;
127
+ console.error("\nHow do you want to connect your WebCake account?");
128
+ console.error(" 1) Log in via browser (recommended — opens WebCake, saves a token)");
129
+ console.error(" 2) Paste a token manually");
130
+ const choice = (await rl.question("Choose [1]: ")).trim() || "1";
131
+ if (choice === "1") {
132
+ try {
133
+ rl.pause();
134
+ await runLogin([]); // loopback browser flow → saves token + session to local config db
135
+ loggedIn = true;
136
+ }
137
+ catch (e) {
138
+ console.error(`Login didn't complete (${e?.message ?? e}). Paste a token now, or run \`npx -y webcake-storefront-mcp login\` later.`);
139
+ }
140
+ }
141
+ if (!loggedIn && choice !== "1") {
142
+ const t = (await rl.question(" Token (paste JWT): ")).trim();
143
+ if (t)
144
+ opts.token = t;
145
+ const s = (await rl.question(" Session id (x-session-id): ")).trim();
146
+ if (s)
147
+ opts.sessionId = s;
148
+ }
116
149
  }
117
- if (!opts.uninstall && !opts.sessionId && !process.env.WEBCAKE_SESSION_ID) {
118
- const s = (await rl.question("Session id (x-session-id, or leave blank to set later): ")).trim();
119
- if (s)
120
- opts.sessionId = s;
150
+ // 3) IDEs to configure.
151
+ if (!opts.ides.length) {
152
+ const ans = (await rl.question(`\nIDE(s) to configure [${ALL_IDES.join(", ")}, all]: `)).trim();
153
+ opts.ides = ans === "all" || ans === "" ? ALL_IDES : ans.split(",").map((s) => s.trim());
121
154
  }
122
155
  // Site is chosen at runtime — use the list_my_sites / switch_site tools in chat.
123
156
  }
124
157
  finally {
125
- rl.close();
158
+ try {
159
+ rl.close();
160
+ }
161
+ catch {
162
+ /* already closed */
163
+ }
126
164
  }
165
+ return { loggedIn };
127
166
  }
128
167
  export async function runInstaller(argv) {
129
168
  const opts = parseArgs(argv);
130
- await promptMissing(opts);
169
+ const { loggedIn } = await promptInteractive(opts);
131
170
  let ides = opts.ides.length ? opts.ides : ALL_IDES;
132
171
  if (ides.includes("all"))
133
172
  ides = ALL_IDES;
@@ -136,6 +175,9 @@ export async function runInstaller(argv) {
136
175
  console.error(opts.uninstall ? "Removing webcake-storefront MCP…" : "Configuring webcake-storefront MCP…");
137
176
  for (const ide of ides)
138
177
  console.error(" " + applyToIde(ide, opts, launch, env));
139
- if (!opts.uninstall)
178
+ if (!opts.uninstall) {
179
+ if (loggedIn)
180
+ console.error("\n✓ Logged in — your token is saved to the local config (no token written into IDE files).");
140
181
  console.error("\nDone. Restart your IDE to pick up the new MCP server.");
182
+ }
141
183
  }
package/dist/legal.js CHANGED
@@ -13,7 +13,7 @@ function page(title, bodyHtml) {
13
13
  <style>
14
14
  :root{color-scheme:light dark}
15
15
  body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;line-height:1.65;color:#1e293b;background:#f8fafc}
16
- @media(prefers-color-scheme:dark){body{color:#e2e8f0;background:#0f172a}}
16
+ @media(prefers-color-scheme:dark){body{color:#eaf1ee;background:#0f1714}a{color:#6fe6c0}}
17
17
  main{max-width:760px;margin:0 auto;padding:48px 24px 80px}
18
18
  h1{font-size:1.9rem;margin:0 0 4px}
19
19
  h2{font-size:1.2rem;margin:32px 0 8px}
package/dist/web-guide.js CHANGED
@@ -14,7 +14,11 @@
14
14
  *
15
15
  * Self-contained (inline CSS + JS, no external fonts/trackers) so it loads instantly.
16
16
  */
17
- const MCP_REMOTE_URL = "https://store.toolvn.io.vn/mcp";
17
+ import { readFileSync } from "node:fs";
18
+ // The SPA page (on the builder app) that shows the user their personal remote
19
+ // connector link with login already built in — see builderx_spa McpRemoteStore.vue
20
+ // (/mcp-remote-store). The raw MCP endpoint itself is {ENDPOINT} = <origin>/mcp.
21
+ const MCP_REMOTE_URL = "https://webcake.io/mcp-remote-store";
18
22
  const INSTALL_CMD = "npx -y webcake-storefront-mcp install";
19
23
  const INSTALL_ALL_CMD = "npx -y webcake-storefront-mcp install --ide all --token &lt;TOKEN&gt; --session &lt;SESSION&gt;";
20
24
  const GITHUB_URL = "https://github.com/vuluu2k/webcake-storefront-mcp";
@@ -226,15 +230,15 @@ const T = {
226
230
  '<b>Mở lại ứng dụng AI.</b> Khi thấy <code class="inl">webcake-storefront</code> xuất hiện trong danh sách công cụ là bạn đã kết nối thành công — hãy thử nói "Tạo cho tôi trang sản phẩm…"',
227
231
  ],
228
232
  m1Note: "Muốn cài cho nhiều ứng dụng cùng lúc — dùng lệnh này:",
229
- m2Tag: "Cách ② · Dùng ngay trên trình duyệt (claude.ai) — không cần cài gì",
230
- m2Sub: "Phù hợp khi bạn dùng Claude trên web (claude.ai) hoặc không muốn cài thêm trên máy. Chỉ cần thêm địa chỉ kết nối, đăng nhập WebCake là dùng được.",
233
+ m2Tag: "Cách ② · Dùng link — không cần cài gì",
234
+ m2Sub: "Phù hợp khi bạn dùng Claude trên web (claude.ai), máy không cài được phần mềm, hoặc dùng theo nhóm.",
231
235
  m2Steps: [
232
- '<b>Copy địa chỉ kết nối</b> bên dưới:<a class="btn" href="{REMOTE}">{REMOTE} {ARROW}</a>',
233
- '<b>Mở ứng dụng AI của bạn:</b><br>• claude.ai: vào <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: mở file <code class="inl">.mcp.json</code>',
234
- "<b>Dán địa chỉ vừa copy vào</b> trình duyệt sẽ mở trang đăng nhập WebCake tự động:<pre>{REMOTE}</pre>",
235
- "<b>Bấm Thêm</b> (hoặc lưu file). Khi biểu tượng WebCake chuyển màu tím kết nối thành công. Nói \"Tạo cho tôi trang…\" là bắt đầu được.",
236
+ '<b>Lấy link riêng của bạn</b> (đã gắn sẵn mã đăng nhập) — mở trang dưới đây rồi bấm <b>Copy</b>:<a class="btn" href="{REMOTE}">Mở {REMOTE_HOST} {ARROW}</a>',
237
+ '<b>Vào nơi thêm kết nối</b> trong ứng dụng:<br>• claude.ai: <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: mở file <code class="inl">.mcp.json</code>',
238
+ "<b>Dán link</b> bạn vừa copy (trông giống như):<pre>{ENDPOINT}?jwt=&lt;MÃ CỦA BẠN&gt;&amp;session_id=&lt;PHIÊN CỦA BẠN&gt;</pre>",
239
+ "<b>Bấm Add</b> (hoặc lưu file) rồi chờ một chút. Khi biểu tượng WebCake chuyển xanhdùng được.",
236
240
  ],
237
- m2Note: "Bạn chỉ cần đăng nhập một lần. Sau đó thể chuyển đổi giữa các cửa hàng của mình ngay trong cuộc trò chuyện.",
241
+ m2Note: "⚠️ Link chứa mã đăng nhập riêng của bạn hãy coi như mật khẩu, đừng chia sẻ cho ai.",
238
242
  toolsH2: "Trợ lý có thể giúp bạn làm gì",
239
243
  toolsSub: "Nói chuyện bình thường với trợ lý — bạn không cần biết tên công cụ hay lệnh nào cả.",
240
244
  toolGroups: [
@@ -278,6 +282,9 @@ const T = {
278
282
  promptSub: "Bạn có thể nói tự nhiên bằng tiếng Việt. Ví dụ:",
279
283
  promptEx: "Tạo cho tôi một trang sản phẩm trên WebCake cho thương hiệu [tên thương hiệu].\nTrang cần có: ảnh sản phẩm lớn, tên và giá, mô tả ngắn, nút \"Mua ngay\".\nKiểm tra kỹ trước khi lưu, rồi xuất bản lên cửa hàng của tôi.",
280
284
  faqH2: "Câu hỏi thường gặp",
285
+ newH2: "Có gì mới",
286
+ newBadge: "MỚI",
287
+ clMore: "Xem tất cả thay đổi",
281
288
  starH2: "Thấy hữu ích? Tặng dự án một ngôi sao nhé",
282
289
  starP: "Đây là dự án miễn phí, mã nguồn mở — mỗi ngôi sao là một lời động viên để dự án tiếp tục phát triển và giúp nhiều người tìm ra nó hơn.",
283
290
  starBtn: "Tặng sao trên GitHub",
@@ -289,6 +296,7 @@ const T = {
289
296
  { href: "#build", label: "Tạo được gì" },
290
297
  { href: "#connect", label: "Kết nối" },
291
298
  { href: "#tools", label: "Trợ lý làm gì" },
299
+ { href: "#new", label: "Có gì mới" },
292
300
  { href: "#faq", label: "Hỏi đáp" },
293
301
  ],
294
302
  },
@@ -384,15 +392,15 @@ const T = {
384
392
  '<b>Reopen your AI app.</b> When you see <code class="inl">webcake-storefront</code> in the tools list, you\'re connected — try saying "Create a product page for…"',
385
393
  ],
386
394
  m1Note: "Want to set up multiple apps at once — use this command:",
387
- m2Tag: "Option ② · In your browser (claude.ai) — nothing to install",
388
- m2Sub: "Best if you use Claude on the web (claude.ai) or prefer not to install anything. Just add the connection address, sign in to WebCake, and you're ready.",
395
+ m2Tag: "Way ② · Use a link — nothing to install",
396
+ m2Sub: "Best if you use Claude on the web (claude.ai), can't install software, or work as a team.",
389
397
  m2Steps: [
390
- '<b>Copy the connection address</b> below:<a class="btn" href="{REMOTE}">{REMOTE} {ARROW}</a>',
391
- '<b>Open your AI app:</b><br>• claude.ai: go to <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: open <code class="inl">.mcp.json</code>',
392
- "<b>Paste the address you copied</b> your browser will open the WebCake login page automatically:<pre>{REMOTE}</pre>",
393
- "<b>Click Add</b> (or save the file). When the WebCake icon turns purple, you're connected. Just say \"Create a page for…\" to get started.",
398
+ '<b>Get your personal link</b> (your login is built in) — open the page below and hit <b>Copy</b>:<a class="btn" href="{REMOTE}">Open {REMOTE_HOST} {ARROW}</a>',
399
+ '<b>Open where you add a connection</b> in your app:<br>• claude.ai: <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: open <code class="inl">.mcp.json</code>',
400
+ "<b>Paste the link</b> you just copied (it looks like):<pre>{ENDPOINT}?jwt=&lt;YOUR TOKEN&gt;&amp;session_id=&lt;YOUR SESSION&gt;</pre>",
401
+ "<b>Click Add</b> (or save the file) and wait a moment. When the WebCake icon turns green, you're good to go.",
394
402
  ],
395
- m2Note: "You only need to sign in once. After that you can switch between your stores anytime during a conversation.",
403
+ m2Note: "⚠️ This link contains your personal login treat it like a password and don't share it.",
396
404
  toolsH2: "What your assistant can help you do",
397
405
  toolsSub: "Just talk to your assistant naturally — you don't need to know any tool names or commands.",
398
406
  toolGroups: [
@@ -436,6 +444,9 @@ const T = {
436
444
  promptSub: "You can speak naturally. For example:",
437
445
  promptEx: "Create a product page on my WebCake store for [brand name].\nThe page should have: a large product image, name and price, a short description, and a \"Buy Now\" button.\nPlease check everything looks right before saving, then publish it to my store.",
438
446
  faqH2: "Frequently asked questions",
447
+ newH2: "What's new",
448
+ newBadge: "NEW",
449
+ clMore: "See all changes",
439
450
  starH2: "Find it useful? Give the project a star",
440
451
  starP: "It's a free, open-source project — every star is a little encouragement to keep it growing and helps more people find it.",
441
452
  starBtn: "Star on GitHub",
@@ -447,10 +458,38 @@ const T = {
447
458
  { href: "#build", label: "What you build" },
448
459
  { href: "#connect", label: "Connect" },
449
460
  { href: "#tools", label: "What it does" },
461
+ { href: "#new", label: "What's new" },
450
462
  { href: "#faq", label: "FAQ" },
451
463
  ],
452
464
  },
453
465
  };
466
+ const CHANGELOG = loadChangelog();
467
+ function loadChangelog() {
468
+ try {
469
+ const raw = readFileSync(new URL("./changelog.json", import.meta.url), "utf8");
470
+ const data = JSON.parse(raw);
471
+ return Array.isArray(data) ? data : [];
472
+ }
473
+ catch {
474
+ return [];
475
+ }
476
+ }
477
+ // English Keep-a-Changelog section names → short Vietnamese tags (en uses the raw name).
478
+ const CL_TYPE_VI = {
479
+ Added: "Thêm mới",
480
+ Changed: "Cải tiến",
481
+ Fixed: "Sửa lỗi",
482
+ Removed: "Gỡ bỏ",
483
+ Deprecated: "Ngừng dùng",
484
+ Security: "Bảo mật",
485
+ Internal: "Nội bộ",
486
+ };
487
+ function clTag(type, lang) {
488
+ if (!type)
489
+ return "";
490
+ const label = lang === "vi" ? (CL_TYPE_VI[type] ?? "") : type;
491
+ return label ? ` <span class="cl-tag">${label}</span>` : "";
492
+ }
454
493
  function steps(items) {
455
494
  return items
456
495
  .map((body, i) => `<li><span class="n">${i + 1}</span><div class="body">${body}</div></li>`)
@@ -504,8 +543,10 @@ export function guideHtml(origin, lang = "vi") {
504
543
  ],
505
544
  };
506
545
  const jsonLdScript = JSON.stringify(jsonLd).replace(/</g, "\\u003c");
546
+ const remoteHost = MCP_REMOTE_URL.replace("https://", "");
507
547
  const fill = (s) => s
508
548
  .replaceAll("{REMOTE}", MCP_REMOTE_URL)
549
+ .replaceAll("{REMOTE_HOST}", remoteHost)
509
550
  .replaceAll("{ENDPOINT}", endpoint)
510
551
  .replaceAll("{ARROW}", icon("arrow"));
511
552
  return `<!doctype html>
@@ -546,10 +587,10 @@ export function guideHtml(origin, lang = "vi") {
546
587
  :root{--g:#108B67;--g7:#0c6f52;--ink:#11121e;--mut:#5e5f7a;--bg:#f6f5ff;--card:#ffffff;
547
588
  --line:rgba(16,14,40,.09);--shadow:0 1px 2px rgba(16,14,40,.05),0 6px 20px -12px rgba(16,14,40,.18);--code:#0e0d1a;
548
589
  --ic-fg:#0c6f52;--btn-hover:#0c6f52;--navbg:rgba(246,245,255,.82)}
549
- @media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#e8e6ff;--mut:#9a98b8;--bg:#0c0b14;--card:#141320;
550
- --line:rgba(255,255,255,.07);--shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px -14px rgba(0,0,0,.7);--code:#07060f;--g7:#5fe0b3;--ic-fg:#8aecc9;--btn-hover:#16a07a;--navbg:rgba(12,11,20,.82)}}
551
- :root[data-theme="dark"]{--ink:#e8e6ff;--mut:#9a98b8;--bg:#0c0b14;--card:#141320;
552
- --line:rgba(255,255,255,.07);--shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px -14px rgba(0,0,0,.7);--code:#07060f;--g7:#5fe0b3;--ic-fg:#8aecc9;--btn-hover:#16a07a;--navbg:rgba(12,11,20,.82)}
590
+ @media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0f1714;--card:#18211d;
591
+ --line:rgba(255,255,255,.09);--shadow:0 1px 2px rgba(0,0,0,.35),0 12px 34px -16px rgba(0,0,0,.6);--code:#0b120f;--g:#16a37c;--g7:#6fe6c0;--ic-fg:#8aecc9;--btn-hover:#1cba8d;--navbg:rgba(15,23,20,.82)}}
592
+ :root[data-theme="dark"]{--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0f1714;--card:#18211d;
593
+ --line:rgba(255,255,255,.09);--shadow:0 1px 2px rgba(0,0,0,.35),0 12px 34px -16px rgba(0,0,0,.6);--code:#0b120f;--g:#16a37c;--g7:#6fe6c0;--ic-fg:#8aecc9;--btn-hover:#1cba8d;--navbg:rgba(15,23,20,.82)}
553
594
  *{box-sizing:border-box}
554
595
  html{scroll-behavior:auto}
555
596
  html.smooth{scroll-behavior:smooth}
@@ -605,12 +646,12 @@ export function guideHtml(origin, lang = "vi") {
605
646
  text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap}
606
647
  .tag .ic{width:30px;height:30px;border-radius:9px}
607
648
  .tag .ic .i{width:16px;height:16px}
608
- pre{margin:0;background:var(--code);color:#e8e6ff;border-radius:11px;padding:12px 14px;overflow-x:auto;
649
+ pre{margin:0;background:var(--code);color:#e7efe9;border-radius:11px;padding:12px 14px;overflow-x:auto;
609
650
  border:1px solid rgba(255,255,255,.06);font:600 .82rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
610
651
  .codewrap{position:relative}
611
652
  .codewrap pre{padding-right:46px}
612
653
  .copy{position:absolute;top:8px;right:8px;width:30px;height:30px;display:grid;place-items:center;cursor:pointer;
613
- border:1px solid rgba(255,255,255,.15);border-radius:8px;background:rgba(255,255,255,.06);color:#cfc9ff;
654
+ border:1px solid rgba(255,255,255,.15);border-radius:8px;background:rgba(255,255,255,.06);color:#cdd8d2;
614
655
  transition:background .15s ease,color .15s ease,border-color .15s ease}
615
656
  .copy:hover{background:rgba(255,255,255,.13);color:#fff}
616
657
  .copy svg{width:15px;height:15px}
@@ -699,6 +740,26 @@ export function guideHtml(origin, lang = "vi") {
699
740
  display:flex;gap:18px;flex-wrap:wrap;align-items:center}
700
741
  footer a{color:var(--g7);font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
701
742
  footer a:hover{text-decoration:underline}
743
+ .cl-wrap{padding:24px 26px 12px}
744
+ .cl{position:relative;margin:0;padding:0 0 0 24px;list-style:none}
745
+ .cl::before{content:"";position:absolute;left:6px;top:8px;bottom:14px;width:2px;
746
+ background:linear-gradient(var(--g),rgba(16,139,103,.08))}
747
+ .cl li{position:relative;padding:0 0 18px}
748
+ .cl li:last-child{padding-bottom:0}
749
+ .cl li::before{content:"";position:absolute;left:-24px;top:4px;width:12px;height:12px;border-radius:50%;
750
+ background:var(--card);border:2.5px solid var(--g);box-sizing:border-box}
751
+ .cl li.is-new::before{box-shadow:0 0 0 0 rgba(16,139,103,.5);animation:ring 2s infinite}
752
+ @keyframes ring{70%{box-shadow:0 0 0 8px rgba(16,139,103,0)}100%{box-shadow:0 0 0 0 rgba(16,139,103,0)}}
753
+ .cl .v{display:inline-flex;align-items:center;gap:8px;font-weight:800;font-size:.97rem;flex-wrap:wrap}
754
+ .cl-tag{font-size:.68rem;font-weight:700;color:var(--g7);background:rgba(16,139,103,.12);
755
+ border:1px solid var(--line);padding:1px 8px;border-radius:999px;margin-left:8px}
756
+ .cl .date{color:var(--mut);font-size:.79rem;margin-left:8px;font-weight:500}
757
+ .cl .t{color:var(--mut);font-size:.91rem;margin:3px 0 0;max-width:62ch}
758
+ .new{font-size:.64rem;font-weight:800;letter-spacing:.06em;color:#fff;background:var(--g);
759
+ padding:2px 7px;border-radius:999px;animation:blink 1.8s ease-in-out infinite}
760
+ @keyframes blink{50%{opacity:.55}}
761
+ .cl-more{display:inline-flex;align-items:center;gap:6px;margin-top:6px;font-size:.86rem;font-weight:600;color:var(--g7);text-decoration:none}
762
+ .cl-more:hover{gap:9px}
702
763
  @media(max-width:640px){
703
764
  .wrap{padding:30px 15px 56px}
704
765
  header{flex-wrap:wrap;gap:12px}
@@ -709,6 +770,7 @@ export function guideHtml(origin, lang = "vi") {
709
770
  .method{padding:18px 15px}
710
771
  .card{padding:18px}
711
772
  .tip{padding:11px 12px}
773
+ .cl-wrap{padding:18px 16px 10px}
712
774
  .langsw{padding:6px 10px}
713
775
  .uses li,.feat li{padding:14px}
714
776
  .flow{flex-direction:column;align-items:stretch;overflow:visible;padding:16px}
@@ -746,7 +808,7 @@ export function guideHtml(origin, lang = "vi") {
746
808
  </div>
747
809
  </header>
748
810
 
749
- <p class="hero-in" style="display:flex;gap:9px;flex-wrap:wrap"><span class="pill"><span class="dot"></span> ${t.running}</span><span class="pill">WebCake · StoreCake</span></p>
811
+ <p class="hero-in" style="display:flex;gap:9px;flex-wrap:wrap"><span class="pill"><span class="dot"></span> ${t.running}</span><span class="pill">WebCake · StoreCake</span>${CHANGELOG[0] ? `<span class="pill">v${CHANGELOG[0].v}</span>` : ""}</p>
750
812
 
751
813
  <p class="lead hero-in">${t.leadPre}<b class="grad">${t.leadGrad}</b>${t.leadPost}</p>
752
814
 
@@ -823,6 +885,16 @@ export function guideHtml(origin, lang = "vi") {
823
885
  <li class="glass reveal">${tile("wand")} <span><pre style="background:transparent;color:inherit;border:none;padding:0;font-size:.88rem;white-space:pre-wrap">${t.promptEx}</pre></span></li>
824
886
  </ul>
825
887
 
888
+ ${CHANGELOG.length
889
+ ? `<h2 id="new" class="reveal">${t.newH2}</h2>
890
+ <div class="glass cl-wrap reveal">
891
+ <ul class="cl">
892
+ ${CHANGELOG.map((c, i) => `<li class="${i === 0 ? "is-new" : ""}"><span class="v">v${c.v}${i === 0 ? ` <span class="new">${t.newBadge}</span>` : ""}${clTag(c.type, L)}<span class="date">${c.d}</span></span><p class="t">${L === "en" ? c.en : c.vi}</p></li>`).join("\n ")}
893
+ </ul>
894
+ <a class="cl-more" href="${GITHUB_URL}/blob/main/${L === "en" ? "CHANGELOG.md" : "CHANGELOG.vi.md"}">${t.clMore} ${icon("arrow")}</a>
895
+ </div>`
896
+ : ""}
897
+
826
898
  <h2 id="faq" class="reveal">${t.faqH2}</h2>
827
899
  ${faq.map((f) => `<details class="glass reveal"><summary>${f.q}</summary><p>${f.a}</p></details>`).join("\n ")}
828
900
 
@@ -910,7 +982,7 @@ export function ogImageSvg() {
910
982
  return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" fill="none" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
911
983
  <defs>
912
984
  <linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
913
- <stop stop-color="#0c0b14"/><stop offset="1" stop-color="#16122a"/>
985
+ <stop stop-color="#0f1714"/><stop offset="1" stop-color="#0c241c"/>
914
986
  </linearGradient>
915
987
  <radialGradient id="glow" cx="0" cy="0" r="1" gradientTransform="translate(960 70) rotate(130) scale(620)" gradientUnits="userSpaceOnUse">
916
988
  <stop stop-color="#108B67" stop-opacity="0.40"/><stop offset="1" stop-color="#108B67" stop-opacity="0"/>
@@ -934,12 +1006,12 @@ export function ogImageSvg() {
934
1006
  <text x="90" y="300" fill="#ffffff" font-size="64" font-weight="800" letter-spacing="-2">Bạn nói điều mình muốn —</text>
935
1007
  <text x="90" y="380" fill="#ffffff" font-size="64" font-weight="800" letter-spacing="-2">AI dựng trang, kiểm tra,</text>
936
1008
  <text x="90" y="460" fill="#108B67" font-size="64" font-weight="800" letter-spacing="-2">đăng lên là xong.</text>
937
- <text x="90" y="534" fill="#9a98b8" font-size="28" font-weight="500">Không cần kéo-thả · Không cần biết lập trình · Luôn xem trước khi lưu</text>
1009
+ <text x="90" y="534" fill="#9fb0a9" font-size="28" font-weight="500">Không cần kéo-thả · Không cần biết lập trình · Luôn xem trước khi lưu</text>
938
1010
  <g transform="translate(90 560)">
939
1011
  <rect width="540" height="52" rx="12" fill="#108B67"/>
940
1012
  <text x="270" y="34" fill="#ffffff" font-size="22" font-weight="700" text-anchor="middle">store.toolvn.io.vn</text>
941
1013
  </g>
942
- <text x="1110" y="600" fill="#5b5a7a" font-size="22" font-weight="600" text-anchor="end">github.com/vuluu2k/webcake-storefront-mcp</text>
1014
+ <text x="1110" y="600" fill="#5a7269" font-size="22" font-weight="600" text-anchor="end">github.com/vuluu2k/webcake-storefront-mcp</text>
943
1015
  </svg>`;
944
1016
  }
945
1017
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-storefront-mcp",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
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",
@@ -24,7 +24,7 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "scripts": {
27
- "build": "tsc && node scripts/copy-assets.mjs",
27
+ "build": "node scripts/gen-changelog.mjs && tsc && node scripts/copy-assets.mjs",
28
28
  "start": "node dist/index.js",
29
29
  "serve": "node dist/index.js serve",
30
30
  "dev": "tsc --watch",