@bettercms-ai/mcp 0.82.0 → 0.83.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.
Files changed (40) hide show
  1. package/README.md +86 -86
  2. package/SKILL.md +160 -160
  3. package/dist/index.js +49 -16
  4. package/dist/index.js.map +1 -1
  5. package/dist/kit-catalog.json +1509 -1509
  6. package/dist/skills/bettercms/SKILL.md +206 -206
  7. package/dist/skills/bettercms/references/accessibility.md +171 -171
  8. package/dist/skills/bettercms/references/animation.md +296 -296
  9. package/dist/skills/bettercms/references/astro.md +148 -148
  10. package/dist/skills/bettercms/references/bindings.md +366 -366
  11. package/dist/skills/bettercms/references/componentize.md +185 -185
  12. package/dist/skills/bettercms/references/components.md +415 -415
  13. package/dist/skills/bettercms/references/content-experiments.md +62 -62
  14. package/dist/skills/bettercms/references/content-modeling.md +382 -382
  15. package/dist/skills/bettercms/references/detection.md +465 -465
  16. package/dist/skills/bettercms/references/dock-input.md +121 -121
  17. package/dist/skills/bettercms/references/editor-chrome.md +92 -92
  18. package/dist/skills/bettercms/references/external-content.md +78 -78
  19. package/dist/skills/bettercms/references/finish-site.md +377 -377
  20. package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
  21. package/dist/skills/bettercms/references/frameworks.md +159 -156
  22. package/dist/skills/bettercms/references/getting-started.md +156 -141
  23. package/dist/skills/bettercms/references/integrations.md +364 -364
  24. package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
  25. package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
  26. package/dist/skills/bettercms/references/localization.md +85 -85
  27. package/dist/skills/bettercms/references/media-and-brand.md +181 -181
  28. package/dist/skills/bettercms/references/next.md +189 -186
  29. package/dist/skills/bettercms/references/other-frameworks.md +85 -68
  30. package/dist/skills/bettercms/references/portable-text.md +213 -213
  31. package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
  32. package/dist/skills/bettercms/references/publishing.md +289 -289
  33. package/dist/skills/bettercms/references/seo.md +357 -357
  34. package/dist/skills/bettercms/references/shopify-headless.md +52 -52
  35. package/dist/skills/bettercms/references/site-blueprints.md +895 -895
  36. package/dist/skills/bettercms/references/site-review.md +74 -74
  37. package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
  38. package/dist/skills/bettercms/references/worked-examples.md +885 -885
  39. package/dist/skills/bettercms/references/write-limits.md +217 -217
  40. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2546,6 +2546,7 @@ var NO_QUESTIONS_INSTRUCTION = "Never stop a setup to ask the user: authoring (c
2546
2546
  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 by \`ruleId\` (skill finish-site lists them). Each item cites its count and may carry \`payload\` {rows, total}, at most 20 rows. \`receipt.complete\` is the setup gate's verdict and \`receipt.gate\` its numbers per built route. Run \`receipt.gate.nextAction\`, call it again after each batch; you are finished only when \`complete\` is true. \`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.`;
2547
2547
  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).';
2548
2548
  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.";
