webcake-storefront-mcp 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +25 -0
- package/dist/changelog.json +14 -14
- package/dist/enums.js +46 -0
- package/dist/guides.js +111 -49
- package/dist/tools/apps.js +37 -4
- package/dist/tools/automation.js +75 -5
- package/dist/web-guide.js +101 -87
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -363,6 +363,13 @@ export class WebcakeCmsApi {
|
|
|
363
363
|
getApp(type) {
|
|
364
364
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/applications/subcriptions/get_app`, { query: { type } });
|
|
365
365
|
}
|
|
366
|
+
/** Install (register) an application on the site. type = Enum.Application value
|
|
367
|
+
* (e.g. automation=2, send_email=7). Body: { site_id, type, is_active }. */
|
|
368
|
+
registerApp(type, is_active = true) {
|
|
369
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/applications/subcriptions/register`, {
|
|
370
|
+
body: { site_id: this.siteId, type, is_active },
|
|
371
|
+
});
|
|
372
|
+
}
|
|
366
373
|
// ── Promotions ──
|
|
367
374
|
listPromotions(query) {
|
|
368
375
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/promotion_advance/all`, { query });
|
|
@@ -425,6 +432,24 @@ export class WebcakeCmsApi {
|
|
|
425
432
|
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/multilingual/update_global_source_contents`, { body: params });
|
|
426
433
|
}
|
|
427
434
|
// ── Automation ──
|
|
435
|
+
/** List the site's automations (id, name, rule/trigger, status). Use this to find the
|
|
436
|
+
* automation_id to pass to send_mail. Returns { data: [...], total_entries, ... }. */
|
|
437
|
+
listAutomations(query) {
|
|
438
|
+
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/automations/all`, { query });
|
|
439
|
+
}
|
|
440
|
+
createAutomation(automationAttrs) {
|
|
441
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/automations/create`, {
|
|
442
|
+
body: { automation_attrs: { ...automationAttrs, site_id: this.siteId } },
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
updateAutomation(automationAttrs) {
|
|
446
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/automations/update`, {
|
|
447
|
+
body: { automation_attrs: automationAttrs },
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
deleteAutomations(ids) {
|
|
451
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/automations/delete`, { body: { ids } });
|
|
452
|
+
}
|
|
428
453
|
sendMail(params) {
|
|
429
454
|
return this.request("POST", `/api/v1/cms_function/${this.siteId}/application/automation/send_mail`, { body: params });
|
|
430
455
|
}
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.8.0",
|
|
4
|
+
"d": "23/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New list_automations tool returns each automation's id, name, status, and trigger info so agents can find the automation_id required by send_mail or…",
|
|
7
|
+
"vi": "Tool mới list_automations trả về id, name, status và thông tin trigger của từng automation, giúp agent tìm được automation_id cần truyền vào…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.7.0",
|
|
11
|
+
"d": "23/06/2026",
|
|
12
|
+
"type": "Changed",
|
|
13
|
+
"en": "The web guide landing page (served via the serve command) is reframed to lead on full-storefront creation: the hero, meta description, \"What you…",
|
|
14
|
+
"vi": "Trang web guide (phục vụ qua lệnh serve) được viết lại để làm nổi bật việc xây dựng toàn bộ cửa hàng: hero, meta description, thư viện \"Bạn dựng…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.6.0",
|
|
4
18
|
"d": "23/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Changed",
|
|
27
41
|
"en": "The install command's interactive wizard now presents numbered choices with ANSI colour output and a completion summary.",
|
|
28
42
|
"vi": "Trình hướng dẫn tương tác của lệnh install nay hiển thị các lựa chọn được đánh số kèm màu ANSI và thông báo tóm tắt sau khi hoàn tất."
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.3.0",
|
|
32
|
-
"d": "23/06/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "New create_site tool creates a brand-new storefront site for the current account (seeded with sample products, categories, and a blog), optionally…",
|
|
35
|
-
"vi": "Tool mới create_site tạo một site storefront hoàn toàn mới cho tài khoản hiện tại (kèm sản phẩm, danh mục và blog mẫu), tự động chuyển sang site vừa…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.2.0",
|
|
39
|
-
"d": "23/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "upload_images replaces upload_image with batch support (1–20 sources per call), parallel uploads, and a dry_run mode that previews what would be…",
|
|
42
|
-
"vi": "upload_images thay thế upload_image với khả năng xử lý hàng loạt (1–20 nguồn mỗi lần gọi), tải song song, và chế độ dry_run cho phép xem trước những…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/enums.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// WebCake enum reference, mirrored from builderx_api so the AI knows what the numeric
|
|
2
|
+
// codes / string values mean. Sources:
|
|
3
|
+
// - Enum.Application (lib/builderx_api/enum.ex) — application/app types
|
|
4
|
+
// - Automation schema (lib/builderx_api/automations/automation.ex) — type/status
|
|
5
|
+
// - Enum.PageType (page kinds) — kept here for one-stop reference
|
|
6
|
+
/** Application types (the `type` used by get_app / install_app / register). */
|
|
7
|
+
export const APP_TYPES = {
|
|
8
|
+
product_review: 0,
|
|
9
|
+
articles_review: 1,
|
|
10
|
+
automation: 2,
|
|
11
|
+
telegram: 3,
|
|
12
|
+
affiliates: 4,
|
|
13
|
+
multilingual: 5,
|
|
14
|
+
appointment: 6,
|
|
15
|
+
send_email: 7,
|
|
16
|
+
botcake: 8,
|
|
17
|
+
sale_channel: 9,
|
|
18
|
+
product_design: 10,
|
|
19
|
+
auth_otp: 11,
|
|
20
|
+
personal_product_design: 12,
|
|
21
|
+
course: 14,
|
|
22
|
+
zalo_mini_app: 15,
|
|
23
|
+
cms: 16,
|
|
24
|
+
recaptcha: 17,
|
|
25
|
+
pwa: 18,
|
|
26
|
+
};
|
|
27
|
+
export const APP_NAMES = Object.keys(APP_TYPES);
|
|
28
|
+
/** name -> code and code -> name helpers. */
|
|
29
|
+
export const APP_TYPE_BY_NAME = { ...APP_TYPES };
|
|
30
|
+
export const APP_NAME_BY_TYPE = Object.fromEntries(Object.entries(APP_TYPES).map(([k, v]) => [v, k]));
|
|
31
|
+
/** Automation.status values. */
|
|
32
|
+
export const AUTOMATION_STATUS = ["ACTIVE", "INACTIVE"];
|
|
33
|
+
/** Automation.type — user-built automations default to CUSTOM. */
|
|
34
|
+
export const AUTOMATION_TYPE_DEFAULT = "CUSTOM";
|
|
35
|
+
/** Page kinds (Enum.PageType) — numeric value stored on a page. */
|
|
36
|
+
export const PAGE_TYPES = {
|
|
37
|
+
main: 1,
|
|
38
|
+
store: 2,
|
|
39
|
+
member: 3,
|
|
40
|
+
blog: 4,
|
|
41
|
+
custom: 5,
|
|
42
|
+
error: 6,
|
|
43
|
+
maintain: 7,
|
|
44
|
+
};
|
|
45
|
+
/** A compact human-readable summary, handy to surface in tool output. */
|
|
46
|
+
export const APP_TYPE_REFERENCE = APP_NAMES.map((n) => `${APP_TYPES[n]}=${n}`).join(", ");
|
package/dist/guides.js
CHANGED
|
@@ -2,65 +2,127 @@ export const HTTP_FUNCTION_GUIDE = `
|
|
|
2
2
|
# HTTP Function Guide
|
|
3
3
|
|
|
4
4
|
## Syntax
|
|
5
|
-
export const [method]_[FunctionName] = (request) => { return result; }
|
|
6
|
-
- Method: lowercase (get, post, put, patch, delete)
|
|
7
|
-
- FunctionName:
|
|
5
|
+
export const [method]_[FunctionName] = async (request) => { return result; }
|
|
6
|
+
- Method: lowercase (get, post, put, patch, delete) — picked from the export-name prefix.
|
|
7
|
+
- FunctionName: keep it stable; it becomes the endpoint name.
|
|
8
|
+
- Make it async; the return value is JSON-serialized and sent back to the caller.
|
|
8
9
|
- Examples: get_Products, post_CreateOrder, delete_RemoveItem
|
|
9
10
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
- request.
|
|
13
|
-
- request.
|
|
14
|
-
- request.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
##
|
|
11
|
+
## The request argument
|
|
12
|
+
Your function is called with ONE object: { params, customer, site_id, account, data }.
|
|
13
|
+
- request.params — the arguments the caller passed (query string for GET, body for POST/…).
|
|
14
|
+
- request.customer — the logged-in storefront customer (when authenticated), else {}. Fields: id, name, email, first_name, last_name, phone_number, avatar.
|
|
15
|
+
- request.account — the logged-in admin account (when called by an admin), else {}.
|
|
16
|
+
- request.site_id — the current site id (string).
|
|
17
|
+
- request.data — extra request data (usually {}).
|
|
18
|
+
IMPORTANT: pass request through to the @webcake/* module functions (their first arg).
|
|
19
|
+
|
|
20
|
+
## Endpoint after deploy
|
|
21
|
+
ANY method → /api/v1/{site_id}/_functions/{FunctionName}
|
|
22
|
+
The caller receives your return value as data.result. From the storefront, the
|
|
23
|
+
webcake-fn client (api.method_FunctionName(params)) returns that result directly.
|
|
24
|
+
|
|
25
|
+
## webcake-data — Database SDK (built-in)
|
|
20
26
|
import { DBConnection } from 'webcake-data';
|
|
21
|
-
const db = new DBConnection();
|
|
22
|
-
const Model = db.model('
|
|
27
|
+
const db = new DBConnection(); // auto-uses the sandbox global site/token
|
|
28
|
+
const Model = db.model('collection_name');
|
|
23
29
|
|
|
24
|
-
### CRUD
|
|
25
|
-
- Model.create(
|
|
26
|
-
- Model.insertMany([...])
|
|
27
|
-
- Model.find(filter)
|
|
30
|
+
### Model CRUD (all async unless noted)
|
|
31
|
+
- Model.create(doc) → created doc
|
|
32
|
+
- Model.insertMany([doc, ...]) → array
|
|
33
|
+
- Model.find(filter) → QueryBuilder (NOT a promise — chain then .exec()/await)
|
|
28
34
|
- Model.findOne(filter, { select, sort, populate })
|
|
29
|
-
- Model.findById(id)
|
|
30
|
-
- Model.updateOne(filter, update)
|
|
31
|
-
- Model.findByIdAndUpdate(id, update)
|
|
35
|
+
- Model.findById(id, { select, populate })
|
|
36
|
+
- Model.updateOne(filter, update) → { acknowledged, matchedCount, modifiedCount }
|
|
37
|
+
- Model.findByIdAndUpdate(id, update, { new: true })
|
|
38
|
+
- Model.findOneAndUpdate(filter, update)
|
|
32
39
|
- Model.updateMany(filter, update)
|
|
33
|
-
- Model.deleteOne(filter)
|
|
34
|
-
- Model.findByIdAndDelete(id)
|
|
40
|
+
- Model.deleteOne(filter) → { acknowledged, deletedCount }
|
|
41
|
+
- Model.findByIdAndDelete(id) / Model.findOneAndDelete(filter)
|
|
35
42
|
- Model.deleteMany(filter)
|
|
36
|
-
- Model.countDocuments(filter)
|
|
37
|
-
- Model.exists(filter)
|
|
38
|
-
|
|
39
|
-
### QueryBuilder
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
- Model.countDocuments(filter) → number
|
|
44
|
+
- Model.exists(filter) → boolean
|
|
45
|
+
|
|
46
|
+
### QueryBuilder (from Model.find())
|
|
47
|
+
Chain then terminate with .exec() (or just await the chain):
|
|
48
|
+
Model.find().where('age').gte(25).lte(40).in('role',['admin']).like('email','%@ex.com')
|
|
49
|
+
.sort({ age:-1 }).limit(20).skip(10).select('name email').exec()
|
|
50
|
+
Operators: where, eq, ne, gt, gte, lt, lte, in, nin, between, like, sort, limit, skip, select, populate.
|
|
51
|
+
|
|
52
|
+
### Populate (join another collection)
|
|
53
|
+
Model.find().populate({
|
|
54
|
+
field:'posts', table:'posts', referenceField:'user_id',
|
|
55
|
+
select:'title', where:{}, sort:{ created_at:-1 }, limit:5, skip:0, justOne:false
|
|
56
|
+
}).exec()
|
|
57
|
+
|
|
58
|
+
## Built-in @webcake/* modules (first arg is always request; they auth via global.token)
|
|
59
|
+
Thin wrappers over the backend's /cms_function/{site_id}/... endpoints. Pass request so
|
|
60
|
+
they pick up site_id. Below is EXACTLY what each call sends to the backend + what it returns.
|
|
61
|
+
|
|
62
|
+
- '@webcake/article' (backend: /cms_function/{site}/blog/article…)
|
|
63
|
+
findArticleById(request, id, opts?)
|
|
64
|
+
GET .../blog/article/{id}?opts=<json> → the article object ({} if not found)
|
|
65
|
+
findArticle(request, payload, opts?)
|
|
66
|
+
GET .../blog/article/all?<payload>&filters=<json>&opts=<json>
|
|
67
|
+
payload: { filters?:{...}, page?, limit? } → { data:[...], ... }
|
|
68
|
+
createArticle(request, data) POST .../blog/article → full response
|
|
69
|
+
data the backend accepts: { name (required), summary, content (HTML),
|
|
70
|
+
images: string[] (hosted URLs), tags: string[], status_approval,
|
|
71
|
+
render_inserted_at, render_expired_at }. slug is auto-generated; the article is
|
|
72
|
+
auto-filed under the default blog category (this endpoint takes NO category_id —
|
|
73
|
+
use the create_article MCP tool if you need explicit category linkage).
|
|
74
|
+
creator_id/customer_id come from the authenticated request.
|
|
75
|
+
updateArticleById(request, id, data) PATCH .../blog/article/{id} (same fields) → response
|
|
76
|
+
deleteArticleById(request, id) DELETE .../blog/article/{id} → response
|
|
77
|
+
|
|
78
|
+
- '@webcake/customer' (backend: /cms_function/{site}/customer/…) → customer object ({} if none)
|
|
79
|
+
findCustomerById(request, id) GET .../customer/identity/{id}
|
|
80
|
+
findCustomerByPhone(request, phone) GET .../customer/phone/{phone}
|
|
81
|
+
findCustomerByEmail(request, email) GET .../customer/email/{email}
|
|
82
|
+
|
|
83
|
+
- '@webcake/promotion' (backend: /cms_function/{site}/promotion/add_bonus)
|
|
84
|
+
addBonus(request, data) POST add_bonus → response.
|
|
85
|
+
It ADDS REWARD POINTS to a customer. data: { customer_id (required),
|
|
86
|
+
point (number, required), message? (defaults "Bạn được cộng điểm") }.
|
|
87
|
+
|
|
88
|
+
- '@webcake/token' (backend: /external/oauth/token)
|
|
89
|
+
getAccessToken(request) → access_token string (throws if none).
|
|
90
|
+
Needs request.x_storecake_refresh_token present (sent as x-storecake-refresh-token).
|
|
91
|
+
|
|
92
|
+
- '@webcake/app/automation' (backend: /cms_function/{site}/application/automation/send_mail)
|
|
93
|
+
sendMail(request, automationId, data) → response (throws if send fails).
|
|
94
|
+
Sends body { automation_id, data }. automation_id MUST be a valid UUID of an
|
|
95
|
+
automation set up on the site — find it with the MCP tool list_automations.
|
|
96
|
+
data is the payload passed into that automation/email template.
|
|
97
|
+
|
|
98
|
+
## Sandbox globals (no import)
|
|
99
|
+
- fetch(url, options) — HTTP requests; response.ok/status/text()/json().
|
|
100
|
+
- URLSearchParams — build/parse query strings.
|
|
101
|
+
- console.log / warn / error — captured in debug mode (returned in data.logs).
|
|
102
|
+
- encodeURIComponent / decodeURIComponent / encodeURI / decodeURI
|
|
60
103
|
- global.domain, global.siteId, global.token, global.headers
|
|
104
|
+
- Standard JS: JSON, Math, Date, Object, Array, String, Number, Map, Set, Promise, Error.
|
|
105
|
+
NOT available: require()/import at runtime, Buffer, crypto, fs, process, setTimeout/setInterval, eval/Function.
|
|
106
|
+
|
|
107
|
+
## Limits
|
|
108
|
+
Runs sandboxed: ~4 MB memory, ~30 s timeout. The return value MUST be JSON-serializable.
|
|
61
109
|
|
|
62
|
-
## Cron
|
|
110
|
+
## Cron jobs (jobs_config JSON)
|
|
63
111
|
{ "jobs": [{ "functionLocation": "backend/http_function", "functionName": "myFunc", "executionConfig": { "cronExpression": "0 2 * * *" } }] }
|
|
112
|
+
|
|
113
|
+
## Example
|
|
114
|
+
import { DBConnection } from 'webcake-data';
|
|
115
|
+
import { findCustomerById } from '@webcake/customer';
|
|
116
|
+
export const post_RecentOrders = async (request) => {
|
|
117
|
+
const { params, customer } = request;
|
|
118
|
+
const db = new DBConnection();
|
|
119
|
+
const orders = await db.model('orders')
|
|
120
|
+
.find().where('customer_id').eq(customer.id || params.customer_id)
|
|
121
|
+
.sort({ created_at:-1 }).limit(10)
|
|
122
|
+
.populate({ field:'items', table:'order_items', referenceField:'order_id', limit:50 })
|
|
123
|
+
.exec();
|
|
124
|
+
return { count: orders.length, orders };
|
|
125
|
+
};
|
|
64
126
|
`;
|
|
65
127
|
export const CUSTOM_CODE_GUIDE = `
|
|
66
128
|
# Custom Code Guide
|
package/dist/tools/apps.js
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { APP_TYPES, APP_NAMES, APP_TYPE_REFERENCE, APP_NAME_BY_TYPE } from "../enums.js";
|
|
2
3
|
export function registerAppTools(server, api, handle) {
|
|
3
|
-
server.tool("list_apps",
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
server.tool("list_apps", `List the site's installed applications (type, status, settings).
|
|
5
|
+
App type codes: ${APP_TYPE_REFERENCE}.`, {}, () => handle(async () => {
|
|
6
|
+
const res = await api.listApps();
|
|
7
|
+
const raw = res?.data ?? res ?? [];
|
|
8
|
+
const list = Array.isArray(raw) ? raw : raw?.data || [];
|
|
9
|
+
return {
|
|
10
|
+
apps: list.map((a) => ({
|
|
11
|
+
type: a.type,
|
|
12
|
+
type_name: APP_NAME_BY_TYPE[a.type] ?? undefined,
|
|
13
|
+
status: a.status,
|
|
14
|
+
id: a.id,
|
|
15
|
+
})),
|
|
16
|
+
app_types: APP_TYPES,
|
|
17
|
+
};
|
|
18
|
+
}));
|
|
19
|
+
server.tool("get_app", `Get one installed app by its type code (returns null if not installed).
|
|
20
|
+
App type codes: ${APP_TYPE_REFERENCE}. You may pass the number or the name.`, {
|
|
21
|
+
type: z.union([z.number(), z.string()]).describe(`App type — a code (e.g. 2) or a name (e.g. "automation"). Codes: ${APP_TYPE_REFERENCE}`),
|
|
22
|
+
}, ({ type }) => handle(() => {
|
|
23
|
+
const code = typeof type === "string" && type in APP_TYPES ? APP_TYPES[type] : type;
|
|
24
|
+
return api.getApp(code);
|
|
25
|
+
}));
|
|
26
|
+
server.tool("install_app", `Install (register) an application on the current site so its features become usable.
|
|
27
|
+
For example, automations need the "automation" app installed first.
|
|
28
|
+
App types: ${APP_TYPE_REFERENCE}.`, {
|
|
29
|
+
app: z.enum(APP_NAMES).describe("App to install (by name)"),
|
|
30
|
+
is_active: z.boolean().default(true).describe("Activate the app on install"),
|
|
31
|
+
}, ({ app, is_active }) => handle(async () => {
|
|
32
|
+
const type = APP_TYPES[app];
|
|
33
|
+
const existing = await api.getApp(type).catch(() => null);
|
|
34
|
+
if (existing?.app ?? existing?.data) {
|
|
35
|
+
return { app, type, already_installed: true };
|
|
36
|
+
}
|
|
37
|
+
await api.registerApp(type, is_active);
|
|
38
|
+
return { app, type, installed: true };
|
|
39
|
+
}));
|
|
7
40
|
}
|
package/dist/tools/automation.js
CHANGED
|
@@ -1,8 +1,78 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { APP_TYPES } from "../enums.js";
|
|
2
3
|
export function registerAutomationTools(server, api, handle) {
|
|
3
|
-
server.tool("
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
server.tool("list_automations", `List the site's automations so you can find an automation_id (e.g. to call send_mail, or to trigger from an HTTP function via @webcake/app/automation sendMail).
|
|
5
|
+
Returns each automation's id, name, status and trigger info. Filter with 'term'. The id is what send_mail / the cms automation flow needs.`, {
|
|
6
|
+
term: z.string().optional().describe("Search by automation name"),
|
|
7
|
+
page: z.number().default(1).describe("Page number"),
|
|
8
|
+
limit: z.number().default(50).describe("Items per page"),
|
|
9
|
+
}, ({ term, page, limit }) => handle(async () => {
|
|
10
|
+
const res = await api.listAutomations({ ...(term ? { term } : {}), page, limit });
|
|
11
|
+
// Response shape: { automations: { data:[...], total_entries, page, limit }, ... }
|
|
12
|
+
const body = res?.automations || res?.data || res;
|
|
13
|
+
const raw = body?.data || body || [];
|
|
14
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
15
|
+
const automations = list.map((a) => ({
|
|
16
|
+
id: a.id,
|
|
17
|
+
name: a.name,
|
|
18
|
+
status: a.status ?? (a.is_completed ? "completed" : undefined),
|
|
19
|
+
is_completed: a.is_completed,
|
|
20
|
+
// surface the trigger so the agent can tell which automation does what
|
|
21
|
+
trigger: a.rule?.trigger?.triggerType || a.rule?.trigger?.triggerKey || a.trigger_type || undefined,
|
|
22
|
+
updated_at: a.updated_at,
|
|
23
|
+
}));
|
|
24
|
+
return {
|
|
25
|
+
automations,
|
|
26
|
+
total: body?.total_entries ?? automations.length,
|
|
27
|
+
page,
|
|
28
|
+
hint: "Pass an automation's id as send_mail.automation_id (or to sendMail(request, automationId, data) inside an HTTP function).",
|
|
29
|
+
};
|
|
30
|
+
}));
|
|
31
|
+
// Automation lives behind the "Automation" application (Enum.Application automation = 2).
|
|
32
|
+
// Creating an automation requires that app installed, so we ensure it first.
|
|
33
|
+
const AUTOMATION_APP = APP_TYPES.automation;
|
|
34
|
+
async function ensureAutomationApp() {
|
|
35
|
+
const res = await api.getApp(AUTOMATION_APP).catch(() => null);
|
|
36
|
+
const app = res?.app ?? res?.data ?? null;
|
|
37
|
+
if (app)
|
|
38
|
+
return { installed: true, just_installed: false };
|
|
39
|
+
await api.registerApp(AUTOMATION_APP, true);
|
|
40
|
+
return { installed: true, just_installed: true };
|
|
41
|
+
}
|
|
42
|
+
server.tool("create_automation", `Create an automation. Checks the Automation app is installed first and installs it if needed.
|
|
43
|
+
An automation = { name, type, status, rule }. 'rule' holds the trigger + actions (a map; shape depends on the trigger). Returns the new automation id (use it with send_mail for cms-triggered email).`, {
|
|
44
|
+
name: z.string().describe("Automation name"),
|
|
45
|
+
description: z.string().optional().describe("Description"),
|
|
46
|
+
type: z.string().default("CUSTOM").describe("Automation type (default CUSTOM)"),
|
|
47
|
+
status: z.enum(["ACTIVE", "INACTIVE"]).default("ACTIVE").describe("ACTIVE to run it, INACTIVE to keep it off"),
|
|
48
|
+
rule: z.record(z.any()).default({}).describe("Trigger + actions map. Leave {} for a blank automation you wire up later."),
|
|
49
|
+
}, ({ name, description, type, status, rule }) => handle(async () => {
|
|
50
|
+
const app = await ensureAutomationApp();
|
|
51
|
+
const res = await api.createAutomation({ name, ...(description ? { description } : {}), type, status, rule });
|
|
52
|
+
const a = res?.automation ?? res?.data ?? res;
|
|
53
|
+
return { success: true, automation_id: a?.id ?? null, name, status, app, hint: "Pass automation_id to send_mail to trigger it." };
|
|
54
|
+
}));
|
|
55
|
+
server.tool("update_automation", "Update an automation by id. Pass only the fields to change (name, description, type, status, rule).", {
|
|
56
|
+
id: z.string().describe("Automation id"),
|
|
57
|
+
name: z.string().optional(),
|
|
58
|
+
description: z.string().optional(),
|
|
59
|
+
type: z.string().optional(),
|
|
60
|
+
status: z.enum(["ACTIVE", "INACTIVE"]).optional(),
|
|
61
|
+
rule: z.record(z.any()).optional().describe("Replace the trigger + actions map"),
|
|
62
|
+
}, ({ id, ...fields }) => handle(async () => {
|
|
63
|
+
const res = await api.updateAutomation({ id, ...fields });
|
|
64
|
+
const a = res?.automation ?? res?.data ?? res;
|
|
65
|
+
return { success: true, automation_id: a?.id ?? id, updated: Object.keys(fields) };
|
|
66
|
+
}));
|
|
67
|
+
server.tool("delete_automation", "Delete one or more automations by id.", {
|
|
68
|
+
ids: z.array(z.string()).describe("Automation ids to delete"),
|
|
69
|
+
}, ({ ids }) => handle(async () => {
|
|
70
|
+
await api.deleteAutomations(ids);
|
|
71
|
+
return { success: true, deleted: ids };
|
|
72
|
+
}));
|
|
73
|
+
server.tool("send_mail", `Trigger a CMS automation to send an email (same endpoint @webcake/app/automation sendMail uses).
|
|
74
|
+
Requires the automation_id — get it from list_automations. 'data' is the payload passed to that automation/email template.`, {
|
|
75
|
+
automation_id: z.string().describe("Automation id (a UUID) — from list_automations"),
|
|
76
|
+
data: z.record(z.any()).default({}).describe("Data object passed into the automation (e.g. recipient, variables for the email template)"),
|
|
77
|
+
}, ({ automation_id, data }) => handle(() => api.sendMail({ automation_id, data })));
|
|
8
78
|
}
|
package/dist/web-guide.js
CHANGED
|
@@ -79,13 +79,13 @@ function tile(name) {
|
|
|
79
79
|
const META = {
|
|
80
80
|
vi: {
|
|
81
81
|
title: "WebCake Storefront — Tạo website bán hàng chỉ bằng cách trò chuyện",
|
|
82
|
-
desc: "Bạn nói ý tưởng, trợ lý AI dựng
|
|
82
|
+
desc: "Bạn nói ý tưởng, trợ lý AI dựng cả cửa hàng — tạo sản phẩm, danh mục, trang bán hàng và bài viết có ảnh — rồi xuất bản. Không cần kéo-thả hay biết lập trình. Kết nối Claude, Cursor, Windsurf với cửa hàng WebCake/StoreCake của bạn.",
|
|
83
83
|
keywords: "WebCake, StoreCake, storefront, AI, Claude, Cursor, Windsurf, tạo trang bán hàng, website bán hàng, no-code, bán hàng online, tạo trang bằng AI",
|
|
84
84
|
locale: "vi_VN",
|
|
85
85
|
},
|
|
86
86
|
en: {
|
|
87
87
|
title: "WebCake Storefront — Build your online store just by chatting",
|
|
88
|
-
desc: "Tell your AI assistant
|
|
88
|
+
desc: "Tell your AI assistant your idea and it builds an entire store — creating products, categories, shop pages and blog posts with images — then publishes. No dragging blocks, no coding. Works with Claude, Cursor, Windsurf and more.",
|
|
89
89
|
keywords: "WebCake, StoreCake, storefront, AI, Claude, Cursor, Windsurf, AI page builder, no-code, e-commerce, online store builder",
|
|
90
90
|
locale: "en_US",
|
|
91
91
|
},
|
|
@@ -139,11 +139,11 @@ const FAQ = {
|
|
|
139
139
|
};
|
|
140
140
|
const T = {
|
|
141
141
|
vi: {
|
|
142
|
-
sub: "
|
|
142
|
+
sub: "Dựng cả một cửa hàng online — sản phẩm, danh mục, trang, bài viết — chỉ bằng cách trò chuyện. Không cần biết lập trình.",
|
|
143
143
|
running: "Đang hoạt động",
|
|
144
|
-
leadPre: "Bạn nói
|
|
145
|
-
leadGrad: "đăng lên là
|
|
146
|
-
leadPost: ". Không kéo-thả, không học gì thêm —
|
|
144
|
+
leadPre: "Bạn nói ý tưởng, trợ lý AI dựng nguyên một cửa hàng trên WebCake — tạo sản phẩm, danh mục và trang bán hàng có ảnh — bạn xem trước rồi ",
|
|
145
|
+
leadGrad: "đăng lên là bán",
|
|
146
|
+
leadPost: ". Không kéo-thả, không học gì thêm — nhanh hơn thuê người làm web cả tuần.",
|
|
147
147
|
ctaStart: "Bắt đầu ngay",
|
|
148
148
|
ctaStar: "Tặng sao trên GitHub",
|
|
149
149
|
flowH2: "Chỉ 4 bước đơn giản",
|
|
@@ -157,67 +157,67 @@ const T = {
|
|
|
157
157
|
howH2: "Tại sao dùng được ngay, không lo hỏng",
|
|
158
158
|
how: [
|
|
159
159
|
{
|
|
160
|
-
icon: "
|
|
161
|
-
t: "
|
|
162
|
-
d: "Trợ lý
|
|
160
|
+
icon: "cart",
|
|
161
|
+
t: "Dữ liệu thật, không phải shop rỗng",
|
|
162
|
+
d: "Trợ lý tạo luôn sản phẩm, danh mục và bài viết kèm ảnh — nên lưới sản phẩm, danh mục, blog hiện nội dung thật ngay, trông như một shop đang chạy.",
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
icon: "check2",
|
|
166
|
-
t: "
|
|
167
|
-
d: "
|
|
166
|
+
t: "Chuẩn trên mọi thiết bị",
|
|
167
|
+
d: "Trang dựng đúng mô hình lưới của WebCake và tự tối ưu cho desktop, laptop, tablet và điện thoại — không lo vỡ layout.",
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
icon: "shield",
|
|
171
171
|
t: "An toàn — luôn xem trước khi lưu",
|
|
172
|
-
d: "Mọi thay đổi đều
|
|
172
|
+
d: "Mọi thay đổi đều hiển thị để bạn xem trước. Chưa xác nhận thì cửa hàng chưa bị đụng tới — bạn hoàn toàn kiểm soát.",
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
icon: "edit",
|
|
176
|
-
t: "
|
|
177
|
-
d: "Nói \"đổi nút sang
|
|
176
|
+
t: "Chỉnh đúng chỗ, xuất bản tức thì",
|
|
177
|
+
d: "Nói \"đổi nút sang tím\" hay \"thêm phần đánh giá\" — chỉ chỗ đó đổi. Ưng rồi bảo \"đăng lên\" là cửa hàng live ngay.",
|
|
178
178
|
},
|
|
179
179
|
],
|
|
180
|
-
buildH2: "Bạn
|
|
180
|
+
buildH2: "Bạn dựng được cả một cửa hàng",
|
|
181
181
|
uses: [
|
|
182
182
|
{
|
|
183
|
-
icon: "
|
|
184
|
-
t: "
|
|
185
|
-
e: '"Tạo
|
|
183
|
+
icon: "server",
|
|
184
|
+
t: "Cả cửa hàng từ con số 0",
|
|
185
|
+
e: '"Tạo cho tôi cửa hàng thời trang mới — vài danh mục, ít sản phẩm có ảnh và giá, rồi dựng trang chủ."',
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
|
-
icon: "
|
|
189
|
-
t: "
|
|
190
|
-
e: '"
|
|
188
|
+
icon: "cart",
|
|
189
|
+
t: "Sản phẩm & danh mục có ảnh",
|
|
190
|
+
e: '"Thêm 6 sản phẩm áo thun vào danh mục \"Áo thun\" — kèm ảnh, giá và mô tả."',
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
|
-
icon: "
|
|
194
|
-
t: "Trang
|
|
195
|
-
e: '"Tạo trang
|
|
193
|
+
icon: "window",
|
|
194
|
+
t: "Trang chủ bán hàng",
|
|
195
|
+
e: '"Tạo trang chủ — banner, lưới danh mục, sản phẩm nổi bật và bài viết mới."',
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
|
-
icon: "
|
|
199
|
-
t: "
|
|
200
|
-
e: '"
|
|
198
|
+
icon: "layers",
|
|
199
|
+
t: "Bộ trang bán hàng đầy đủ",
|
|
200
|
+
e: '"Dựng đủ bộ: trang sản phẩm, danh mục, giỏ hàng, thanh toán và cảm ơn."',
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
|
-
icon: "
|
|
204
|
-
t: "
|
|
205
|
-
e: '"Tạo
|
|
203
|
+
icon: "flame",
|
|
204
|
+
t: "Trang flash sale",
|
|
205
|
+
e: '"Tạo trang flash sale — đồng hồ đếm ngược, sản phẩm giảm giá, nút mua dính cố định."',
|
|
206
206
|
},
|
|
207
207
|
{
|
|
208
208
|
icon: "newspaper",
|
|
209
|
-
t: "
|
|
210
|
-
e: '"Tạo
|
|
209
|
+
t: "Blog & bài viết",
|
|
210
|
+
e: '"Tạo chuyên mục blog và 3 bài viết có ảnh bìa, rồi gắn vào trang blog."',
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
icon: "
|
|
214
|
-
t: "
|
|
215
|
-
e: '"Tạo trang
|
|
213
|
+
icon: "shield",
|
|
214
|
+
t: "Trang thành viên",
|
|
215
|
+
e: '"Tạo trang đăng nhập, đăng ký và lịch sử đơn hàng cho khách."',
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
icon: "wand",
|
|
219
|
-
t: "
|
|
220
|
-
e: '"
|
|
219
|
+
t: "Rồi chỉnh & đăng lên",
|
|
220
|
+
e: '"…\"đổi màu nút\", \"thêm phần hỏi đáp\" rồi \"đăng lên cửa hàng\" — xong."',
|
|
221
221
|
},
|
|
222
222
|
],
|
|
223
223
|
connectH2: "Kết nối trợ lý AI với cửa hàng của bạn",
|
|
@@ -254,8 +254,8 @@ const T = {
|
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
icon: "cart",
|
|
257
|
-
t: "
|
|
258
|
-
d: "
|
|
257
|
+
t: "Tạo & quản lý sản phẩm",
|
|
258
|
+
d: "Tạo sản phẩm, danh mục, combo, khuyến mãi (kèm ảnh); xem đơn hàng, bộ sưu tập và thông tin khách hàng.",
|
|
259
259
|
},
|
|
260
260
|
{
|
|
261
261
|
icon: "newspaper",
|
|
@@ -279,8 +279,8 @@ const T = {
|
|
|
279
279
|
},
|
|
280
280
|
],
|
|
281
281
|
promptH2: "Ví dụ — nói với trợ lý như thế này",
|
|
282
|
-
promptSub: "Bạn có thể nói tự nhiên bằng tiếng Việt. Ví d
|
|
283
|
-
promptEx: "Tạo cho tôi một
|
|
282
|
+
promptSub: "Bạn có thể nói tự nhiên bằng tiếng Việt. Ví dụ, dựng cả cửa hàng:",
|
|
283
|
+
promptEx: "Tạo cho tôi một cửa hàng WebCake mới cho thương hiệu [tên thương hiệu].\nTạo 2-3 danh mục, mỗi danh mục vài sản phẩm có ảnh, giá và mô tả.\nDựng trang chủ: banner, lưới danh mục, sản phẩm nổi bật và mục bài viết mới.\nThêm 1 chuyên mục blog với 1 bài viết.\nKiểm tra kỹ rồi xuất bản lên cửa hàng giúp tôi.",
|
|
284
284
|
faqH2: "Câu hỏi thường gặp",
|
|
285
285
|
newH2: "Có gì mới",
|
|
286
286
|
newBadge: "MỚI",
|
|
@@ -301,11 +301,11 @@ const T = {
|
|
|
301
301
|
],
|
|
302
302
|
},
|
|
303
303
|
en: {
|
|
304
|
-
sub: "Build
|
|
304
|
+
sub: "Build a whole online store — products, categories, pages, blog posts — just by chatting. No coding required.",
|
|
305
305
|
running: "Up and running",
|
|
306
|
-
leadPre: "You describe
|
|
306
|
+
leadPre: "You describe your idea, your AI assistant builds an entire store on WebCake — creating products, categories and shop pages with images — you preview it and ",
|
|
307
307
|
leadGrad: "publish it — done",
|
|
308
|
-
leadPost: ". No dragging blocks, nothing to learn —
|
|
308
|
+
leadPost: ". No dragging blocks, nothing to learn — faster than waiting weeks for a web agency.",
|
|
309
309
|
ctaStart: "Get started",
|
|
310
310
|
ctaStar: "Star on GitHub",
|
|
311
311
|
flowH2: "Just 4 simple steps",
|
|
@@ -319,14 +319,14 @@ const T = {
|
|
|
319
319
|
howH2: "Why you can trust it straight away",
|
|
320
320
|
how: [
|
|
321
321
|
{
|
|
322
|
-
icon: "
|
|
323
|
-
t: "
|
|
324
|
-
d: "The assistant
|
|
322
|
+
icon: "cart",
|
|
323
|
+
t: "Real data, not an empty shell",
|
|
324
|
+
d: "The assistant also creates products, categories and blog posts with images — so product grids, category tiles and the blog show real content immediately, like a store that's already trading.",
|
|
325
325
|
},
|
|
326
326
|
{
|
|
327
327
|
icon: "check2",
|
|
328
|
-
t: "
|
|
329
|
-
d: "
|
|
328
|
+
t: "Looks right on every device",
|
|
329
|
+
d: "Pages are built on WebCake's real grid model and auto-tuned for desktop, laptop, tablet and phone — no broken layouts.",
|
|
330
330
|
},
|
|
331
331
|
{
|
|
332
332
|
icon: "shield",
|
|
@@ -335,51 +335,51 @@ const T = {
|
|
|
335
335
|
},
|
|
336
336
|
{
|
|
337
337
|
icon: "edit",
|
|
338
|
-
t: "
|
|
339
|
-
d: "Say \"
|
|
338
|
+
t: "Precise edits, instant publish",
|
|
339
|
+
d: "Say \"make the button purple\" or \"add a reviews section\" — only that spot changes. Happy? Say \"publish\" and your store is live.",
|
|
340
340
|
},
|
|
341
341
|
],
|
|
342
|
-
buildH2: "
|
|
342
|
+
buildH2: "You build a whole store",
|
|
343
343
|
uses: [
|
|
344
|
+
{
|
|
345
|
+
icon: "server",
|
|
346
|
+
t: "A whole store from scratch",
|
|
347
|
+
e: '"Build me a new fashion store — a few categories, some products with images and prices, then a homepage."',
|
|
348
|
+
},
|
|
344
349
|
{
|
|
345
350
|
icon: "cart",
|
|
346
|
-
t: "
|
|
347
|
-
e: '"
|
|
351
|
+
t: "Products & categories with images",
|
|
352
|
+
e: '"Add 6 t-shirt products to the \"T-shirts\" category — with images, prices and descriptions."',
|
|
348
353
|
},
|
|
349
354
|
{
|
|
350
355
|
icon: "window",
|
|
351
356
|
t: "Store homepage",
|
|
352
|
-
e: '"Create a homepage —
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
icon: "flame",
|
|
356
|
-
t: "Flash sale page",
|
|
357
|
-
e: '"Create a flash sale page — big countdown timer, discounted products, sticky buy button."',
|
|
357
|
+
e: '"Create a homepage — banner, category grid, featured products and latest posts."',
|
|
358
358
|
},
|
|
359
359
|
{
|
|
360
|
-
icon: "
|
|
361
|
-
t: "
|
|
362
|
-
e: '"
|
|
360
|
+
icon: "layers",
|
|
361
|
+
t: "A full set of shop pages",
|
|
362
|
+
e: '"Build the full set: product page, category, cart, checkout and thank-you."',
|
|
363
363
|
},
|
|
364
364
|
{
|
|
365
|
-
icon: "
|
|
366
|
-
t: "
|
|
367
|
-
e: '"Create a
|
|
365
|
+
icon: "flame",
|
|
366
|
+
t: "Flash sale page",
|
|
367
|
+
e: '"Create a flash sale page — countdown timer, discounted products, sticky buy button."',
|
|
368
368
|
},
|
|
369
369
|
{
|
|
370
370
|
icon: "newspaper",
|
|
371
|
-
t: "Blog
|
|
372
|
-
e: '"Create a blog
|
|
371
|
+
t: "Blog & articles",
|
|
372
|
+
e: '"Create a blog category and 3 articles with cover images, then wire up the blog page."',
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
|
-
icon: "
|
|
376
|
-
t: "
|
|
377
|
-
e: '"Create
|
|
375
|
+
icon: "shield",
|
|
376
|
+
t: "Member pages",
|
|
377
|
+
e: '"Create login, register and order-history pages for customers."',
|
|
378
378
|
},
|
|
379
379
|
{
|
|
380
380
|
icon: "wand",
|
|
381
|
-
t: "
|
|
382
|
-
e: '"
|
|
381
|
+
t: "Then tweak & publish",
|
|
382
|
+
e: '"…\"change the button colour\", \"add an FAQ\", then \"publish to my store\" — done."',
|
|
383
383
|
},
|
|
384
384
|
],
|
|
385
385
|
connectH2: "Connect your AI assistant to your store",
|
|
@@ -416,8 +416,8 @@ const T = {
|
|
|
416
416
|
},
|
|
417
417
|
{
|
|
418
418
|
icon: "cart",
|
|
419
|
-
t: "
|
|
420
|
-
d: "
|
|
419
|
+
t: "Create & manage products",
|
|
420
|
+
d: "Create products, categories, bundles and promotions (with images); view orders, collections and customer info.",
|
|
421
421
|
},
|
|
422
422
|
{
|
|
423
423
|
icon: "newspaper",
|
|
@@ -441,8 +441,8 @@ const T = {
|
|
|
441
441
|
},
|
|
442
442
|
],
|
|
443
443
|
promptH2: "Example — here's what to say to your assistant",
|
|
444
|
-
promptSub: "You can speak naturally. For example:",
|
|
445
|
-
promptEx: "Create a
|
|
444
|
+
promptSub: "You can speak naturally. For example, build a whole store:",
|
|
445
|
+
promptEx: "Create a new WebCake store for [brand name].\nCreate 2-3 categories, each with a few products that have images, prices and descriptions.\nBuild the homepage: banner, category grid, featured products and a latest-posts section.\nAdd a blog category with one article.\nCheck everything, then publish it to my store.",
|
|
446
446
|
faqH2: "Frequently asked questions",
|
|
447
447
|
newH2: "What's new",
|
|
448
448
|
newBadge: "NEW",
|
|
@@ -584,22 +584,36 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
584
584
|
<meta name="twitter:image:alt" content="${m.title}">
|
|
585
585
|
<script type="application/ld+json">${jsonLdScript}</script>
|
|
586
586
|
<style>
|
|
587
|
-
:root{--g:#108B67;--g7:#0c6f52;--ink:#
|
|
588
|
-
--line:rgba(16,
|
|
589
|
-
--ic-fg:#0c6f52;--btn-hover:#0c6f52;--navbg:rgba(246,
|
|
590
|
-
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#eaf1ee;--mut:#9fb0a9;--bg:#
|
|
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(
|
|
592
|
-
:root[data-theme="dark"]{--ink:#eaf1ee;--mut:#9fb0a9;--bg:#
|
|
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(
|
|
587
|
+
:root{--g:#108B67;--g7:#0c6f52;--ink:#13211c;--mut:#5a6b64;--bg:#f6faf8;--bg2:#e9f2ec;--card:#ffffff;
|
|
588
|
+
--line:rgba(16,48,38,.10);--shadow:0 1px 2px rgba(16,48,38,.05),0 6px 20px -12px rgba(16,48,38,.18);--code:#0c1a15;
|
|
589
|
+
--ic-fg:#0c6f52;--btn-hover:#0c6f52;--navbg:rgba(246,250,248,.82)}
|
|
590
|
+
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0d1714;--bg2:#091310;--card:#161f1b;
|
|
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(13,23,20,.82)}}
|
|
592
|
+
:root[data-theme="dark"]{--ink:#eaf1ee;--mut:#9fb0a9;--bg:#0d1714;--bg2:#091310;--card:#161f1b;
|
|
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(13,23,20,.82)}
|
|
594
594
|
*{box-sizing:border-box}
|
|
595
595
|
html{scroll-behavior:auto}
|
|
596
596
|
html.smooth{scroll-behavior:smooth}
|
|
597
597
|
body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:var(--ink);
|
|
598
|
-
background:var(--bg);
|
|
598
|
+
background:linear-gradient(180deg,var(--bg) 0%,var(--bg2) 100%);background-attachment:fixed;
|
|
599
|
+
min-height:100vh;line-height:1.62;overflow-x:hidden}
|
|
600
|
+
/* layered "aurora" mesh of soft teal glows */
|
|
601
|
+
body::before{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
|
|
602
|
+
background:
|
|
603
|
+
radial-gradient(58% 46% at 50% -8%,rgba(16,139,103,.12),transparent 70%),
|
|
604
|
+
radial-gradient(40% 38% at 100% 4%,rgba(20,168,124,.09),transparent 70%),
|
|
605
|
+
radial-gradient(46% 46% at 0% 100%,rgba(16,139,103,.08),transparent 70%)}
|
|
606
|
+
/* faint grid texture, masked so it only whispers near the top */
|
|
607
|
+
body::after{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;opacity:.55;
|
|
608
|
+
background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
|
|
609
|
+
background-size:46px 46px;
|
|
610
|
+
-webkit-mask:radial-gradient(120% 70% at 50% 0%,#000 0%,transparent 62%);
|
|
611
|
+
mask:radial-gradient(120% 70% at 50% 0%,#000 0%,transparent 62%)}
|
|
599
612
|
.blobs{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none}
|
|
600
|
-
.blobs b{position:absolute;border-radius:50%;filter:blur(
|
|
613
|
+
.blobs b{position:absolute;border-radius:50%;filter:blur(100px);opacity:.13;will-change:transform}
|
|
601
614
|
.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}
|
|
602
615
|
.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}
|
|
616
|
+
.blobs b:nth-child(3){width:380px;height:380px;left:46%;top:34%;background:radial-gradient(circle,#3fcf9e,transparent 70%);opacity:.08;animation:drift1 56s ease-in-out infinite}
|
|
603
617
|
@keyframes drift1{50%{transform:translate(-50px,60px)}}
|
|
604
618
|
@keyframes drift2{50%{transform:translate(40px,-50px)}}
|
|
605
619
|
.wrap{max-width:900px;margin:0 auto;padding:48px 20px 72px}
|
|
@@ -793,7 +807,7 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
793
807
|
}
|
|
794
808
|
</style></head>
|
|
795
809
|
<body>
|
|
796
|
-
<div class="blobs"><b></b><b></b></div>
|
|
810
|
+
<div class="blobs"><b></b><b></b><b></b></div>
|
|
797
811
|
<div class="wrap">
|
|
798
812
|
|
|
799
813
|
<header class="hero-in">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "MCP server for the WebCake/StoreCake storefront builder — page CRUD, page authoring, products, orders, and more",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-storefront-mcp",
|
|
6
6
|
"license": "MIT",
|