@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: "Other pages", icon: "documents", sort: 1e3 },
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. **Other pages.** EVERY remaining standalone page goes in the system folder \`home:pages\`
2331
- ("Other pages") \u2014 no standalone page ever gets a top-level folder. Code-only routes (a 404, a
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 Other pages, Shared, Settings.
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 Other pages, \`folder\` by default. Lucide names already stored
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\` (Other pages), \`home:collections\` (Shared) and
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\`): Other pages holds only pages at any depth, Shared
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 (Other pages / Shared).
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] Other pages icon=files
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 Other 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.`,
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 (Other pages for a page, the Shared list for a collection)'),
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: Other 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}`,
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 (Other pages, the Shared list, or the top level for a folder)")
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), help text as \`e.g. "49"\`; repeated cards are one \`table\` prop.
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, Other pages for the rest, Settings last, at most 2 levels. With a worked set_content_structure payload.",
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
  () => ({