2549
+ var NAMING_INSTRUCTION = "Names are 1-2 words of meaning per part: playbook \xA72 Naming.";
2549
2550
  var STRUCTURE_EXAMPLE_PAYLOAD = {
2550
2551
  version: 0,
2551
2552
  doc: {
@@ -2898,6 +2899,9 @@ var LISTING_TAIL = /* @__PURE__ */ new Set([
2898
2899
  "translate_entry",
2899
2900
  "update_field",
2900
2901
  "refresh_search_index",
2902
+ // The owner's questions: a core client answers them through editor_setup `declare` (always loaded).
2903
+ "get_editable_plan",
2904
+ "confirm_editable_plan",
2901
2905
  // Automation, branches and AI images.
2902
2906
  "list_webhooks",
2903
2907
  "create_webhook",
@@ -3002,6 +3006,8 @@ var NEW_TOOL_TEXT = {
3002
3006
  refresh_search_index: "Tell search engines and site search about published changes; costs nothing. action 'indexnow' submits `urls` (this site's own, up to 1000) to IndexNow, or every change since the last submission when omitted. action 'reindex' rebuilds the project's site-search index. 503 means the index is unreachable: try again later.",
3003
3007
  import_redirects: "Bulk-create redirects for a migration, from `rows` or from `text` in `format` (our CSV export, a Netlify `_redirects` file, or a vercel.json). Call it with `dryRun: true` FIRST: nothing is written and each row comes back `would-create`, `existing` or `skipped` with a reason. Rows get create_redirect's rules. Max 5000.",
3004
3008
  editor_setup: "Make the live build editable: derives pages, collections, forms, bindings and components from the live release (skill bindings). Run it after an import or a rebuild, before the conversion brief. Idempotent: `ready` when it already ran for the live sha, else it starts (`running`; poll with `check: true`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
3009
+ get_editable_plan: "Questions only the owner answers (logo, header, seeds, kit sections, brand, blocks): `rows` with closed `options` and `recommended`. Ask; never guess.",
3010
+ confirm_editable_plan: "Record answers: each `decisions` item is an option's `decision`, unchanged. `check: true` = dry run.",
3005
3011
  transform_image: "Edit an existing Media Library image with an instruction (e.g. 'remove the background', 'make it dusk'); the result is saved as a NEW asset and the original is kept (5 AI credits). Returns the new asset (id, url, altText).",
3006
3012
  write_alt_text: "Alt text for a Media Library image (mediaId) or an https image URL (url), written from its pixels (1 AI credit). A suggestion: save it with update_media.",
3007
3013
  list_integrations: "List the project's custom integrations (any vendor with a public API) and the workspace's integration `templates`. Each has its hosts, secret NAMES with `secretsPresent` (never a value), syncs, hooks, endpoints, functions and `approved`. Read bettercms://skill/integrations first.",
@@ -3123,7 +3129,7 @@ var DELIVERY_KEY_PARAMS = {
3123
3129
  var FRONT_END_DESCRIPTION = 'Record where this project\'s own front end lives, so editors open drafts on it ("Preview on site") and a publish reaches it: `siteUrl` (its deployed origin), `framework` (next or astro: which preview route the SDK mounts), `draftPath` (Next only, when not /api/draft) and `revalidation` (a front end that revalidates instead of rebuilding). Call it after the front end is deployed; `npx @bettercms-ai/cli@latest deploy --to <host>` already registers the rebuild hook. Read the current values from get_project. Fields you omit are left as they are; null clears one. Requires artifact:write.';
3124
3130
  var FRONT_END_PARAMS = {
3125
3131
  siteUrl: "the deployed front end's origin, https://\u2026 (null clears it)",
3126
- framework: "next or astro",
3132
+ framework: "next, astro, react-ts, sveltekit or nuxt: picks the draft route (astro: /api/bcms/draft/enable; the rest: draftPath or /api/draft)",
3127
3133
  draftPath: "Next only: the draft-mode route when not /api/draft (null clears it)",
3128
3134
  revalidation: "a front end that revalidates instead of rebuilding: { webhookUrl (https), webhookSecret (16+ chars, also stored as BETTERCMS_WEBHOOK_SECRET on the front end) } (null clears it)"
3129
3135
  };
@@ -3709,10 +3715,15 @@ var HOSTING_TEXT = {
3709
3715
  deploy_to_host: "Deploy this project to the user's own Vercel or Netlify account through their connection. BetterCMS builds the site and the host serves that build (skill publishing). The first call creates the site on the host and makes it the project's host; a later call for the same provider redeploys that site (`redeployed: true`). `connectionId` (from list_hosting_connections) defaults to the newest working one, `siteName` to the project's handle. A refusal carries a `code` and a `fixUrl` (not_connected, reconnect, pick_existing_site, grant_required, ADMIN_REQUIRED): each is fixed by the user at `fixUrl`, so relay it and stop. `deploymentQueued: false` means the project has no live build yet: deploy_project first, and that release reaches the host on its own. Never run the Vercel or Netlify CLI while a connection exists. A headless project is not deployed here: its own front end deploys with `npx @bettercms-ai/cli@latest deploy --to <host>`. Then poll get_host_deploy_status. Needs artifact:write, like deploy_project.",
3710
3716
  get_host_deploy_status: "The connected project's recent deployments to Vercel, Netlify or Cloudflare, newest first: state (queued|deploying|live|failed), providerUrl, error, sha and targetName. Poll it after deploy_to_host until the newest one is `live`, whose `providerUrl` is the live URL to hand the user, or `failed`, whose `error` you read back to them."
3711
3717
  };
3712
- async function builtinCopyId(client, builtinId) {
3718
+ async function builtinCopyId(client, builtinId, verdict) {
3713
3719
  const listed = await client.fetchJSON(client.url(`/management/components?select=summary`));
3714
3720
  const copy = (listed.data ?? []).find((row) => row.sourceKey === builtinId && !row.builtin);
3715
3721
  if (copy) return copy.id;
3722
+ try {
3723
+ await verdict();
3724
+ } catch (err) {
3725
+ if (apiError(err)?.bodyCode !== "BUILTIN_NOT_COPIED") throw err;
3726
+ }
3716
3727
  const made = await client.fetchJSON(client.url(`/management/components`), { method: "POST", body: JSON.stringify({ from: builtinId }) });
3717
3728
  return made.data.id;
3718
3729
  }
@@ -4291,7 +4302,7 @@ function buildToolDefs(deps) {
4291
4302
  propUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(PROP_UI_ARG_NOTE),
4292
4303
  propGroup: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.group ({id, label?, kind?, icon?, collapsed?}), null takes it out"),
4293
4304
  propTab: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.tab ({id, label?, icon?, order?}), the V2 schema group it sits in. The FIRST top-level prop in a tab defines {id, label, icon?, order?}, later ones send {id}; \u22648 tabs; one group's members share one tab. null takes it out"),
4294
- propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label: one or two words of meaning (playbook \xA72, Naming)"),
4305
+ propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key or dotted sub-field path \u2192 label"),
4295
4306
  propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text; null = no help on purpose (stays empty, never refilled)"),
4296
4307
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
4297
4308
  ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
@@ -4823,7 +4834,7 @@ ${d.outline}` : "Proposed Content structure.", d);
4823
4834
  FRONT_END_DESCRIPTION,
4824
4835
  z.object({
4825
4836
  siteUrl: z.string().nullable().optional().describe(FRONT_END_PARAMS.siteUrl),
4826
- framework: z.enum(["next", "astro"]).optional().describe(FRONT_END_PARAMS.framework),
4837
+ framework: z.enum(["next", "astro", "react-ts", "sveltekit", "nuxt"]).optional().describe(FRONT_END_PARAMS.framework),
4827
4838
  draftPath: z.string().nullable().optional().describe(FRONT_END_PARAMS.draftPath),
4828
4839
  revalidation: z.object({ webhookUrl: z.string(), webhookSecret: z.string() }).nullable().optional().describe(FRONT_END_PARAMS.revalidation)
4829
4840
  }).shape,
@@ -4890,7 +4901,7 @@ ${d.outline}` : "Proposed Content structure.", d);
4890
4901
  fieldUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(FIELD_UI_ARG_NOTE),
4891
4902
  fieldFieldset: z.record(z.string(), z.string().min(1).nullable()).optional().describe("path \u2192 fieldset id, null takes it out"),
4892
4903
  fieldOrder: z.strictObject({ parentPath: z.string().min(1).optional(), order: z.array(z.string().min(1)).min(1).max(200) }).optional().describe("the full new order of one parent's child keys (top level without parentPath). An array with both zones needs 'KEY.zones.nonRepeatable' or 'KEY.zones.repeatable'; its bare key is a 400"),
4893
- fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("path \u2192 label: one or two words of meaning (playbook \xA72, Naming)"),
4904
+ fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe('path or "<family>#section" \u2192 label'),
4894
4905
  fieldHelpText: z.record(z.string(), z.string().max(500).nullable()).optional().describe("path \u2192 help text, null clears it"),
4895
4906
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
4896
4907
  ifMatch: z.number().int().min(0).optional().describe(MODEL_IF_MATCH_NOTE),
@@ -4961,13 +4972,16 @@ ${d.outline}` : "Proposed Content structure.", d);
4961
4972
  async (c, a) => {
4962
4973
  const edit = Array.isArray(a.blockJson) ? void 0 : a.blockJson;
4963
4974
  const insert = edit?.insertSection;
4964
- const insertSection = insert?.componentId.startsWith("builtin:") ? { ...insert, componentId: await builtinCopyId(c, insert.componentId) } : insert;
4975
+ const pageBody = (sectionInsert) => ({ title: a.title, slug: a.slug, blockJson: Array.isArray(a.blockJson) ? a.blockJson : void 0, insertSection: sectionInsert, sectionStyle: edit?.sectionStyle, metaTitle: a.metaTitle, metaDescription: a.metaDescription, schemaType: a.schemaType, schema: a.schema, status: a.status, noindex: a.noindex, canonical: a.canonical, og: a.og, twitter: a.twitter, scheduledAt: a.scheduledAt, unpublishAt: a.unpublishAt, locale: a.locale, translationOf: a.translationOf, ...a.dynamic });
4976
+ const ifMatchHeaders = a.ifMatch !== void 0 ? { headers: { "If-Match": `W/"${a.ifMatch}"` } } : {};
4977
+ const verdict = () => c.fetchJSON(c.url(`/management/pages/${s(a.pageId)}/meta`), { method: "PATCH", ...ifMatchHeaders, body: JSON.stringify({ ...pageBody(insert), dryRun: true }) });
4978
+ const insertSection = insert?.componentId.startsWith("builtin:") ? { ...insert, componentId: await builtinCopyId(c, insert.componentId, verdict) } : insert;
4965
4979
  const res = await c.fetchJSON(
4966
4980
  c.url(`/management/pages/${s(a.pageId)}/meta`),
4967
4981
  {
4968
4982
  method: "PATCH",
4969
- ...a.ifMatch !== void 0 ? { headers: { "If-Match": `W/"${a.ifMatch}"` } } : {},
4970
- body: JSON.stringify({ title: a.title, slug: a.slug, blockJson: Array.isArray(a.blockJson) ? a.blockJson : void 0, insertSection, sectionStyle: edit?.sectionStyle, metaTitle: a.metaTitle, metaDescription: a.metaDescription, schemaType: a.schemaType, schema: a.schema, status: a.status, noindex: a.noindex, canonical: a.canonical, og: a.og, twitter: a.twitter, scheduledAt: a.scheduledAt, unpublishAt: a.unpublishAt, locale: a.locale, translationOf: a.translationOf, ...a.dynamic })
4983
+ ...ifMatchHeaders,
4984
+ body: JSON.stringify(pageBody(insertSection))
4971
4985
  }
4972
4986
  );
4973
4987
  const note = res.redirectNote;
@@ -5511,7 +5525,7 @@ ${d.outline}` : "Proposed Content structure.", d);
5511
5525
  def(
5512
5526
  "get_binding_report",
5513
5527
  "Check what on the live site is editable",
5514
- "The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason (reasons: skill bindings). DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: a HEADLESS site never gets one (`never-generated`); `layoutRoutes` and `coverage.error`: skill bindings. `unaddressable` is the visible live text no field owns, by route (null = unmeasured: diff visible text against entry values); over `count / visible > 0.02`, fix it in the SOURCE and re-read (playbook \xA711). `entryRowUnbound`: a bound card's words no field of its entry claims, by route, with `reason` (reference, boolean, locale-format, counter, no-field); never also in `unaddressable`. `canvas.lane`: `none` \u2192 get_next_steps.",
5528
+ "The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason (reasons: skill bindings). DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: a HEADLESS site never gets one (`never-generated`); `layoutRoutes` and `coverage.error`: skill bindings. `unaddressable` is the visible live text no field owns, by route (null = unmeasured: diff visible text against entry values); over `count / visible > 0.02`, fix it in the SOURCE and re-read (playbook \xA711). `entryRowUnbound`: a bound card's words no field of its entry claims, by route, with `reason` (reference, boolean, locale-format, counter, no-field); never also in `unaddressable`. `canvas.lane`: `none` \u2192 get_next_steps. `sectionsHeld` likewise; `unstampedIds`: no stamp.",
5515
5529
  z.object({ slot: z.enum(["current", "staging"]).optional().describe("which release tree to read \u2014 everything here, `unaddressable` included, is measured PER SLOT, so on a promote-gated project pass 'current' to read the tree the editor frames. Defaults to the slot this project's releases land in. The response's `sha` is the build the report describes; `refreshRequired` is true when that is not the build this slot serves. Other per-slot fields: skill bindings.") }).shape,
5516
5530
  async (c, a) => ok("Binding report.", await data(c, "GET", `/management/projects/current/binding-report${q({ slot: a.slot })}`))
5517
5531
  ),
@@ -5782,7 +5796,7 @@ ${d.outline}` : "Proposed Content structure.", d);
5782
5796
  def(
5783
5797
  "get_deploy_status",
5784
5798
  "Get deploy/build status",
5785
- "Get the connected project's deploy/build status: state (idle|queued|building|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `editorSetup`: whether editing is set up for the live build (never or stale = call editor_setup). Poll this after deploy_project until state is idle with your sha live. `release`, `held` and `publishes` (each `live` or `partial`): skill publishing. `runtime` ({state: ok|crash-loop, restarts, oomKilled}) appears for a server app only: crash-loop means its container keeps restarting (often out of memory) though the build went live fine. Also returns `canvas.lane` (skill bindings) and `editorUrl`, the visual editor to hand the user.",
5799
+ "Get the connected project's deploy/build status: state (idle|queued|building|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `editorSetup`: whether editing is set up for the live build (never or stale = call editor_setup). Poll this after deploy_project until state is idle with your sha live. `release`, `held` and `publishes` (each `live` or `partial`): skill publishing. `runtime` ({state: ok|crash-loop, restarts, oomKilled}) appears for a server app only: crash-loop means its container keeps restarting (often out of memory) though the build went live fine. Also returns `canvas.lane` (skill bindings) and `editorUrl`, the visual editor to hand the user. `heldSections`: sections held off the page.",
5786
5800
  z.object({ waitSeconds: z.number().optional().describe(PARAM_TEXT.waitSeconds) }).shape,
5787
5801
  // Flat body, no `data` envelope: `data()` read it as undefined.
5788
5802
  async (c, a) => ok("Deploy status.", await c.fetchJSON(c.url(withWait(`/management/projects/deploy-status`, a.waitSeconds)), { method: "GET" }))
@@ -5810,6 +5824,21 @@ ${d.outline}` : "Proposed Content structure.", d);
5810
5824
  // `declare` records binding decisions; with `check: true` it is a dry run (the body's `dryRun`).
5811
5825
  async (c, a) => a.exempt ? ok("Setup gate exceptions.", await c.fetchJSON(c.url(`/management/projects/setup-gate/exceptions`), { method: "POST", body: JSON.stringify({ exempt: a.exempt }) })) : a.declare ? ok("Binding decisions.", await c.fetchJSON(c.url(`/management/projects/binding-decisions`), { method: "POST", body: JSON.stringify({ declare: a.declare, ...a.check === true ? { dryRun: true } : {} }) })) : ok("Editor setup.", await c.fetchJSON(c.url(a.check === true ? withWait(`/management/projects/editor-setup`, a.waitSeconds) : `/management/projects/editor-setup`), a.check === true ? { method: "GET" } : { method: "POST", body: JSON.stringify({ force: a.force }) }))
5812
5826
  ),
5827
+ // Ask, don't guess: the plan, and its answers through editor_setup's declare route.
5828
+ def(
5829
+ "get_editable_plan",
5830
+ "Get the owner's open questions",
5831
+ NEW_TOOL_TEXT.get_editable_plan,
5832
+ z.object({}).shape,
5833
+ async (c) => ok("Editable plan.", await c.fetchJSON(c.url(`/management/projects/editable-plan`), { method: "GET" }))
5834
+ ),
5835
+ def(
5836
+ "confirm_editable_plan",
5837
+ "Record the owner's answers",
5838
+ NEW_TOOL_TEXT.confirm_editable_plan,
5839
+ z.object({ decisions: z.array(z.record(z.string(), z.unknown())), check: z.boolean().optional() }).shape,
5840
+ async (c, a) => ok("Binding decisions.", await c.fetchJSON(c.url(`/management/projects/binding-decisions`), { method: "POST", body: JSON.stringify({ declare: a.decisions, ...a.check === true ? { dryRun: true } : {} }) }))
5841
+ ),
5813
5842
  // ── Deploy to the user's own host (parity with remote /mcp) ──
5814
5843
  // A person connects Vercel or Netlify in the dashboard; nothing here touches OAuth or a host
5815
5844
  // token. deploy_to_host takes artifact:write like deploy_project. @see management/deploy-targets.ts
@@ -6241,7 +6270,7 @@ ${notes.join("\n")}` : summary, created);
6241
6270
  name: "create_form",
6242
6271
  config: {
6243
6272
  title: "Create a form",
6244
- description: "Create a form in the connected project from a field schema. Confirm the fields with the user first. Field types: text, email, textarea, select (needs options), radio (needs options), checkboxes (needs options), checkbox, number, phone, date, url, consent, hidden, step (a divider: the fields after it are one page). Returns the new form's id. It lands as a DRAFT; call publish_form before embedding (a draft form returns 403 on submit). Embed it with `<BcmsForm form={getForm('Name')} />` from @bettercms-ai/next.",
6273
+ description: "Create a form in the connected project from a field schema. Confirm the fields with the user first. Field types: text, email, textarea, select (needs options), radio (needs options), checkboxes (needs options), checkbox, number, phone, date, url, consent, hidden, step (a divider: the fields after it are one page). Returns the new form's id. It is created PUBLISHED when this connection may publish; otherwise it lands as a DRAFT (a draft form returns 403 on submit) \u2014 check `status` and call publish_form, or let the user publish it. A draft form placed on a page also goes live when that page is published. Embed it with `<BcmsForm form={getForm('Name')} />` from @bettercms-ai/next.",
6245
6274
  inputSchema: createFormInput.shape
6246
6275
  },
6247
6276
  handler: guard(
@@ -6270,7 +6299,7 @@ ${notes.join("\n")}` : summary, created);
6270
6299
  name: "publish_form",
6271
6300
  config: {
6272
6301
  title: "Publish a form",
6273
- description: "Publish a form by id so it accepts submissions. create_form and update_form only ever write a DRAFT, and a draft form answers every submit with 403 \u2014 call this before embedding it with `<BcmsForm form={getForm('Name')} />`. It re-bakes the project's site; `meta.rebuildQueued` says whether a rebuild was actually queued (false for a project with no connected source). 403 PUBLISH_NOT_GRANTED means this connection may author but not publish: say so and let the user publish from the dashboard Forms tab. 404 means the form is not in this connection's project.",
6302
+ description: "Publish a form by id so it accepts submissions. A form create_form made without the publish right is a DRAFT, and a draft form answers every submit with 403 \u2014 call this before embedding it with `<BcmsForm form={getForm('Name')} />`. It re-bakes the project's site; `meta.rebuildQueued` says whether a rebuild was actually queued (false for a project with no connected source). 403 PUBLISH_NOT_GRANTED means this connection may author but not publish: say so and let the user publish from the dashboard Forms tab. 404 means the form is not in this connection's project.",
6274
6303
  inputSchema: getFormInput.shape
6275
6304
  },
6276
6305
  handler: guard(
@@ -6338,7 +6367,7 @@ ${notes.join("\n")}` : summary, created);
6338
6367
  name: "publish_layout",
6339
6368
  config: {
6340
6369
  title: "Publish the Global Layout draft",
6341
- description: "Publish the connected project's GLOBAL Layout draft (navigation, footer, every reserved section). On a site BetterCMS serves, a Layout publish never builds: it writes the VALUES into the live pages, and a STRUCTURE change (Sections added, removed, moved) waits for a Rebuild or a push (`layout-rebuild-required`); a custom host gets its deploy hook. Until published, get_layout copy:'published' answers PUBLISHED_LAYOUT_UNAVAILABLE. Pass get_layout's `revision` as ifMatch; a stale one returns 409 \u2014 re-read, never retry blindly. A 422 lists validation issues (global, or per page override: pageSlug, copy) to fix with update_layout. A new project's chrome is draft: publish_component navigation-default and footer-default first (the order and the optional logo: skill components). Verify with get_layout copy:'published' \u2014 this response is the write's echo, not a receipt. Page Layout overrides go live with the page (update_page status:'published'). 403 PUBLISH_NOT_GRANTED: this connection may author drafts, not publish \u2014 say so; the user allows it or publishes from the dashboard.",
6370
+ description: "Publish the connected project's GLOBAL Layout draft (navigation, footer, every reserved section). On a site BetterCMS serves, a Layout publish never builds: it writes the VALUES into the live pages, and a STRUCTURE change (Sections added, removed, moved) waits for a Rebuild or a push (`layout-rebuild-required`); a custom host gets its deploy hook. Until published, get_layout copy:'published' answers PUBLISHED_LAYOUT_UNAVAILABLE. Pass get_layout's `revision` as ifMatch; a stale one returns 409 \u2014 re-read, never retry blindly. A 422 lists validation issues (global, or per page override: pageSlug, copy) to fix with update_layout. The platform's Navigation/Footer Defaults publish themselves \u2014 never publish_component them first; the optional logo: skill components. Verify with get_layout copy:'published' \u2014 this response is the write's echo, not a receipt. Page Layout overrides go live with the page (update_page status:'published'). 403 PUBLISH_NOT_GRANTED: this connection may author drafts, not publish \u2014 say so; the user allows it or publishes from the dashboard.",
6342
6371
  inputSchema: publishLayoutInput.shape
6343
6372
  },
6344
6373
  handler: guard(async (args) => withClient(async (client) => {
@@ -7404,8 +7433,12 @@ block: "About \u203A Services", its columns bare roles read under it (Title, Des
7404
7433
  its block plus its ELEMENT, said the way Portable Text titles its types (heading, paragraph,
7405
7434
  quote, list item, link, image, button) with a modifier only to tell two apart ("Hero headline",
7406
7435
  "Footer link 2 URL"). Never body copy, never a sentence. Rename anything to say its meaning better
7407
- \u2014 \`propLabel\`, \`fieldLabel\`, or \`data-bcms-section-label\` / \`data-bcms-label\` in the source \u2014
7408
- within the bound; a name you give is yours and is never re-derived.
7436
+ \u2014 \`propLabel\` (a table's sub-field by dotted path, \`plans.features\`), \`fieldLabel\` (a section by
7437
+ \`"<family>#section"\`), or \`data-bcms-section-label\` / \`data-bcms-label\` in the source \u2014 within
7438
+ the bound; a name you give is yours and is never re-derived. It is ENFORCED: a name outside the bound
7439
+ is refused with \`NAME_NOT_A_NAME\`, naming the rule and a \`suggested\` name per path (send it, or a
7440
+ better one), and a section the import could only call "Section N" holds \`get_next_steps\` until you
7441
+ name it. \`nameSource\` on a field says whose name it is: \`auto\` the platform's guess, \`human\` yours.
7409
7442
 
7410
7443
  On a DERIVED site run \`get_componentize_plan\` \u2192 \`componentize_sections\` FIRST \u2014 it does most
7411
7444
  of this in one call, and once \`components\` is recorded each \`editor_setup\` of a derived site
@@ -8708,7 +8741,7 @@ function buildServer(deps) {
8708
8741
  // 🔴 The plain-language ask, routed — the stdio twin of the hosted connector's line
8709
8742
  // (src/routes/mcp/index.ts MCP_INSTRUCTIONS). "Make my site editable" has one recipe and
8710
8743
  // one definition of done; without this an agent converts the page it landed on and stops.
8711
- instructions: RECEIPT_FIRST_INSTRUCTION + " " + NO_QUESTIONS_INSTRUCTION + " " + TOOL_LOADING_INSTRUCTION + " When the user asks to make a site or all of its pages editable and they were DERIVED at import: editor_setup, then get_next_steps until `receipt.gate.complete`. To convert its source or 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
8744
+ instructions: RECEIPT_FIRST_INSTRUCTION + " " + NO_QUESTIONS_INSTRUCTION + " " + TOOL_LOADING_INSTRUCTION + " When the user asks to make a site or all of its pages editable and they were DERIVED at import: editor_setup, then get_next_steps until `receipt.gate.complete`. To convert its source or 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 + " " + NAMING_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
8712
8745
  // sentence as the hosted connector's MCP_INSTRUCTIONS.
8713
8746
  STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION + " " + DOCK_UI_ROUTING_INSTRUCTION + " " + PUBLISH_HOLD_INSTRUCTION
8714
8747
  }