@bettercms-ai/mcp 0.66.3 → 0.66.6
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 +68 -24
- 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
|
@@ -2692,7 +2692,9 @@ var NEW_TOOL_TEXT = {
|
|
|
2692
2692
|
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.",
|
|
2693
2693
|
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)."
|
|
2694
2694
|
};
|
|
2695
|
+
var DELETE_PAGE_RUNTIME_BUNDLE = " Or, with no pageId, `runtimeBundle`: 'report' lists the duplicate pages derived from a server app's own bundle (`app/` folders); 'apply' retires them and their empty folders.";
|
|
2695
2696
|
var PARAM_TEXT = {
|
|
2697
|
+
runtimeBundle: "instead of pageId: the server-app bundle cleanup",
|
|
2696
2698
|
editorSetupForce: "re-run a setup already ready for the live build",
|
|
2697
2699
|
editorSetupCheck: "true = only report the state; starts nothing",
|
|
2698
2700
|
pageDynamic: "a dynamic (collection template) page only",
|
|
@@ -2710,8 +2712,10 @@ var PARAM_TEXT = {
|
|
|
2710
2712
|
sourceUrl: "instead of data: a public http(s) URL the server fetches and stores (private or internal addresses are refused)",
|
|
2711
2713
|
focalX: "focal point, 0-1 across from the left edge; null resets to the centre",
|
|
2712
2714
|
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"
|
|
2715
|
+
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",
|
|
2716
|
+
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
2717
|
};
|
|
2718
|
+
var INPUT_CONFIRMED_HEADER = "x-bcms-confirmed-input";
|
|
2715
2719
|
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
2720
|
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
2721
|
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.";
|
|
@@ -2892,7 +2896,7 @@ var fieldType = z.enum([
|
|
|
2892
2896
|
"sections"
|
|
2893
2897
|
]);
|
|
2894
2898
|
var slug = z.string().regex(/^[a-z0-9-]+$/, "lowercase letters, numbers, and hyphens only");
|
|
2895
|
-
var fieldKey = z.string().regex(/^[a-zA-Z0-9_]+$/, "letters, numbers, and
|
|
2899
|
+
var fieldKey = z.string().regex(/^[a-zA-Z0-9_-]+$/, "letters, numbers, underscores, and hyphens only");
|
|
2896
2900
|
var uiObject = z.strictObject({
|
|
2897
2901
|
collapsed: z.boolean().optional().describe("start this panel collapsed. Use it for long optional sections."),
|
|
2898
2902
|
preview: z.strictObject({
|
|
@@ -2914,7 +2918,13 @@ var uiObject = z.strictObject({
|
|
|
2914
2918
|
// A refine, not an enum: 236 names inlined into every schema that carries `ui` would be paid on
|
|
2915
2919
|
// every tools/list. The layoutIcon precedent; the server's z.enum is the guard.
|
|
2916
2920
|
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.")
|
|
2921
|
+
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."),
|
|
2922
|
+
input: z.object({
|
|
2923
|
+
name: z.string().regex(/^[a-z][a-z0-9-]{0,47}$/),
|
|
2924
|
+
writes: z.array(z.string().min(1).max(64)).max(16).optional()
|
|
2925
|
+
}).optional().describe(
|
|
2926
|
+
"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."
|
|
2927
|
+
)
|
|
2918
2928
|
});
|
|
2919
2929
|
var propUiObject = uiObject.extend({
|
|
2920
2930
|
group: z.strictObject({
|
|
@@ -2927,7 +2937,7 @@ var propUiObject = uiObject.extend({
|
|
|
2927
2937
|
});
|
|
2928
2938
|
var fieldShape = {
|
|
2929
2939
|
key: fieldKey.describe(
|
|
2930
|
-
"machine field key. \u{1F534} UNIQUE ACROSS THE WHOLE MODEL \u2014 API IDs share ONE FLAT NAMESPACE, so a field nested inside a group must NOT reuse a key used by another field or group. Every section's heading cannot be 'title'. Prefix with the section: 'hero_title', 'pricing_title', 'faq_title'. Reusing a key is refused, and if it slips through it leaves permanent errors in the editor and breaks conditional-visibility rules, which reference fields by bare key."
|
|
2940
|
+
"machine field key. \u{1F534} UNIQUE ACROSS THE WHOLE MODEL \u2014 API IDs share ONE FLAT NAMESPACE, so a field nested inside a group must NOT reuse a key used by another field or group. Every section's heading cannot be 'title'. Prefix with the section: 'hero_title', 'pricing_title', 'faq_title'. Reusing a key is refused, and if it slips through it leaves permanent errors in the editor and breaks conditional-visibility rules, which reference fields by bare key. Keys derived from an imported site may contain hyphens ('hero-h1-welcome'): address them exactly as returned."
|
|
2931
2941
|
),
|
|
2932
2942
|
label: z.string().min(1).describe("human label shown in the editor"),
|
|
2933
2943
|
type: fieldType.describe(`field type. ${BUTTON_FIELD_NOTE}`),
|
|
@@ -3066,6 +3076,8 @@ function proseType(f) {
|
|
|
3066
3076
|
if (f.type !== "text") return f.type;
|
|
3067
3077
|
return f.richText === false ? "text" : "richtext";
|
|
3068
3078
|
}
|
|
3079
|
+
var confirmedHeaders = (confirmed) => confirmed === true ? { [INPUT_CONFIRMED_HEADER]: "true" } : {};
|
|
3080
|
+
var withHeaders = (headers) => Object.keys(headers).length > 0 ? { headers } : {};
|
|
3069
3081
|
function toField(f) {
|
|
3070
3082
|
const base = {
|
|
3071
3083
|
key: f.key,
|
|
@@ -3250,7 +3262,7 @@ function authPrompt(err) {
|
|
|
3250
3262
|
var currentProject = new AsyncLocalStorage();
|
|
3251
3263
|
var currentBranch = new AsyncLocalStorage();
|
|
3252
3264
|
var projectIdArg = z.string().min(1).optional().describe(
|
|
3253
|
-
"
|
|
3265
|
+
"list_projects id; workspace-wide grants only"
|
|
3254
3266
|
);
|
|
3255
3267
|
var branchIdArg = z.string().min(1).optional().describe("content branch id (from list_branches); omit to work on main");
|
|
3256
3268
|
var withProjectId = (def) => ({
|
|
@@ -3385,7 +3397,8 @@ function buildToolDefs(deps) {
|
|
|
3385
3397
|
modelId: z.string().min(1).optional().describe("the content model to extend (from list_content_models); or send pageId"),
|
|
3386
3398
|
pageId: z.string().min(1).optional().describe("instead of modelId: the page whose schema to extend (from list_pages / create_page)"),
|
|
3387
3399
|
...fieldShape,
|
|
3388
|
-
after: afterArg
|
|
3400
|
+
after: afterArg,
|
|
3401
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
3389
3402
|
});
|
|
3390
3403
|
const uploadAssetInput = z.object({
|
|
3391
3404
|
localPath: z.string().min(1).optional().describe("absolute path to a local file (e.g. a repo image); provide this OR url"),
|
|
@@ -3457,7 +3470,8 @@ function buildToolDefs(deps) {
|
|
|
3457
3470
|
translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf)
|
|
3458
3471
|
});
|
|
3459
3472
|
const deletePageInput = z.object({
|
|
3460
|
-
pageId: z.string().min(1).describe("id of the page to delete (from list_pages)")
|
|
3473
|
+
pageId: z.string().min(1).optional().describe("id of the page to delete (from list_pages)"),
|
|
3474
|
+
runtimeBundle: z.enum(["report", "apply"]).optional().describe(PARAM_TEXT.runtimeBundle)
|
|
3461
3475
|
});
|
|
3462
3476
|
const deleteEntryInput = z.object({
|
|
3463
3477
|
entryId: z.string().min(1).describe("id of the content entry to delete (from list_content_entries)")
|
|
@@ -3675,7 +3689,8 @@ function buildToolDefs(deps) {
|
|
|
3675
3689
|
propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label"),
|
|
3676
3690
|
propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text, null clears it"),
|
|
3677
3691
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
3678
|
-
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE)
|
|
3692
|
+
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
|
|
3693
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
3679
3694
|
});
|
|
3680
3695
|
const setComponentSourceInput = z.object({
|
|
3681
3696
|
componentId: z.string().min(1).describe("component id (from list_components)"),
|
|
@@ -3858,7 +3873,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3858
3873
|
def(
|
|
3859
3874
|
"set_content_structure",
|
|
3860
3875
|
"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,
|
|
3876
|
+
`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
3877
|
z.object({
|
|
3863
3878
|
doc: z.object({
|
|
3864
3879
|
schema: z.literal(1),
|
|
@@ -4256,7 +4271,8 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4256
4271
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
4257
4272
|
ifMatch: z.number().int().min(0).optional().describe(MODEL_IF_MATCH_NOTE),
|
|
4258
4273
|
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)
|
|
4274
|
+
allowFieldRemoval: z.boolean().optional().describe(ALLOW_FIELD_REMOVAL_NOTE),
|
|
4275
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
4260
4276
|
}).shape,
|
|
4261
4277
|
// `fields` is sent only by removeFields, rebuilt from the model just read: the PATCH
|
|
4262
4278
|
// replaces `fields` wholesale when present.
|
|
@@ -4281,7 +4297,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4281
4297
|
(await c.fetchJSON(c.url(path), {
|
|
4282
4298
|
method: "PATCH",
|
|
4283
4299
|
body: JSON.stringify(body),
|
|
4284
|
-
...ifMatch !== void 0 ? {
|
|
4300
|
+
...withHeaders({ ...ifMatch !== void 0 ? { "if-match": `W/"${ifMatch}"` } : {}, ...confirmedHeaders(a.confirmed) })
|
|
4285
4301
|
})).data
|
|
4286
4302
|
);
|
|
4287
4303
|
}
|
|
@@ -4480,7 +4496,8 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4480
4496
|
allowRetype: z.boolean().optional().describe("confirm a type change, once the user has agreed"),
|
|
4481
4497
|
allowFieldRemoval: z.boolean().optional().describe("with a rename: discard the old key's stored values"),
|
|
4482
4498
|
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")
|
|
4499
|
+
ifMatch: z.number().int().min(0).optional().describe("the model's optimisticVersion; the version read when omitted"),
|
|
4500
|
+
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
4484
4501
|
}).shape,
|
|
4485
4502
|
// Read the model (a page's through its backingModel), patch ONE field with the helper the
|
|
4486
4503
|
// hosted catalogue uses, and PATCH the whole list at the version read. `mergeHref` is the
|
|
@@ -4508,7 +4525,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4508
4525
|
const updated = (await c.fetchJSON(c.url(path), {
|
|
4509
4526
|
method: "PATCH",
|
|
4510
4527
|
body: JSON.stringify({ fields: plan.fields, ...a.allowFieldRemoval === true ? { allowFieldRemoval: true } : {} }),
|
|
4511
|
-
...plan.ifMatch !== void 0 ? {
|
|
4528
|
+
...withHeaders({ ...plan.ifMatch !== void 0 ? { "If-Match": `W/"${plan.ifMatch}"` } : {}, ...confirmedHeaders(a.confirmed) })
|
|
4512
4529
|
})).data;
|
|
4513
4530
|
return ok(plan.renamed ? `Renamed '${plan.renamed.from}' to '${plan.renamed.to}'.` : `Updated field '${s(a.path)}'.`, updated);
|
|
4514
4531
|
}
|
|
@@ -4697,9 +4714,11 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4697
4714
|
"get_next_steps",
|
|
4698
4715
|
"Get what to do next",
|
|
4699
4716
|
NEXT_STEPS_DESCRIPTION,
|
|
4700
|
-
z.object({
|
|
4717
|
+
z.object({
|
|
4718
|
+
importJunk: z.boolean().optional().describe("true = add rule import-junk: what an old import left, with removal calls")
|
|
4719
|
+
}).shape,
|
|
4701
4720
|
// The whole body: `receipt` rides beside `data`, and `data()` would drop it.
|
|
4702
|
-
async (c) => ok("Next steps.", await c.fetchJSON(c.url(`/management/insights/next-steps`), { method: "GET" }))
|
|
4721
|
+
async (c, a) => ok("Next steps.", await c.fetchJSON(c.url(`/management/insights/next-steps${a.importJunk === true ? "?importJunk=true" : ""}`), { method: "GET" }))
|
|
4703
4722
|
),
|
|
4704
4723
|
def(
|
|
4705
4724
|
"get_binding_report",
|
|
@@ -4975,7 +4994,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4975
4994
|
def(
|
|
4976
4995
|
"get_deploy_status",
|
|
4977
4996
|
"Get deploy/build status",
|
|
4978
|
-
"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.",
|
|
4997
|
+
"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. `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` (the editor's live-preview lane for this site: `bridge` / `draft-route` / `none`) and `editorUrl`, the visual editor to hand the user.",
|
|
4979
4998
|
z.object({}).shape,
|
|
4980
4999
|
// Flat body, no `data` envelope: `data()` read it as undefined.
|
|
4981
5000
|
async (c) => ok("Deploy status.", await c.fetchJSON(c.url(`/management/projects/deploy-status`), { method: "GET" }))
|
|
@@ -5144,12 +5163,17 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5144
5163
|
async (args) => withClient(async (client) => {
|
|
5145
5164
|
if (!args.modelId === !args.pageId) return fail("Pass exactly one of modelId (a content model) or pageId (a page).");
|
|
5146
5165
|
if (args.pageId) {
|
|
5147
|
-
const
|
|
5166
|
+
const input2 = {
|
|
5148
5167
|
// Cast: the SDK types `options` as string[], narrower than the API, which also takes
|
|
5149
5168
|
// {label, value} (backend selectFieldSchema).
|
|
5150
5169
|
addFields: [toField(args)],
|
|
5151
5170
|
...args.after !== void 0 ? { after: args.after } : {}
|
|
5152
|
-
}
|
|
5171
|
+
};
|
|
5172
|
+
const page = args.confirmed === true ? (await client.fetchJSON(client.url(`/management/pages/${args.pageId}`), {
|
|
5173
|
+
method: "PATCH",
|
|
5174
|
+
body: JSON.stringify(input2),
|
|
5175
|
+
headers: confirmedHeaders(true)
|
|
5176
|
+
})).data : await client.addPageFields(args.pageId, input2);
|
|
5153
5177
|
return ok(`Added field '${args.key}' to page '${page.title}'. Page now has ${page.fields.length} field(s).`, page);
|
|
5154
5178
|
}
|
|
5155
5179
|
const modelId = args.modelId;
|
|
@@ -5172,7 +5196,12 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5172
5196
|
const nextFields = [...existingFields];
|
|
5173
5197
|
nextFields.splice(at, 0, toField(args));
|
|
5174
5198
|
const input = { fields: nextFields };
|
|
5175
|
-
const
|
|
5199
|
+
const opts = model.optimisticVersion !== void 0 ? { ifMatch: model.optimisticVersion } : void 0;
|
|
5200
|
+
const updated = args.confirmed === true ? (await client.fetchJSON(client.url(`/management/content/models/${modelId}`), {
|
|
5201
|
+
method: "PATCH",
|
|
5202
|
+
body: JSON.stringify(input),
|
|
5203
|
+
headers: { ...opts ? { "If-Match": `W/"${opts.ifMatch}"` } : {}, ...confirmedHeaders(true) }
|
|
5204
|
+
})).data : opts ? await client.updateModel(modelId, input, opts) : await client.updateModel(modelId, input);
|
|
5176
5205
|
return ok(
|
|
5177
5206
|
`Added field '${args.key}' to '${updated.name}'${args.after ? ` after '${args.after}'` : ""}. Model now has ${updated.fields.length} field(s).`,
|
|
5178
5207
|
updated
|
|
@@ -5310,11 +5339,19 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5310
5339
|
name: "delete_page",
|
|
5311
5340
|
config: {
|
|
5312
5341
|
title: "Delete a page",
|
|
5313
|
-
description: "DELETE a page and its content. Destructive but REVERSIBLE \u2014 it soft-deletes (can be restored from the dashboard) and is audit-logged. Use it to remove a page you created by mistake. Confirm with the user before deleting content they may want.",
|
|
5342
|
+
description: "DELETE a page and its content. Destructive but REVERSIBLE \u2014 it soft-deletes (can be restored from the dashboard) and is audit-logged. Use it to remove a page you created by mistake. Confirm with the user before deleting content they may want." + DELETE_PAGE_RUNTIME_BUNDLE,
|
|
5314
5343
|
inputSchema: deletePageInput.shape
|
|
5315
5344
|
},
|
|
5316
5345
|
handler: guard(
|
|
5317
5346
|
async (args) => withClient(async (client) => {
|
|
5347
|
+
if (!args.pageId === !args.runtimeBundle) return fail("Pass exactly one of pageId or runtimeBundle.");
|
|
5348
|
+
if (args.runtimeBundle) {
|
|
5349
|
+
const res2 = await client.fetchJSON(client.url("/management/pages/retire-runtime-bundle"), {
|
|
5350
|
+
method: "POST",
|
|
5351
|
+
body: JSON.stringify({ dryRun: args.runtimeBundle !== "apply" })
|
|
5352
|
+
});
|
|
5353
|
+
return ok(args.runtimeBundle === "apply" ? "Bundle pages retired." : "Bundle pages (dry run, nothing written).", res2.data);
|
|
5354
|
+
}
|
|
5318
5355
|
const res = await client.deletePage(args.pageId);
|
|
5319
5356
|
return ok(`Deleted page ${res.id} (soft-delete \u2014 restorable from the dashboard).`, res);
|
|
5320
5357
|
})
|
|
@@ -5695,8 +5732,12 @@ ${lines.join("\n")}`, found);
|
|
|
5695
5732
|
},
|
|
5696
5733
|
handler: guard(
|
|
5697
5734
|
async (args) => withClient(async (client) => {
|
|
5698
|
-
const { componentId, ifMatch, ...input } = args;
|
|
5699
|
-
const cmp = await client.
|
|
5735
|
+
const { componentId, ifMatch, confirmed, ...input } = args;
|
|
5736
|
+
const cmp = confirmed === true ? (await client.fetchJSON(client.url(`/management/components/${componentId}`), {
|
|
5737
|
+
method: "PATCH",
|
|
5738
|
+
body: JSON.stringify(input),
|
|
5739
|
+
headers: { ...ifMatch !== void 0 ? { "If-Match": `W/"${ifMatch}"` } : {}, ...confirmedHeaders(true) }
|
|
5740
|
+
})).data : await client.updateComponent(componentId, input, { ifMatch });
|
|
5700
5741
|
return ok(`Updated component '${cmp.name}' (id ${cmp.id}).`, cmp);
|
|
5701
5742
|
})
|
|
5702
5743
|
)
|
|
@@ -6136,7 +6177,9 @@ components \u2014 is \`editor_setup\`, run on demand against the live build. The
|
|
|
6136
6177
|
-> submit_conversion_receipt / submit_componentize_receipt -> deploy
|
|
6137
6178
|
|
|
6138
6179
|
\`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.
|
|
6180
|
+
build: \`never\` or \`stale\` \u2192 run it before anything below. It also fills missing meta from the
|
|
6181
|
+
pages' own copy, merges components that are identical, and switches to declared bindings when the
|
|
6182
|
+
build proves every binding declared; get_next_steps lists only what it could not do.
|
|
6140
6183
|
|
|
6141
6184
|
**Builds run \`build:bcms\` instead of \`build\`** when package.json declares one: the build alone,
|
|
6142
6185
|
without CI checks (\`"build:bcms": "astro build"\` beside \`"build": "astro check && astro build"\`),
|
|
@@ -6614,7 +6657,8 @@ hand. THE ORDER, and every step of it matters:
|
|
|
6614
6657
|
\`unmatched\` 0 and \`unaddressable\` under 2% of each route's visible text (\xA711). Fix what it
|
|
6615
6658
|
names IN THE SOURCE and release again until \`coverage.pending\` is empty.
|
|
6616
6659
|
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.
|
|
6660
|
+
the next release, not to the one already out. (editor_setup sets it itself when the build
|
|
6661
|
+
already declares every binding; skip this step when get_binding_report reads \`declared\`.)
|
|
6618
6662
|
|
|
6619
6663
|
**Or let BetterCMS propose the edit.** \`get_conversion_plan\` returns an APPROVED conversion \u2014 the
|
|
6620
6664
|
exact new contents of each template file, reviewed by a human in the dashboard and already checked
|