sbuilder-mcp 0.60.4 → 0.61.1
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 +18 -0
- package/CHANGELOG.vi.md +18 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/dist/catalog/api.generated.js +31 -3
- package/dist/catalog/deadkeys.generated.js +2 -2
- package/dist/catalog/elements.generated.js +109 -0
- package/dist/catalog/shapes.generated.js +17 -3
- package/dist/catalog/source.generated.js +7 -0
- package/dist/domains/site/vocabulary.js +89 -1
- package/dist/tools/page.js +26 -1
- package/dist/tools/session.js +22 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ 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.61.1] - 2026-09-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_connect` now reports how old the generated catalog's platform knowledge is, in words (e.g. "45 days old"), along with the web_builder commit it was generated from, so a stale install reads as staleness instead of as a missing element or key.
|
|
13
|
+
- `sb_set`'s precondition warning now also covers `filterMatch: "all"` on filter elements, which only takes effect where a product can hold several of a source's values.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `sb_set`'s precondition warning no longer reports a false positive when a sibling `specials` key is simply unset, since a `null` clause now correctly means "not set" instead of being compared as a literal value.
|
|
17
|
+
|
|
18
|
+
## [0.61.0] - 2026-09-16
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `sb_set` now warns when a write on a `specials` key is legal on its own but does nothing because a sibling key it depends on is missing or set to the wrong value, naming both keys, what the node renders as meanwhile, and which value would fix it, read from the platform's own precondition declarations rather than a list kept in this repo.
|
|
22
|
+
- `sb_set` now separately warns when a `specials` key is not honored by the target element's renderer at all, naming the element types that do honor it, so the write is not mistaken for a fixable missing-neighbour case.
|
|
23
|
+
|
|
24
|
+
### Internal
|
|
25
|
+
- The generated catalog is refreshed against a current platform checkout (a new tool-results API route); operation count moves from 530 to 531 (179 of 238 writes now carrying a body shape read off the handler).
|
|
26
|
+
|
|
9
27
|
## [0.60.4] - 2026-09-16
|
|
10
28
|
|
|
11
29
|
### Internal
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,24 @@ 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.61.1] - 2026-09-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_connect` giờ báo cáo kiến thức nền tảng của catalog được sinh ra đã cũ đến mức nào, bằng lời (ví dụ "45 days old"), cùng với commit web_builder mà nó được sinh ra từ đó, để một bản cài đặt lỗi thời được đọc đúng là lỗi thời thay vì bị hiểu nhầm là thiếu element hay key.
|
|
13
|
+
- Cảnh báo tiền điều kiện của `sb_set` giờ bao phủ thêm `filterMatch: "all"` trên các element bộ lọc, giá trị này chỉ có tác dụng ở nơi một sản phẩm có thể mang nhiều giá trị của một nguồn.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Cảnh báo tiền điều kiện của `sb_set` không còn báo sai (false positive) khi một key `specials` liền kề đơn giản là chưa được thiết lập, vì một clause `null` giờ được hiểu đúng là "chưa thiết lập" thay vì bị so sánh như một giá trị literal.
|
|
17
|
+
|
|
18
|
+
## [0.61.0] - 2026-09-16
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `sb_set` giờ cảnh báo khi một lệnh ghi lên key `specials` tự nó hợp lệ nhưng không làm gì cả vì một key liền kề mà nó phụ thuộc vào đang thiếu hoặc sai giá trị — nêu rõ cả hai key, node sẽ render ra sao trong lúc đó, và giá trị nào sẽ khắc phục, được đọc từ chính khai báo tiền điều kiện của nền tảng thay vì một danh sách tự giữ trong repo này.
|
|
22
|
+
- `sb_set` giờ cảnh báo riêng khi một key `specials` hoàn toàn không được renderer của element đích đọc tới, nêu rõ những element type nào thực sự đọc key đó, để tránh bị nhầm thành trường hợp thiếu key liền kề có thể sửa được.
|
|
23
|
+
|
|
24
|
+
### Internal
|
|
25
|
+
- Catalog được sinh ra giờ được làm mới dựa trên một bản checkout hiện tại của nền tảng (route API tool-results mới); số operation tăng từ 530 lên 531 (179 trong 238 lệnh ghi giờ mang hình dạng body đọc thẳng từ handler).
|
|
26
|
+
|
|
9
27
|
## [0.60.4] - 2026-09-16
|
|
10
28
|
|
|
11
29
|
### Internal
|
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
112
112
|
| `sb_store` | Run a store flow that must happen in a fixed order — the four writes that make a working checkout, or any of the platform's 17 form templates (login, register, forgot, contact, subscribe …) with its own field document |
|
|
113
113
|
| `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 |
|
|
114
114
|
|
|
115
|
-
Twenty-eight tools, **
|
|
115
|
+
Twenty-eight tools, **531 API operations** (179 of the 238 writes carrying a body shape read
|
|
116
116
|
off the handler), 113 elements, 79 binding sources. `sb_api_find`
|
|
117
117
|
is an index rather than a tool per endpoint, so the tool list stays short while everything
|
|
118
118
|
the platform can do stays reachable — and operations added to the platform arrive with the
|
package/README.vi.md
CHANGED
|
@@ -109,7 +109,7 @@ nhét ngược lại vào mọi bản cài.
|
|
|
109
109
|
| `sb_store` | Chạy một luồng cửa hàng bắt buộc đúng thứ tự — bốn lệnh ghi tạo nên trang thanh toán, hoặc gieo bất kỳ template nào trong 17 form của nền tảng (login, register, forgot, contact, subscribe …) kèm field document của nó |
|
|
110
110
|
| `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 |
|
|
111
111
|
|
|
112
|
-
Hai mươi tám tool, **
|
|
112
|
+
Hai mươi tám tool, **531 operation API** (179 trong 238 lệnh ghi có hình dạng body đọc thẳng
|
|
113
113
|
từ handler), 113 element, 79 nguồn binding. `sb_api_find` là
|
|
114
114
|
một chỉ mục chứ không phải mỗi endpoint một tool, nên danh sách tool vẫn ngắn trong khi mọi
|
|
115
115
|
thứ nền tảng làm được vẫn với tới — và operation mới thêm bên nền tảng sẽ tự có sau lần
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const SWAGGER_SOURCE = {
|
|
2
|
-
"operations":
|
|
2
|
+
"operations": 531,
|
|
3
3
|
"definitions": 107,
|
|
4
|
-
"bodyCarrying":
|
|
5
|
-
"bodyUndescribed":
|
|
4
|
+
"bodyCarrying": 197,
|
|
5
|
+
"bodyUndescribed": 78,
|
|
6
6
|
"generatedFrom": "server/docs/swagger.json"
|
|
7
7
|
};
|
|
8
8
|
export const API_OPERATIONS = [
|
|
@@ -3489,6 +3489,34 @@ export const API_OPERATIONS = [
|
|
|
3489
3489
|
"bodyRef": null,
|
|
3490
3490
|
"credential": "siteScoped"
|
|
3491
3491
|
},
|
|
3492
|
+
{
|
|
3493
|
+
"id": "post:/api/sites/{siteId}/ai/tool-results",
|
|
3494
|
+
"method": "POST",
|
|
3495
|
+
"path": "/api/sites/{siteId}/ai/tool-results",
|
|
3496
|
+
"tags": [
|
|
3497
|
+
"sites"
|
|
3498
|
+
],
|
|
3499
|
+
"summary": "Deliver the result of a client-side tool call",
|
|
3500
|
+
"params": [
|
|
3501
|
+
{
|
|
3502
|
+
"name": "siteId",
|
|
3503
|
+
"in": "path",
|
|
3504
|
+
"required": true,
|
|
3505
|
+
"type": "string",
|
|
3506
|
+
"description": "Site ID"
|
|
3507
|
+
},
|
|
3508
|
+
{
|
|
3509
|
+
"name": "result",
|
|
3510
|
+
"in": "body",
|
|
3511
|
+
"required": true,
|
|
3512
|
+
"type": "object",
|
|
3513
|
+
"description": "id (the tool_request's own id, from the SSE event) and result (what the tool answered, as a string)"
|
|
3514
|
+
}
|
|
3515
|
+
],
|
|
3516
|
+
"bodyDescribed": false,
|
|
3517
|
+
"bodyRef": null,
|
|
3518
|
+
"credential": "siteScoped"
|
|
3519
|
+
},
|
|
3492
3520
|
{
|
|
3493
3521
|
"id": "get:/api/sites/{siteId}/api-keys",
|
|
3494
3522
|
"method": "GET",
|
|
@@ -36830,6 +36830,115 @@ export const ELEMENT_SEEDS = {
|
|
|
36830
36830
|
export const FIRST_CHILD_ONLY = [
|
|
36831
36831
|
"list-dataset"
|
|
36832
36832
|
];
|
|
36833
|
+
/**
|
|
36834
|
+
* What a setting needs from its NEIGHBOURS before it does anything.
|
|
36835
|
+
*
|
|
36836
|
+
* Every other table here publishes one key's legal values, which cannot express
|
|
36837
|
+
* "these two are legal apart and meaningless together" — and that is a real
|
|
36838
|
+
* write: filterValueMode "auto" with filterSource "blog_category" stores,
|
|
36839
|
+
* saves, publishes and renders an empty filter.
|
|
36840
|
+
*
|
|
36841
|
+
* Declared by the platform (schema/src/filters/preconditions.ts) and pinned
|
|
36842
|
+
* there against both Go renderers.
|
|
36843
|
+
*/
|
|
36844
|
+
export const WRITE_PRECONDITIONS = [
|
|
36845
|
+
{
|
|
36846
|
+
"key": "filterValueMode",
|
|
36847
|
+
"value": "auto",
|
|
36848
|
+
"types": [
|
|
36849
|
+
"filter-checkbox",
|
|
36850
|
+
"filter-radio",
|
|
36851
|
+
"filter-color",
|
|
36852
|
+
"filter-tag"
|
|
36853
|
+
],
|
|
36854
|
+
"requires": [
|
|
36855
|
+
{
|
|
36856
|
+
"key": "filterSource",
|
|
36857
|
+
"anyOf": [
|
|
36858
|
+
"category"
|
|
36859
|
+
]
|
|
36860
|
+
}
|
|
36861
|
+
],
|
|
36862
|
+
"otherwise": "the filter offers NOTHING and renders nothing at all. What this mode follows is the page's own PRODUCT collection (`/collections/{slug}`), so the blog's taxonomy — a tree too, which is why it looks like it should work — has nothing here to follow."
|
|
36863
|
+
},
|
|
36864
|
+
{
|
|
36865
|
+
"key": "filterBehavior",
|
|
36866
|
+
"value": "navigate",
|
|
36867
|
+
"types": [
|
|
36868
|
+
"filter-checkbox",
|
|
36869
|
+
"filter-radio",
|
|
36870
|
+
"filter-color",
|
|
36871
|
+
"filter-tag"
|
|
36872
|
+
],
|
|
36873
|
+
"requires": [
|
|
36874
|
+
{
|
|
36875
|
+
"key": "filterSource",
|
|
36876
|
+
"anyOf": [
|
|
36877
|
+
"category",
|
|
36878
|
+
"blog_category"
|
|
36879
|
+
]
|
|
36880
|
+
}
|
|
36881
|
+
],
|
|
36882
|
+
"otherwise": "every row renders as an ordinary filtering label instead of a link. A row becomes an `<a href>` only where its value HAS a page, and both renderers decide that from the source's link kind — a brand, a tag or a price band is a way of narrowing a shelf, not a place."
|
|
36883
|
+
},
|
|
36884
|
+
{
|
|
36885
|
+
"key": "filterShowAll",
|
|
36886
|
+
"value": true,
|
|
36887
|
+
"types": [
|
|
36888
|
+
"filter-checkbox",
|
|
36889
|
+
"filter-radio",
|
|
36890
|
+
"filter-color",
|
|
36891
|
+
"filter-tag"
|
|
36892
|
+
],
|
|
36893
|
+
"requires": [
|
|
36894
|
+
{
|
|
36895
|
+
"key": "filterValueMode",
|
|
36896
|
+
"anyOf": [
|
|
36897
|
+
"auto"
|
|
36898
|
+
]
|
|
36899
|
+
},
|
|
36900
|
+
{
|
|
36901
|
+
"key": "filterBehavior",
|
|
36902
|
+
"anyOf": [
|
|
36903
|
+
"navigate"
|
|
36904
|
+
]
|
|
36905
|
+
}
|
|
36906
|
+
],
|
|
36907
|
+
"otherwise": "no extra row is drawn. It is the way BACK OUT of a browse tree — a link to the category the page is serving — so it needs the page to be followed and the rows to be links. Under in-place filtering, \"everything\" is already what an untouched filter shows."
|
|
36908
|
+
},
|
|
36909
|
+
{
|
|
36910
|
+
"key": "filterMatch",
|
|
36911
|
+
"value": "all",
|
|
36912
|
+
"types": [
|
|
36913
|
+
"filter-checkbox",
|
|
36914
|
+
"filter-radio",
|
|
36915
|
+
"filter-color",
|
|
36916
|
+
"filter-tag"
|
|
36917
|
+
],
|
|
36918
|
+
"requires": [
|
|
36919
|
+
{
|
|
36920
|
+
"key": "filterSource",
|
|
36921
|
+
"anyOf": [
|
|
36922
|
+
"category",
|
|
36923
|
+
"attribute",
|
|
36924
|
+
"tag",
|
|
36925
|
+
"blog_category",
|
|
36926
|
+
"course_tag"
|
|
36927
|
+
]
|
|
36928
|
+
},
|
|
36929
|
+
{
|
|
36930
|
+
"key": "filterBehavior",
|
|
36931
|
+
"anyOf": [
|
|
36932
|
+
"filter",
|
|
36933
|
+
"navigate",
|
|
36934
|
+
"",
|
|
36935
|
+
null
|
|
36936
|
+
]
|
|
36937
|
+
}
|
|
36938
|
+
],
|
|
36939
|
+
"otherwise": "the filter combines ticked values with OR, as it always did. Both predicates IGNORE the mode outside these cases rather than honouring it — a product holds ONE brand, one price and one stock state, so \"match all\" on two ticked values could only return an empty shelf, and a stale URL degrades to the ordinary OR instead of to a blank page."
|
|
36940
|
+
}
|
|
36941
|
+
];
|
|
36833
36942
|
export const HOVER_HOMES = {
|
|
36834
36943
|
"button": {
|
|
36835
36944
|
"home": "legacy"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const SHAPE_SOURCE = {
|
|
2
|
-
"writeOperations":
|
|
3
|
-
"shaped":
|
|
4
|
-
"fromHandlers":
|
|
2
|
+
"writeOperations": 238,
|
|
3
|
+
"shaped": 179,
|
|
4
|
+
"fromHandlers": 179,
|
|
5
5
|
"fromSwaggerOnly": 0,
|
|
6
6
|
"withReadOnly": 26,
|
|
7
7
|
"structsRead": 1726
|
|
@@ -3038,6 +3038,20 @@ export const REQUEST_SHAPES = {
|
|
|
3038
3038
|
"source": "go",
|
|
3039
3039
|
"goType": "(inline)"
|
|
3040
3040
|
},
|
|
3041
|
+
"post:/api/sites/{siteId}/ai/tool-results": {
|
|
3042
|
+
"fields": [
|
|
3043
|
+
{
|
|
3044
|
+
"name": "id",
|
|
3045
|
+
"type": "string"
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
"name": "result",
|
|
3049
|
+
"type": "string"
|
|
3050
|
+
}
|
|
3051
|
+
],
|
|
3052
|
+
"source": "go",
|
|
3053
|
+
"goType": "(inline)"
|
|
3054
|
+
},
|
|
3041
3055
|
"put:/api/sites/{siteId}/ai/settings": {
|
|
3042
3056
|
"fields": [
|
|
3043
3057
|
{
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-catalog.ts — do not edit by hand.
|
|
2
|
+
// The platform commit every other generated file here describes.
|
|
3
|
+
export const PLATFORM_SOURCE = {
|
|
4
|
+
"commit": "d6a68b77a7a00a6a9ca66da5161d57e845aae628",
|
|
5
|
+
"committedAt": "2026-09-17T00:01:02+07:00",
|
|
6
|
+
"dirty": false
|
|
7
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEAD_KEYS } from '../../catalog/deadkeys.generated.js';
|
|
2
|
-
import { ANIMATION, CONFIG_VALUES, ELEMENT_VALUES } from '../../catalog/elements.generated.js';
|
|
2
|
+
import { ANIMATION, CONFIG_VALUES, ELEMENT_VALUES, WRITE_PRECONDITIONS, } from '../../catalog/elements.generated.js';
|
|
3
3
|
/** The vocabulary for a config key, or null when the key has no fixed one. */
|
|
4
4
|
export function vocabularyFor(key) {
|
|
5
5
|
return CONFIG_VALUES[key] ?? null;
|
|
@@ -395,3 +395,91 @@ export function deadKeyNote(namespace, key) {
|
|
|
395
395
|
'draws no row for it either, so only an agent can reach it. Nothing here can fix that — the ' +
|
|
396
396
|
`fix is upstream, in schema/src/elements/${dead.seededBy[0]}/meta.ts.`);
|
|
397
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* THE WARNING A PER-KEY VOCABULARY CANNOT GIVE.
|
|
400
|
+
*
|
|
401
|
+
* Every table above publishes ONE key's legal values, so a write whose values
|
|
402
|
+
* are each legal passes in silence — and several combinations in this platform
|
|
403
|
+
* are legal apart and meaningless together. `filterValueMode: "auto"` with
|
|
404
|
+
* `filterSource: "blog_category"` is the one that prompted this: both values
|
|
405
|
+
* are real, the pair stores, saves, publishes, and renders an empty filter,
|
|
406
|
+
* with no error at any layer. A caller had no way to see it coming.
|
|
407
|
+
*
|
|
408
|
+
* Takes the node AS IT WILL BE — the caller merges its write in first — because
|
|
409
|
+
* these are questions about the finished node. Asking about one key alone is
|
|
410
|
+
* exactly the blindness being fixed.
|
|
411
|
+
*
|
|
412
|
+
* A WARNING AND NEVER A REFUSAL, the rule its siblings above already follow:
|
|
413
|
+
* the platform stores what it is given, so refusing would invent a rule it does
|
|
414
|
+
* not have, and a newer deployment may honour a combination this catalog was
|
|
415
|
+
* generated before. The sentence says what the PAGE does, never "invalid".
|
|
416
|
+
*/
|
|
417
|
+
/**
|
|
418
|
+
* One clause against one stored value — a MIRROR of `holds` in
|
|
419
|
+
* schema/src/filters/preconditions.ts, and the two must agree.
|
|
420
|
+
*
|
|
421
|
+
* `null` in a clause means NOT SET, which is a real state rather than a hole:
|
|
422
|
+
* a node written before a key existed does not carry it, and both renderers
|
|
423
|
+
* then read their own fallback. Without this mapping the two copies disagreed
|
|
424
|
+
* within an hour of being written — this one called a perfectly good tag filter
|
|
425
|
+
* broken, which is a FALSE POSITIVE and worse than the silence the whole
|
|
426
|
+
* mechanism replaced, because a caller warned about correct work stops reading
|
|
427
|
+
* the warnings.
|
|
428
|
+
*
|
|
429
|
+
* It is a copy because this package has no runtime dependency on the schema —
|
|
430
|
+
* the table arrives as generated data. The cases that separate the two are
|
|
431
|
+
* asserted on both sides (`test/write-preconditions.test.ts` here,
|
|
432
|
+
* `schema/test/filter-preconditions.test.ts` there) rather than left to the
|
|
433
|
+
* reading.
|
|
434
|
+
*/
|
|
435
|
+
function holdsClause(c, stored) {
|
|
436
|
+
return c.anyOf.includes(stored === undefined ? null : stored);
|
|
437
|
+
}
|
|
438
|
+
export function preconditionNotes(type, specials) {
|
|
439
|
+
const out = [];
|
|
440
|
+
for (const p of WRITE_PRECONDITIONS) {
|
|
441
|
+
if (!p.types.includes(type))
|
|
442
|
+
continue;
|
|
443
|
+
if (specials[p.key] !== p.value)
|
|
444
|
+
continue;
|
|
445
|
+
const missing = p.requires.filter((c) => !holdsClause(c, specials[c.key]));
|
|
446
|
+
if (missing.length === 0)
|
|
447
|
+
continue;
|
|
448
|
+
const held = missing
|
|
449
|
+
.map((c) => {
|
|
450
|
+
const now = specials[c.key];
|
|
451
|
+
const want = c.anyOf
|
|
452
|
+
.map((v) => (v === null ? 'to be left unset' : JSON.stringify(v)))
|
|
453
|
+
.join(' or ');
|
|
454
|
+
// An ABSENT key is not `null`, and saying so would send a caller looking
|
|
455
|
+
// for a null they never wrote. The platform's own default applies here,
|
|
456
|
+
// which is a different thing to fix than a wrong value.
|
|
457
|
+
const has = now === undefined ? 'is not set' : `is ${JSON.stringify(now)}`;
|
|
458
|
+
return `${c.key} ${has}, and this needs ${want}`;
|
|
459
|
+
})
|
|
460
|
+
.join('; ');
|
|
461
|
+
out.push(`specials.${p.key} = ${JSON.stringify(p.value)} does nothing on this node: ${held}. ` +
|
|
462
|
+
`Left as it is, ${p.otherwise}`);
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* The same question for a shape that does not honour the setting AT ALL.
|
|
468
|
+
*
|
|
469
|
+
* Split from the clause check because the answer is different in kind: a
|
|
470
|
+
* missing neighbour is something the caller can fix with another write, while a
|
|
471
|
+
* `select` will never read the auto value mode however the rest of the node is
|
|
472
|
+
* arranged.
|
|
473
|
+
*/
|
|
474
|
+
export function unsupportedSettingNote(type, key, value) {
|
|
475
|
+
const p = WRITE_PRECONDITIONS.find((x) => x.key === key && x.value === value);
|
|
476
|
+
if (!p || p.types.includes(type))
|
|
477
|
+
return null;
|
|
478
|
+
// NOT `p.otherwise`. That sentence explains what a node MISSING A NEIGHBOUR
|
|
479
|
+
// renders as, and borrowing it here would hand a caller the wrong diagnosis —
|
|
480
|
+
// it would send them to fix a sibling key on a shape that will never read
|
|
481
|
+
// this one however the rest of the node is arranged.
|
|
482
|
+
return (`specials.${key} = ${JSON.stringify(value)} is not a setting ${type} reads — ` +
|
|
483
|
+
`only ${p.types.join(', ')} honour it. It stores, saves and publishes with no error, ` +
|
|
484
|
+
`and this element's renderer never looks at it.`);
|
|
485
|
+
}
|
package/dist/tools/page.js
CHANGED
|
@@ -9,7 +9,7 @@ import { detachNote, presetIdOf, presetLayer } from '../domains/site/theme.js';
|
|
|
9
9
|
import { inertHintsFor } from '../domains/site/inert.js';
|
|
10
10
|
import { hasSeed, layoutDocument, seedDocument, seedSummary, seededTypes, } from '../domains/site/storepage.js';
|
|
11
11
|
import { PAGE_TYPES } from '../catalog/storepages.generated.js';
|
|
12
|
-
import { animationNote, deadKeyNote, unknownValueNote, unknownWriteNote, } from '../domains/site/vocabulary.js';
|
|
12
|
+
import { animationNote, deadKeyNote, unknownValueNote, unknownWriteNote, preconditionNotes, unsupportedSettingNote, } from '../domains/site/vocabulary.js';
|
|
13
13
|
import { skinLevelNote } from '../domains/site/fieldskin.js';
|
|
14
14
|
import { siteTheme } from '../domains/site/theme-fetch.js';
|
|
15
15
|
import { request, redact } from '../transport/http.js';
|
|
@@ -787,6 +787,31 @@ export function registerPageTools(server, ctx) {
|
|
|
787
787
|
if (once)
|
|
788
788
|
valueNotes.push(once);
|
|
789
789
|
}
|
|
790
|
+
// …AND THE ONE A PER-KEY TABLE CANNOT GIVE: values that are each legal
|
|
791
|
+
// and mean nothing TOGETHER. `filterValueMode: "auto"` with
|
|
792
|
+
// `filterSource: "blog_category"` passed every check above and
|
|
793
|
+
// published a filter that renders nothing.
|
|
794
|
+
//
|
|
795
|
+
// Asked of the node AS IT WILL BE, not of the write: the node may
|
|
796
|
+
// already carry the neighbour this write needs, and the write may
|
|
797
|
+
// supply the neighbour a stored value was missing — so a question
|
|
798
|
+
// about one key alone answers the wrong thing in both directions.
|
|
799
|
+
if (e.namespace === 'specials') {
|
|
800
|
+
const after = { ...(d.doc.nodes[e.id]?.specials ?? {}), ...e.keys };
|
|
801
|
+
for (const [k, v] of Object.entries(e.keys)) {
|
|
802
|
+
const un = unsupportedSettingNote(type, k, v);
|
|
803
|
+
if (!un)
|
|
804
|
+
continue;
|
|
805
|
+
const once = ctx.notices.once(`unsupported:${type}.${k}=${JSON.stringify(v)}`, un);
|
|
806
|
+
if (once)
|
|
807
|
+
valueNotes.push(once);
|
|
808
|
+
}
|
|
809
|
+
for (const n of preconditionNotes(type, after)) {
|
|
810
|
+
const once = ctx.notices.once(`precondition:${type}:${n.slice(0, 60)}`, n);
|
|
811
|
+
if (once)
|
|
812
|
+
valueNotes.push(once);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
790
815
|
}
|
|
791
816
|
// AND THE QUIETER ONE: a key an element SEEDS that no renderer anywhere
|
|
792
817
|
// reads. The vocabularies above answer "this value means something other
|
package/dist/tools/session.js
CHANGED
|
@@ -2,6 +2,26 @@ import { z } from 'zod';
|
|
|
2
2
|
import { request } from '../transport/http.js';
|
|
3
3
|
import { text } from '../mcp/response.js';
|
|
4
4
|
import { API_OPERATIONS } from '../catalog/api.generated.js';
|
|
5
|
+
import { PLATFORM_SOURCE } from '../catalog/source.generated.js';
|
|
6
|
+
/**
|
|
7
|
+
* The provenance line, or '' when the generator had no git to ask.
|
|
8
|
+
*
|
|
9
|
+
* Reads as an AGE rather than a commit id, because the age is the actionable
|
|
10
|
+
* half — nobody can tell whether `d6a68b7` is recent, and everybody can tell
|
|
11
|
+
* whether four months is. The id rides along for the person who then has to
|
|
12
|
+
* find it.
|
|
13
|
+
*/
|
|
14
|
+
export function catalogAge(now = new Date()) {
|
|
15
|
+
const { commit, committedAt, dirty } = PLATFORM_SOURCE;
|
|
16
|
+
if (!commit || !committedAt)
|
|
17
|
+
return '';
|
|
18
|
+
const days = Math.floor((now.getTime() - new Date(committedAt).getTime()) / 86_400_000);
|
|
19
|
+
const age = days <= 1 ? 'today' : days < 45 ? `${days} days old` : `${Math.floor(days / 30)} months old`;
|
|
20
|
+
return (`Built from web_builder ${commit.slice(0, 9)} (${committedAt.slice(0, 10)}, ${age})` +
|
|
21
|
+
(dirty ? ', from an UNCOMMITTED tree — it may describe work no deployment has' : '') +
|
|
22
|
+
`. The platform reports no version of its own, so nothing here can compare the two: if an ` +
|
|
23
|
+
`element or key you expect is missing, regenerate rather than working around it.`);
|
|
24
|
+
}
|
|
5
25
|
/**
|
|
6
26
|
* Log in and report what this server can actually reach.
|
|
7
27
|
*
|
|
@@ -26,6 +46,7 @@ export async function connect(ctx, args) {
|
|
|
26
46
|
...(ctx.siteId ? { site: ctx.siteId } : {}),
|
|
27
47
|
...(ctx.siteId && ctx.siteName ? { site_name: ctx.siteName } : {}),
|
|
28
48
|
operations: API_OPERATIONS.length,
|
|
49
|
+
...(catalogAge() ? { catalog: catalogAge() } : {}),
|
|
29
50
|
note: ctx.siteId
|
|
30
51
|
? `Connected with an API key alone, on site ${ctx.siteId} (SB_SITE)${ctx.siteName ? `, the store called ${JSON.stringify(ctx.siteName)}` : ''}. Every tool ` +
|
|
31
52
|
'defaults to it, so site_id is optional. Set SB_EMAIL and SB_PASSWORD as well if ' +
|
|
@@ -71,6 +92,7 @@ export async function connect(ctx, args) {
|
|
|
71
92
|
sites: (listed?.sites ?? []).map((s) => ({ id: s.id, name: s.name })),
|
|
72
93
|
api_key: ctx.apiKey ? 'present' : 'missing',
|
|
73
94
|
operations: API_OPERATIONS.length,
|
|
95
|
+
...(catalogAge() ? { catalog: catalogAge() } : {}),
|
|
74
96
|
};
|
|
75
97
|
if (!ctx.apiKey) {
|
|
76
98
|
result.note =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.1",
|
|
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",
|