@bettercms-ai/mcp 0.66.2 → 0.66.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -2277,7 +2277,7 @@ var STRUCTURE_EXAMPLE_PAYLOAD = {
|
|
|
2277
2277
|
doc: {
|
|
2278
2278
|
schema: 1,
|
|
2279
2279
|
folders: [
|
|
2280
|
-
{ id: "home:pages", parentId: null, name: "
|
|
2280
|
+
{ id: "home:pages", parentId: null, name: "Pages", icon: "documents", sort: 1e3 },
|
|
2281
2281
|
{ id: "home:collections", parentId: null, name: "Shared", icon: null, sort: 1001 },
|
|
2282
2282
|
{ id: "fld_std_blog", parentId: null, name: "Blog posts", icon: "document-text", sort: 0 },
|
|
2283
2283
|
{ id: "fld_std_products", parentId: null, name: "Products", icon: "basket", sort: 1 }
|
|
@@ -2327,18 +2327,20 @@ ever changes.
|
|
|
2327
2327
|
The family's main collection comes first, then the others in the order its fields reference
|
|
2328
2328
|
them.
|
|
2329
2329
|
4. **Family order:** the site's navigation order; failing that, entry count, then name.
|
|
2330
|
-
5. **
|
|
2331
|
-
("
|
|
2330
|
+
5. **Pages.** EVERY remaining standalone page goes in the system folder \`home:pages\`
|
|
2331
|
+
("Pages") \u2014 no standalone page ever gets a top-level folder. Code-only routes (a 404, a
|
|
2332
2332
|
page built by code with nothing to edit) stay there too, marked with the \`file-code\` icon.
|
|
2333
2333
|
Pages that share a route (/legal/terms, /legal/privacy, /docs/...) get a subfolder of it.
|
|
2334
|
+
A site in more than one language files every page, home included, under Pages \u203A <language>
|
|
2335
|
+
(named in itself: English, Fran\xE7ais, Deutsch), default language first.
|
|
2334
2336
|
6. **Shared taxonomies.** A vocabulary several families reference (Tags used by posts AND
|
|
2335
2337
|
products) goes in the system folder \`home:collections\`, named "Shared". So does supporting
|
|
2336
2338
|
content no family owns (testimonials, FAQs with no list page).
|
|
2337
2339
|
7. **Settings closes the sidebar.** \`home:globals\` ("Settings") holds NO pages or collections \u2014
|
|
2338
2340
|
navigation, header, footer, site settings and SEO defaults open from it in the dashboard. The
|
|
2339
|
-
three system folders always sort BELOW the folders you make: \u2026
|
|
2341
|
+
three system folders always sort BELOW the folders you make: \u2026 Pages, Shared, Settings.
|
|
2340
2342
|
8. **Icons** (kit names, bettercms://icons/dock): \`document-text\` for posts, \`basket\` for products,
|
|
2341
|
-
\`book\` for docs, \`documents\` for
|
|
2343
|
+
\`book\` for docs, \`documents\` for Pages, \`folder\` by default. Lucide names already stored
|
|
2342
2344
|
still draw.
|
|
2343
2345
|
9. **Depth:** nest at most 2 levels by default. The hard limit is 4; do not use it without a
|
|
2344
2346
|
reason the user gave you.
|
|
@@ -2354,15 +2356,15 @@ ever changes.
|
|
|
2354
2356
|
|
|
2355
2357
|
What the structure writes refuse, and the code each refusal carries:
|
|
2356
2358
|
|
|
2357
|
-
- **System folders.** \`home:pages\` (
|
|
2359
|
+
- **System folders.** \`home:pages\` (Pages), \`home:collections\` (Shared) and
|
|
2358
2360
|
\`home:globals\` (Settings) always exist and can never be moved or deleted (400
|
|
2359
2361
|
\`NAV_SYSTEM_FOLDER\`); \`rename_folder\` and \`set_icon\` work on them.
|
|
2360
|
-
- **Type purity** (400 \`NAV_TYPE_MISMATCH\`):
|
|
2362
|
+
- **Type purity** (400 \`NAV_TYPE_MISMATCH\`): Pages holds only pages at any depth, Shared
|
|
2361
2363
|
only collections, Settings neither \u2014 globals are not sidebar items.
|
|
2362
2364
|
- **Pins** sit above the folders in their own order, pages or collections. In a document a pin is
|
|
2363
2365
|
\`folderId: "home:root"\`, a virtual id with no folder record; a folder record with that id is
|
|
2364
2366
|
400 \`NAV_SYSTEM_FOLDER\`. In \`move_to_folder\`, \`folderId: 'root'\` pins and \`null\` returns
|
|
2365
|
-
the item to its home (
|
|
2367
|
+
the item to its home (Pages / Shared).
|
|
2366
2368
|
- **Kind.** \`move_to_folder\` and \`set_icon\` VERIFY what an id is: a page id sent as a
|
|
2367
2369
|
collection is 400 \`NAV_KIND_MISMATCH\` naming the kind to use. \`set_content_structure\` never
|
|
2368
2370
|
refuses a kind mismatch: it returns \`warnings\` and \`kindMismatches\` instead.
|
|
@@ -2407,7 +2409,7 @@ collection (\`cm_posts\`), a /shop list page (\`pg_shop\`, dynamic) for Products
|
|
|
2407
2409
|
Blog
|
|
2408
2410
|
[folder] Products icon=shopping-bag
|
|
2409
2411
|
Products, Product categories, Colours (referenced only by Products)
|
|
2410
|
-
[system folder]
|
|
2412
|
+
[system folder] Pages icon=files
|
|
2411
2413
|
About, Contact, Not found (icon=file-code)
|
|
2412
2414
|
[system folder] Shared
|
|
2413
2415
|
Tags (referenced by Blog AND Products: shared)
|
|
@@ -2518,6 +2520,14 @@ function mergeConfig(current, patch) {
|
|
|
2518
2520
|
}
|
|
2519
2521
|
return out;
|
|
2520
2522
|
}
|
|
2523
|
+
var wordsOf = (v) => (typeof v === "string" ? v : typeof v?.label === "string" ? v.label : "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2524
|
+
function helpTextEcho(helpText, field) {
|
|
2525
|
+
if (typeof helpText !== "string" || !helpText.trim()) return null;
|
|
2526
|
+
const said = wordsOf(helpText.replace(/^\s*e\.?g\.?[:,]?\s*/i, "")).replace(/^["'“”‘’]+|["'“”‘’.…]+$/g, "");
|
|
2527
|
+
const value = wordsOf(field.defaultValue);
|
|
2528
|
+
const echoes = said === wordsOf(field.label) || value !== "" && (said === value || /…["”]?$/.test(helpText.trim()) && value.startsWith(said));
|
|
2529
|
+
return echoes ? `helpText repeats the field's label or value, which the editor already shows. Say what belongs here or its format in under 60 characters ("Whole number, no currency symbol."), or send null for none.` : null;
|
|
2530
|
+
}
|
|
2521
2531
|
function planFieldEdit(model, edit, callerIfMatch) {
|
|
2522
2532
|
const fields = model.fields ?? [];
|
|
2523
2533
|
const keys = typeof edit.path === "string" && edit.path ? edit.path.split(".") : [];
|
|
@@ -2540,6 +2550,8 @@ function planFieldEdit(model, edit, callerIfMatch) {
|
|
|
2540
2550
|
if (!retype && rename === void 0 && edit.config === void 0 && changes.length === 0) {
|
|
2541
2551
|
return { ok: false, error: "Nothing to change: pass label, helpText, required, config, showIf, ui, placement, type or key." };
|
|
2542
2552
|
}
|
|
2553
|
+
const echo = helpTextEcho(edit.helpText, { label: edit.label ?? current.label, defaultValue: current.defaultValue });
|
|
2554
|
+
if (echo) return { ok: false, error: echo };
|
|
2543
2555
|
const next = patchAt(fields, keys, (f) => {
|
|
2544
2556
|
let out = { ...f };
|
|
2545
2557
|
for (const k of changes) {
|
|
@@ -3833,7 +3845,7 @@ ${d.summary.outline}` : "Content structure.", d);
|
|
|
3833
3845
|
def(
|
|
3834
3846
|
"suggest_content_structure",
|
|
3835
3847
|
"Propose the standard Content structure",
|
|
3836
|
-
`READ-ONLY: propose the standard Content sidebar structure for the connected project (${STRUCTURE_PLAYBOOK_URI}) and write NOTHING. Runs the standard over the project's pages, collections, references and built routes: the home page and each collection's list page pinned, one folder per content family named for its detail type (Blog posts, Case studies, Products with its taxonomies and an Attributes subfolder), ordered by entry count (or the caller's nav order), shared taxonomies in Shared, every remaining page in
|
|
3848
|
+
`READ-ONLY: propose the standard Content sidebar structure for the connected project (${STRUCTURE_PLAYBOOK_URI}) and write NOTHING. Runs the standard over the project's pages, collections, references and built routes: the home page and each collection's list page pinned, one folder per content family named for its detail type (Blog posts, Case studies, Products with its taxonomies and an Attributes subfolder), ordered by entry count (or the caller's nav order), shared taxonomies in Shared, every remaining page in Pages, at most 2 levels deep. Page-bound models are never filed. Returns { doc, version, hasDocument, outline, rationale }. To apply it: show the user the outline, then call set_content_structure with this doc and this version (the If-Match; a 412 means someone changed the sidebar, so suggest again). When hasDocument is true the project already has a structure somebody made: never replace it without the user's yes; file only what is new with move_to_folder instead.`,
|
|
3837
3849
|
z.object({
|
|
3838
3850
|
maxDepth: z.number().int().min(1).max(4).optional().describe("folder nesting to propose; default 2 (the standard), 4 is the hard limit")
|
|
3839
3851
|
}).shape,
|
|
@@ -3860,7 +3872,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3860
3872
|
items: z.array(z.object({
|
|
3861
3873
|
kind: z.enum(["page", "collection"]),
|
|
3862
3874
|
id: z.string().min(1).describe("page id or collection (content model) id"),
|
|
3863
|
-
folderId: z.string().nullable().describe('folder id; "home:root" = pinned at the top level; null = its home (
|
|
3875
|
+
folderId: z.string().nullable().describe('folder id; "home:root" = pinned at the top level; null = its home (Pages for a page, the Shared list for a collection)'),
|
|
3864
3876
|
sort: z.number(),
|
|
3865
3877
|
icon: z.string().nullable()
|
|
3866
3878
|
}))
|
|
@@ -3893,13 +3905,13 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3893
3905
|
def(
|
|
3894
3906
|
"move_to_folder",
|
|
3895
3907
|
"Move into a Content sidebar folder",
|
|
3896
|
-
`Move a page, a collection or a folder into a folder. folderId null moves it back to its home:
|
|
3908
|
+
`Move a page, a collection or a folder into a folder. folderId null moves it back to its home: Pages for a page, the Shared list for a collection, the top level for a folder. It lands after what is already there. Moving a folder takes its contents with it and is refused if the result would nest deeper than 4 levels or put a folder inside itself. ${STRUCTURE_POINTER}`,
|
|
3897
3909
|
z.object({
|
|
3898
3910
|
kind: z.enum(["page", "collection", "folder"]).describe(
|
|
3899
3911
|
"what the id IS on this branch \u2014 VERIFIED, not assumed: an id is either a page or a collection, never both, and a mismatch is refused with 400 NAV_KIND_MISMATCH naming the kind to use. Call it again with that kind, which also clears the wrong-kind entry."
|
|
3900
3912
|
),
|
|
3901
3913
|
id: z.string().min(1).describe("the page id, collection (content model) id, or folder id"),
|
|
3902
|
-
folderId: z.string().min(1).nullable().describe("target folder id; 'root' pins a page or collection at the top level (for a folder, 'root' is the top level); null moves it back to its home (
|
|
3914
|
+
folderId: z.string().min(1).nullable().describe("target folder id; 'root' pins a page or collection at the top level (for a folder, 'root' is the top level); null moves it back to its home (Pages, the Shared list, or the top level for a folder)")
|
|
3903
3915
|
}).shape,
|
|
3904
3916
|
async (c, a) => structureResult(await data(c, "POST", `/management/content-structure/move`, { kind: a.kind, id: a.id, folderId: a.folderId }))
|
|
3905
3917
|
),
|
|
@@ -6852,7 +6864,8 @@ Importance orders a screen: required fields first, then array order.
|
|
|
6852
6864
|
schema: the same call with \`pageId\` instead of \`modelId\`, and the version from \`get_page\`'s
|
|
6853
6865
|
\`backingModel\`.
|
|
6854
6866
|
- A component's props: \`update_component\` with \`propUi\`, \`propGroup\`, \`propLabel\`, \`propHelpText\` (keyed by prop key).
|
|
6855
|
-
Name props from the code (\`plan.cents\` \u2192 Cents)
|
|
6867
|
+
Name props from the code (\`plan.cents\` \u2192 Cents); help text guides ("Whole number, no currency symbol."), never repeats
|
|
6868
|
+
the value; repeated cards are one \`table\` prop. An icon (\`config.media: "icon"\`) is content: group it, never prune it.
|
|
6856
6869
|
They write the DRAFT props; no publish is needed for the editor to show them.
|
|
6857
6870
|
- Always send \`ifMatch\`, the \`optimisticVersion\` you read.
|
|
6858
6871
|
- A NEW field carries its chrome in the same \`add_field\` call.
|
|
@@ -7647,7 +7660,7 @@ function buildServer(deps) {
|
|
|
7647
7660
|
STRUCTURE_PLAYBOOK_URI,
|
|
7648
7661
|
{
|
|
7649
7662
|
title: "BetterCMS Content structure playbook",
|
|
7650
|
-
description: "How to organise a project's Content sidebar: one place per document, the home page and list pages pinned, one folder per content family named for the detail type its route holds and ordered by entry count, shared taxonomies in Shared,
|
|
7663
|
+
description: "How to organise a project's Content sidebar: one place per document, the home page and list pages pinned, one folder per content family named for the detail type its route holds and ordered by entry count, shared taxonomies in Shared, Pages for the rest, Settings last, at most 2 levels. With a worked set_content_structure payload.",
|
|
7651
7664
|
mimeType: "text/markdown"
|
|
7652
7665
|
},
|
|
7653
7666
|
() => ({
|