webcake-storefront-mcp 1.1.1 → 1.1.3
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 +1 -1
- package/README.vi.md +1 -1
- package/dist/config.js +1 -1
- package/dist/db.js +48 -61
- package/dist/index.js +1 -1
- package/dist/install.js +58 -16
- package/dist/legal.js +3 -3
- package/dist/tools/context.js +2 -2
- package/dist/tools/images.js +3 -3
- package/dist/web-guide.js +62 -68
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -150,7 +150,7 @@ Base URLs come from a **named environment** — set `WEBCAKE_ENV` (or `--env`) a
|
|
|
150
150
|
|
|
151
151
|
Override a preset with `WEBCAKE_API_URL` / `WEBCAKE_APP_URL`. Optional, configured server-side:
|
|
152
152
|
`PEXELS_API_KEY` (search_images), `MONGO_URI` (image-alt cache). Token / session / site can also be set
|
|
153
|
-
in chat via `update_auth` and `switch_site` — saved to a local
|
|
153
|
+
in chat via `update_auth` and `switch_site` — saved to a local config file at `~/.webcake-storefront-mcp/`.
|
|
154
154
|
|
|
155
155
|
<details>
|
|
156
156
|
<summary><b>How to get your token + session</b></summary>
|
package/README.vi.md
CHANGED
|
@@ -149,7 +149,7 @@ URL gốc lấy theo **môi trường có tên** — đặt `WEBCAKE_ENV` (hoặ
|
|
|
149
149
|
|
|
150
150
|
Override bằng `WEBCAKE_API_URL` / `WEBCAKE_APP_URL`. Tuỳ chọn, đặt phía server:
|
|
151
151
|
`PEXELS_API_KEY` (search_images), `MONGO_URI` (cache alt ảnh). Token / session / site cũng có thể đặt
|
|
152
|
-
trong chat bằng `update_auth` và `switch_site` — lưu vào
|
|
152
|
+
trong chat bằng `update_auth` và `switch_site` — lưu vào file cấu hình tại `~/.webcake-storefront-mcp/`.
|
|
153
153
|
|
|
154
154
|
<details>
|
|
155
155
|
<summary><b>Cách lấy token + session</b></summary>
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Central resolution of connection settings for every entry path (stdio, install,
|
|
2
2
|
// login, remote HTTP). Precedence: explicit overrides > environment variables >
|
|
3
|
-
// saved config in the local
|
|
3
|
+
// saved config in the local config file.
|
|
4
4
|
import { WebcakeCmsApi } from "./api.js";
|
|
5
5
|
import { getSavedConfig } from "./tools/context.js";
|
|
6
6
|
// Per-environment endpoints so you can switch with `--env <name>` / WEBCAKE_ENV
|
package/dist/db.js
CHANGED
|
@@ -1,96 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Tiny JSON-file persistence (no native deps) for: (1) the saved connection config
|
|
2
|
+
// (token / session / site / api_url / confirm_mode) and (2) the image-alt cache.
|
|
3
|
+
//
|
|
4
|
+
// Stored under a stable home dir so it survives `npx` (ephemeral package cache) and
|
|
5
|
+
// container restarts. Two flat JSON files instead of SQLite — keeps the package light
|
|
6
|
+
// and works in any runtime (Alpine, Docker `--ignore-scripts`, serverless) with no
|
|
7
|
+
// native binding to build. The API is synchronous to match the call sites.
|
|
8
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
9
|
import { homedir } from "node:os";
|
|
4
10
|
import { join } from "node:path";
|
|
5
|
-
// Persist in a stable home directory so saved config survives `npx` (where the
|
|
6
|
-
// package lives in an ephemeral cache dir) and rebuilds.
|
|
7
11
|
const CONFIG_DIR = process.env.WEBCAKE_CONFIG_DIR || join(homedir(), ".webcake-storefront-mcp");
|
|
8
12
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
14
|
+
const ALT_FILE = join(CONFIG_DIR, "image-alt-cache.json");
|
|
15
|
+
function readJson(file, fallback) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function writeJson(file, data) {
|
|
24
|
+
try {
|
|
25
|
+
writeFileSync(file, JSON.stringify(data), "utf-8");
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
console.error("[db] write failed:", e?.message ?? e);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// ── Config (key/value) ───────────────────────────────────────────────────────
|
|
32
|
+
const config = readJson(CONFIG_FILE, {});
|
|
25
33
|
export function getConfig(key) {
|
|
26
|
-
|
|
27
|
-
return row ? row.value : null;
|
|
34
|
+
return key in config ? config[key] : null;
|
|
28
35
|
}
|
|
29
36
|
export function setConfig(key, value) {
|
|
30
|
-
|
|
37
|
+
config[key] = String(value);
|
|
38
|
+
writeJson(CONFIG_FILE, config);
|
|
31
39
|
}
|
|
32
40
|
export function delConfig(key) {
|
|
33
|
-
|
|
41
|
+
delete config[key];
|
|
42
|
+
writeJson(CONFIG_FILE, config);
|
|
34
43
|
}
|
|
35
44
|
export function getAllConfig() {
|
|
36
|
-
|
|
37
|
-
const result = {};
|
|
38
|
-
for (const row of rows)
|
|
39
|
-
result[row.key] = row.value;
|
|
40
|
-
return result;
|
|
45
|
+
return { ...config };
|
|
41
46
|
}
|
|
42
|
-
|
|
43
|
-
db.exec(`
|
|
44
|
-
CREATE TABLE IF NOT EXISTS image_alt_cache (
|
|
45
|
-
url_key TEXT PRIMARY KEY,
|
|
46
|
-
url TEXT NOT NULL,
|
|
47
|
-
alt TEXT NOT NULL,
|
|
48
|
-
source TEXT,
|
|
49
|
-
updated_at INTEGER NOT NULL
|
|
50
|
-
);
|
|
51
|
-
`);
|
|
52
|
-
const stmtAltGet = db.prepare("SELECT url_key, url, alt, source, updated_at FROM image_alt_cache WHERE url_key = ?");
|
|
53
|
-
const stmtAltSet = db.prepare(`
|
|
54
|
-
INSERT INTO image_alt_cache (url_key, url, alt, source, updated_at)
|
|
55
|
-
VALUES (@url_key, @url, @alt, @source, @updated_at)
|
|
56
|
-
ON CONFLICT(url_key) DO UPDATE SET
|
|
57
|
-
url = excluded.url,
|
|
58
|
-
alt = excluded.alt,
|
|
59
|
-
source = excluded.source,
|
|
60
|
-
updated_at = excluded.updated_at
|
|
61
|
-
`);
|
|
62
|
-
const stmtAltList = db.prepare("SELECT url_key, url, alt, source, updated_at FROM image_alt_cache ORDER BY updated_at DESC LIMIT ? OFFSET ?");
|
|
63
|
-
const stmtAltCount = db.prepare("SELECT COUNT(*) AS n FROM image_alt_cache");
|
|
47
|
+
const altCache = readJson(ALT_FILE, {});
|
|
64
48
|
export function getImageAlt(urlKey) {
|
|
65
|
-
return
|
|
49
|
+
return altCache[urlKey] || null;
|
|
66
50
|
}
|
|
67
51
|
export function getImageAlts(urlKeys) {
|
|
68
52
|
const out = new Map();
|
|
69
53
|
for (const k of urlKeys) {
|
|
70
|
-
const row =
|
|
54
|
+
const row = altCache[k];
|
|
71
55
|
if (row)
|
|
72
56
|
out.set(k, row);
|
|
73
57
|
}
|
|
74
58
|
return out;
|
|
75
59
|
}
|
|
76
60
|
export function setImageAlt({ url_key, url, alt, source = "ai" }) {
|
|
77
|
-
|
|
61
|
+
altCache[url_key] = { url_key, url, alt, source, updated_at: Date.now() };
|
|
62
|
+
writeJson(ALT_FILE, altCache);
|
|
78
63
|
}
|
|
79
|
-
export
|
|
64
|
+
export function setImageAlts(items) {
|
|
80
65
|
for (const it of items) {
|
|
81
|
-
|
|
66
|
+
altCache[it.url_key] = {
|
|
82
67
|
url_key: it.url_key,
|
|
83
68
|
url: it.url,
|
|
84
69
|
alt: it.alt,
|
|
85
70
|
source: it.source || "ai",
|
|
86
71
|
updated_at: Date.now(),
|
|
87
|
-
}
|
|
72
|
+
};
|
|
88
73
|
}
|
|
89
|
-
|
|
74
|
+
writeJson(ALT_FILE, altCache);
|
|
75
|
+
}
|
|
90
76
|
export function listImageAlts(limit = 100, offset = 0) {
|
|
91
|
-
return
|
|
77
|
+
return Object.values(altCache)
|
|
78
|
+
.sort((a, b) => b.updated_at - a.updated_at)
|
|
79
|
+
.slice(offset, offset + limit);
|
|
92
80
|
}
|
|
93
81
|
export function countImageAlts() {
|
|
94
|
-
return
|
|
82
|
+
return Object.keys(altCache).length;
|
|
95
83
|
}
|
|
96
|
-
export default db;
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
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,12 +13,12 @@ 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:#
|
|
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}
|
|
20
20
|
.meta{color:#64748b;font-size:.9rem;margin-bottom:28px}
|
|
21
|
-
a{color:#
|
|
21
|
+
a{color:#108B67}
|
|
22
22
|
code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:rgba(127,127,127,.15);padding:1px 5px;border-radius:4px}
|
|
23
23
|
ul{padding-left:22px}
|
|
24
24
|
footer{margin-top:48px;padding-top:20px;border-top:1px solid rgba(127,127,127,.25);color:#64748b;font-size:.85rem}
|
|
@@ -55,7 +55,7 @@ handles, why, who receives it, and how long it is kept.</p>
|
|
|
55
55
|
tokens are never written to disk by the connector. Access tokens expire automatically after ~1 hour, refresh
|
|
56
56
|
tokens after ~30 days. A server restart clears all tokens.</li>
|
|
57
57
|
<li><strong>Local CLI config (stdio mode).</strong> When you run <code>npx webcake-storefront-mcp login</code>,
|
|
58
|
-
your token and session ID are saved to a local
|
|
58
|
+
your token and session ID are saved to a local file on <em>your own machine</em> (at
|
|
59
59
|
<code>~/.webcake-storefront-mcp.db</code> or similar). This file stays on your device and is not transmitted
|
|
60
60
|
anywhere by the connector.</li>
|
|
61
61
|
<li>The connector does <strong>not</strong> run an analytics database, does <strong>not</strong> sell or share
|
package/dist/tools/context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getConfig, setConfig } from "../db.js";
|
|
3
|
-
/** Read all saved credentials from
|
|
3
|
+
/** Read all saved credentials from the local config file for startup */
|
|
4
4
|
export function getSavedConfig() {
|
|
5
5
|
return {
|
|
6
6
|
token: getConfig("token") || "",
|
|
@@ -118,7 +118,7 @@ Get token and session_id from browser DevTools → Network tab → copy from any
|
|
|
118
118
|
api.baseUrl = oldBaseUrl;
|
|
119
119
|
throw new Error("Authentication failed — credentials were NOT changed. Make sure token and session_id are both correct.");
|
|
120
120
|
}
|
|
121
|
-
// Persist all to
|
|
121
|
+
// Persist all to the local config file
|
|
122
122
|
if (token)
|
|
123
123
|
setConfig("token", token);
|
|
124
124
|
if (session_id)
|
package/dist/tools/images.js
CHANGED
|
@@ -606,7 +606,7 @@ If alt_path is omitted, it is auto-detected via the same probe used by list_imag
|
|
|
606
606
|
}));
|
|
607
607
|
// ── Alt cache tools ──
|
|
608
608
|
server.tool("get_cached_image_alts", `Look up cached alt descriptions for image URLs. URLs are matched by normalized form (query string stripped, lowercase). Use BEFORE running read_image/OCR — skip already-described URLs.
|
|
609
|
-
When MONGO_URI is set, misses are then checked against MongoDB and successful hits are backfilled into the local
|
|
609
|
+
When MONGO_URI is set, misses are then checked against MongoDB and successful hits are backfilled into the local cache for fast re-lookup.`, {
|
|
610
610
|
urls: z.array(z.string()).min(1).describe("Image URLs to look up"),
|
|
611
611
|
}, ({ urls }) => handle(async () => {
|
|
612
612
|
const hits = [];
|
|
@@ -693,7 +693,7 @@ When MONGO_URI is set, misses are then checked against MongoDB and successful hi
|
|
|
693
693
|
return { total, count: rows.length, entries: rows };
|
|
694
694
|
}));
|
|
695
695
|
// ── Mongo sync (active when MONGO_URI is set) ──
|
|
696
|
-
server.tool("sync_image_alts_to_mongo", `Push local
|
|
696
|
+
server.tool("sync_image_alts_to_mongo", `Push local alt cache entries up to MongoDB. Bulk upsert keyed by url_key. Use when you want to back up local-only entries to the shared central store, or after a session of heavy AI describes.
|
|
697
697
|
Requires MONGO_URI env var.`, {
|
|
698
698
|
limit: z.number().default(1000).describe("Max entries to push per call"),
|
|
699
699
|
offset: z.number().default(0).describe("Offset into local cache"),
|
|
@@ -706,7 +706,7 @@ Requires MONGO_URI env var.`, {
|
|
|
706
706
|
const res = await mongoUpsertAlts(rows.map((r) => ({ url_key: r.url_key, url: r.url, alt: r.alt, source: r.source })));
|
|
707
707
|
return { pushed: rows.length, ...res, total_local: countImageAlts() };
|
|
708
708
|
}));
|
|
709
|
-
server.tool("sync_image_alts_from_mongo", `Pull MongoDB alt entries down into local
|
|
709
|
+
server.tool("sync_image_alts_from_mongo", `Pull MongoDB alt entries down into local cache. Useful when starting on a new machine/site to warm the local cache from the central store.
|
|
710
710
|
Requires MONGO_URI env var.`, {
|
|
711
711
|
limit: z.number().default(1000).describe("Max entries to pull"),
|
|
712
712
|
offset: z.number().default(0).describe("Offset into Mongo collection"),
|
package/dist/web-guide.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Self-contained (inline CSS + JS, no external fonts/trackers) so it loads instantly.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
// The SPA page (on the builder app) that shows the user their personal remote
|
|
18
|
+
// connector link with login already built in — see builderx_spa McpRemoteStore.vue
|
|
19
|
+
// (/mcp-remote-store). The raw MCP endpoint itself is {ENDPOINT} = <origin>/mcp.
|
|
20
|
+
const MCP_REMOTE_URL = "https://webcake.io/mcp-remote-store";
|
|
18
21
|
const INSTALL_CMD = "npx -y webcake-storefront-mcp install";
|
|
19
22
|
const INSTALL_ALL_CMD = "npx -y webcake-storefront-mcp install --ide all --token <TOKEN> --session <SESSION>";
|
|
20
23
|
const GITHUB_URL = "https://github.com/vuluu2k/webcake-storefront-mcp";
|
|
@@ -24,8 +27,8 @@ const DOCS_URL = `${GITHUB_URL}#readme`;
|
|
|
24
27
|
const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
25
28
|
<rect width="32" height="32" rx="7" fill="url(#sg)"/>
|
|
26
29
|
<defs><linearGradient id="sg" x1="0" y1="0" x2="1" y2="1">
|
|
27
|
-
<stop offset="0%" stop-color="#
|
|
28
|
-
<stop offset="100%" stop-color="#
|
|
30
|
+
<stop offset="0%" stop-color="#108B67"/>
|
|
31
|
+
<stop offset="100%" stop-color="#14a87c"/>
|
|
29
32
|
</linearGradient></defs>
|
|
30
33
|
<text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="17" fill="white">S</text>
|
|
31
34
|
<circle cx="24" cy="9" r="4" fill="#FFD591"/>
|
|
@@ -226,15 +229,15 @@ const T = {
|
|
|
226
229
|
'<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
230
|
],
|
|
228
231
|
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
|
|
230
|
-
m2Sub: "Phù hợp khi bạn dùng Claude trên web (claude.ai)
|
|
232
|
+
m2Tag: "Cách ② · Dùng link — không cần cài gì",
|
|
233
|
+
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
234
|
m2Steps: [
|
|
232
|
-
'<b>
|
|
233
|
-
'<b>
|
|
234
|
-
"<b>Dán
|
|
235
|
-
"<b>Bấm
|
|
235
|
+
'<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>',
|
|
236
|
+
'<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>',
|
|
237
|
+
"<b>Dán link</b> bạn vừa copy (trông giống như):<pre>{ENDPOINT}?jwt=<MÃ CỦA BẠN>&session_id=<PHIÊN CỦA BẠN></pre>",
|
|
238
|
+
"<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 xanh là dùng được.",
|
|
236
239
|
],
|
|
237
|
-
m2Note: "
|
|
240
|
+
m2Note: "⚠️ Link có 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
241
|
toolsH2: "Trợ lý có thể giúp bạn làm gì",
|
|
239
242
|
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
243
|
toolGroups: [
|
|
@@ -384,15 +387,15 @@ const T = {
|
|
|
384
387
|
'<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
388
|
],
|
|
386
389
|
m1Note: "Want to set up multiple apps at once — use this command:",
|
|
387
|
-
m2Tag: "
|
|
388
|
-
m2Sub: "Best if you use Claude on the web (claude.ai)
|
|
390
|
+
m2Tag: "Way ② · Use a link — nothing to install",
|
|
391
|
+
m2Sub: "Best if you use Claude on the web (claude.ai), can't install software, or work as a team.",
|
|
389
392
|
m2Steps: [
|
|
390
|
-
'<b>
|
|
391
|
-
'<b>Open your
|
|
392
|
-
"<b>Paste the
|
|
393
|
-
"<b>Click Add</b> (or save the file). When the WebCake icon turns
|
|
393
|
+
'<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>',
|
|
394
|
+
'<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>',
|
|
395
|
+
"<b>Paste the link</b> you just copied (it looks like):<pre>{ENDPOINT}?jwt=<YOUR TOKEN>&session_id=<YOUR SESSION></pre>",
|
|
396
|
+
"<b>Click Add</b> (or save the file) and wait a moment. When the WebCake icon turns green, you're good to go.",
|
|
394
397
|
],
|
|
395
|
-
m2Note: "
|
|
398
|
+
m2Note: "⚠️ This link contains your personal login — treat it like a password and don't share it.",
|
|
396
399
|
toolsH2: "What your assistant can help you do",
|
|
397
400
|
toolsSub: "Just talk to your assistant naturally — you don't need to know any tool names or commands.",
|
|
398
401
|
toolGroups: [
|
|
@@ -504,8 +507,10 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
504
507
|
],
|
|
505
508
|
};
|
|
506
509
|
const jsonLdScript = JSON.stringify(jsonLd).replace(/</g, "\\u003c");
|
|
510
|
+
const remoteHost = MCP_REMOTE_URL.replace("https://", "");
|
|
507
511
|
const fill = (s) => s
|
|
508
512
|
.replaceAll("{REMOTE}", MCP_REMOTE_URL)
|
|
513
|
+
.replaceAll("{REMOTE_HOST}", remoteHost)
|
|
509
514
|
.replaceAll("{ENDPOINT}", endpoint)
|
|
510
515
|
.replaceAll("{ARROW}", icon("arrow"));
|
|
511
516
|
return `<!doctype html>
|
|
@@ -518,7 +523,7 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
518
523
|
<meta name="keywords" content="${m.keywords}">
|
|
519
524
|
<meta name="author" content="WebCake">
|
|
520
525
|
<meta name="robots" content="index,follow">
|
|
521
|
-
<meta name="theme-color" content="#
|
|
526
|
+
<meta name="theme-color" content="#108B67">
|
|
522
527
|
<link rel="canonical" href="${canonical}">
|
|
523
528
|
<link rel="alternate" hreflang="vi" href="${origin}/">
|
|
524
529
|
<link rel="alternate" hreflang="en" href="${origin}/?lang=en">
|
|
@@ -543,13 +548,13 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
543
548
|
<meta name="twitter:image:alt" content="${m.title}">
|
|
544
549
|
<script type="application/ld+json">${jsonLdScript}</script>
|
|
545
550
|
<style>
|
|
546
|
-
:root{--g:#
|
|
551
|
+
:root{--g:#108B67;--g7:#0c6f52;--ink:#11121e;--mut:#5e5f7a;--bg:#f6f5ff;--card:#ffffff;
|
|
547
552
|
--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
|
-
--ic-fg:#
|
|
549
|
-
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#
|
|
550
|
-
--line:rgba(255,255,255,.
|
|
551
|
-
:root[data-theme="dark"]{--ink:#
|
|
552
|
-
--line:rgba(255,255,255,.
|
|
553
|
+
--ic-fg:#0c6f52;--btn-hover:#0c6f52;--navbg:rgba(246,245,255,.82)}
|
|
554
|
+
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0f1714;--card:#18211d;
|
|
555
|
+
--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)}}
|
|
556
|
+
:root[data-theme="dark"]{--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0f1714;--card:#18211d;
|
|
557
|
+
--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
558
|
*{box-sizing:border-box}
|
|
554
559
|
html{scroll-behavior:auto}
|
|
555
560
|
html.smooth{scroll-behavior:smooth}
|
|
@@ -557,8 +562,8 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
557
562
|
background:var(--bg);line-height:1.62;overflow-x:hidden}
|
|
558
563
|
.blobs{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none}
|
|
559
564
|
.blobs b{position:absolute;border-radius:50%;filter:blur(90px);opacity:.16;will-change:transform}
|
|
560
|
-
.blobs b:nth-child(1){width:560px;height:560px;right:-160px;top:-180px;background:radial-gradient(circle,#
|
|
561
|
-
.blobs b:nth-child(2){width:440px;height:440px;left:-160px;bottom:-160px;background:radial-gradient(circle,#
|
|
565
|
+
.blobs b:nth-child(1){width:560px;height:560px;right:-160px;top:-180px;background:radial-gradient(circle,#108B67,transparent 70%);animation:drift1 40s ease-in-out infinite}
|
|
566
|
+
.blobs b:nth-child(2){width:440px;height:440px;left:-160px;bottom:-160px;background:radial-gradient(circle,#14a87c,transparent 70%);animation:drift2 48s ease-in-out infinite}
|
|
562
567
|
@keyframes drift1{50%{transform:translate(-50px,60px)}}
|
|
563
568
|
@keyframes drift2{50%{transform:translate(40px,-50px)}}
|
|
564
569
|
.wrap{max-width:900px;margin:0 auto;padding:48px 20px 72px}
|
|
@@ -568,7 +573,7 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
568
573
|
transition:box-shadow .2s ease,border-color .2s ease}
|
|
569
574
|
header{display:flex;align-items:center;gap:14px;margin-bottom:14px}
|
|
570
575
|
header .logo{width:50px;height:50px;border-radius:14px;overflow:hidden;flex:0 0 auto;
|
|
571
|
-
box-shadow:0 6px 16px -4px rgba(
|
|
576
|
+
box-shadow:0 6px 16px -4px rgba(16,139,103,.4)}
|
|
572
577
|
header .logo svg{width:100%;height:100%;display:block}
|
|
573
578
|
.hgrow{flex:1 1 auto;min-width:0}
|
|
574
579
|
.controls{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;gap:8px}
|
|
@@ -583,16 +588,16 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
583
588
|
.sub{color:var(--mut);margin:3px 0 0;font-size:.98rem}
|
|
584
589
|
.lead{font-size:1.16rem;margin:20px 0 18px;max-width:60ch}
|
|
585
590
|
.lead b{color:var(--ink)}
|
|
586
|
-
.grad{background:linear-gradient(95deg,#
|
|
591
|
+
.grad{background:linear-gradient(95deg,#108B67,#14a87c 60%,#3fcf9e);-webkit-background-clip:text;background-clip:text;color:transparent;
|
|
587
592
|
background-size:200% auto;animation:shim 7s linear infinite}
|
|
588
593
|
@keyframes shim{to{background-position:200% center}}
|
|
589
594
|
.pill{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:999px;font-size:.82rem;font-weight:600;
|
|
590
|
-
color:var(--g7);background:rgba(
|
|
591
|
-
.dot{width:8px;height:8px;border-radius:50%;background:var(--g);box-shadow:0 0 0 0 rgba(
|
|
592
|
-
@keyframes pulse{70%{box-shadow:0 0 0 7px rgba(
|
|
595
|
+
color:var(--g7);background:rgba(16,139,103,.10);border:1px solid var(--line)}
|
|
596
|
+
.dot{width:8px;height:8px;border-radius:50%;background:var(--g);box-shadow:0 0 0 0 rgba(16,139,103,.5);animation:pulse 2s infinite}
|
|
597
|
+
@keyframes pulse{70%{box-shadow:0 0 0 7px rgba(16,139,103,0)}100%{box-shadow:0 0 0 0 rgba(16,139,103,0)}}
|
|
593
598
|
h2{font-size:1.32rem;margin:46px 0 16px;font-weight:800;letter-spacing:-.01em;scroll-margin-top:72px}
|
|
594
599
|
.ic{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto;color:var(--ic-fg);
|
|
595
|
-
background:rgba(
|
|
600
|
+
background:rgba(16,139,103,.11);border:1px solid var(--line);transition:transform .2s ease}
|
|
596
601
|
.ic .i{width:22px;height:22px}
|
|
597
602
|
.grid{display:grid;gap:16px;grid-template-columns:1fr 1fr}
|
|
598
603
|
.grid-3{display:grid;gap:16px;grid-template-columns:1fr 1fr 1fr}
|
|
@@ -605,16 +610,16 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
605
610
|
text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap}
|
|
606
611
|
.tag .ic{width:30px;height:30px;border-radius:9px}
|
|
607
612
|
.tag .ic .i{width:16px;height:16px}
|
|
608
|
-
pre{margin:0;background:var(--code);color:#
|
|
613
|
+
pre{margin:0;background:var(--code);color:#e7efe9;border-radius:11px;padding:12px 14px;overflow-x:auto;
|
|
609
614
|
border:1px solid rgba(255,255,255,.06);font:600 .82rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
610
615
|
.codewrap{position:relative}
|
|
611
616
|
.codewrap pre{padding-right:46px}
|
|
612
617
|
.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:#
|
|
618
|
+
border:1px solid rgba(255,255,255,.15);border-radius:8px;background:rgba(255,255,255,.06);color:#cdd8d2;
|
|
614
619
|
transition:background .15s ease,color .15s ease,border-color .15s ease}
|
|
615
620
|
.copy:hover{background:rgba(255,255,255,.13);color:#fff}
|
|
616
621
|
.copy svg{width:15px;height:15px}
|
|
617
|
-
.copy.done{color:#
|
|
622
|
+
.copy.done{color:#5fe0b3;border-color:rgba(95,224,179,.55)}
|
|
618
623
|
.feat{list-style:none;padding:0;margin:0;display:grid;gap:12px}
|
|
619
624
|
.feat li{display:flex;gap:13px;align-items:center;font-size:.97rem;padding:13px 16px}
|
|
620
625
|
.feat li b{color:var(--ink)}
|
|
@@ -626,9 +631,9 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
626
631
|
.flow .node b{font-size:.93rem}
|
|
627
632
|
.flow .node span{font-size:.75rem;color:var(--mut)}
|
|
628
633
|
.flow .wire{flex:1 1 auto;min-width:30px;position:relative;height:2px;margin-top:27px;
|
|
629
|
-
background:linear-gradient(90deg,var(--line),rgba(
|
|
634
|
+
background:linear-gradient(90deg,var(--line),rgba(16,139,103,.45),var(--line))}
|
|
630
635
|
.flow .wire .pkt{position:absolute;top:50%;left:0;width:9px;height:9px;margin:-5px 0 0 -4px;border-radius:50%;
|
|
631
|
-
background:var(--g);box-shadow:0 0 9px 1px rgba(
|
|
636
|
+
background:var(--g);box-shadow:0 0 9px 1px rgba(16,139,103,.7)}
|
|
632
637
|
.flow .wire::after{content:"";position:absolute;right:-1px;top:50%;width:7px;height:7px;margin-top:-4px;
|
|
633
638
|
border-top:2px solid var(--g7);border-right:2px solid var(--g7);transform:rotate(45deg)}
|
|
634
639
|
.flow-cap{color:var(--mut);font-size:.9rem;margin:2px 2px 0;max-width:68ch}
|
|
@@ -638,10 +643,10 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
638
643
|
.flow .node .ic{animation:nodepop 2.4s ease-in-out infinite}
|
|
639
644
|
}
|
|
640
645
|
@media(prefers-reduced-motion:reduce){.flow .wire .pkt{display:none}}
|
|
641
|
-
@keyframes nodepop{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 4px rgba(
|
|
646
|
+
@keyframes nodepop{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 4px rgba(16,139,103,.12)}}
|
|
642
647
|
.btn{display:inline-flex;align-items:center;gap:9px;padding:11px 19px;border-radius:11px;cursor:pointer;
|
|
643
648
|
background:var(--g);color:#fff;text-decoration:none;font-weight:700;font-size:.93rem;
|
|
644
|
-
box-shadow:0 4px 12px -4px rgba(
|
|
649
|
+
box-shadow:0 4px 12px -4px rgba(16,139,103,.5);transition:transform .15s ease,background .15s ease}
|
|
645
650
|
.btn .i{width:18px;height:18px}
|
|
646
651
|
.btn:hover{transform:translateY(-1px);background:var(--btn-hover)}
|
|
647
652
|
.btn.ghost{background:var(--card);color:var(--ink);border:1px solid var(--line);box-shadow:none}
|
|
@@ -652,17 +657,17 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
652
657
|
.nav::-webkit-scrollbar{display:none}
|
|
653
658
|
.nav a{flex:0 0 auto;font-size:.84rem;font-weight:600;color:var(--mut);text-decoration:none;
|
|
654
659
|
padding:7px 13px;border-radius:999px;white-space:nowrap;transition:color .15s ease,background .15s ease}
|
|
655
|
-
.nav a:hover{color:var(--g7);background:rgba(
|
|
656
|
-
.nav a.active{color:var(--g7);background:rgba(
|
|
660
|
+
.nav a:hover{color:var(--g7);background:rgba(16,139,103,.10)}
|
|
661
|
+
.nav a.active{color:var(--g7);background:rgba(16,139,103,.13)}
|
|
657
662
|
.uses{display:grid;gap:14px;grid-template-columns:1fr 1fr;padding:0;margin:0;list-style:none}
|
|
658
663
|
@media(max-width:640px){.uses{grid-template-columns:1fr}}
|
|
659
664
|
.uses li{display:flex;gap:13px;padding:16px 18px;align-items:flex-start;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
|
|
660
|
-
.uses li:hover{transform:translateY(-3px);border-color:rgba(
|
|
665
|
+
.uses li:hover{transform:translateY(-3px);border-color:rgba(16,139,103,.4);box-shadow:0 10px 26px -14px rgba(16,14,40,.4)}
|
|
661
666
|
.uses b{display:block;font-size:.96rem;margin-bottom:2px}
|
|
662
667
|
.uses span{color:var(--mut);font-size:.88rem}
|
|
663
668
|
.card{transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
|
|
664
669
|
.card:hover{transform:translateY(-3px);box-shadow:0 10px 26px -14px rgba(16,14,40,.4)}
|
|
665
|
-
.card:hover,.method:hover{border-color:rgba(
|
|
670
|
+
.card:hover,.method:hover{border-color:rgba(16,139,103,.32)}
|
|
666
671
|
.method{margin-bottom:16px;padding:24px}
|
|
667
672
|
.method>.tag{margin-bottom:4px}
|
|
668
673
|
.msub{color:var(--mut);font-size:.92rem;margin:.5rem 0 1.2rem}
|
|
@@ -670,16 +675,16 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
670
675
|
.steps li{display:flex;gap:14px;align-items:flex-start;position:relative}
|
|
671
676
|
.steps li:not(:last-child)::after{content:"";position:absolute;left:13px;top:30px;bottom:-18px;width:2px;background:var(--line)}
|
|
672
677
|
.steps .n{flex:0 0 auto;width:28px;height:28px;border-radius:50%;color:var(--ic-fg);
|
|
673
|
-
background:rgba(
|
|
678
|
+
background:rgba(16,139,103,.12);border:1px solid var(--line);
|
|
674
679
|
font:800 .85rem/1 system-ui;display:flex;align-items:center;justify-content:center}
|
|
675
680
|
.steps .body{flex:1;min-width:0;font-size:.95rem}
|
|
676
681
|
.steps .body pre{margin-top:9px}
|
|
677
682
|
.steps .body .btn{display:flex;width:fit-content;margin-top:10px}
|
|
678
|
-
code.inl{background:rgba(
|
|
683
|
+
code.inl{background:rgba(16,139,103,.13);color:var(--g7);padding:1px 6px;border-radius:6px;font-size:.85em;font-weight:600;
|
|
679
684
|
overflow-wrap:anywhere;word-break:break-word}
|
|
680
685
|
.note{font-size:.86rem;color:var(--mut);margin-top:10px}
|
|
681
686
|
.note + pre,.note + .codewrap{margin-top:9px}
|
|
682
|
-
.tip{margin-top:16px;background:rgba(
|
|
687
|
+
.tip{margin-top:16px;background:rgba(16,139,103,.06);border:1px solid var(--line);border-radius:12px;padding:13px 15px}
|
|
683
688
|
.tip .note{margin:0}
|
|
684
689
|
details{padding:2px 18px;margin-bottom:11px}
|
|
685
690
|
details summary{cursor:pointer;font-weight:600;padding:15px 0;list-style:none;display:flex;align-items:center;gap:10px}
|
|
@@ -690,7 +695,7 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
690
695
|
details p{color:var(--mut);font-size:.92rem;margin:0 0 16px;padding-left:0}
|
|
691
696
|
.star{margin-top:48px;text-align:center;padding:38px 24px;overflow:hidden;position:relative}
|
|
692
697
|
.star::before{content:"";position:absolute;inset:-40% 0 auto;height:70%;
|
|
693
|
-
background:radial-gradient(closest-side,rgba(
|
|
698
|
+
background:radial-gradient(closest-side,rgba(16,139,103,.10),transparent);pointer-events:none}
|
|
694
699
|
.star h2{margin:0 0 6px;position:relative;display:inline-flex;align-items:center;gap:9px;justify-content:center}
|
|
695
700
|
.star h2 .i{color:var(--g7)}
|
|
696
701
|
.star p{color:var(--mut);max-width:48ch;margin:0 auto 18px;position:relative}
|
|
@@ -699,8 +704,6 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
699
704
|
display:flex;gap:18px;flex-wrap:wrap;align-items:center}
|
|
700
705
|
footer a{color:var(--g7);font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
|
|
701
706
|
footer a:hover{text-decoration:underline}
|
|
702
|
-
.badges{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 4px}
|
|
703
|
-
.badges img{height:20px;border-radius:4px}
|
|
704
707
|
@media(max-width:640px){
|
|
705
708
|
.wrap{padding:30px 15px 56px}
|
|
706
709
|
header{flex-wrap:wrap;gap:12px}
|
|
@@ -757,13 +760,6 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
757
760
|
<a class="btn ghost" href="${GITHUB_URL}">${icon("star")} ${t.ctaStar}</a>
|
|
758
761
|
</div>
|
|
759
762
|
|
|
760
|
-
<div class="badges hero-in">
|
|
761
|
-
<a href="${NPM_URL}" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/webcake-storefront-mcp?label=npm&color=6d5efc" alt="npm version"></a>
|
|
762
|
-
<a href="${NPM_URL}" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/dm/webcake-storefront-mcp?color=8b5cf6" alt="npm downloads"></a>
|
|
763
|
-
<a href="${GITHUB_URL}/blob/main/LICENSE" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
764
|
-
<a href="https://modelcontextprotocol.io" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/Model_Context_Protocol-server-6E56CF" alt="MCP server"></a>
|
|
765
|
-
</div>
|
|
766
|
-
|
|
767
763
|
<nav class="nav" aria-label="${L === "en" ? "Sections" : "Mục lục"}">
|
|
768
764
|
${t.nav.map((n) => `<a href="${n.href}">${n.label}</a>`).join("\n ")}
|
|
769
765
|
</nav>
|
|
@@ -845,8 +841,6 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
845
841
|
<span>Endpoint: <code class="inl">${endpoint}</code></span>
|
|
846
842
|
<a href="${DOCS_URL}">${icon("book")} ${t.footGuide}</a>
|
|
847
843
|
<a href="${GITHUB_URL}">${icon("github")} GitHub</a>
|
|
848
|
-
<a href="${NPM_URL}">${icon("package")} npm</a>
|
|
849
|
-
<a href="${selfPath === "/" ? "/health" : "/health"}">Health</a>
|
|
850
844
|
<a href="/privacy">Privacy</a>
|
|
851
845
|
<a href="/terms">Terms</a>
|
|
852
846
|
</footer>
|
|
@@ -921,10 +915,10 @@ export function ogImageSvg() {
|
|
|
921
915
|
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">
|
|
922
916
|
<defs>
|
|
923
917
|
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
|
|
924
|
-
<stop stop-color="#
|
|
918
|
+
<stop stop-color="#0f1714"/><stop offset="1" stop-color="#0c241c"/>
|
|
925
919
|
</linearGradient>
|
|
926
920
|
<radialGradient id="glow" cx="0" cy="0" r="1" gradientTransform="translate(960 70) rotate(130) scale(620)" gradientUnits="userSpaceOnUse">
|
|
927
|
-
<stop stop-color="#
|
|
921
|
+
<stop stop-color="#108B67" stop-opacity="0.40"/><stop offset="1" stop-color="#108B67" stop-opacity="0"/>
|
|
928
922
|
</radialGradient>
|
|
929
923
|
</defs>
|
|
930
924
|
<rect width="1200" height="630" fill="url(#bg)"/>
|
|
@@ -933,24 +927,24 @@ export function ogImageSvg() {
|
|
|
933
927
|
<svg x="0" y="0" width="80" height="80" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
934
928
|
<rect width="32" height="32" rx="7" fill="url(#sg2)"/>
|
|
935
929
|
<defs><linearGradient id="sg2" x1="0" y1="0" x2="1" y2="1">
|
|
936
|
-
<stop offset="0%" stop-color="#
|
|
937
|
-
<stop offset="100%" stop-color="#
|
|
930
|
+
<stop offset="0%" stop-color="#108B67"/>
|
|
931
|
+
<stop offset="100%" stop-color="#14a87c"/>
|
|
938
932
|
</linearGradient></defs>
|
|
939
933
|
<text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="17" fill="white">S</text>
|
|
940
934
|
<circle cx="24" cy="9" r="4" fill="#FFD591"/>
|
|
941
935
|
</svg>
|
|
942
936
|
<text x="100" y="42" fill="#ffffff" font-size="40" font-weight="800" letter-spacing="-1">WebCake Storefront MCP</text>
|
|
943
|
-
<text x="100" y="74" fill="#
|
|
937
|
+
<text x="100" y="74" fill="#5fe0b3" font-size="22" font-weight="600">Tạo website bán hàng chỉ bằng cách trò chuyện · Build your store just by chatting</text>
|
|
944
938
|
</g>
|
|
945
939
|
<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>
|
|
946
940
|
<text x="90" y="380" fill="#ffffff" font-size="64" font-weight="800" letter-spacing="-2">AI dựng trang, kiểm tra,</text>
|
|
947
|
-
<text x="90" y="460" fill="#
|
|
948
|
-
<text x="90" y="534" fill="#
|
|
941
|
+
<text x="90" y="460" fill="#108B67" font-size="64" font-weight="800" letter-spacing="-2">đăng lên là xong.</text>
|
|
942
|
+
<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>
|
|
949
943
|
<g transform="translate(90 560)">
|
|
950
|
-
<rect width="540" height="52" rx="12" fill="#
|
|
951
|
-
<text x="270" y="34" fill="#ffffff" font-size="22" font-weight="700" text-anchor="middle">
|
|
944
|
+
<rect width="540" height="52" rx="12" fill="#108B67"/>
|
|
945
|
+
<text x="270" y="34" fill="#ffffff" font-size="22" font-weight="700" text-anchor="middle">store.toolvn.io.vn</text>
|
|
952
946
|
</g>
|
|
953
|
-
<text x="1110" y="600" fill="#
|
|
947
|
+
<text x="1110" y="600" fill="#5a7269" font-size="22" font-weight="600" text-anchor="end">github.com/vuluu2k/webcake-storefront-mcp</text>
|
|
954
948
|
</svg>`;
|
|
955
949
|
}
|
|
956
950
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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",
|
|
@@ -35,14 +35,12 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
38
|
-
"better-sqlite3": "^12.8.0",
|
|
39
38
|
"mongodb": "^6.21.0",
|
|
40
39
|
"node-html-parser": "^8.0.2",
|
|
41
40
|
"sharp": "^0.34.5",
|
|
42
41
|
"zod": "^3.25.0"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
46
44
|
"@types/node": "^26.0.0",
|
|
47
45
|
"typescript": "^6.0.3"
|
|
48
46
|
},
|