@bettercms-ai/mcp 0.65.2 → 0.66.0
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 +86 -42
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +4 -0
- package/dist/skills/bettercms/references/componentize.md +5 -3
- package/dist/skills/bettercms/references/components.md +1 -1
- package/dist/skills/bettercms/references/content-modeling.md +3 -1
- package/dist/skills/bettercms/references/finish-site.md +1 -0
- package/dist/skills/bettercms/references/forms-and-interactions.md +1 -0
- package/dist/skills/bettercms/references/getting-started.md +8 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2676,9 +2676,17 @@ var NEW_TOOL_TEXT = {
|
|
|
2676
2676
|
search_docs: "Search the BetterCMS documentation. Returns the best pages (title, url, breadcrumbs, snippet). Use it before guessing how a feature works; `meta.unavailable` means docs search is down right now.",
|
|
2677
2677
|
generate_image: "Generate an image with AI and add it to the Media Library (5 AI credits; free with the workspace's own Together key). `width`/`height` are 256-1440 in multiples of 16 (default 1024x768). Returns the new asset (id, url, altText): use the url as an 'image' field value. Always set `altText`.",
|
|
2678
2678
|
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.",
|
|
2679
|
+
editor_setup: "Make the live build editable. A build imports only SEO/meta, media, brand and the models bcms-content.json declares; this derives the rest from the live release: pages, collections, forms, bindings, button and section components (when the authoring preference is components), Component Groups and the Content sidebar. 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` or get_deploy_status `editorSetup`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
|
|
2679
2680
|
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)."
|
|
2680
2681
|
};
|
|
2681
2682
|
var PARAM_TEXT = {
|
|
2683
|
+
editorSetupForce: "re-run a setup already ready for the live build",
|
|
2684
|
+
editorSetupCheck: "true = only report the state; starts nothing",
|
|
2685
|
+
pageDynamic: "a dynamic (collection template) page only",
|
|
2686
|
+
previewPath: "its route pattern, e.g. /blog/{slug}; null clears",
|
|
2687
|
+
pageModelId: "an existing collection this page lists (when it has no model yet)",
|
|
2688
|
+
formIgnored: "true = not one of the site's forms (a search box, a widget); false restores it",
|
|
2689
|
+
componentizeCodemod: "true = componentize nothing; run the section-order codemod (`--componentize`) in the connected repo on its own Actions, which commits `<Sections>` templates for the next build. After the components and pages are published. `skipped` = no repo to commit to: run the codemod yourself",
|
|
2682
2690
|
where: "field filters: {field: value} is equality, {field: {op: value}} with op eq|ne|gt|gte|lt|lte|contains|in, and an array means `in`. System fields slug, status, publishedAt, locale work anywhere; a data field needs modelId. Compares the served copy (live for published, working for draft)",
|
|
2683
2691
|
sort: "comma list, a leading - sorts descending, e.g. '-publishedAt,title'",
|
|
2684
2692
|
page: "1-based page of `limit` rows; the response's meta says totalPages",
|
|
@@ -3227,7 +3235,7 @@ function authPrompt(err) {
|
|
|
3227
3235
|
var currentProject = new AsyncLocalStorage();
|
|
3228
3236
|
var currentBranch = new AsyncLocalStorage();
|
|
3229
3237
|
var projectIdArg = z.string().min(1).optional().describe(
|
|
3230
|
-
"
|
|
3238
|
+
"project id (list_projects); workspace-wide grants only"
|
|
3231
3239
|
);
|
|
3232
3240
|
var branchIdArg = z.string().min(1).optional().describe("content branch id (from list_branches); omit to work on main");
|
|
3233
3241
|
var withProjectId = (def) => ({
|
|
@@ -3510,7 +3518,8 @@ function buildToolDefs(deps) {
|
|
|
3510
3518
|
formId: z.string().min(1).describe("form id (from list_forms)"),
|
|
3511
3519
|
name: z.string().optional(),
|
|
3512
3520
|
fields: z.array(formFieldObject).optional().describe("REPLACES the field array \u2014 include all fields to keep"),
|
|
3513
|
-
...formSettingsShape
|
|
3521
|
+
...formSettingsShape,
|
|
3522
|
+
ignored: z.boolean().optional().describe(PARAM_TEXT.formIgnored)
|
|
3514
3523
|
});
|
|
3515
3524
|
const componentPropObject = z.object({
|
|
3516
3525
|
key: z.string().min(1),
|
|
@@ -4138,7 +4147,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4138
4147
|
def(
|
|
4139
4148
|
"set_authoring_preference",
|
|
4140
4149
|
"Set the site's authoring architecture",
|
|
4141
|
-
"Record which authoring architecture this site uses \u2014 'components' (RECOMMENDED: reusable section components placed as blocks; editors add, reorder and swap sections without touching a schema \u2014 recommend it for every marketing, landing, agency or product site) or 'fields' (a typed field schema per page \u2014 only for a blog, catalogue or directory where many rows share one shape). ASK THE USER; do not pick for them. Called without `preference`, this tool asks them directly (or hands you the question to ask). deploy_project and promote_project both refuse with 409 AUTHORING_DECISION_REQUIRED until it is set, carrying this project's real page counts to show the user. Unset,
|
|
4150
|
+
"Record which authoring architecture this site uses \u2014 'components' (RECOMMENDED: reusable section components placed as blocks; editors add, reorder and swap sections without touching a schema \u2014 recommend it for every marketing, landing, agency or product site) or 'fields' (a typed field schema per page \u2014 only for a blog, catalogue or directory where many rows share one shape). ASK THE USER; do not pick for them. Called without `preference`, this tool asks them directly (or hands you the question to ask). deploy_project and promote_project both refuse with 409 AUTHORING_DECISION_REQUIRED until it is set, carrying this project's real page counts to show the user. Unset, editor_setup componentizes nothing; only `components` lets it place sections. On a site whose pages were DERIVED at import, answering 'components' redoes nothing: after set_authoring_preference, get_componentize_plan proposes one component per section (it reads the PUBLISHED pages \u2014 no deploy needed to reach it), you confirm it with the user, then componentize_sections places them with `props.bind` (dry run first \u2014 they land as drafts and the page keeps its fields, values and bindings, so click-to-edit and the coverage meter are unchanged), then publish_component each one, publish the pages, run `npx @bettercms-ai/convert --componentize` in the repo, and deploy. Asked once per project; re-callable if the user changes their mind.",
|
|
4142
4151
|
// Optional in the schema for exactly the reason `framework` is above: a required arg is
|
|
4143
4152
|
// rejected by the SDK before the handler runs, which would kill the elicitation below
|
|
4144
4153
|
// and leave the model guessing. Optional here, answered by a human there. The backend
|
|
@@ -4292,14 +4301,14 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4292
4301
|
"update_page",
|
|
4293
4302
|
"Edit a page",
|
|
4294
4303
|
"Edit a page: title, slug, SEO metaTitle/metaDescription, structured data (`schemaType`, `schema`), publish status (draft|published), and `blockJson` (its block composition \u2014 passing it REPLACES the whole array, so read get_page first and send its `optimisticVersion` as `ifMatch`). A `blockJson` that swaps a component block to another component comes back with `warnings` (COMPONENT_SWAP_DROPS_OVERRIDES) naming the overrides the new component does not render; they stay on the block, so swapping back restores them. It does NOT change the field SCHEMA \u2014 use add_field (pageId) / set_page_content for that. Renaming the slug keeps content intact. Publishing copies the draft blocks live in the same call \u2014 and on a singleton it publishes the page's content entry too; if the entry's workflow stage refuses, the page still goes live and `warnings` carries ENTRY_STILL_DRAFT with the entry id and the reason. STRUCTURED DATA is native SEO, never a field: leave it out and the page gets Automatic JSON-LD from its content (WebSite on the home page, Blog/CollectionPage on a collection's list page, WebPage otherwise); `schemaType` picks an explicit schema.org type whose properties are filled from the content; `schema` is pasted JSON-LD and wins over both (null clears it). " + PAGE_HEAD_NOTE + " " + SECTION_DOCTRINE,
|
|
4295
|
-
z.object({ ...headPatchShape, noindex: z.boolean().optional().describe("hide this page from search engines and site search"), pageId: z.string().min(1), title: z.string().optional(), slug: z.string().optional(), blockJson: z.array(blockObject).optional().describe("REPLACES the page's block composition"), metaTitle: z.string().optional(), metaDescription: z.string().optional(), schemaType: z.enum(["auto", "WebPage", "AboutPage", "ContactPage", "CollectionPage", "Blog", "BlogPosting", "Article", "Product", "FAQPage", "Event", "Organization", "LocalBusiness"]).optional().describe("structured data type; 'auto' (the default) derives it from the content"), schema: z.union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))]).nullable().optional().describe("custom JSON-LD; wins over schemaType; null clears it"), status: z.enum(["draft", "published"]).optional(), scheduledAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageScheduledAt), unpublishAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageUnpublishAt), locale: z.string().nullable().optional().describe(PARAM_TEXT.locale), translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf), ifMatch: z.number().int().min(0).optional().describe(UPDATE_PAGE_IF_MATCH_NOTE) }).shape,
|
|
4304
|
+
z.object({ ...headPatchShape, noindex: z.boolean().optional().describe("hide this page from search engines and site search"), pageId: z.string().min(1), title: z.string().optional(), slug: z.string().optional(), blockJson: z.array(blockObject).optional().describe("REPLACES the page's block composition"), metaTitle: z.string().optional(), metaDescription: z.string().optional(), schemaType: z.enum(["auto", "WebPage", "AboutPage", "ContactPage", "CollectionPage", "Blog", "BlogPosting", "Article", "Product", "FAQPage", "Event", "Organization", "LocalBusiness"]).optional().describe("structured data type; 'auto' (the default) derives it from the content"), schema: z.union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))]).nullable().optional().describe("custom JSON-LD; wins over schemaType; null clears it"), status: z.enum(["draft", "published"]).optional(), scheduledAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageScheduledAt), unpublishAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageUnpublishAt), locale: z.string().nullable().optional().describe(PARAM_TEXT.locale), translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf), ifMatch: z.number().int().min(0).optional().describe(UPDATE_PAGE_IF_MATCH_NOTE), dynamic: z.object({ previewPath: z.string().nullable().optional().describe(PARAM_TEXT.previewPath), modelId: z.string().min(1).optional().describe(PARAM_TEXT.pageModelId) }).optional().describe(PARAM_TEXT.pageDynamic) }).shape,
|
|
4296
4305
|
async (c, a) => {
|
|
4297
4306
|
const res = await c.fetchJSON(
|
|
4298
4307
|
c.url(`/management/pages/${s(a.pageId)}/meta`),
|
|
4299
4308
|
{
|
|
4300
4309
|
method: "PATCH",
|
|
4301
4310
|
...a.ifMatch !== void 0 ? { headers: { "If-Match": `W/"${a.ifMatch}"` } } : {},
|
|
4302
|
-
body: JSON.stringify({ title: a.title, slug: a.slug, blockJson: a.blockJson, 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 })
|
|
4311
|
+
body: JSON.stringify({ title: a.title, slug: a.slug, blockJson: a.blockJson, 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 })
|
|
4303
4312
|
}
|
|
4304
4313
|
);
|
|
4305
4314
|
const note = res.redirectNote;
|
|
@@ -4721,7 +4730,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4721
4730
|
def(
|
|
4722
4731
|
"componentize_sections",
|
|
4723
4732
|
"Turn this site's derived sections into components",
|
|
4724
|
-
"Turn this site's derived sections into components. CONFIRM WITH THE USER FIRST: show them get_componentize_plan's sections, how many components it will create and which pages it will rewrite; `dryRun` first. It creates each proposed component as a DRAFT and replaces each page's DRAFT blocks with ordered `component` instances, one per group, each carrying `props.bind: \"<groupKey>\"` \u2014 the page field group that already holds the copy. Nothing is copied or moved: the page keeps its `fields`, values and bindings, so click-to-edit and the coverage meter do not change (`copy: \"instance\"` moves the words onto each placement instead \u2014 see `copy`). Pass the plan's `digest`; a 409 `stale-plan` means the site changed: read the plan again, show the user what changed and confirm again. Read `components.wouldDuplicate` first: each id is an existing same-family component a NEW one would sit beside (reuse is by identity and slug, never family). Re-running is safe: a placed group returns as ALREADY_COMPONENTIZED. DRAFTS ONLY \u2014 an unpublished component renders as an EMPTY STRING on the live site, so publish_component each one and publish the pages, then run `npx @bettercms-ai/convert --componentize` in the repo so its templates render these sections from `pages[].blocks`. Every component it CREATES is filed into a Component Group (see `group`). When the user says a proposed group is NOT a section, pass `decline: true` with those `sections` (and `pageIds`): nothing is componentized, the plan reports them `NOT_A_SECTION` / `DECLINED` and stops proposing them, and a plan that proposes nothing undeclined is done.",
|
|
4733
|
+
"Turn this site's derived sections into components. CONFIRM WITH THE USER FIRST: show them get_componentize_plan's sections, how many components it will create and which pages it will rewrite; `dryRun` first. It creates each proposed component as a DRAFT and replaces each page's DRAFT blocks with ordered `component` instances, one per group, each carrying `props.bind: \"<groupKey>\"` \u2014 the page field group that already holds the copy. Nothing is copied or moved: the page keeps its `fields`, values and bindings, so click-to-edit and the coverage meter do not change (`copy: \"instance\"` moves the words onto each placement instead \u2014 see `copy`). Pass the plan's `digest`; a 409 `stale-plan` means the site changed: read the plan again, show the user what changed and confirm again. Read `components.wouldDuplicate` first: each id is an existing same-family component a NEW one would sit beside (reuse is by identity and slug, never family). Re-running is safe: a placed group returns as ALREADY_COMPONENTIZED. DRAFTS ONLY \u2014 an unpublished component renders as an EMPTY STRING on the live site, so publish_component each one and publish the pages, then call it again with `codemod: true` (or run `npx @bettercms-ai/convert --componentize` in the repo) so its templates render these sections from `pages[].blocks`. Every component it CREATES is filed into a Component Group (see `group`). When the user says a proposed group is NOT a section, pass `decline: true` with those `sections` (and `pageIds`): nothing is componentized, the plan reports them `NOT_A_SECTION` / `DECLINED` and stops proposing them, and a plan that proposes nothing undeclined is done.",
|
|
4725
4734
|
z.object({
|
|
4726
4735
|
digest: z.string().min(1).describe("The `digest` get_componentize_plan returned. A different one is refused with 409 stale-plan."),
|
|
4727
4736
|
pageIds: z.array(z.string().min(1)).optional().describe("Componentize only these pages (ids from the plan). Omit for every page the plan lists."),
|
|
@@ -4731,9 +4740,10 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4731
4740
|
dryRun: z.boolean().optional().describe("true = return the receipt without writing anything. Do this first."),
|
|
4732
4741
|
group: z.string().min(1).max(100).optional().describe("File the components this run creates into this Component Group (a folder of the dashboard's Components tab; by NAME, created when missing), else its section family: nav, header, footer or menu \u2192 Layout, an unnamed section \u2192 Sections, otherwise the family name (Hero, FAQ). A reused component keeps its Group; the receipt's `components.groups` lists the Groups filed into."),
|
|
4733
4742
|
decline: z.boolean().optional().describe("true = record `sections` as NOT sections (nothing is componentized; the plan stops proposing them). false = offer them again. Needs `sections`."),
|
|
4734
|
-
joinTwins: z.boolean().optional().describe("true = move each locale copy (`/fr`) still on its own component onto its twin's (`bindPaths` keeps its fields). A dry run lists each in `joins`. Whole plan only.")
|
|
4743
|
+
joinTwins: z.boolean().optional().describe("true = move each locale copy (`/fr`) still on its own component onto its twin's (`bindPaths` keeps its fields). A dry run lists each in `joins`. Whole plan only."),
|
|
4744
|
+
codemod: z.boolean().optional().describe(PARAM_TEXT.componentizeCodemod)
|
|
4735
4745
|
}).shape,
|
|
4736
|
-
async (c, a) => ok("Componentized the sections.", await data(c, "POST", `/management/projects/current/componentize`, { digest: a.digest, pageIds: a.pageIds, pages: a.pages, sections: a.sections, copy: a.copy, dryRun: a.dryRun, group: a.group, decline: a.decline, joinTwins: a.joinTwins }))
|
|
4746
|
+
async (c, a) => ok(a.codemod === true ? "Section-order codemod." : "Componentized the sections.", await data(c, "POST", a.codemod === true ? `/management/projects/current/section-order` : `/management/projects/current/componentize`, { digest: a.digest, pageIds: a.pageIds, pages: a.pages, sections: a.sections, copy: a.copy, dryRun: a.dryRun, group: a.group, decline: a.decline, joinTwins: a.joinTwins, codemod: a.codemod }))
|
|
4737
4747
|
),
|
|
4738
4748
|
def(
|
|
4739
4749
|
"get_analytics_overview",
|
|
@@ -4949,9 +4959,21 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4949
4959
|
def(
|
|
4950
4960
|
"get_deploy_status",
|
|
4951
4961
|
"Get deploy/build status",
|
|
4952
|
-
"Get the connected project's deploy/build status: state (idle|queued|building|
|
|
4962
|
+
"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` 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.",
|
|
4953
4963
|
z.object({}).shape,
|
|
4954
|
-
|
|
4964
|
+
// Flat body, no `data` envelope: `data()` read it as undefined.
|
|
4965
|
+
async (c) => ok("Deploy status.", await c.fetchJSON(c.url(`/management/projects/deploy-status`), { method: "GET" }))
|
|
4966
|
+
),
|
|
4967
|
+
def(
|
|
4968
|
+
"editor_setup",
|
|
4969
|
+
"Set up editing for the live build",
|
|
4970
|
+
NEW_TOOL_TEXT.editor_setup,
|
|
4971
|
+
z.object({
|
|
4972
|
+
force: z.boolean().optional().describe(PARAM_TEXT.editorSetupForce),
|
|
4973
|
+
check: z.boolean().optional().describe(PARAM_TEXT.editorSetupCheck)
|
|
4974
|
+
}).shape,
|
|
4975
|
+
// The route answers `{ state, … }` with no `data` envelope, so the whole body is the result.
|
|
4976
|
+
async (c, a) => ok("Editor setup.", await c.fetchJSON(c.url(`/management/projects/editor-setup`), a.check === true ? { method: "GET" } : { method: "POST", body: JSON.stringify({ force: a.force }) }))
|
|
4955
4977
|
),
|
|
4956
4978
|
// ── Deploy to the user's own host (parity with remote /mcp) ──
|
|
4957
4979
|
// A person connects Vercel or Netlify in the dashboard; nothing here touches OAuth or a host
|
|
@@ -5890,7 +5912,7 @@ A collection (\`create_content_model\`) plus entries.
|
|
|
5890
5912
|
|
|
5891
5913
|
Most real sites are both: components for the marketing pages, a collection for the blog.
|
|
5892
5914
|
Decide before your first call; converting later means rewriting content \u2014 unless the site was
|
|
5893
|
-
DERIVED
|
|
5915
|
+
DERIVED by \`editor_setup\`, where the componentize lane in \xA710 reuses every field.
|
|
5894
5916
|
|
|
5895
5917
|
## 2. The decision tree
|
|
5896
5918
|
|
|
@@ -5967,8 +5989,8 @@ sibling calls it \`headline\` silently drops that content on the swap.
|
|
|
5967
5989
|
Pair \`sectionType\` with a library \`category\` (hero, content, social-proof, conversion) or
|
|
5968
5990
|
the component never appears in the editor's "Add a section" picker.
|
|
5969
5991
|
|
|
5970
|
-
**An IMPORTED site gets there without you writing any of that.** Its pages
|
|
5971
|
-
|
|
5992
|
+
**An IMPORTED site gets there without you writing any of that.** Its pages are derived by
|
|
5993
|
+
\`editor_setup\` \u2014 flat field groups (\`hero-title\`, \`faq-question\`, the repeaters folded as \`group-*\`)
|
|
5972
5994
|
and an empty \`blockJson\` \u2014 so there is nothing in the picker and no section to swap. Read
|
|
5973
5995
|
\`get_componentize_plan\`: it groups those fields into sections, names each family, and proposes
|
|
5974
5996
|
one component per SHAPE (the same hero on two pages is one component with two placements),
|
|
@@ -6096,14 +6118,27 @@ written to the wrong project can only be demoted (\`sectionType: null\`) and lef
|
|
|
6096
6118
|
|
|
6097
6119
|
## 10. You imported a site, or you are about to deploy one
|
|
6098
6120
|
|
|
6121
|
+
**A build imports what the build itself ships, and nothing more:** SEO and meta, every media
|
|
6122
|
+
asset, the brand, and the models the repository declares in \`bcms-content.json\`. Everything
|
|
6123
|
+
that makes the site EDITABLE \u2014 pages, inferred collections, forms, bindings, button and section
|
|
6124
|
+
components \u2014 is \`editor_setup\`, run on demand against the live build. The import recipe:
|
|
6125
|
+
|
|
6126
|
+
import (import_github_repo / deploy_project) -> editor_setup (poll { check: true } until ready)
|
|
6127
|
+
-> the authoring answer -> componentize_sections, or get_conversion_brief (\xA713)
|
|
6128
|
+
-> the codemods (componentize_sections { codemod: true } runs the section-order one in the repo)
|
|
6129
|
+
-> submit_conversion_receipt / submit_componentize_receipt -> deploy
|
|
6130
|
+
|
|
6131
|
+
\`get_deploy_status\` \`editorSetup\` (and get_next_steps) say when it has not run for the live
|
|
6132
|
+
build: \`never\` or \`stale\` \u2192 run it before anything below.
|
|
6133
|
+
|
|
6099
6134
|
\`deploy_project\` and \`promote_project\` both answer **409
|
|
6100
6135
|
AUTHORING_DECISION_REQUIRED** until a human has chosen this project's architecture. This is
|
|
6101
6136
|
asked ONCE per project, ever. It is not an error to retry or route around: read the message
|
|
6102
6137
|
out, let the user pick, call \`set_authoring_preference\`, then deploy again.
|
|
6103
6138
|
|
|
6104
|
-
Until someone answers, the
|
|
6105
|
-
|
|
6106
|
-
|
|
6139
|
+
Until someone answers, \`editor_setup\` derives the pages but componentizes nothing (the
|
|
6140
|
+
preference is null). Once \`components\` is recorded, each \`editor_setup\` componentizes the
|
|
6141
|
+
derived pages' NEW sections in bind mode; \`fields\` stops it for good. And a run over the WHOLE plan splits a section component an earlier run placed on
|
|
6107
6142
|
2 pages into one per page (the first page keeps it, the others get a clone), so two pages' heroes
|
|
6108
6143
|
stop editing each other; a run narrowed to one page or section never splits.
|
|
6109
6144
|
|
|
@@ -6112,9 +6147,9 @@ It exists because an imported site arrives **field-driven whether anyone chose t
|
|
|
6112
6147
|
and an empty block tree, because that is all a crawl can infer. Nobody decided it. On a
|
|
6113
6148
|
marketing site it is the wrong answer. So the platform stops once, at the last moment it is
|
|
6114
6149
|
still cheap \u2014 and on a derived site that moment costs nothing, because the componentize lane
|
|
6115
|
-
below reuses every field
|
|
6150
|
+
below reuses every field \`editor_setup\` already derived.
|
|
6116
6151
|
|
|
6117
|
-
**Recommend \`components\`.** On a site whose pages were DERIVED
|
|
6152
|
+
**Recommend \`components\`.** On a site whose pages were DERIVED by \`editor_setup\` \u2014 the site \xA713
|
|
6118
6153
|
describes \u2014 it redoes nothing: the componentize lane turns each top-level field GROUP into a
|
|
6119
6154
|
component placement, and the page keeps its fields, its values and its bindings. In this order:
|
|
6120
6155
|
|
|
@@ -6122,7 +6157,7 @@ component placement, and the page keeps its fields, its values and its bindings.
|
|
|
6122
6157
|
2. get_componentize_plan one component per section, computed live; creates nothing. It
|
|
6123
6158
|
reads this project's PUBLISHED pages, so no deploy is needed
|
|
6124
6159
|
to reach it \u2014 the deploy is the last step, not the first.
|
|
6125
|
-
On a
|
|
6160
|
+
On a DERIVED site, each editor_setup after this answer
|
|
6126
6161
|
runs this plan in bind mode for NEW sections (it never splits
|
|
6127
6162
|
or joins a placement, and runs nothing while the answer is
|
|
6128
6163
|
unset) and PUBLISHES what it created, so after one expect
|
|
@@ -6137,8 +6172,11 @@ component placement, and the page keeps its fields, its values and its bindings.
|
|
|
6137
6172
|
5. publish_component each one \u2014 unpublished renders as NOTHING, on a page that 200s
|
|
6138
6173
|
5b. publish_layout if you authored site chrome \u2014 the layout is a separate publish
|
|
6139
6174
|
6. publish the pages
|
|
6140
|
-
7.
|
|
6141
|
-
|
|
6175
|
+
7. componentize_sections { digest, codemod: true } the section-order codemod
|
|
6176
|
+
(\`--componentize\`) on the repo's own Actions, so its templates
|
|
6177
|
+
render these sections from \`pages[].blocks\`; \`skipped\` = no
|
|
6178
|
+
repo it may commit to: run \`npx @bettercms-ai/convert
|
|
6179
|
+
--componentize\` in your checkout
|
|
6142
6180
|
8. deploy the ONLY deploy this sequence needs
|
|
6143
6181
|
|
|
6144
6182
|
Step 5 is not a formality: \`componentize_sections\` and \`create_component\` both land components
|
|
@@ -6371,10 +6409,10 @@ JSON. On an IMPORTED site, never materialize a builtin: read the repo's own comp
|
|
|
6371
6409
|
(\`src/components\`, its sections folder) and make ONE CMS component per source component, named
|
|
6372
6410
|
as the source names it \u2014 the site's Pricing section is "Pricing", not "Section 2" or "Simple".
|
|
6373
6411
|
On a DERIVED site run \`get_componentize_plan\` \u2192 \`componentize_sections\` FIRST \u2014 it does most
|
|
6374
|
-
of this in one call, and once \`components\` is recorded each
|
|
6412
|
+
of this in one call, and once \`components\` is recorded each \`editor_setup\` of a derived site
|
|
6375
6413
|
runs it in bind mode and publishes the components, so expect ALREADY_COMPONENTIZED \u2014 the placements it wrote are
|
|
6376
|
-
still DRAFT blocks on the pages, so publish the pages, then \`
|
|
6377
|
-
--componentize
|
|
6414
|
+
still DRAFT blocks on the pages, so publish the pages, then \`componentize_sections { codemod: true }\`
|
|
6415
|
+
(or \`npx @bettercms-ai/convert --componentize\` in your checkout).
|
|
6378
6416
|
|
|
6379
6417
|
**3. The hierarchy editors see is the GROUP.** \`create_component\` and \`update_component\` take
|
|
6380
6418
|
\`group\`: a NAME, resolved against the project's component groups and created when missing. That
|
|
@@ -6479,9 +6517,10 @@ anything. See \xA79.
|
|
|
6479
6517
|
|
|
6480
6518
|
**Which recipe.** There are TWO below and they are not alternatives you pick by taste \u2014 call
|
|
6481
6519
|
\`get_binding_report\` and \`get_conversion_brief\` first and let the answer choose. A brief that
|
|
6482
|
-
comes back WITH PAGES means this project was imported and
|
|
6483
|
-
|
|
6484
|
-
|
|
6520
|
+
comes back WITH PAGES means this project was imported and \`editor_setup\` derived its schema and
|
|
6521
|
+
values already \u2192 **Recipe A**. On an import whose \`get_deploy_status\` \`editorSetup\` is \`never\`
|
|
6522
|
+
or \`stale\`, run \`editor_setup\` FIRST and read the brief again. A 404 or an empty brief after
|
|
6523
|
+
that means the site is not in the CMS yet \u2192 **Recipe B**. \`get_next_steps\` works on every plan and names the next unfinished step
|
|
6485
6524
|
either way.
|
|
6486
6525
|
|
|
6487
6526
|
**If you know Sanity, this is the same shape under different names:**
|
|
@@ -6497,8 +6536,8 @@ either way.
|
|
|
6497
6536
|
draft reads
|
|
6498
6537
|
Presentation tool overlays -> the visual editor canvas, framing your own build
|
|
6499
6538
|
|
|
6500
|
-
**Imported site whose pages were DERIVED.** If this project was imported and
|
|
6501
|
-
its build, the schema and the values already exist \u2014 a page per route, a field per element, and
|
|
6539
|
+
**Imported site whose pages were DERIVED.** If this project was imported and \`editor_setup\` ran
|
|
6540
|
+
over its build, the schema and the values already exist \u2014 a page per route, a field per element, and
|
|
6502
6541
|
the original copy carried on each field as its \`defaultValue\`. Call \`get_conversion_brief\`
|
|
6503
6542
|
first: it lists those pages, their routes, every bindable path with its current and original
|
|
6504
6543
|
value, and the attributes to declare. SKIP Recipe B's steps 3 and 4 below and bind the keys it
|
|
@@ -6902,7 +6941,7 @@ Do not stop at the plan or at a partial receipt \u2014 finish the whole site, ne
|
|
|
6902
6941
|
|
|
6903
6942
|
1. \`list_projects\`; pass \`projectId\`${projectId ? ` (${projectId})` : ""} on every project tool. Not listed = the grant is on
|
|
6904
6943
|
another workspace: have the user re-authenticate there. Never convert a different project.
|
|
6905
|
-
2. \`get_conversion_plan
|
|
6944
|
+
2. \`editor_setup\` until it is \`ready\` (poll with \`check: true\`), then \`get_conversion_plan\`; on a 404 \`no-approved-plan\`,
|
|
6906
6945
|
\`get_conversion_brief { complete: true }\` and follow \`cursor\` to the LAST page, keeping every
|
|
6907
6946
|
page \u2014 a codemod fed the capped brief converts a truncated site. Write them to \`brief.json\`.
|
|
6908
6947
|
3. \`pull_project_source\` (or clone the \`github\` remote it names) and work from the SOURCE, never
|
|
@@ -7056,8 +7095,8 @@ Read \`bettercms://playbook/schema\` \xA712 first \u2014 this is the short form
|
|
|
7056
7095
|
2. **One component per distinct section**, one-offs included. \`sectionType\` is the family;
|
|
7057
7096
|
components sharing one are VARIANTS and must share prop KEYS. A one-off gets its own family
|
|
7058
7097
|
plus \`allowedOn: ["slug:<page>"]\`. Start from the \`builtin:*\` blueprints in
|
|
7059
|
-
\`list_components\`. On a DERIVED site run \`get_componentize_plan\` \u2192
|
|
7060
|
-
first (dryRun first, and confirm the plan with the user).
|
|
7098
|
+
\`list_components\`. On a DERIVED site (\`editor_setup\` derives it) run \`get_componentize_plan\` \u2192
|
|
7099
|
+
\`componentize_sections\` first (dryRun first, and confirm the plan with the user).
|
|
7061
7100
|
3. **Hierarchy** = \`group\` on create_component/update_component (a NAME; created when missing).
|
|
7062
7101
|
Use Global, Navigation, Actions, Content, Forms, Media, Sections, Page-specific. \`category\`
|
|
7063
7102
|
is the separate picker TAB.
|
|
@@ -7461,7 +7500,7 @@ On 401/403, the MCP key needs (re)authorizing.`
|
|
|
7461
7500
|
"import-site",
|
|
7462
7501
|
{
|
|
7463
7502
|
title: "Make an imported site editable (guided)",
|
|
7464
|
-
description: "After deploying an existing site:
|
|
7503
|
+
description: "After importing or deploying an existing site: run the editor setup, componentize or convert it, declare its presentation manifest, and publish everything that renders. A build makes a site LIVE, not EDITABLE \u2014 this flow closes that gap.",
|
|
7465
7504
|
argsSchema: {
|
|
7466
7505
|
request: z2.string().optional().describe("scope, e.g. 'all routes' or 'just the home page'")
|
|
7467
7506
|
}
|
|
@@ -7476,17 +7515,22 @@ On 401/403, the MCP key needs (re)authorizing.`
|
|
|
7476
7515
|
|
|
7477
7516
|
Read bettercms://playbook/schema section 11 first, then work this order:
|
|
7478
7517
|
|
|
7479
|
-
1.
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7518
|
+
1. EDITOR SETUP \u2014 the build imported only SEO/meta, media, brand and the models the repo
|
|
7519
|
+
declares. Call editor_setup and poll editor_setup { check: true } until \`ready\`: it derives
|
|
7520
|
+
the pages, collections, forms and bindings from the live build.
|
|
7521
|
+
2. AUTHORING GATE \u2014 if get_next_steps or a deploy (409 AUTHORING_DECISION_REQUIRED) asks,
|
|
7522
|
+
ask ME components-or-fields (do not choose), then set_authoring_preference.
|
|
7523
|
+
3. COMPONENTS: get_componentize_plan -> componentize_sections (dryRun first) -> publish the
|
|
7524
|
+
components and pages -> componentize_sections { codemod: true } (the section-order codemod in
|
|
7525
|
+
the repo). FIELDS: the convert_site loop (get_conversion_brief, the codemod, the receipt).
|
|
7526
|
+
Only where the setup derived nothing: create_page (slug = route) -> add_field {pageId} ->
|
|
7527
|
+
set_page_content with values EXACTLY equal to the rendered text (binding matches by value; a
|
|
7528
|
+
paraphrase binds nothing) -> update_page status 'published'.
|
|
7529
|
+
4. PRESENTATION MANIFEST \u2014 add bcms-presentation.json to public/ (container width, type
|
|
7486
7530
|
scale, nav position/background, footer surface as DTCG {"$type","$value"} entries) and
|
|
7487
7531
|
redeploy, or structural draft previews render in a generic theme, not this site's design.
|
|
7488
|
-
|
|
7489
|
-
|
|
7532
|
+
5. VERIFY \u2014 call get_next_steps and fix what it lists (it knows about missing manifests,
|
|
7533
|
+
a setup that has not run, and placed-but-unpublished components), or tell me why
|
|
7490
7534
|
an item is being left.
|
|
7491
7535
|
|
|
7492
7536
|
On 401/403, the MCP key needs (re)authorizing.`
|