@bettercms-ai/mcp 0.64.1 → 0.65.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/dist/index.js CHANGED
@@ -2270,7 +2270,8 @@ var SKILLS_ROUTING_INSTRUCTION = "Before writing code in a repo that uses Better
2270
2270
  var DOCK_UI_ROUTING_INSTRUCTION = "How fields and props look in the editor (groups, tabs, rows, icons, item names) is playbook \xA714 or the `field-ui` prompt: write it with update_content_model field* or update_component prop* plus ifMatch, read it back, and finish when get_next_steps reports no dock-* step.";
2271
2271
  var RECEIPT_FIRST_INSTRUCTION = "Start every project with get_next_steps: its `receipt` lists what is already done (`done`), what is still missing (`missing`, each item citing the count it reacted to) and the one `nextAction`. Work from that receipt instead of re-reading the site, call get_next_steps again after each batch of edits, and before you tell the user you are finished. Fix what it lists, or say why you are leaving it.";
2272
2272
  var NEXT_STEPS_DESCRIPTION = `Call this FIRST on a project. Returns \`receipt\` \u2014 \`done\` (live release, bound elements, conversion coverage, composed pages, published components and Layout), \`missing\`, \`nextAction\` and \`notes\` (unjudged: unknown, not done) \u2014 plus \`data\`, the unfinished items: missing meta descriptions, drafts, empty collections, unnotified forms, writes awaiting approval, unstamped bindings or sections, \`authoring-undecided\` and the Layout lane (\`layout-write-blocked\`, \`layout-field-unread\`, \`layout-locator-stale\`, \`layout-rebuild-required\`, \`chrome-duplicated\`). Each item cites its count and may carry \`payload\` {rows, total}, at most 20 rows. Call it again after a batch of edits and before telling the user you are done. \`organise-content\`: pages and collections are in no folder \u2014 read ${STRUCTURE_PLAYBOOK_URI}, call suggest_content_structure and, once the user agrees, set_content_structure.`;
