@bettercms-ai/mcp 0.66.3 → 0.66.4
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 +48 -17
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +3 -1
- package/dist/skills/bettercms/references/dock-input.md +115 -0
- package/dist/skills/bettercms/references/getting-started.md +3 -1
- package/dist/skills/bettercms/references/localization.md +6 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2710,8 +2710,10 @@ var PARAM_TEXT = {
|
|
|
2710
2710
|
sourceUrl: "instead of data: a public http(s) URL the server fetches and stores (private or internal addresses are refused)",
|
|
2711
2711
|
focalX: "focal point, 0-1 across from the left edge; null resets to the centre",
|
|
2712
2712
|
focalY: "focal point, 0-1 down from the top edge; null resets to the centre",
|
|
2713
|
-
mergeHref: "pageId only: the link field paired with `path` (the label). Both become ONE `button` field keyed `key` (default: their stem, primaryCtaHref \u2192 primaryCta). Each entry's DRAFT gets {label, href} and keeps the old values; the live copy changes on publish. ifMatch is the page's version"
|
|
2713
|
+
mergeHref: "pageId only: the link field paired with `path` (the label). Both become ONE `button` field keyed `key` (default: their stem, primaryCtaHref \u2192 primaryCta). Each entry's DRAFT gets {label, href} and keeps the old values; the live copy changes on publish. ifMatch is the page's version",
|
|
2714
|
+
confirmedInput: "true only after the user explicitly approved adding this custom input (`ui.input`); else setting or changing one is refused input_needs_confirmation. Removing one needs no approval"
|
|
2714
2715
|
};
|
|
2716
|
+
var INPUT_CONFIRMED_HEADER = "x-bcms-confirmed-input";
|
|
2715
2717
|
var BUTTON_FIELD_NOTE = "'button' is ONE call to action, value {label, href, variant?, size?, arrow?, icon?, newTab?}: never a text field beside a link field.";
|
|
2716
2718
|
var UPDATE_FIELD_DESCRIPTION = "Change ONE existing field of a content model (`modelId`) or of a page's schema (`pageId`), addressed by `path`: its key, or dot-separated keys through groups and repeaters ('hero.title'). Set its label, helpText, required, validation `config` (merged by key; null deletes one), showIf, ui or placement (null clears those). The rest of the schema is written back unchanged at the version read; a 409 means it changed since, so read again. `type` changes how every stored value is read and needs allowRetype: true. `key` renames it: stored values stay under the old key, so the route answers 409 with the entry count unless allowFieldRemoval: true discards them \u2014 migrate the values first. Ask the user before a retype or a rename. A page's label + link pair (primaryCtaText + primaryCtaHref) becomes one button with `mergeHref`.";
|
|
2717
2719
|
var GET_SEO_DESCRIPTION = "Get the connected project's site-wide SEO settings \u2014 every key update_seo writes: seoDefaults (metaTitle, metaDescription, ogImage, twitterHandle, og, twitter, collectionSchemaTypes), siteSchema (the site's JSON-LD), robotsConfig, sitemapConfig, rssConfig, indexnowConfig and searchConfig.";
|
|
@@ -2914,7 +2916,13 @@ var uiObject = z.strictObject({
|
|
|
2914
2916
|
// A refine, not an enum: 236 names inlined into every schema that carries `ui` would be paid on
|
|
2915
2917
|
// every tools/list. The layoutIcon precedent; the server's z.enum is the guard.
|
|
2916
2918
|
icon: z.string().min(1).max(40).refine((v) => DOCK_ICON_SET.has(v), `not a dock icon, see ${DOCK_ICONS_URI}`).optional().describe(`the branch row's icon, a name from ${DOCK_ICONS_URI}. Nesting fields only.`),
|
|
2917
|
-
itemLabel: z.string().min(1).max(24).optional().describe("what ONE item is called ('Slide'): the add button, crumbs and empty state. Nesting fields only.")
|
|
2919
|
+
itemLabel: z.string().min(1).max(24).optional().describe("what ONE item is called ('Slide'): the add button, crumbs and empty state. Nesting fields only."),
|
|
2920
|
+
input: z.object({
|
|
2921
|
+
name: z.string().regex(/^[a-z][a-z0-9-]{0,47}$/),
|
|
2922
|
+
writes: z.array(z.string().min(1).max(64)).max(16).optional()
|
|
2923
|
+
}).optional().describe(
|
|
2924
|
+
"a custom dock input by NAME (the project's bettercms/inputs/<name>.tsx), a last resort after `control`, `preview` and groups. `writes`: sibling field keys it may also write, nothing else. Setting or changing it needs `confirmed: true`, sent only after the user approved it."
|
|
2925
|
+
)
|
|
2918
2926
|
});
|
|
2919
2927
|
var propUiObject = uiObject.extend({
|
|
2920
2928
|
group: z.strictObject({
|
|
@@ -3066,6 +3074,8 @@ function proseType(f) {
|
|
|
3066
3074
|
if (f.type !== "text") return f.type;
|
|
3067
3075
|
return f.richText === false ? "text" : "richtext";
|
|
3068
3076
|
}
|
|
3077
|
+
var confirmedHeaders = (confirmed) => confirmed === true ? { [INPUT_CONFIRMED_HEADER]: "true" } : {};
|
|
3078
|
+
var withHeaders = (headers) => Object.keys(headers).length > 0 ? { headers } : {};
|
|
3069
3079
|
function toField(f) {
|
|
3070
3080
|
const base = {
|
|
3071
3081
|
key: f.key,
|
|
@@ -3250,7 +3260,7 @@ function authPrompt(err) {
|
|
|
3250
3260
|
var currentProject = new AsyncLocalStorage();
|
|
3251
3261
|
var currentBranch = new AsyncLocalStorage();
|
|
3252
3262
|
var projectIdArg = z.string().min(1).optional().describe(
|
|
3253
|
-
"
|
|
3263
|
+
"list_projects id; workspace-wide grants only"
|
|
3254
3264
|
);
|
|
3255
3265
|
var branchIdArg = z.string().min(1).optional().describe("content branch id (from list_branches); omit to work on main");
|
|
3256
3266
|
var withProjectId = (def) => ({
|
|
@@ -3385,7 +3395,8 @@ function buildToolDefs(deps) {
|
|
|
3385
3395
|
modelId: z.string().min(1).optional().describe("the content model to extend (from list_content_models); or send pageId"),
|
|
3386
3396
|
pageId: z.string().min(1).optional().describe("instead of modelId: the page whose schema to extend (from list_pages / create_page)"),
|
|
3387
3397
|
...fieldShape,
|
|
3388
|
-
after: afterArg
|
|
3398
|
+
after: afterArg,
|
|
3399
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
3389
3400
|
});
|
|
3390
3401
|
const uploadAssetInput = z.object({
|
|
3391
3402
|
localPath: z.string().min(1).optional().describe("absolute path to a local file (e.g. a repo image); provide this OR url"),
|
|
@@ -3675,7 +3686,8 @@ function buildToolDefs(deps) {
|
|
|
3675
3686
|
propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label"),
|
|
3676
3687
|
propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text, null clears it"),
|
|
3677
3688
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
3678
|
-
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE)
|
|
3689
|
+
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
|
|
3690
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
3679
3691
|
});
|
|
3680
3692
|
const setComponentSourceInput = z.object({
|
|
3681
3693
|
componentId: z.string().min(1).describe("component id (from list_components)"),
|
|
@@ -3858,7 +3870,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3858
3870
|
def(
|
|
3859
3871
|
"set_content_structure",
|
|
3860
3872
|
"Replace the Content sidebar structure",
|
|
3861
|
-
`Replace the whole Content sidebar document in one write. Follow the structure standard (read ${STRUCTURE_PLAYBOOK_URI} first; suggest_content_structure returns a ready doc). Send the complete \`doc\` and the \`version\` get_content_structure returned (0 when the project has none yet). Validated exactly like the dashboard: unique folder ids, every parentId/folderId a folder in the doc, no cycles, depth \u22644, names 1-80 chars,
|
|
3873
|
+
`Replace the whole Content sidebar document in one write. Follow the structure standard (read ${STRUCTURE_PLAYBOOK_URI} first; suggest_content_structure returns a ready doc). Send the complete \`doc\` and the \`version\` get_content_structure returned (0 when the project has none yet). Validated exactly like the dashboard: unique folder ids, every parentId/folderId a folder in the doc, no cycles, depth \u22644, names 1-80 chars, kit icons (bettercms://icons/dock). A 412 VERSION_CONFLICT means someone changed it since you read it: read again and re-apply. Returns the new version and a diff. ${STRUCTURE_FENCE}`,
|
|
3862
3874
|
z.object({
|
|
3863
3875
|
doc: z.object({
|
|
3864
3876
|
schema: z.literal(1),
|
|
@@ -4256,7 +4268,8 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4256
4268
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
4257
4269
|
ifMatch: z.number().int().min(0).optional().describe(MODEL_IF_MATCH_NOTE),
|
|
4258
4270
|
removeFields: z.array(z.string().min(1)).min(1).max(100).optional().describe(FIELD_REMOVAL_NOTE),
|
|
4259
|
-
allowFieldRemoval: z.boolean().optional().describe(ALLOW_FIELD_REMOVAL_NOTE)
|
|
4271
|
+
allowFieldRemoval: z.boolean().optional().describe(ALLOW_FIELD_REMOVAL_NOTE),
|
|
4272
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
4260
4273
|
}).shape,
|
|
4261
4274
|
// `fields` is sent only by removeFields, rebuilt from the model just read: the PATCH
|
|
4262
4275
|
// replaces `fields` wholesale when present.
|
|
@@ -4281,7 +4294,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4281
4294
|
(await c.fetchJSON(c.url(path), {
|
|
4282
4295
|
method: "PATCH",
|
|
4283
4296
|
body: JSON.stringify(body),
|
|
4284
|
-
...ifMatch !== void 0 ? {
|
|
4297
|
+
...withHeaders({ ...ifMatch !== void 0 ? { "if-match": `W/"${ifMatch}"` } : {}, ...confirmedHeaders(a.confirmed) })
|
|
4285
4298
|
})).data
|
|
4286
4299
|
);
|
|
4287
4300
|
}
|
|
@@ -4480,7 +4493,8 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4480
4493
|
allowRetype: z.boolean().optional().describe("confirm a type change, once the user has agreed"),
|
|
4481
4494
|
allowFieldRemoval: z.boolean().optional().describe("with a rename: discard the old key's stored values"),
|
|
4482
4495
|
mergeHref: z.string().min(1).optional().describe(PARAM_TEXT.mergeHref),
|
|
4483
|
-
ifMatch: z.number().int().min(0).optional().describe("the model's optimisticVersion; the version read when omitted")
|
|
4496
|
+
ifMatch: z.number().int().min(0).optional().describe("the model's optimisticVersion; the version read when omitted"),
|
|
4497
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
4484
4498
|
}).shape,
|
|
4485
4499
|
// Read the model (a page's through its backingModel), patch ONE field with the helper the
|
|
4486
4500
|
// hosted catalogue uses, and PATCH the whole list at the version read. `mergeHref` is the
|
|
@@ -4508,7 +4522,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4508
4522
|
const updated = (await c.fetchJSON(c.url(path), {
|
|
4509
4523
|
method: "PATCH",
|
|
4510
4524
|
body: JSON.stringify({ fields: plan.fields, ...a.allowFieldRemoval === true ? { allowFieldRemoval: true } : {} }),
|
|
4511
|
-
...plan.ifMatch !== void 0 ? {
|
|
4525
|
+
...withHeaders({ ...plan.ifMatch !== void 0 ? { "If-Match": `W/"${plan.ifMatch}"` } : {}, ...confirmedHeaders(a.confirmed) })
|
|
4512
4526
|
})).data;
|
|
4513
4527
|
return ok(plan.renamed ? `Renamed '${plan.renamed.from}' to '${plan.renamed.to}'.` : `Updated field '${s(a.path)}'.`, updated);
|
|
4514
4528
|
}
|
|
@@ -5144,12 +5158,17 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5144
5158
|
async (args) => withClient(async (client) => {
|
|
5145
5159
|
if (!args.modelId === !args.pageId) return fail("Pass exactly one of modelId (a content model) or pageId (a page).");
|
|
5146
5160
|
if (args.pageId) {
|
|
5147
|
-
const
|
|
5161
|
+
const input2 = {
|
|
5148
5162
|
// Cast: the SDK types `options` as string[], narrower than the API, which also takes
|
|
5149
5163
|
// {label, value} (backend selectFieldSchema).
|
|
5150
5164
|
addFields: [toField(args)],
|
|
5151
5165
|
...args.after !== void 0 ? { after: args.after } : {}
|
|
5152
|
-
}
|
|
5166
|
+
};
|
|
5167
|
+
const page = args.confirmed === true ? (await client.fetchJSON(client.url(`/management/pages/${args.pageId}`), {
|
|
5168
|
+
method: "PATCH",
|
|
5169
|
+
body: JSON.stringify(input2),
|
|
5170
|
+
headers: confirmedHeaders(true)
|
|
5171
|
+
})).data : await client.addPageFields(args.pageId, input2);
|
|
5153
5172
|
return ok(`Added field '${args.key}' to page '${page.title}'. Page now has ${page.fields.length} field(s).`, page);
|
|
5154
5173
|
}
|
|
5155
5174
|
const modelId = args.modelId;
|
|
@@ -5172,7 +5191,12 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5172
5191
|
const nextFields = [...existingFields];
|
|
5173
5192
|
nextFields.splice(at, 0, toField(args));
|
|
5174
5193
|
const input = { fields: nextFields };
|
|
5175
|
-
const
|
|
5194
|
+
const opts = model.optimisticVersion !== void 0 ? { ifMatch: model.optimisticVersion } : void 0;
|
|
5195
|
+
const updated = args.confirmed === true ? (await client.fetchJSON(client.url(`/management/content/models/${modelId}`), {
|
|
5196
|
+
method: "PATCH",
|
|
5197
|
+
body: JSON.stringify(input),
|
|
5198
|
+
headers: { ...opts ? { "If-Match": `W/"${opts.ifMatch}"` } : {}, ...confirmedHeaders(true) }
|
|
5199
|
+
})).data : opts ? await client.updateModel(modelId, input, opts) : await client.updateModel(modelId, input);
|
|
5176
5200
|
return ok(
|
|
5177
5201
|
`Added field '${args.key}' to '${updated.name}'${args.after ? ` after '${args.after}'` : ""}. Model now has ${updated.fields.length} field(s).`,
|
|
5178
5202
|
updated
|
|
@@ -5695,8 +5719,12 @@ ${lines.join("\n")}`, found);
|
|
|
5695
5719
|
},
|
|
5696
5720
|
handler: guard(
|
|
5697
5721
|
async (args) => withClient(async (client) => {
|
|
5698
|
-
const { componentId, ifMatch, ...input } = args;
|
|
5699
|
-
const cmp = await client.
|
|
5722
|
+
const { componentId, ifMatch, confirmed, ...input } = args;
|
|
5723
|
+
const cmp = confirmed === true ? (await client.fetchJSON(client.url(`/management/components/${componentId}`), {
|
|
5724
|
+
method: "PATCH",
|
|
5725
|
+
body: JSON.stringify(input),
|
|
5726
|
+
headers: { ...ifMatch !== void 0 ? { "If-Match": `W/"${ifMatch}"` } : {}, ...confirmedHeaders(true) }
|
|
5727
|
+
})).data : await client.updateComponent(componentId, input, { ifMatch });
|
|
5700
5728
|
return ok(`Updated component '${cmp.name}' (id ${cmp.id}).`, cmp);
|
|
5701
5729
|
})
|
|
5702
5730
|
)
|
|
@@ -6136,7 +6164,9 @@ components \u2014 is \`editor_setup\`, run on demand against the live build. The
|
|
|
6136
6164
|
-> submit_conversion_receipt / submit_componentize_receipt -> deploy
|
|
6137
6165
|
|
|
6138
6166
|
\`get_deploy_status\` \`editorSetup\` (and get_next_steps) say when it has not run for the live
|
|
6139
|
-
build: \`never\` or \`stale\` \u2192 run it before anything below.
|
|
6167
|
+
build: \`never\` or \`stale\` \u2192 run it before anything below. It also fills missing meta from the
|
|
6168
|
+
pages' own copy, merges components that are identical, and switches to declared bindings when the
|
|
6169
|
+
build proves every binding declared; get_next_steps lists only what it could not do.
|
|
6140
6170
|
|
|
6141
6171
|
**Builds run \`build:bcms\` instead of \`build\`** when package.json declares one: the build alone,
|
|
6142
6172
|
without CI checks (\`"build:bcms": "astro build"\` beside \`"build": "astro check && astro build"\`),
|
|
@@ -6614,7 +6644,8 @@ hand. THE ORDER, and every step of it matters:
|
|
|
6614
6644
|
\`unmatched\` 0 and \`unaddressable\` under 2% of each route's visible text (\xA711). Fix what it
|
|
6615
6645
|
names IN THE SOURCE and release again until \`coverage.pending\` is empty.
|
|
6616
6646
|
7. \`set_binding_mode { declaredBindings: true }\` and release ONE MORE TIME \u2014 the mode applies to
|
|
6617
|
-
the next release, not to the one already out.
|
|
6647
|
+
the next release, not to the one already out. (editor_setup sets it itself when the build
|
|
6648
|
+
already declares every binding; skip this step when get_binding_report reads \`declared\`.)
|
|
6618
6649
|
|
|
6619
6650
|
**Or let BetterCMS propose the edit.** \`get_conversion_plan\` returns an APPROVED conversion \u2014 the
|
|
6620
6651
|
exact new contents of each template file, reviewed by a human in the dashboard and already checked
|