@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.
- package/README.md +86 -86
- package/SKILL.md +160 -160
- package/dist/index.js +49 -16
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +1509 -1509
- package/dist/skills/bettercms/SKILL.md +206 -206
- package/dist/skills/bettercms/references/accessibility.md +171 -171
- package/dist/skills/bettercms/references/animation.md +296 -296
- package/dist/skills/bettercms/references/astro.md +148 -148
- package/dist/skills/bettercms/references/bindings.md +366 -366
- package/dist/skills/bettercms/references/componentize.md +185 -185
- package/dist/skills/bettercms/references/components.md +415 -415
- package/dist/skills/bettercms/references/content-experiments.md +62 -62
- package/dist/skills/bettercms/references/content-modeling.md +382 -382
- package/dist/skills/bettercms/references/detection.md +465 -465
- package/dist/skills/bettercms/references/dock-input.md +121 -121
- package/dist/skills/bettercms/references/editor-chrome.md +92 -92
- package/dist/skills/bettercms/references/external-content.md +78 -78
- package/dist/skills/bettercms/references/finish-site.md +377 -377
- package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
- package/dist/skills/bettercms/references/frameworks.md +159 -156
- package/dist/skills/bettercms/references/getting-started.md +156 -141
- package/dist/skills/bettercms/references/integrations.md +364 -364
- package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
- package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
- package/dist/skills/bettercms/references/localization.md +85 -85
- package/dist/skills/bettercms/references/media-and-brand.md +181 -181
- package/dist/skills/bettercms/references/next.md +189 -186
- package/dist/skills/bettercms/references/other-frameworks.md +85 -68
- package/dist/skills/bettercms/references/portable-text.md +213 -213
- package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
- package/dist/skills/bettercms/references/publishing.md +289 -289
- package/dist/skills/bettercms/references/seo.md +357 -357
- package/dist/skills/bettercms/references/shopify-headless.md +52 -52
- package/dist/skills/bettercms/references/site-blueprints.md +895 -895
- package/dist/skills/bettercms/references/site-review.md +74 -74
- package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
- package/dist/skills/bettercms/references/worked-examples.md +885 -885
- package/dist/skills/bettercms/references/write-limits.md +217 -217
- 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
|
|
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(
|
|
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
|
|
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
|
-
...
|
|
4970
|
-
body: JSON.stringify(
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
7408
|
-
|
|
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
|
}
|