2273
- var LAYOUT_CHROME_INSTRUCTION = 'When the user asks to make the navigation, footer or other site chrome editable, or get_next_steps lists a `layout-*` or `chrome-duplicated` item: this is playbook \xA711 item 3. Chrome is the project Layout, never page fields: in the source, wrap each Section in `data-bcms-layout-section` (the slot, its base slug) and mark each value `data-bcms-layout-field="layout:<sections[slug].id>:<fieldId>"`, never `data-bcms-field` inside it, then push; the strip removes the page copies, never delete them by hand. On a site BetterCMS serves, publish_layout writes Layout VALUES into the live pages; a structure change waits for a Rebuild.';
2273
+ var LAYOUT_CHROME_INSTRUCTION = 'When the user asks to make the navigation, footer or other site chrome editable, or get_next_steps lists a `layout-*` or `chrome-duplicated` item: this is playbook \xA711 item 3. Chrome is the project Layout, never page fields: in the source, wrap each Section in `data-bcms-layout-section` (the slot, its base slug) and mark each value `data-bcms-layout-field="layout:<sections[slug].id>:<fieldId>"`, never `data-bcms-field` inside it, then push; the strip removes the page copies, never delete them by hand. On a site BetterCMS serves, publish_layout writes Layout VALUES into the live pages; a structure change waits for a Rebuild. Never place nav, footer or modal blocks or fields on a page: the Layout (or the site\'s own layout) draws chrome, so a page copy either duplicates it or renders nowhere (get_componentize_plan reports one as LAYOUT_CHROME).';
2274
+ var CTA_BUTTON_INSTRUCTION = "A call to action is ONE `button` field (value {label, href, variant?, size?, arrow?, icon?, newTab?}), rendered with BcmsButton from @bettercms-ai/astro or @bettercms-ai/next: never a text field beside a link field. A page that already holds such a pair (primaryCtaText + primaryCtaHref) becomes one button with update_field `mergeHref`. Inside a component a CTA is a `button` block whose text and href two props target.";
2274
2275
  var STRUCTURE_EXAMPLE_PAYLOAD = {
2275
2276
  version: 0,
2276
2277
  doc: {
@@ -2687,9 +2688,11 @@ var PARAM_TEXT = {
2687
2688
  pageUnpublishAt: "ISO datetime at which the published page returns to draft; null cancels; omitting it never cancels it",
2688
2689
  sourceUrl: "instead of data: a public http(s) URL the server fetches and stores (private or internal addresses are refused)",
2689
2690
  focalX: "focal point, 0-1 across from the left edge; null resets to the centre",
2690
- focalY: "focal point, 0-1 down from the top edge; null resets to the centre"
2691
+ focalY: "focal point, 0-1 down from the top edge; null resets to the centre",
2692
+ mergeHref: "pageId only: the link field paired with `path` (the label). Both become ONE `button` field keyed `key` (default: their stem, primaryCtaHref \u2192 primaryCta). Each entry's DRAFT gets {label, href} and keeps the old values; the live copy changes on publish. ifMatch is the page's version"
2691
2693
  };
2692
- var UPDATE_FIELD_DESCRIPTION = "Change ONE existing field of a content model (`modelId`) or of a page's schema (`pageId`), addressed by `path`: its key, or dot-separated keys through groups and repeaters ('hero.title'). Set its label, helpText, required, validation `config` (merged by key; null deletes one), showIf, ui or placement (null clears those). The rest of the schema is written back unchanged at the version read; a 409 means it changed since, so read again. `type` changes how every stored value is read and needs allowRetype: true. `key` renames it: stored values stay under the old key, so the route answers 409 with the entry count unless allowFieldRemoval: true discards them \u2014 migrate the values first. Ask the user before a retype or a rename.";
2694
+ var BUTTON_FIELD_NOTE = "'button' is ONE call to action, value {label, href, variant?, size?, arrow?, icon?, newTab?}: never a text field beside a link field.";
2695
+ var UPDATE_FIELD_DESCRIPTION = "Change ONE existing field of a content model (`modelId`) or of a page's schema (`pageId`), addressed by `path`: its key, or dot-separated keys through groups and repeaters ('hero.title'). Set its label, helpText, required, validation `config` (merged by key; null deletes one), showIf, ui or placement (null clears those). The rest of the schema is written back unchanged at the version read; a 409 means it changed since, so read again. `type` changes how every stored value is read and needs allowRetype: true. `key` renames it: stored values stay under the old key, so the route answers 409 with the entry count unless allowFieldRemoval: true discards them \u2014 migrate the values first. Ask the user before a retype or a rename. A page's label + link pair (primaryCtaText + primaryCtaHref) becomes one button with `mergeHref`.";
2693
2696
  var GET_SEO_DESCRIPTION = "Get the connected project's site-wide SEO settings \u2014 every key update_seo writes: seoDefaults (metaTitle, metaDescription, ogImage, twitterHandle, og, twitter, collectionSchemaTypes), siteSchema (the site's JSON-LD), robotsConfig, sitemapConfig, rssConfig, indexnowConfig and searchConfig.";
2694
2697
  var UPDATE_SEO_DESCRIPTION = "Set the connected project's site-wide SEO defaults and config. Each key you send is REPLACED whole (not deep-merged), so read get_seo and send the complete object. `seoDefaults` applies to every page that does not override it, and its `collectionSchemaTypes` maps a collection's model id to the schema.org type of its entries. `siteSchema` is the site's own JSON-LD (Organization/WebSite, emitted on the home page). `robotsConfig`, `sitemapConfig` and `rssConfig` shape robots.txt, sitemap.xml and the feed; `indexnowConfig` pings search engines on publish; `searchConfig` is site search. A key the plan does not include answers 403 FEATURE_LOCKED. Rebuilds the site.";
2695
2698
  var SEO_KEY_TEXT = {
@@ -2905,7 +2908,7 @@ var fieldShape = {
2905
2908
  "machine field key. \u{1F534} UNIQUE ACROSS THE WHOLE MODEL \u2014 API IDs share ONE FLAT NAMESPACE, so a field nested inside a group must NOT reuse a key used by another field or group. Every section's heading cannot be 'title'. Prefix with the section: 'hero_title', 'pricing_title', 'faq_title'. Reusing a key is refused, and if it slips through it leaves permanent errors in the editor and breaks conditional-visibility rules, which reference fields by bare key."
2906
2909
  ),
2907
2910
  label: z.string().min(1).describe("human label shown in the editor"),
2908
- type: fieldType,
2911
+ type: fieldType.describe(`field type. ${BUTTON_FIELD_NOTE}`),
2909
2912
  required: z.boolean().optional().describe(
2910
2913
  "an entry cannot be saved without a value \u2014 set it only where the site genuinely cannot render without one"
2911
2914
  ),
@@ -3118,7 +3121,7 @@ var ENTRY_SCHEDULED_AT_NOTE = "ISO datetime to publish this draft at (needs publ
3118
3121
  var ENTRY_UNPUBLISH_AT_NOTE = "ISO datetime at which the published entry returns to draft; null cancels. Omitting it never cancels it.";
3119
3122
  var ENTRY_LIST_LIMIT_NOTE = "entries per page, 1-200; 50 when omitted. Read `meta.totalPages` and ask for the next `page`.";
3120
3123
  var PAGE_HEAD_NOTE = "noindex, canonical, og and twitter set the page's head extras; each MERGES into what is stored (a key you leave out is kept, null or an empty string clears it).";
3121
- var STRUCTURE_FENCE = "ORGANISATION ONLY: Content sidebar folders never change a URL, slug, content or publish state, and nothing is rebuilt or delivered; URL folders (page folders) are set in the dashboard. Read the resource bettercms://playbook/structure before you organise anything: it holds the rules this refuses on \u2014 system folders, type purity, the Admin/Developer access check, root pins and page-bound models. A resource is never truncated; a long description is.";
3124
+ var STRUCTURE_FENCE = "ORGANISATION ONLY: Content sidebar folders never change a URL, slug, content or publish state, and nothing is rebuilt or delivered; URL folders (page folders) are set in the dashboard. Read the resource bettercms://playbook/structure before you organise anything: it holds the rules this refuses on \u2014 system folders, type purity, the Admin/Developer access check, root pins and page-bound models.";
3122
3125
  var STRUCTURE_POINTER = "Sidebar only, never a URL; rules: bettercms://playbook/structure.";
3123
3126
  var BRAND_KIT_POINTER = "The brand kit's rules (consume its tokens, never copy values or invent brand facts) are on get_project.";
3124
3127
  function structureResult(d) {
@@ -3806,7 +3809,7 @@ function buildToolDefs(deps) {
3806
3809
  def(
3807
3810
  "get_content_structure",
3808
3811
  "Get the Content sidebar structure",
3809
- `Returns { doc, version, summary }: \`doc\` is the raw document ({ schema: 1, folders: [{ id, parentId, name, icon, sort }], items: [{ kind: 'page'|'collection', id, folderId, sort, icon }] }), \`version\` is what set_content_structure needs, and \`summary\` is the resolved tree with page and collection TITLES (\`outline\` is a readable version), ids that no longer resolve (\`missing\`), and the pages and collections not in any folder yet. Call it before changing the structure, and read the resource ${STRUCTURE_PLAYBOOK_URI} (the structure standard) before you organise anything. Changing it needs Admin or Developer; a 403 SCHEMA_ACCESS_REQUIRED means neither: stop and tell the user, do not retry. ${STRUCTURE_FENCE}`,
3812
+ `Returns { doc, version, summary }: \`doc\` is the raw document ({ schema: 1, folders: [{ id, parentId, name, icon, sort }], items: [{ kind: 'page'|'collection', id, folderId, sort, icon }] }), \`version\` is what set_content_structure needs, and \`summary\` is the resolved tree with page and collection TITLES (\`outline\` is a readable version), ids that no longer resolve (\`missing\`), and the pages and collections not in any folder yet. Call it before changing the structure; a 403 SCHEMA_ACCESS_REQUIRED on a change means this connection lacks Admin or Developer: stop and tell the user. ${STRUCTURE_FENCE}`,
3810
3813
  z.object({}).shape,
3811
3814
  async (c) => {
3812
3815
  const d = await data(c, "GET", `/management/content-structure`);
@@ -4451,11 +4454,22 @@ ${res.warnings.join("\n")}` : summary, res.data);
4451
4454
  key: fieldKey.optional().describe("a new key (flat namespace, unique in the schema)"),
4452
4455
  allowRetype: z.boolean().optional().describe("confirm a type change, once the user has agreed"),
4453
4456
  allowFieldRemoval: z.boolean().optional().describe("with a rename: discard the old key's stored values"),
4457
+ mergeHref: z.string().min(1).optional().describe(PARAM_TEXT.mergeHref),
4454
4458
  ifMatch: z.number().int().min(0).optional().describe("the model's optimisticVersion; the version read when omitted")
4455
4459
  }).shape,
4456
4460
  // Read the model (a page's through its backingModel), patch ONE field with the helper the
4457
- // hosted catalogue uses, and PATCH the whole list at the version read.
4461
+ // hosted catalogue uses, and PATCH the whole list at the version read. `mergeHref` is the
4462
+ // page route's "Make it a full button" instead, at the PAGE's version.
4458
4463
  async (c, a) => {
4464
+ if (a.mergeHref !== void 0) {
4465
+ if (!a.pageId) return fail("mergeHref merges two fields of a PAGE: pass pageId.");
4466
+ const ifMatch = a.ifMatch ?? (await data(c, "GET", `/management/pages/${s(a.pageId)}`))?.optimisticVersion ?? 0;
4467
+ const merged = await c.fetchJSON(c.url(`/management/pages/${s(a.pageId)}/fields/merge-button`), {
4468
+ method: "POST",
4469
+ body: JSON.stringify({ labelKey: a.path, hrefKey: a.mergeHref, ...a.key !== void 0 ? { key: a.key } : {}, ifMatch })
4470
+ });
4471
+ return ok(`Made '${s(a.path)}' + '${s(a.mergeHref)}' one button field '${merged.key ?? ""}' (${merged.entries ?? 0} draft entries).`, merged);
4472
+ }
4459
4473
  let modelId = a.modelId;
4460
4474
  if (a.pageId) {
4461
4475
  const page = await data(c, "GET", `/management/pages/${s(a.pageId)}`);
@@ -4935,7 +4949,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
4935
4949
  def(
4936
4950
  "get_deploy_status",
4937
4951
  "Get deploy/build status",
4938
- "Get the connected project's deploy/build status: state (idle|queued|building|categorizing|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `categorizing` means the bytes are live but the import's setup lanes (content, pages, bindings) are still running, so `publishing` is still true and the site is not ready to edit yet. Poll this after deploy_project until state is idle with your sha live. `release` is the newest release (id, sha, status, phaseMarks, error, createdAt): while an uploaded build ingests or releases, state is `building`, `release.sha` the new sha and `lastLiveSha` still the old one; a failed deploy shows `release.status` failed with its `error`, and `held` state (`held.by`: staff, archived or no-subdomain) means the release waits until that is cleared. Also returns `canvas.lane` (the editor's live-preview lane for this site: `bridge` / `draft-route` / `none`) and `editorUrl`, the visual editor to hand the user.",
4952
+ "Get the connected project's deploy/build status: state (idle|queued|building|categorizing|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `categorizing` means the bytes are live (`publishing` false) but the setup lanes (content, pages, bindings) are still running: not ready to edit yet. Poll this after deploy_project until state is idle with your sha live. `release` is the newest release (id, sha, status, phaseMarks, error, createdAt): while an uploaded build ingests or releases, state is `building`, `release.sha` the new sha and `lastLiveSha` still the old one; a failed deploy shows `release.status` failed with its `error`, and `held` state (`held.by`: staff, archived or no-subdomain) means the release waits until that is cleared. Also returns `canvas.lane` (the editor's live-preview lane for this site: `bridge` / `draft-route` / `none`) and `editorUrl`, the visual editor to hand the user.",
4939
4953
  z.object({}).shape,
4940
4954
  async (c) => ok("Deploy status.", await data(c, "GET", `/management/projects/deploy-status`))
4941
4955
  ),
@@ -5523,7 +5537,7 @@ ${notes.join("\n")}` : summary, created);
5523
5537
  name: "publish_components",
5524
5538
  config: {
5525
5539
  title: "Publish many components in one call",
5526
- description: "Publish up to 100 components in ONE call \u2014 the same act as publish_component, once per item, copying each DRAFT definition to the live copy. Each item reports `{ ok, status, error, readiness }` and a failure never stops the run. \u{1F534} A 409 COMPONENT_IMPLEMENTATION_NOT_READY is NOT retried and must not be: no tool on this connection can clear it, and it has no bulk form. It is never a component you just made with create_component in no variant group \u2014 that is dashboard-managed and publishes on the preflight alone. The refusal is for a REPO-managed component without exact validation evidence and a human's approval, or for a variant-group member (the provisioned navigation and footer) whose group's canonical-input contract is not published. The item's `readiness` names what is missing \u2014 relay that to the user as the list of what only they can approve. 403 PUBLISH_NOT_GRANTED means this connection may author but not publish; 422 means the publish would break a Layout, and the item names which.",
5540
+ description: "Publish up to 100 components in ONE call \u2014 the same act as publish_component, once per item, copying each DRAFT definition to the live copy. Each item reports `{ ok, status, error, readiness }` and a failure never stops the run. \u{1F534} A 409 COMPONENT_IMPLEMENTATION_NOT_READY is NOT retried: only the owner, in the dashboard, clears it (publish_component says when); relay the item's `readiness` to the user as what only they can approve. 403 PUBLISH_NOT_GRANTED means this connection may author but not publish; 422 means the publish would break a Layout, and the item names which.",
5527
5541
  inputSchema: publishComponentsInput.shape
5528
5542
  },
5529
5543
  handler: guard(
@@ -5891,6 +5905,9 @@ DERIVED at import, where the componentize lane in \xA710 reuses every field.
5891
5905
  A fixed cluster of fields? -> group (never a 1-item repeater)
5892
5906
  A repeating cluster? -> repeater
5893
5907
  A list of plain strings? -> array (never a repeater of one text)
5908
+ A call to action (words + link)? -> ONE \`button\` field {label, href, variant?, size?,
5909
+ arrow?, icon?, newTab?} \u2014 never text + link fields
5910
+ (in a component: a \`button\` block, \xA73)
5894
5911
  Site-wide nav/footer/modal? -> the LAYOUT (get_layout/update_layout), never a
5895
5912
  page section and never one component per page
5896
5913
 
@@ -5912,6 +5929,12 @@ Section's root section has no \`style\`; treat it as a defect, not a note:
5912
5929
  ] }
5913
5930
  }
5914
5931
 
5932
+ \`style\` takes the design tokens the editor's dock sliders and pickers write: \`theme\`, \`bg\` /
5933
+ \`bgCustom\` / \`bgImage\` (an image url), \`spaceTop\` / \`spaceBottom\` / \`spaceSides\` (token steps,
5934
+ winning over the \`padding*\` pixels), \`contentWidth\`, \`contentAlign\`, \`opacity\` (0\u2013100), \`corner\`,
5935
+ \`shadow\`, \`border\` / \`borderTop\` / \`borderBottom\`, and on a button \`hover\` { bg, bgCustom, textColor,
5936
+ border }.
5937
+
5915
5938
  Then declare what an editor may change, via \`props\`:
5916
5939
 
5917
5940
  props: [
@@ -6010,7 +6033,10 @@ Section zone, which also owns the document canvas. Use \`richtext\` for a short
6010
6033
  Then the pages:
6011
6034
 
6012
6035
  create_page slug:"blog" pageType:"dynamic" fields:[...the post schema...]
6013
- blockJson: [ nav, heading, { type:"collection", id:"list", props:{} }, footer ]
6036
+ blockJson: [ heading, { type:"collection", id:"list", props:{} } ]
6037
+
6038
+ Never put a \`navbar\` or \`footer\` block on a page: site chrome is the Layout (get_layout /
6039
+ update_layout), drawn around every page already, so a page copy duplicates it.
6014
6040
 
6015
6041
  create_content_entry per post, with pageId set to that page
6016
6042
 
@@ -6266,6 +6292,25 @@ real design, with no publish and no Draft render detour. A bridge that answers w
6266
6292
  toggle. Sites without one keep Draft render's approximate fallback, and a route the build does
6267
6293
  not carry falls back always (a static build has no file to frame).
6268
6294
 
6295
+ **Sections on a site whose CODE renders the page.** The editor's outline is the page's \`blockJson\`,
6296
+ but a code-built site ships only what its own templates draw:
6297
+ - An added or moved section reaches the live site only when the site renders the page's block list
6298
+ itself, in order, each section root stamped \`data-bcms-block="<blockId>"\`. \`npx @bettercms-ai/convert
6299
+ --componentize\` writes that loop (\`src/lib/bcms-sections.ts\`) and the stamps. Until it ships, the canvas
6300
+ only ARRANGES the static build to match the draft (a section the build never rendered is stood in for
6301
+ by a server render), the editor locks Add section and reordering on routes whose code sets the
6302
+ sections, and each such route leads \`submit_conversion_receipt\`'s \`pending\` with a
6303
+ \`SECTIONS_UNSTAMPED\` row, so \`complete\` stays false. Never hard-code a page's bands in a template
6304
+ whose order the CMS owns.
6305
+ - A placement the site never renders (a leftover nav, footer or modal its layout draws) moves
6306
+ nothing: the editor counts section moves in RENDERED sections only and locks that row. Remove
6307
+ such placements; do not reorder around them.
6308
+ - Section spacing and style handles need every stamped root to also carry \`data-bcms-style\` and
6309
+ paint \`block.style\`: spread \`sectionRootAttrs(block)\` (\`@bettercms-ai/astro\`, \`@bettercms-ai/next\`)
6310
+ on the root. A site converted before that existed shows none (\`get_next_steps\`
6311
+ \`upgrade-section-roots\`): run \`npx @bettercms-ai/convert@latest --upgrade-sections --root .\` (with
6312
+ \`--dry-run\` first), fix the roots it lists as skipped by hand, check \`--verify-styles\`, push, release.
6313
+
6269
6314
  **Hosting decides whether a canvas exists at all.** A site deployed here is framed through a
6270
6315
  same-origin proxy \u2014 that is what the canvas requires. A site hosted elsewhere (your own Vercel,
6271
6316
  your own server) has NO canvas today: the SDK's draft mode with \`stega: true\` embeds invisible
@@ -6300,7 +6345,9 @@ below is one heading of that ask.
6300
6345
  the \`add-component\` command. CTA buttons are \`button\` BLOCKS inside the section that uses them,
6301
6346
  exposed as \`text\` + \`url\` props that target that ONE block \u2014 never the words as a separate text
6302
6347
  block beside it, or the Button panel edits nothing \u2014 variant, size and fullWidth are that block's own props and
6303
- \`style\`, never a standalone Button component. Rich text is a \`richtext\` prop over a
6348
+ \`style\`. Do not hand-build a Button component: every button look you write joins the project's Button
6349
+ family (Globals/Buttons) on its own, and a new look is \`create_component\` with \`variantOf\` naming a
6350
+ Button. On a page's FIELDS a CTA is one \`button\` field (\xA72). Rich text is a \`richtext\` prop over a
6304
6351
  \`richtext\`/\`text\` block. Forms are \`create_form\` plus a \`form\` block, and form FIELDS are the
6305
6352
  one place the platform models \`required\`, \`placeholder\`, \`helpText\` and real \`validation\`
6306
6353
  (emailPolicy, min/max, phoneFormat, pattern) \u2014 put a site's validated inputs there. Cards,
@@ -6308,7 +6355,8 @@ badges, tags, links, lists and media are BLOCKS inside a section, exposed throug
6308
6355
  repeatable row of them is ONE \`table\` prop whose \`config.fields\` describe the row. Accordions
6309
6356
  and FAQs: start from the \`builtin:faq-list\` blueprint, or use a \`tabs\` block. NOT MODELLED \u2014
6310
6357
  cookie banners, modals, breadcrumbs and pagination have no primitive at all. Leave them in the
6311
- site's code and say so in your final summary.
6358
+ site's code and say so in your final summary. A modal is chrome, like the nav: never a page section
6359
+ (the plan reports it \`LAYOUT_CHROME\`).
6312
6360
 
6313
6361
  **2. One component per distinct section, one-offs included.** \`create_component\` with a
6314
6362
  \`sectionType\` IS the section. \`sectionType\` is the FAMILY (Hero, Features, Testimonials,
@@ -6739,7 +6787,7 @@ Importance orders a screen: required fields first, then array order.
6739
6787
  | FAQ | \`preview:{title:'question'}\`, \`itemLabel:'Question'\`, \`icon:'help-circle'\` |
6740
6788
  | Nav links | \`preview:{title:'label', subtitle:'href'}\`, \`itemLabel:'Link'\`, \`icon:'link'\` (never a table: a label and its href are one link) |
6741
6789
  | Stats | \`layout:'table'\` over \u22643 scalar columns (value, label, suffix), \`itemLabel:'Stat'\`, \`icon:'bar-chart'\` |
6742
- | CTA | one \`kind:'group'\` fieldset holding the heading, text, button label AND href (a button's pair shares one fieldset), \`icon:'launch'\` |
6790
+ | CTA | one \`kind:'group'\` fieldset holding the heading, text and ONE \`button\` field, \`icon:'launch'\` |
6743
6791
  | Dimensions | one \`kind:'row'\` fieldset of up to 3 numbers (width, height, gap) |
6744
6792
 
6745
6793
  **Tabs.** A \`kind: 'tab'\` fieldset is a tab:
@@ -6778,10 +6826,12 @@ Importance orders a screen: required fields first, then array order.
6778
6826
  - \`fieldset_unknown\` / \`fieldset_orphan\` \u2014 a \`fieldsetId\` naming no fieldset, or a list that drops a
6779
6827
  fieldset its fields still name.
6780
6828
  - \`row_member_type\`, \`row_member_count\`, \`row_member_control\` \u2014 the row rule.
6781
- - \`pair_split\` \u2014 a button's label and href in different fieldsets.
6829
+ - \`pair_split\` \u2014 a legacy button's label and href in different fieldsets.
6782
6830
  - \`cta_pair\` \u2014 a NEW text field beside a link keyed like it plus \`-href\` (\`hero-span-go\` + \`hero-a-go-href\`): one
6783
6831
  call to action. Add ONE \`type: "button"\` field (value \`{ label, href }\`); on a component, point both props
6784
- at one button block.
6832
+ at one button block. A pair a page ALREADY holds (\`primaryCtaText\` + \`primaryCtaHref\`) is never refused:
6833
+ turn it into one button with \`update_field { pageId, path: <label key>, mergeHref: <link key> }\` \u2014
6834
+ drafts only, the old values stay, the live copy changes on publish.
6785
6835
  - \`ui.icon\` or \`ui.itemLabel\` on a leaf; an icon name not in \`bettercms://icons/dock\`.
6786
6836
  - \`layout_grid\` \u2014 a grid with no image or file child. \`layout_table\` \u2014 more than 3 columns, or a
6787
6837
  column that is not a scalar.
@@ -6923,10 +6973,12 @@ Author a page (the page-first schema). Ask, in order, via AskUserQuestion:
6923
6973
  2. **Identity** \u2014 title; derive a slug (lowercase, a\u2013z 0\u20139 -) and confirm; optional
6924
6974
  metaTitle/metaDescription.
6925
6975
  3. **Fields (loop until done)** \u2014 for each: key (^[a-zA-Z0-9_]+$), label, type,
6926
- required?. Types (13): text, richtext, image, boolean, number, select (needs
6976
+ required?. Types (14): text, richtext, image, boolean, number, select (needs
6927
6977
  \`options: string[]\`), reference / multi-reference (\`config.contentModelId\`,
6928
6978
  multi adds min/max), array (primitives \u2014 \`config.itemType\`: text|number|date),
6929
- date (\`config.includeTime\`?), datetime, **group** (Non-Repeatable Zone: ONE
6979
+ date (\`config.includeTime\`?), datetime, **button** (ONE call to action, value
6980
+ {label, href, variant?, size?, arrow?, icon?, newTab?} \u2014 never a text field plus a
6981
+ link field), **group** (Non-Repeatable Zone: ONE
6930
6982
  nested object \u2014 recurse to collect its \`fields\`, e.g. blog_hero \u2192 heading,
6931
6983
  description, hero_image), **repeater** (Repeatable Zone: an ARRAY of such objects
6932
6984
  \u2014 recurse to collect item \`fields\`, e.g. testimonials \u2192 quote, author). Nesting
@@ -6937,7 +6989,7 @@ Author a page (the page-first schema). Ask, in order, via AskUserQuestion:
6937
6989
  var FIELD_FLOW = `### Add a field \u2192 \`add_field\` (a model or a page)
6938
6990
  Append a field to an existing schema. First decide the target: a **content model**
6939
6991
  or a **page** (Home, About, a blog template). Ask: which target (id \u2014 use
6940
- \`list_pages\` to find a page id), then the field (key, label, type \u2014 any of the 13
6992
+ \`list_pages\` to find a page id), then the field (key, label, type \u2014 any of the 14
6941
6993
  above, including nested group/repeater), required?. Confirm, then call:
6942
6994
  - a model \u2192 \`add_field\` { modelId, key, label, type, ... }
6943
6995
  - a page \u2192 \`add_field\` { pageId, key, label, type, ... }
@@ -6992,7 +7044,8 @@ Read \`bettercms://playbook/schema\` \xA712 first \u2014 this is the short form
6992
7044
  1. **Global elements.** Navbar / mobile nav / header / footer are the LAYOUT (\`get_layout\`,
6993
7045
  \`update_layout\`; a chrome component is placed with the \`add-component\` command). CTA buttons
6994
7046
  are \`button\` BLOCKS inside the section that uses them, exposed as \`text\` + \`url\` props \u2014
6995
- variant/size/icon are that block's own props and \`style\`, never a Button component. Rich text
7047
+ variant/size/icon are that block's own props and \`style\`; each look joins the project's Button
7048
+ family by itself, so never hand-build a Button component. Rich text
6996
7049
  is a \`richtext\` prop. Forms are \`create_form\` + a \`form\` block, and form FIELDS are the only
6997
7050
  place with real \`required\` / \`placeholder\` / \`helpText\` / \`validation\`. Cards, badges, tags,
6998
7051
  links, lists and media are blocks inside a section, repeatables as one \`table\` prop.
@@ -7539,7 +7592,7 @@ function buildServer(deps) {
7539
7592
  // 🔴 The plain-language ask, routed — the stdio twin of the hosted connector's line
7540
7593
  // (src/routes/mcp/index.ts MCP_INSTRUCTIONS). "Make my site editable" has one recipe and
7541
7594
  // one definition of done; without this an agent converts the page it landed on and stops.
7542
- instructions: RECEIPT_FIRST_INSTRUCTION + " When the user asks to make a site or all of its pages editable, to convert it, or to bind its fields: this is playbook \xA713. Read `bettercms://playbook/schema` \xA713, call get_binding_report and get_conversion_brief { complete: true }, convert EVERY route the brief lists, and finish only when get_binding_report shows coverage.pending empty on every route \u2014 not when the first page works. On a workspace-wide connection pass projectId on every call; never ask the user to re-scope the connection. " + LAYOUT_CHROME_INSTRUCTION + " When the user asks to componentize the whole site, to turn every section into a component, or to build a component library from the site: this is playbook \xA712. Read `bettercms://playbook/schema` \xA712, start with get_site_composition, and use the batch tools \u2014 componentize_sections, compose_pages, update_layout with `commands`, publish_components \u2014 rather than one call per component. Finish with get_site_composition and tell the user what the platform does not model (cookie banners, modals, breadcrumbs, pagination) and which components still need the owner's approval in the dashboard before they can be published. " + // The default after authoring (the structure standard): organise what you made. Same
7595
+ instructions: RECEIPT_FIRST_INSTRUCTION + " When the user asks to make a site or all of its pages editable, to convert it, or to bind its fields: this is playbook \xA713. Read `bettercms://playbook/schema` \xA713, call get_binding_report and get_conversion_brief { complete: true }, convert EVERY route the brief lists, and finish only when get_binding_report shows coverage.pending empty on every route \u2014 not when the first page works. On a workspace-wide connection pass projectId on every call; never ask the user to re-scope the connection. " + LAYOUT_CHROME_INSTRUCTION + " " + CTA_BUTTON_INSTRUCTION + " When the user asks to componentize the whole site, to turn every section into a component, or to build a component library from the site: this is playbook \xA712. Read `bettercms://playbook/schema` \xA712, start with get_site_composition, and use the batch tools \u2014 componentize_sections, compose_pages, update_layout with `commands`, publish_components \u2014 rather than one call per component. Finish with get_site_composition and tell the user what the platform does not model (cookie banners, modals, breadcrumbs, pagination) and which components still need the owner's approval in the dashboard before they can be published. " + // The default after authoring (the structure standard): organise what you made. Same
7543
7596
  // sentence as the hosted connector's MCP_INSTRUCTIONS.
7544
7597
  STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION + " " + DOCK_UI_ROUTING_INSTRUCTION
7545
7598
  }