sbuilder-mcp 0.66.1 → 0.67.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/CHANGELOG.md +12 -0
- package/CHANGELOG.vi.md +12 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/dist/catalog/checkout.generated.js +19 -19
- package/dist/catalog/deadkeys.generated.js +1 -1
- package/dist/catalog/elements.generated.js +7 -7
- package/dist/catalog/source.generated.js +2 -2
- package/dist/catalog/storepages.generated.js +2 -2
- package/dist/domains/site/cartlang.js +58 -0
- package/dist/domains/site/readiness-fetch.js +5 -0
- package/dist/domains/site/readiness.js +18 -0
- package/dist/live/session.js +7 -0
- package/dist/tools/live.js +1 -0
- package/dist/tools/overlay.js +65 -0
- package/dist/tools/page.js +37 -1
- package/dist/tools/store.js +9 -3
- package/dist/transport/http.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ All notable changes to this project are documented in this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.67.0] - 2026-09-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_review` warns once per site (`cartDrawerLanguage`) when the site's cart drawer still carries another locale's seed words, and `sb_store` action `cart` gains a `relocalize` argument that rewrites exactly those words to the site locale's seed, leaving merchant-edited text untouched; a real run needs a page of the site open, since the drawer is written through the page save.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- The generated catalog was regenerated against the platform: a category/search card's media-dataset repeater now shows a single thumbnail instead of a strip, and several defaults (form/menu hover, cart-count badge) now read the theme's `var(--wb-sc-*)` tokens instead of a hardcoded color.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- A non-GET write to a shared global section, or a global/overlay frame from the live room, now marks the open page's copy stale when it composes that master, so the next save re-pulls the page instead of decomposing an old header, footer, or cart drawer back over the merchant's change.
|
|
19
|
+
- `sb_store` action `cart`'s dry run now explains why a newly created drawer would come out in English — unreadable site settings, an unset `settings.locale`, or a locale with no seed — instead of staying silent about the fallback.
|
|
20
|
+
|
|
9
21
|
## [0.66.1] - 2026-09-25
|
|
10
22
|
|
|
11
23
|
### Fixed
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,18 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
|
|
|
6
6
|
Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.67.0] - 2026-09-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_review` cảnh báo một lần cho mỗi site (`cartDrawerLanguage`) khi cart drawer của site vẫn còn mang từ seed của một locale khác, và `sb_store` action `cart` có thêm tham số `relocalize` để viết lại đúng những từ đó thành seed của locale site, giữ nguyên text đã được merchant chỉnh sửa; một lần chạy thật cần có một trang của site đang mở, vì drawer được ghi thông qua lần lưu trang.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Catalog được sinh ra đã được làm mới dựa trên nền tảng: repeater media-dataset trên thẻ category/search giờ hiện một thumbnail duy nhất thay vì một dải ảnh, và một số giá trị mặc định (hover của form/menu, badge cart-count) giờ đọc theo token `var(--wb-sc-*)` của theme thay vì màu cố định.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Một lần ghi không phải GET vào một global section dùng chung, hoặc một frame global/overlay từ phòng live, giờ đánh dấu bản sao của trang đang mở là cũ khi nó compose từ master đó, để lần lưu tiếp theo đọc lại trang thay vì decompose header, footer hay cart drawer cũ đè lên thay đổi của merchant.
|
|
19
|
+
- Dry run của action `cart` trong `sb_store` giờ giải thích lý do vì sao một drawer mới tạo sẽ ra tiếng Anh — không đọc được settings của site, `settings.locale` chưa được đặt, hoặc một locale không có seed — thay vì im lặng về việc rơi về tiếng Anh.
|
|
20
|
+
|
|
9
21
|
## [0.66.1] - 2026-09-25
|
|
10
22
|
|
|
11
23
|
### Fixed
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
111
111
|
| `sb_import` | Read a page from any public URL and add its structure and content to the open page as real elements, styled with THIS page's own tokens — a translation, not a clone |
|
|
112
112
|
| `sb_import_site` | Read a WHOLE site from one URL — its sitemap, or the links on that page — and give each page found its own draft page here, built from this site's tokens; the entry page's own colours and type scale also patch into this SITE'S theme, so it stops being purely a read |
|
|
113
113
|
| `sb_theme` | Read or patch the site's palette and type scale — the layer every style preset resolves from, so one token repaints every page; `locale` sets the site's language |
|
|
114
|
-
| `sb_store` | Run a store flow that must happen in a fixed order — `checkout` (the four writes that make a working one), `form` (any of the platform's 17 templates with its own field document), `chrome` (one shared header or footer on a real site menu — desktop menu, mobile drawer, cart and account icons), `menu` (a menu node bound to the site's menu, its links resolved), `overlay_attach` (a pop-up or quick view on the open page), `cart` (the site's cart drawer, when it has none) and `app` (a built-in app plus the pages it needs), `global_attach` / `global_detach` (put an EXISTING shared section on the open page, or take it off) |
|
|
114
|
+
| `sb_store` | Run a store flow that must happen in a fixed order — `checkout` (the four writes that make a working one), `form` (any of the platform's 17 templates with its own field document), `chrome` (one shared header or footer on a real site menu — desktop menu, mobile drawer, cart and account icons), `menu` (a menu node bound to the site's menu, its links resolved), `overlay_attach` (a pop-up or quick view on the open page), `cart` (the site's cart drawer, when it has none; `relocalize` puts an existing one in the site's language) and `app` (a built-in app plus the pages it needs), `global_attach` / `global_detach` (put an EXISTING shared section on the open page, or take it off) |
|
|
115
115
|
| `sb_undo` | Put back what a PUT replaced. The SECOND answer for a page, not the only one: the platform has versions, history and restore (`sb_api_find` "page versions"), which outlive this process — reach for those first and use this for every other shaped PUT |
|
|
116
116
|
|
|
117
117
|
Twenty-eight tools, **568 API operations** (195 of the 253 writes carrying a body shape read
|
package/README.vi.md
CHANGED
|
@@ -108,7 +108,7 @@ nhét ngược lại vào mọi bản cài.
|
|
|
108
108
|
| `sb_import` | Đọc một trang từ URL công khai bất kỳ và thêm cấu trúc + nội dung của nó vào trang đang mở dưới dạng element thật, mang token của CHÍNH trang này — là dịch lại, không phải sao chép |
|
|
109
109
|
| `sb_import_site` | Đọc CẢ website từ một URL — sitemap của nó, hoặc các link trên trang đó — và tạo cho mỗi trang tìm được một trang nháp riêng ở đây, dựng bằng token của site này; màu và thang chữ của trang gốc cũng được vá vào THEME của site này, nên đây không còn thuần là đọc |
|
|
110
110
|
| `sb_theme` | Đọc hoặc vá bảng màu và thang chữ của site — tầng mà mọi style preset phân giải từ đó, nên một token thay áo cho mọi trang; `locale` đặt ngôn ngữ của site |
|
|
111
|
-
| `sb_store` | Chạy một luồng cửa hàng bắt buộc đúng thứ tự — `checkout` (bốn lệnh ghi tạo nên trang thanh toán), `form` (một trong 17 template của nền tảng kèm field document của nó), `chrome` (một header hoặc footer dùng chung trên menu thật của site — menu desktop, drawer mobile, icon giỏ hàng và tài khoản), `menu` (một node menu bind vào menu của site, link đã phân giải), `overlay_attach` (một pop-up hay quick view trên trang đang mở), `cart` (ngăn giỏ hàng của site, khi chưa có), `app` (một app dựng sẵn kèm những trang nó cần), và `global_attach` / `global_detach` (đặt một section dùng chung ĐÃ CÓ lên trang đang mở, hoặc gỡ ra) |
|
|
111
|
+
| `sb_store` | Chạy một luồng cửa hàng bắt buộc đúng thứ tự — `checkout` (bốn lệnh ghi tạo nên trang thanh toán), `form` (một trong 17 template của nền tảng kèm field document của nó), `chrome` (một header hoặc footer dùng chung trên menu thật của site — menu desktop, drawer mobile, icon giỏ hàng và tài khoản), `menu` (một node menu bind vào menu của site, link đã phân giải), `overlay_attach` (một pop-up hay quick view trên trang đang mở), `cart` (ngăn giỏ hàng của site, khi chưa có; `relocalize` đưa ngăn giỏ đã có về ngôn ngữ của site), `app` (một app dựng sẵn kèm những trang nó cần), và `global_attach` / `global_detach` (đặt một section dùng chung ĐÃ CÓ lên trang đang mở, hoặc gỡ ra) |
|
|
112
112
|
| `sb_undo` | Trả lại thứ mà một lệnh PUT đã ghi đè. Với TRANG thì đây là đường về thứ hai chứ không phải duy nhất: nền tảng có version, history và restore (`sb_api_find` "page versions"), và chúng sống lâu hơn tiến trình này — hãy dùng chúng trước, còn tool này cho mọi PUT có hình dạng khác |
|
|
113
113
|
|
|
114
114
|
Hai mươi tám tool, **568 operation API** (195 trong 253 lệnh ghi có hình dạng body đọc thẳng
|
|
@@ -70,7 +70,7 @@ export const CHECKOUT_FORM_DOCUMENT = {
|
|
|
70
70
|
"lineHeight": "130%",
|
|
71
71
|
"fontSize": "20px",
|
|
72
72
|
"fontWeight": "600",
|
|
73
|
-
"color": "#171717"
|
|
73
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
74
74
|
},
|
|
75
75
|
"config": {},
|
|
76
76
|
"specials": {
|
|
@@ -902,7 +902,7 @@ export const FORM_TEMPLATES = {
|
|
|
902
902
|
"lineHeight": "130%",
|
|
903
903
|
"fontSize": "20px",
|
|
904
904
|
"fontWeight": "600",
|
|
905
|
-
"color": "#171717"
|
|
905
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
906
906
|
},
|
|
907
907
|
"config": {},
|
|
908
908
|
"specials": {
|
|
@@ -1168,7 +1168,7 @@ export const FORM_TEMPLATES = {
|
|
|
1168
1168
|
"lineHeight": "130%",
|
|
1169
1169
|
"fontSize": "20px",
|
|
1170
1170
|
"fontWeight": "600",
|
|
1171
|
-
"color": "#171717"
|
|
1171
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
1172
1172
|
},
|
|
1173
1173
|
"config": {},
|
|
1174
1174
|
"specials": {
|
|
@@ -1345,7 +1345,7 @@ export const FORM_TEMPLATES = {
|
|
|
1345
1345
|
"lineHeight": "130%",
|
|
1346
1346
|
"fontSize": "20px",
|
|
1347
1347
|
"fontWeight": "600",
|
|
1348
|
-
"color": "#171717"
|
|
1348
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
1349
1349
|
},
|
|
1350
1350
|
"config": {},
|
|
1351
1351
|
"specials": {
|
|
@@ -1745,7 +1745,7 @@ export const FORM_TEMPLATES = {
|
|
|
1745
1745
|
"lineHeight": "130%",
|
|
1746
1746
|
"fontSize": "20px",
|
|
1747
1747
|
"fontWeight": "600",
|
|
1748
|
-
"color": "#171717"
|
|
1748
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
1749
1749
|
},
|
|
1750
1750
|
"config": {},
|
|
1751
1751
|
"specials": {
|
|
@@ -2255,7 +2255,7 @@ export const FORM_TEMPLATES = {
|
|
|
2255
2255
|
"lineHeight": "130%",
|
|
2256
2256
|
"fontSize": "20px",
|
|
2257
2257
|
"fontWeight": "600",
|
|
2258
|
-
"color": "#171717"
|
|
2258
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
2259
2259
|
},
|
|
2260
2260
|
"config": {},
|
|
2261
2261
|
"specials": {
|
|
@@ -2627,7 +2627,7 @@ export const FORM_TEMPLATES = {
|
|
|
2627
2627
|
"lineHeight": "130%",
|
|
2628
2628
|
"fontSize": "20px",
|
|
2629
2629
|
"fontWeight": "600",
|
|
2630
|
-
"color": "#171717"
|
|
2630
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
2631
2631
|
},
|
|
2632
2632
|
"config": {},
|
|
2633
2633
|
"specials": {
|
|
@@ -2993,7 +2993,7 @@ export const FORM_TEMPLATES = {
|
|
|
2993
2993
|
"lineHeight": "130%",
|
|
2994
2994
|
"fontSize": "20px",
|
|
2995
2995
|
"fontWeight": "600",
|
|
2996
|
-
"color": "#171717"
|
|
2996
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
2997
2997
|
},
|
|
2998
2998
|
"config": {},
|
|
2999
2999
|
"specials": {
|
|
@@ -3248,7 +3248,7 @@ export const FORM_TEMPLATES = {
|
|
|
3248
3248
|
"lineHeight": "130%",
|
|
3249
3249
|
"fontSize": "20px",
|
|
3250
3250
|
"fontWeight": "600",
|
|
3251
|
-
"color": "#171717"
|
|
3251
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
3252
3252
|
},
|
|
3253
3253
|
"config": {},
|
|
3254
3254
|
"specials": {
|
|
@@ -3547,7 +3547,7 @@ export const FORM_TEMPLATES = {
|
|
|
3547
3547
|
"lineHeight": "130%",
|
|
3548
3548
|
"fontSize": "20px",
|
|
3549
3549
|
"fontWeight": "600",
|
|
3550
|
-
"color": "#171717"
|
|
3550
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
3551
3551
|
},
|
|
3552
3552
|
"config": {},
|
|
3553
3553
|
"specials": {
|
|
@@ -3914,7 +3914,7 @@ export const FORM_TEMPLATES = {
|
|
|
3914
3914
|
"lineHeight": "130%",
|
|
3915
3915
|
"fontSize": "20px",
|
|
3916
3916
|
"fontWeight": "600",
|
|
3917
|
-
"color": "#171717"
|
|
3917
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
3918
3918
|
},
|
|
3919
3919
|
"config": {},
|
|
3920
3920
|
"specials": {
|
|
@@ -4138,7 +4138,7 @@ export const FORM_TEMPLATES = {
|
|
|
4138
4138
|
"lineHeight": "130%",
|
|
4139
4139
|
"fontSize": "20px",
|
|
4140
4140
|
"fontWeight": "600",
|
|
4141
|
-
"color": "#171717"
|
|
4141
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
4142
4142
|
},
|
|
4143
4143
|
"config": {},
|
|
4144
4144
|
"specials": {
|
|
@@ -4401,7 +4401,7 @@ export const FORM_TEMPLATES = {
|
|
|
4401
4401
|
"lineHeight": "130%",
|
|
4402
4402
|
"fontSize": "20px",
|
|
4403
4403
|
"fontWeight": "600",
|
|
4404
|
-
"color": "#171717"
|
|
4404
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
4405
4405
|
},
|
|
4406
4406
|
"config": {},
|
|
4407
4407
|
"specials": {
|
|
@@ -4670,7 +4670,7 @@ export const FORM_TEMPLATES = {
|
|
|
4670
4670
|
"lineHeight": "130%",
|
|
4671
4671
|
"fontSize": "20px",
|
|
4672
4672
|
"fontWeight": "600",
|
|
4673
|
-
"color": "#171717"
|
|
4673
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
4674
4674
|
},
|
|
4675
4675
|
"config": {},
|
|
4676
4676
|
"specials": {
|
|
@@ -4969,7 +4969,7 @@ export const FORM_TEMPLATES = {
|
|
|
4969
4969
|
"lineHeight": "130%",
|
|
4970
4970
|
"fontSize": "20px",
|
|
4971
4971
|
"fontWeight": "600",
|
|
4972
|
-
"color": "#171717"
|
|
4972
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
4973
4973
|
},
|
|
4974
4974
|
"config": {},
|
|
4975
4975
|
"specials": {
|
|
@@ -5216,7 +5216,7 @@ export const FORM_TEMPLATES = {
|
|
|
5216
5216
|
"lineHeight": "130%",
|
|
5217
5217
|
"fontSize": "20px",
|
|
5218
5218
|
"fontWeight": "600",
|
|
5219
|
-
"color": "#171717"
|
|
5219
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
5220
5220
|
},
|
|
5221
5221
|
"config": {},
|
|
5222
5222
|
"specials": {
|
|
@@ -5511,7 +5511,7 @@ export const FORM_TEMPLATES = {
|
|
|
5511
5511
|
"lineHeight": "130%",
|
|
5512
5512
|
"fontSize": "20px",
|
|
5513
5513
|
"fontWeight": "600",
|
|
5514
|
-
"color": "#171717"
|
|
5514
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
5515
5515
|
},
|
|
5516
5516
|
"config": {},
|
|
5517
5517
|
"specials": {
|
|
@@ -5686,7 +5686,7 @@ export const FORM_TEMPLATES = {
|
|
|
5686
5686
|
"lineHeight": "130%",
|
|
5687
5687
|
"fontSize": "20px",
|
|
5688
5688
|
"fontWeight": "600",
|
|
5689
|
-
"color": "#171717"
|
|
5689
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
5690
5690
|
},
|
|
5691
5691
|
"config": {},
|
|
5692
5692
|
"specials": {
|
|
@@ -5874,7 +5874,7 @@ export const FORM_TEMPLATES = {
|
|
|
5874
5874
|
"lineHeight": "130%",
|
|
5875
5875
|
"fontSize": "20px",
|
|
5876
5876
|
"fontWeight": "600",
|
|
5877
|
-
"color": "#171717"
|
|
5877
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
5878
5878
|
},
|
|
5879
5879
|
"config": {},
|
|
5880
5880
|
"specials": {
|
|
@@ -9376,7 +9376,7 @@ export const ELEMENTS = {
|
|
|
9376
9376
|
"lineHeight": "130%",
|
|
9377
9377
|
"fontSize": "20px",
|
|
9378
9378
|
"fontWeight": "600",
|
|
9379
|
-
"color": "#171717"
|
|
9379
|
+
"color": "var(--wb-sc-heading, #171717)"
|
|
9380
9380
|
}
|
|
9381
9381
|
},
|
|
9382
9382
|
"inspector": [
|
|
@@ -13819,7 +13819,7 @@ export const ELEMENTS = {
|
|
|
13819
13819
|
"states": {
|
|
13820
13820
|
"hover": {
|
|
13821
13821
|
"style": {
|
|
13822
|
-
"color": "#676767"
|
|
13822
|
+
"color": "var(--wb-sc-text, #676767)"
|
|
13823
13823
|
}
|
|
13824
13824
|
}
|
|
13825
13825
|
}
|
|
@@ -13942,7 +13942,7 @@ export const ELEMENTS = {
|
|
|
13942
13942
|
"contentTips": [
|
|
13943
13943
|
"this skin carries no text of its own — labels come from the menu data",
|
|
13944
13944
|
"Default and Hover are separate state slots; edit Hover with the State switcher",
|
|
13945
|
-
"the default look is the theme link colour (ink #171717 on a default theme) and the text-2 font, with a
|
|
13945
|
+
"the default look is the theme link colour (ink #171717 on a default theme) and the text-2 font, with a hover in the theme body-text colour"
|
|
13946
13946
|
],
|
|
13947
13947
|
"semantics": [
|
|
13948
13948
|
"menu-item",
|
|
@@ -14834,8 +14834,8 @@ export const ELEMENTS = {
|
|
|
14834
14834
|
"paddingLeft": "6px",
|
|
14835
14835
|
"paddingRight": "6px",
|
|
14836
14836
|
"borderRadius": "999px",
|
|
14837
|
-
"backgroundColor": "#171717",
|
|
14838
|
-
"color": "#ffffff",
|
|
14837
|
+
"backgroundColor": "var(--wb-sc-buttonBg, #171717)",
|
|
14838
|
+
"color": "var(--wb-sc-buttonText, #ffffff)",
|
|
14839
14839
|
"fontSize": "12px",
|
|
14840
14840
|
"fontWeight": "600",
|
|
14841
14841
|
"lineHeight": "1"
|
|
@@ -26460,8 +26460,8 @@ export const ELEMENTS = {
|
|
|
26460
26460
|
"height": "fit-content",
|
|
26461
26461
|
"padding": "2px 8px",
|
|
26462
26462
|
"borderRadius": "4px",
|
|
26463
|
-
"backgroundColor": "#171717",
|
|
26464
|
-
"color": "#ffffff",
|
|
26463
|
+
"backgroundColor": "var(--wb-sc-buttonBg, #171717)",
|
|
26464
|
+
"color": "var(--wb-sc-buttonText, #ffffff)",
|
|
26465
26465
|
"fontSize": "12px",
|
|
26466
26466
|
"fontWeight": "600",
|
|
26467
26467
|
"lineHeight": "1"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GENERATED by scripts/gen-catalog.ts — do not edit by hand.
|
|
2
2
|
// The platform commit every other generated file here describes.
|
|
3
3
|
export const PLATFORM_SOURCE = {
|
|
4
|
-
"commit": "
|
|
5
|
-
"committedAt": "2026-09-
|
|
4
|
+
"commit": "2fba72c10b84e624cb05709f9759bba93d36dbec",
|
|
5
|
+
"committedAt": "2026-09-25T22:15:50+07:00",
|
|
6
6
|
"dirty": false
|
|
7
7
|
};
|
|
@@ -3100,7 +3100,7 @@ export const STORE_PAGE_SEEDS = {
|
|
|
3100
3100
|
"alt": ""
|
|
3101
3101
|
}
|
|
3102
3102
|
],
|
|
3103
|
-
"layout": "
|
|
3103
|
+
"layout": "single",
|
|
3104
3104
|
"activeIndex": 0
|
|
3105
3105
|
},
|
|
3106
3106
|
"specials": {},
|
|
@@ -3812,7 +3812,7 @@ export const STORE_PAGE_SEEDS = {
|
|
|
3812
3812
|
"alt": ""
|
|
3813
3813
|
}
|
|
3814
3814
|
],
|
|
3815
|
-
"layout": "
|
|
3815
|
+
"layout": "single",
|
|
3816
3816
|
"activeIndex": 0
|
|
3817
3817
|
},
|
|
3818
3818
|
"specials": {},
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { walk } from '../../core/tree.js';
|
|
2
|
+
import { CART_SEEDS } from '../../catalog/overlays.generated.js';
|
|
3
|
+
/**
|
|
4
|
+
* A CART DRAWER IN ANOTHER LANGUAGE THAN ITS SITE. A store made before the
|
|
5
|
+
* per-locale seed keeps "Your cart" / "Checkout" / "Your cart is empty" on a
|
|
6
|
+
* Vietnamese site, and nothing on the page says so. Only strings EXACTLY equal
|
|
7
|
+
* to another locale's seed word count — anything else is the merchant's text.
|
|
8
|
+
*/
|
|
9
|
+
const KEYS = ['text', 'label', 'emptyText'];
|
|
10
|
+
/** The seed a site's `settings.locale` picks: primary subtag, `en` when there is none. */
|
|
11
|
+
export function cartSeedLocale(locale) {
|
|
12
|
+
const l = String(locale ?? '').toLowerCase().split('-')[0];
|
|
13
|
+
return Object.hasOwn(CART_SEEDS, l) ? l : 'en';
|
|
14
|
+
}
|
|
15
|
+
const order = (d) => {
|
|
16
|
+
const out = [];
|
|
17
|
+
walk(d, d.root_node_id, (n) => out.push(n));
|
|
18
|
+
return out;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* `key \0 other-locale word` → the site-locale word, by pairing each other
|
|
22
|
+
* seed's nodes with the target seed's in walk order (same seed shape, so the
|
|
23
|
+
* same position is the same word). A pair whose types differ is skipped.
|
|
24
|
+
*/
|
|
25
|
+
function seedWords(to) {
|
|
26
|
+
const target = order(CART_SEEDS[to]);
|
|
27
|
+
const out = new Map();
|
|
28
|
+
for (const [l, seed] of Object.entries(CART_SEEDS)) {
|
|
29
|
+
if (l === to)
|
|
30
|
+
continue;
|
|
31
|
+
order(seed).forEach((n, i) => {
|
|
32
|
+
const t = target[i];
|
|
33
|
+
if (!t || t.data.type !== n.data.type)
|
|
34
|
+
return;
|
|
35
|
+
for (const k of KEYS) {
|
|
36
|
+
const a = n.specials?.[k];
|
|
37
|
+
const b = t.specials?.[k];
|
|
38
|
+
if (typeof a === 'string' && typeof b === 'string' && a !== b)
|
|
39
|
+
out.set(`${k}\0${a}`, b);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/** Every seed word under `rootId` that belongs to a locale other than `locale`'s seed. */
|
|
46
|
+
export function cartRelocalize(doc, rootId, locale) {
|
|
47
|
+
const words = seedWords(locale);
|
|
48
|
+
const out = [];
|
|
49
|
+
walk(doc, rootId, (n) => {
|
|
50
|
+
for (const key of KEYS) {
|
|
51
|
+
const from = n.specials?.[key];
|
|
52
|
+
const to = typeof from === 'string' ? words.get(`${key}\0${from}`) : undefined;
|
|
53
|
+
if (to !== undefined)
|
|
54
|
+
out.push({ node_id: n.id, key, from: from, to });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
@@ -62,6 +62,8 @@ export async function gatherReadiness(ctx, siteId, pageNodes, openPageId) {
|
|
|
62
62
|
get(`/api/sites/${site}`),
|
|
63
63
|
// THE OVERLAYS, for the one a cart control opens: no `cart` overlay means
|
|
64
64
|
// every `open_cart` on the site opens nothing.
|
|
65
|
+
// The list ships each overlay's FULL document (overlays/rest/rest.go), so the
|
|
66
|
+
// cart drawer's words are read here too — see cartDrawerLanguage.
|
|
65
67
|
get(`/api/sites/${site}/overlays`),
|
|
66
68
|
// `<html lang>` is served from this; see siteLanguage in readiness.ts.
|
|
67
69
|
get(`/api/sites/${site}/settings`),
|
|
@@ -116,6 +118,9 @@ export async function gatherReadiness(ctx, siteId, pageNodes, openPageId) {
|
|
|
116
118
|
pageNodes,
|
|
117
119
|
globalNodes,
|
|
118
120
|
globalKinds,
|
|
121
|
+
cartOverlay: Array.isArray(overlayList?.overlays)
|
|
122
|
+
? (overlayList.overlays.find((o) => o.kind === 'cart')?.document ?? null)
|
|
123
|
+
: null,
|
|
119
124
|
overlayKinds: Array.isArray(overlayList?.overlays) ? overlayList.overlays.map((o) => o.kind ?? '') : null,
|
|
120
125
|
categories,
|
|
121
126
|
categoryPageLinks,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* fetched reports nothing rather than a gap the store may not have. A warning
|
|
17
17
|
* that fires on a correctly built site is one the reader learns to ignore.
|
|
18
18
|
*/
|
|
19
|
+
import { cartRelocalize, cartSeedLocale } from './cartlang.js';
|
|
19
20
|
/** The purchase binding an Add-to-cart / Buy-now button carries. */
|
|
20
21
|
const PRODUCT_ACTION_BINDING_ID = 'bind-product-action';
|
|
21
22
|
/** Element types that only make sense in a store. */
|
|
@@ -262,6 +263,23 @@ export function readinessGaps(input) {
|
|
|
262
263
|
: `Create a page of type "${type}" and publish it.`,
|
|
263
264
|
});
|
|
264
265
|
}
|
|
266
|
+
// A CART DRAWER IN ANOTHER LANGUAGE. Asked before the store gate: a drawer
|
|
267
|
+
// that exists is shown on every page, whatever this page sells.
|
|
268
|
+
if (input.siteLocale && input.cartOverlay?.root_node_id) {
|
|
269
|
+
const locale = cartSeedLocale(input.siteLocale);
|
|
270
|
+
const changes = cartRelocalize(input.cartOverlay, input.cartOverlay.root_node_id, locale);
|
|
271
|
+
if (changes.length > 0) {
|
|
272
|
+
gaps.push({
|
|
273
|
+
id: 'cartDrawerLanguage',
|
|
274
|
+
draft: false,
|
|
275
|
+
problem: `The site's locale is "${input.siteLocale}", but the cart drawer still carries another ` +
|
|
276
|
+
`language's seed words: ${[...new Set(changes.map((c) => `"${c.from}"`))].join(', ')}. ` +
|
|
277
|
+
'Every page shows it.',
|
|
278
|
+
fix: 'Open any page of this site, then sb_store action:"cart" relocalize:true dry_run:false — it ' +
|
|
279
|
+
`rewrites exactly those seed strings to the "${locale}" seed's, and leaves edited text alone.`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
265
283
|
if (!isStore(input))
|
|
266
284
|
return gaps;
|
|
267
285
|
if (pages && !published(pages, 'checkout')) {
|
package/dist/live/session.js
CHANGED
|
@@ -231,6 +231,13 @@ export class LiveSession {
|
|
|
231
231
|
}
|
|
232
232
|
break;
|
|
233
233
|
}
|
|
234
|
+
case 'global':
|
|
235
|
+
case 'overlay': {
|
|
236
|
+
const id = e.t === 'global' ? e.globalId : e.overlayId;
|
|
237
|
+
if (typeof id === 'string' && id)
|
|
238
|
+
this.opts.onMaster?.(e.t, id, String(e.op ?? ''), Number(e.rev ?? 0));
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
234
241
|
case 'snapreq':
|
|
235
242
|
// THE YIELD RULE. Never answered. Sending a snapshot would make this
|
|
236
243
|
// client an authority on the document — exactly the position it declines
|
package/dist/tools/live.js
CHANGED
|
@@ -260,6 +260,7 @@ export function joinRoom(ctx, session, siteId) {
|
|
|
260
260
|
onRemote: (patches) => session.applyRemote(patches),
|
|
261
261
|
onDesync: (reason) => session.markStale(reason),
|
|
262
262
|
onSource: (pageId, rev, peerId) => session.sourceSaved(pageId, rev, peerId),
|
|
263
|
+
onMaster: (kind, id, op, rev) => session.masterSaved(kind, id, op, rev),
|
|
263
264
|
});
|
|
264
265
|
socket.connect();
|
|
265
266
|
session.attachLive(live, siteId);
|
package/dist/tools/overlay.js
CHANGED
|
@@ -57,6 +57,7 @@ import { siteToken } from './credentialpick.js';
|
|
|
57
57
|
import { setKeys } from '../domains/site/builder.js';
|
|
58
58
|
import { CART_SEEDS, OVERLAY_SEEDS } from '../catalog/overlays.generated.js';
|
|
59
59
|
import { withFreshIds } from '../domains/site/ids.js';
|
|
60
|
+
import { cartRelocalize, cartSeedLocale } from '../domains/site/cartlang.js';
|
|
60
61
|
const KIND_NAME = {
|
|
61
62
|
popup: 'Pop-up',
|
|
62
63
|
quickview: 'Quick view',
|
|
@@ -306,12 +307,18 @@ export async function ensureCartDrawer(ctx, session, siteId, dryRun) {
|
|
|
306
307
|
}).catch(() => null));
|
|
307
308
|
const locale = String(settings?.settings?.locale ?? '').toLowerCase().split('-')[0];
|
|
308
309
|
const seedLocale = Object.hasOwn(CART_SEEDS, locale) ? locale : 'en';
|
|
310
|
+
const languageNote = seedLocale === locale
|
|
311
|
+
? undefined
|
|
312
|
+
: settings === null
|
|
313
|
+
? 'The site settings could not be read, so the drawer is in English — edit its words if the store is not.'
|
|
314
|
+
: `settings.locale is ${locale ? `"${locale}", which has no seed` : 'unset'}, so the drawer is in English — set it with sb_theme locale first if the store is not.`;
|
|
309
315
|
const seed = CART_SEEDS[seedLocale] ?? OVERLAY_SEEDS.cart;
|
|
310
316
|
const body = { kind: 'cart', name: 'Cart', document: withFreshIds(seed) };
|
|
311
317
|
if (dryRun) {
|
|
312
318
|
return {
|
|
313
319
|
dry_run: true,
|
|
314
320
|
language: seedLocale,
|
|
321
|
+
...(languageNote ? { language_note: languageNote } : {}),
|
|
315
322
|
plan: redact([{ step: 1, what: 'create the site\'s cart drawer', method: 'POST', path, body }]),
|
|
316
323
|
note: 'Nothing was sent. Re-call with dry_run:false to create it; it shows on every page.',
|
|
317
324
|
};
|
|
@@ -332,7 +339,65 @@ export async function ensureCartDrawer(ctx, session, siteId, dryRun) {
|
|
|
332
339
|
overlay_id: made.overlay.id,
|
|
333
340
|
created: true,
|
|
334
341
|
language: seedLocale,
|
|
342
|
+
...(languageNote ? { language_note: languageNote } : {}),
|
|
335
343
|
...(nodeId ? { node_id: nodeId } : {}),
|
|
336
344
|
next: 'Publish the pages: the drawer reaches a shopper through each published page.',
|
|
337
345
|
};
|
|
338
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* `sb_store action:"cart" relocalize:true` — PUT THE SITE'S LANGUAGE ON AN
|
|
349
|
+
* EXISTING DRAWER. Rewrites only strings EXACTLY equal to another locale's seed
|
|
350
|
+
* word, node ids and styles untouched.
|
|
351
|
+
*
|
|
352
|
+
* THROUGH THE PAGE SAVE, never the overlays API: `PATCH /overlays/{id}` ignores
|
|
353
|
+
* a `document` and answers 200, and there is no document PUT — the page save
|
|
354
|
+
* carries the composed drawer and the page context decomposes it into the
|
|
355
|
+
* master (overlays/rest/rest.go). So a real run needs a page of this site open.
|
|
356
|
+
*/
|
|
357
|
+
export async function relocalizeCartDrawer(ctx, session, siteId, dryRun) {
|
|
358
|
+
const site = encodeURIComponent(siteId);
|
|
359
|
+
const get = (path) => request({ base: ctx.base, method: 'GET', path, token: siteToken(ctx), fetchImpl: ctx.fetchImpl });
|
|
360
|
+
const listed = (await get(`/api/sites/${site}/overlays`));
|
|
361
|
+
const cart = (listed.overlays ?? []).find((o) => o.kind === 'cart');
|
|
362
|
+
if (!cart?.id)
|
|
363
|
+
throw new Error('sbuilder: this site has no cart drawer — sb_store action:"cart" without relocalize creates one.');
|
|
364
|
+
const settings = (await get(`/api/sites/${site}/settings`));
|
|
365
|
+
const raw = settings.settings?.locale;
|
|
366
|
+
if (typeof raw !== 'string' || !raw) {
|
|
367
|
+
throw new Error('sbuilder: settings.locale is unset, so there is no site language to relocalize to — set it with sb_theme locale first.');
|
|
368
|
+
}
|
|
369
|
+
const language = cartSeedLocale(raw);
|
|
370
|
+
const open = session.peek() ? session.location() : null;
|
|
371
|
+
const composed = open?.siteId === siteId
|
|
372
|
+
? Object.values(session.current().doc.nodes).find((n) => n.specials?.overlayId === cart.id)
|
|
373
|
+
: undefined;
|
|
374
|
+
const changes = composed
|
|
375
|
+
? cartRelocalize(session.current().doc, composed.id, language)
|
|
376
|
+
: cart.document?.root_node_id
|
|
377
|
+
? cartRelocalize(cart.document, cart.document.root_node_id, language)
|
|
378
|
+
: [];
|
|
379
|
+
if (dryRun || changes.length === 0) {
|
|
380
|
+
return {
|
|
381
|
+
...(dryRun ? { dry_run: true } : {}),
|
|
382
|
+
overlay_id: cart.id,
|
|
383
|
+
language,
|
|
384
|
+
changes,
|
|
385
|
+
...(changes.length === 0
|
|
386
|
+
? { note: 'Nothing to change — no seed word from another language is left in the drawer.' }
|
|
387
|
+
: {
|
|
388
|
+
plan: redact([{ step: 1, what: 'rewrite those strings in the composed drawer and save the open page', method: 'PUT', path: `/api/sites/${site}/pages/{open page}/source` }]),
|
|
389
|
+
note: 'Nothing was sent. Re-call with dry_run:false; it writes through the open page\'s save.',
|
|
390
|
+
}),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (!composed) {
|
|
394
|
+
throw new Error(`sbuilder: the drawer is written through a page save, so relocalize needs a page of site "${siteId}" open — sb_page_open one, then re-run.`);
|
|
395
|
+
}
|
|
396
|
+
await session.applyAndSave(changes.map((c) => ({ op: 'set', path: ['nodes', c.node_id, 'specials', c.key], value: c.to })));
|
|
397
|
+
return {
|
|
398
|
+
overlay_id: cart.id,
|
|
399
|
+
language,
|
|
400
|
+
changes,
|
|
401
|
+
next: 'Publish the pages: the drawer reaches a shopper through each published page.',
|
|
402
|
+
};
|
|
403
|
+
}
|
package/dist/tools/page.js
CHANGED
|
@@ -17,7 +17,8 @@ import { animationNote, deadKeyNote, unknownValueNote, unknownWriteNote, precond
|
|
|
17
17
|
import { skinLevelNote } from '../domains/site/fieldskin.js';
|
|
18
18
|
import { siteTheme } from '../domains/site/theme-fetch.js';
|
|
19
19
|
import { ensureSiteTheme } from './theme.js';
|
|
20
|
-
import { request, redact, watchPageWrites, onPageSourceWrite } from '../transport/http.js';
|
|
20
|
+
import { request, redact, watchPageWrites, onPageSourceWrite, onGlobalWrite } from '../transport/http.js';
|
|
21
|
+
import { SPEC_GLOBAL_ID, SPEC_GLOBAL_REV, SPEC_OVERLAY_ID, SPEC_OVERLAY_REV } from '../core/tree.js';
|
|
21
22
|
import { siteToken } from './credentialpick.js';
|
|
22
23
|
import { validateForSave } from '../domains/site/validate.js';
|
|
23
24
|
import { reviewDesign, REVIEW_NOTICE } from '../domains/site/review.js';
|
|
@@ -112,6 +113,41 @@ export class PageSession {
|
|
|
112
113
|
if (doc !== this.doc?.doc && this.isOpen(site, page))
|
|
113
114
|
this.markStale('the page was written by another call');
|
|
114
115
|
});
|
|
116
|
+
onGlobalWrite((site, id) => {
|
|
117
|
+
if (this.siteId === site && this.composedMaster('global', id)) {
|
|
118
|
+
this.markStale(`shared section ${id} was written by another call`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** The node this copy composed from that master (stamped `globalId` / `overlayId`), if any. */
|
|
123
|
+
composedMaster(kind, id) {
|
|
124
|
+
if (!this.doc)
|
|
125
|
+
return undefined;
|
|
126
|
+
const stamp = kind === 'global' ? SPEC_GLOBAL_ID : SPEC_OVERLAY_ID;
|
|
127
|
+
return Object.values(this.doc.doc.nodes).find((n) => n.specials?.[stamp] === id);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A shared master was saved or deleted (the room's `global` / `overlay`
|
|
131
|
+
* frame). Past the rev this copy composed — or re-stamped from its OWN save,
|
|
132
|
+
* whose echo this is otherwise — the copy is stale: the yield rule, the next
|
|
133
|
+
* write re-pulls first. `meta` is a rename, and moves no content. Judged
|
|
134
|
+
* after any save in flight, whose re-stamp carries the new rev.
|
|
135
|
+
*/
|
|
136
|
+
masterSaved(kind, id, op, rev) {
|
|
137
|
+
if (op === 'meta')
|
|
138
|
+
return;
|
|
139
|
+
const check = () => {
|
|
140
|
+
const n = this.composedMaster(kind, id);
|
|
141
|
+
if (!n || this.siteId !== this.liveSite)
|
|
142
|
+
return;
|
|
143
|
+
const had = Number(n.specials?.[kind === 'global' ? SPEC_GLOBAL_REV : SPEC_OVERLAY_REV] ?? 0);
|
|
144
|
+
if (op === 'deleted' || rev > had)
|
|
145
|
+
this.markStale(`${kind} ${id} was ${op} elsewhere (rev ${rev})`);
|
|
146
|
+
};
|
|
147
|
+
if (this.saving)
|
|
148
|
+
void this.saving.then(check, check);
|
|
149
|
+
else
|
|
150
|
+
check();
|
|
115
151
|
}
|
|
116
152
|
/**
|
|
117
153
|
* ONE ROOM AT A TIME. A second join (sb_live_join again, or a page on another
|
package/dist/tools/store.js
CHANGED
|
@@ -44,7 +44,7 @@ import { siteChrome, wearChrome } from './page.js';
|
|
|
44
44
|
import { addSubtree } from '../domains/site/builder.js';
|
|
45
45
|
import { attachGlobal, buildChrome, detachGlobal } from './chrome.js';
|
|
46
46
|
import { bindMenu } from './menu.js';
|
|
47
|
-
import { attachOverlay, ensureCartDrawer } from './overlay.js';
|
|
47
|
+
import { attachOverlay, ensureCartDrawer, relocalizeCartDrawer } from './overlay.js';
|
|
48
48
|
import { installApp } from './app.js';
|
|
49
49
|
import { BUILTIN_APP_KEYS } from '../catalog/appscaffolds.generated.js';
|
|
50
50
|
import { ELEMENTS } from '../catalog/elements.generated.js';
|
|
@@ -436,10 +436,15 @@ export function registerStoreTools(server, ctx, session) {
|
|
|
436
436
|
.optional()
|
|
437
437
|
.describe('action:"global_attach"/"global_detach" — the shared section; omitted on attach it ' +
|
|
438
438
|
'lists the site\'s own'),
|
|
439
|
+
relocalize: z
|
|
440
|
+
.boolean()
|
|
441
|
+
.optional()
|
|
442
|
+
.describe('action:"cart" — rewrite an EXISTING drawer\'s seed words that are in another language ' +
|
|
443
|
+
'to the site locale\'s (edited text untouched); needs a page of the site open'),
|
|
439
444
|
dry_run: z.boolean().optional(),
|
|
440
445
|
},
|
|
441
446
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
442
|
-
}, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, dry_run, }) => {
|
|
447
|
+
}, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, relocalize, dry_run, }) => {
|
|
443
448
|
const siteId = siteFor(ctx, given);
|
|
444
449
|
if (action === 'global_attach' || action === 'global_detach') {
|
|
445
450
|
// THE COMPOSED STAMP IS NEVER THE CALLER'S TO WRITE. A page references
|
|
@@ -498,7 +503,8 @@ export function registerStoreTools(server, ctx, session) {
|
|
|
498
503
|
}));
|
|
499
504
|
}
|
|
500
505
|
if (action === 'cart') {
|
|
501
|
-
|
|
506
|
+
const run = relocalize === true ? relocalizeCartDrawer : ensureCartDrawer;
|
|
507
|
+
return text(await run(ctx, session, siteId, dry_run !== false));
|
|
502
508
|
}
|
|
503
509
|
if (action === 'chrome') {
|
|
504
510
|
return text(await buildChrome(ctx, session, siteId, footer === true ? 'footer' : 'header', {
|
package/dist/transport/http.js
CHANGED
|
@@ -134,6 +134,14 @@ export function onPageSourceWrite(fn) {
|
|
|
134
134
|
sourceWritten.delete(fn);
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
+
const globalWritten = new Set();
|
|
138
|
+
export function onGlobalWrite(fn) {
|
|
139
|
+
globalWritten.add(fn);
|
|
140
|
+
return () => {
|
|
141
|
+
globalWritten.delete(fn);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const GLOBAL_WRITE = /^\/api\/sites\/([^/]+)\/global-sections\/([^/]+)(?:\/document)?$/;
|
|
137
145
|
const SOURCE_WRITE = /^\/api\/sites\/([^/]+)\/pages\/([^/]+)\/source$/;
|
|
138
146
|
/**
|
|
139
147
|
* A restore replaces the draft ON THE PLATFORM with no document in the body —
|
|
@@ -151,6 +159,13 @@ const SOURCE_RESTORE = /^\/api\/sites\/([^/]+)\/pages\/([^/]+)\/(?:versions|hist
|
|
|
151
159
|
* peer is on that page) and published as node-level ops.
|
|
152
160
|
*/
|
|
153
161
|
export async function request(opts) {
|
|
162
|
+
const global = opts.method !== 'GET' ? GLOBAL_WRITE.exec(opts.path) : null;
|
|
163
|
+
if (global) {
|
|
164
|
+
const out = await send(opts);
|
|
165
|
+
for (const fn of globalWritten)
|
|
166
|
+
fn(decodeURIComponent(global[1]), decodeURIComponent(global[2]));
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
154
169
|
const restore = opts.method === 'POST' ? SOURCE_RESTORE.exec(opts.path) : null;
|
|
155
170
|
if (restore) {
|
|
156
171
|
const out = await send(opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/sbuilder-mcp",
|
|
6
6
|
"type": "module",
|