@bettercms-ai/mcp 0.46.0 → 0.48.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 +117 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1991,8 +1991,8 @@ var STRUCTURE_EXAMPLE_PAYLOAD = {
|
|
|
1991
1991
|
doc: {
|
|
1992
1992
|
schema: 1,
|
|
1993
1993
|
folders: [
|
|
1994
|
-
{ id: "home:pages", parentId: null, name: "
|
|
1995
|
-
{ id: "home:collections", parentId: null, name: "
|
|
1994
|
+
{ id: "home:pages", parentId: null, name: "Other pages", icon: "files", sort: 1e3 },
|
|
1995
|
+
{ id: "home:collections", parentId: null, name: "Shared", icon: null, sort: 1001 },
|
|
1996
1996
|
{ id: "fld_std_blog", parentId: null, name: "Blog posts", icon: "newspaper", sort: 0 },
|
|
1997
1997
|
{ id: "fld_std_products", parentId: null, name: "Products", icon: "shopping-bag", sort: 1 }
|
|
1998
1998
|
],
|
|
@@ -2028,30 +2028,36 @@ ever changes.
|
|
|
2028
2028
|
2. **Pinned at the top:** the home page, then each collection's LIST page (the dynamic page that
|
|
2029
2029
|
lists it, e.g. /blog). A single document opens straight into the editor, so it is a pin, not a
|
|
2030
2030
|
folder with one page in it.
|
|
2031
|
-
3. **One folder per content family.**
|
|
2031
|
+
3. **One folder per content family, named for the DETAIL type its route holds.** /blog gives
|
|
2032
|
+
"Blog posts", /case-studies "Case studies", /careers "Jobs".
|
|
2032
2033
|
- Blog: a "Blog posts" folder holding the blog collection. A folder with exactly one
|
|
2033
2034
|
collection opens that collection's table directly.
|
|
2034
2035
|
- Commerce: a "Products" folder holding the product collection AND its taxonomies (product
|
|
2035
|
-
categories, product tags,
|
|
2036
|
+
categories, product tags, merch collections, ...). Once there are more than four taxonomies,
|
|
2037
|
+
the VARIANT OPTIONS (colours, sizes, materials) move into an "Attributes" subfolder; the
|
|
2038
|
+
categories and tags stay beside the catalogue.
|
|
2036
2039
|
- Decide the rest by the REFERENCE GRAPH: a collection that is referenced only by one family,
|
|
2037
2040
|
or that references only one family (reviews \u2192 products), belongs in that family's folder.
|
|
2038
2041
|
The family's main collection comes first, then the others in the order its fields reference
|
|
2039
2042
|
them.
|
|
2040
|
-
4. **
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
products) goes in the system folder \`home:collections\`, named "
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2043
|
+
4. **Family order:** the site's navigation order; failing that, entry count, then name.
|
|
2044
|
+
5. **Other pages.** EVERY remaining standalone page goes in the system folder \`home:pages\`
|
|
2045
|
+
("Other pages") \u2014 no standalone page ever gets a top-level folder. Code-only routes (a 404, a
|
|
2046
|
+
page built by code with nothing to edit) stay there too, marked with the \`file-code\` icon.
|
|
2047
|
+
Pages that share a route (/legal/terms, /legal/privacy, /docs/...) get a subfolder of it.
|
|
2048
|
+
6. **Shared taxonomies.** A vocabulary several families reference (Tags used by posts AND
|
|
2049
|
+
products) goes in the system folder \`home:collections\`, named "Shared". So does supporting
|
|
2050
|
+
content no family owns (testimonials, FAQs with no list page).
|
|
2051
|
+
7. **Settings closes the sidebar.** \`home:globals\` ("Settings") holds NO pages or collections \u2014
|
|
2052
|
+
navigation, header, footer, site settings and SEO defaults open from it in the dashboard. The
|
|
2053
|
+
three system folders always sort BELOW the folders you make: \u2026 Other pages, Shared, Settings.
|
|
2054
|
+
8. **Icons** (lucide names): \`newspaper\` for posts, \`shopping-bag\` for products, \`book-open\`
|
|
2055
|
+
for docs, \`files\` for Other pages, \`folder\` by default.
|
|
2056
|
+
9. **Depth:** nest at most 2 levels by default. The hard limit is 4; do not use it without a
|
|
2051
2057
|
reason the user gave you.
|
|
2052
|
-
|
|
2058
|
+
10. **Organisation only.** Never rename a page, change a slug, touch a schema or move content to
|
|
2053
2059
|
make a structure fit. The structure fits the site, not the other way round.
|
|
2054
|
-
|
|
2060
|
+
11. **Page-bound models are not collections.** A page with fields has a content model bound to
|
|
2055
2061
|
it; it is never filed (400 \`NAV_PAGE_BOUND_MODEL\`). Organise the PAGE.
|
|
2056
2062
|
|
|
2057
2063
|
## The workflow
|
|
@@ -2084,14 +2090,15 @@ collection (\`cm_posts\`), a /shop list page (\`pg_shop\`, dynamic) for Products
|
|
|
2084
2090
|
[pinned page] Home
|
|
2085
2091
|
[pinned page] Blog (/blog lists Blog)
|
|
2086
2092
|
[pinned page] Shop (/shop lists Products)
|
|
2087
|
-
[system folder] Static pages icon=files
|
|
2088
|
-
About, Contact, Not found (icon=file-code)
|
|
2089
|
-
[system folder] Other content
|
|
2090
|
-
Tags (referenced by Blog AND Products: shared)
|
|
2091
2093
|
[folder] Blog posts icon=newspaper
|
|
2092
2094
|
Blog
|
|
2093
2095
|
[folder] Products icon=shopping-bag
|
|
2094
2096
|
Products, Product categories, Colours (referenced only by Products)
|
|
2097
|
+
[system folder] Other pages icon=files
|
|
2098
|
+
About, Contact, Not found (icon=file-code)
|
|
2099
|
+
[system folder] Shared
|
|
2100
|
+
Tags (referenced by Blog AND Products: shared)
|
|
2101
|
+
[system folder] Settings (navigation, header, footer, site settings, SEO defaults)
|
|
2095
2102
|
\`\`\`
|
|
2096
2103
|
|
|
2097
2104
|
The exact \`set_content_structure\` payload (\`version: 0\` because the project had no structure):
|
|
@@ -2102,6 +2109,19 @@ ${JSON.stringify(STRUCTURE_EXAMPLE_PAYLOAD, null, 2)}
|
|
|
2102
2109
|
`;
|
|
2103
2110
|
|
|
2104
2111
|
// src/tools.ts
|
|
2112
|
+
async function askHuman(deps, q) {
|
|
2113
|
+
if (!deps.elicit) return { prompt: q.prompt };
|
|
2114
|
+
try {
|
|
2115
|
+
const res = await deps.elicit({
|
|
2116
|
+
message: q.message,
|
|
2117
|
+
requestedSchema: { type: "object", properties: { [q.key]: q.property }, required: [q.key] }
|
|
2118
|
+
});
|
|
2119
|
+
const picked = res.action === "accept" ? res.content?.[q.key] : void 0;
|
|
2120
|
+
if (q.accept(picked)) return { value: picked };
|
|
2121
|
+
} catch {
|
|
2122
|
+
}
|
|
2123
|
+
return { prompt: q.prompt };
|
|
2124
|
+
}
|
|
2105
2125
|
var FRAMEWORK_CHOICES = ["astro", "next", "react-ts", "other"];
|
|
2106
2126
|
var FRAMEWORK_LABELS = {
|
|
2107
2127
|
astro: "Astro \u2014 recommended default, static by default and fastest to publish",
|
|
@@ -2115,32 +2135,20 @@ var FRAMEWORK_PROMPT = [
|
|
|
2115
2135
|
"",
|
|
2116
2136
|
"Do not choose on their behalf. Sites cannot be built as plain HTML/CSS \u2014 every project is backed by one of these starters, which is what keeps its content editable in the CMS."
|
|
2117
2137
|
].join("\n");
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
}
|
|
2133
|
-
},
|
|
2134
|
-
required: ["framework"]
|
|
2135
|
-
}
|
|
2136
|
-
});
|
|
2137
|
-
const picked = res.action === "accept" ? res.content?.framework : void 0;
|
|
2138
|
-
if (typeof picked === "string" && FRAMEWORK_CHOICES.includes(picked)) {
|
|
2139
|
-
return { framework: picked };
|
|
2140
|
-
}
|
|
2141
|
-
} catch {
|
|
2142
|
-
}
|
|
2143
|
-
return { prompt: FRAMEWORK_PROMPT };
|
|
2138
|
+
function askFramework(deps) {
|
|
2139
|
+
return askHuman(deps, {
|
|
2140
|
+
key: "framework",
|
|
2141
|
+
message: "Which technology should this site be built with?",
|
|
2142
|
+
property: {
|
|
2143
|
+
type: "string",
|
|
2144
|
+
title: "Technology",
|
|
2145
|
+
description: "The frontend stack this project's starter is based on.",
|
|
2146
|
+
enum: [...FRAMEWORK_CHOICES],
|
|
2147
|
+
enumNames: FRAMEWORK_CHOICES.map((c) => FRAMEWORK_LABELS[c])
|
|
2148
|
+
},
|
|
2149
|
+
accept: (v) => typeof v === "string" && FRAMEWORK_CHOICES.includes(v),
|
|
2150
|
+
prompt: FRAMEWORK_PROMPT
|
|
2151
|
+
});
|
|
2144
2152
|
}
|
|
2145
2153
|
var AUTHORING_CHOICES = ["components", "fields"];
|
|
2146
2154
|
var AUTHORING_LABELS = {
|
|
@@ -2156,32 +2164,41 @@ var AUTHORING_PROMPT = [
|
|
|
2156
2164
|
"Recommend components unless the user says the site is schema-first.",
|
|
2157
2165
|
"Do not choose on their behalf. This is asked once per project."
|
|
2158
2166
|
].join("\n");
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2167
|
+
function askAuthoring(deps) {
|
|
2168
|
+
return askHuman(deps, {
|
|
2169
|
+
key: "preference",
|
|
2170
|
+
message: "Which authoring architecture should this site use? Components is recommended for a marketing site.",
|
|
2171
|
+
property: {
|
|
2172
|
+
type: "string",
|
|
2173
|
+
title: "Authoring architecture",
|
|
2174
|
+
description: "How this site's pages are composed. Components is recommended unless this is a schema-first site (a blog, catalogue or directory). Asked once per project.",
|
|
2175
|
+
enum: [...AUTHORING_CHOICES],
|
|
2176
|
+
enumNames: AUTHORING_CHOICES.map((c) => AUTHORING_LABELS[c])
|
|
2177
|
+
},
|
|
2178
|
+
accept: (v) => typeof v === "string" && AUTHORING_CHOICES.includes(v),
|
|
2179
|
+
prompt: AUTHORING_PROMPT
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
var USE_CDN_PROMPT = [
|
|
2183
|
+
"Ask the user where this site's media should be served from, then call set_media_delivery again with their answer as `useCdn`:",
|
|
2184
|
+
" 1. true \u2014 the BetterCMS CDN (recommended, and the default). Media URLs are minted on the CDN host, or on the project's own CDN base if it stores media in its own bucket.",
|
|
2185
|
+
" 2. false \u2014 the API origin. Media URLs are minted on the API host instead. Same bytes, same service; only the hostname differs.",
|
|
2186
|
+
"",
|
|
2187
|
+
"This applies to URLs minted FROM NOW ON. Already-published content keeps the URLs it has, and they keep working."
|
|
2188
|
+
].join("\n");
|
|
2189
|
+
function askMediaDelivery(deps) {
|
|
2190
|
+
return askHuman(deps, {
|
|
2191
|
+
key: "useCdn",
|
|
2192
|
+
message: "Serve this site's media from the BetterCMS CDN (recommended), or from the API origin?",
|
|
2193
|
+
property: {
|
|
2194
|
+
type: "boolean",
|
|
2195
|
+
title: "Use the CDN",
|
|
2196
|
+
description: "On (default): media URLs are minted on the CDN \u2014 or on this project's own CDN base if it uses its own bucket. Off: they are minted on the API origin instead. Same bytes either way; only the host changes, and only for URLs minted from now on.",
|
|
2197
|
+
default: true
|
|
2198
|
+
},
|
|
2199
|
+
accept: (v) => typeof v === "boolean",
|
|
2200
|
+
prompt: USE_CDN_PROMPT
|
|
2201
|
+
});
|
|
2185
2202
|
}
|
|
2186
2203
|
var fieldType = z.enum([
|
|
2187
2204
|
"text",
|
|
@@ -2393,7 +2410,7 @@ function toField(f) {
|
|
|
2393
2410
|
...f.config ? { config: f.config } : {}
|
|
2394
2411
|
};
|
|
2395
2412
|
}
|
|
2396
|
-
var STRUCTURE_NOTE = 'ORGANISATION ONLY: folders never change a URL, slug, content or publish state, and nothing is rebuilt or delivered. Folders nest at most 4 levels deep (a page or collection may sit in a level-4 folder). Icons are lucide icon names, e.g. "folder", "file-text", "rows-3", "house" (a-z, 0-9 and dashes); null clears one. SYSTEM FOLDERS (CPO-129): "home:pages" (
|
|
2413
|
+
var STRUCTURE_NOTE = 'ORGANISATION ONLY: folders never change a URL, slug, content or publish state, and nothing is rebuilt or delivered. Folders nest at most 4 levels deep (a page or collection may sit in a level-4 folder). Icons are lucide icon names, e.g. "folder", "file-text", "rows-3", "house" (a-z, 0-9 and dashes); null clears one. SYSTEM FOLDERS (CPO-129): "home:pages" (Other pages), "home:collections" (Shared) and "home:globals" (Settings) always exist, even when the document has no record for them. They close the sidebar, below the folders you make, and can never be moved or deleted (400 NAV_SYSTEM_FOLDER), but rename_folder and set_icon work on them and create their record on first use. TYPE PURITY: anything inside Other pages, at any depth, must be a page, and anything inside Shared must be a collection, and Settings holds NO pages or collections at all, because globals are not sidebar items (subfolders under Settings are allowed but stay empty). Any of these is 400 NAV_TYPE_MISMATCH. Your own folders hold either kind. ACCESS (CPO-131): Requires Admin or Developer access to CHANGE the structure, the same access as editing the schema; anyone who can read content can call get_content_structure. A 403 SCHEMA_ACCESS_REQUIRED means the person behind this connection is not an Admin or Developer: stop and tell the user, do not retry. ROOT PINS (CPO-132): folderId \'root\' pins a page or collection at the top level; null returns it to its home (Other pages / Shared). Pins sit above the folders, in their own order, and may be pages or collections. In a document a pin is folderId "home:root", a virtual id with no folder record (a folder record with that id is 400 NAV_SYSTEM_FOLDER). KIND CHECK: an id is either a page or a collection, never both, and a custom folder holds either \u2014 so move_to_folder and set_icon VERIFY that the id really is the `kind` you named on this branch, and refuse a mismatch with 400 NAV_KIND_MISMATCH naming the kind to use; call it again with that kind, which also clears the wrong-kind entry. An id that resolves to nothing is allowed (it may exist on another branch) and the result says so. set_content_structure never refuses: it returns `warnings` and `kindMismatches`. get_content_structure marks such items `kindMismatch: true` and lists them in `summary.kindMismatches`. PAGE-BOUND MODELS (CPO-135): a page with fields has a content model bound to it. That model is NOT a collection \u2014 get_content_structure never lists it among collections or in the tree, and move_to_folder / set_icon / set_content_structure refuse it with 400 NAV_PAGE_BOUND_MODEL naming the owning page (\'This model belongs to the page "\u2026"; organise the page.\'). Organise the PAGE instead, by its page id. If a stored document still references one it appears in `summary.hiddenItems` with `boundToPageId`, and the next single write drops it.';
|
|
2397
2414
|
function structureResult(d) {
|
|
2398
2415
|
const message = d?.message;
|
|
2399
2416
|
return ok(typeof message === "string" ? message : "Updated the Content structure.", d);
|
|
@@ -3067,7 +3084,7 @@ ${d.summary.outline}` : "Content structure.", d);
|
|
|
3067
3084
|
def(
|
|
3068
3085
|
"suggest_content_structure",
|
|
3069
3086
|
"Propose the standard Content structure",
|
|
3070
|
-
`READ-ONLY: propose the standard Content sidebar structure for the connected project (${STRUCTURE_PLAYBOOK_URI}) and write NOTHING. Runs the platform's own implementation of the standard over the project's pages, collections, their references and the routes its build serves: the home page and each collection's list page pinned at the top, one folder per content family (Blog posts, Products with its taxonomies,
|
|
3087
|
+
`READ-ONLY: propose the standard Content sidebar structure for the connected project (${STRUCTURE_PLAYBOOK_URI}) and write NOTHING. Runs the platform's own implementation of the standard over the project's pages, collections, their references and the routes its build serves: the home page and each collection's list page pinned at the top, one folder per content family named for the detail type its route holds (Blog posts, Case studies, Jobs, Products with its taxonomies and an Attributes subfolder for its variant options), ordered by entry count (or the site navigation, when the caller supplies its link order), shared taxonomies in Shared, every remaining standalone and code-only page in Other pages, at most 2 levels deep. Page-bound models are never filed. Returns { doc, version, hasDocument, outline, rationale }. To apply it: show the user the outline, then call set_content_structure with this doc and this version (the If-Match; a 412 means someone changed the sidebar, so suggest again). When hasDocument is true the project already has a structure somebody made: never replace it without the user's yes; file only what is new with move_to_folder instead.`,
|
|
3071
3088
|
z.object({
|
|
3072
3089
|
maxDepth: z.number().int().min(1).max(4).optional().describe("folder nesting to propose; default 2 (the standard), 4 is the hard limit")
|
|
3073
3090
|
}).shape,
|
|
@@ -3094,7 +3111,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3094
3111
|
items: z.array(z.object({
|
|
3095
3112
|
kind: z.enum(["page", "collection"]),
|
|
3096
3113
|
id: z.string().min(1).describe("page id or collection (content model) id"),
|
|
3097
|
-
folderId: z.string().nullable().describe('folder id; "home:root" = pinned at the top level; null = its home (
|
|
3114
|
+
folderId: z.string().nullable().describe('folder id; "home:root" = pinned at the top level; null = its home (Other pages for a page, the Shared list for a collection)'),
|
|
3098
3115
|
sort: z.number(),
|
|
3099
3116
|
icon: z.string().nullable()
|
|
3100
3117
|
}))
|
|
@@ -3127,11 +3144,11 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3127
3144
|
def(
|
|
3128
3145
|
"move_to_folder",
|
|
3129
3146
|
"Move into a Content sidebar folder",
|
|
3130
|
-
`Move a page, a collection or a folder into a folder. folderId null moves it back to its home:
|
|
3147
|
+
`Move a page, a collection or a folder into a folder. folderId null moves it back to its home: Other pages for a page, the Shared list for a collection, the top level for a folder. It lands after what is already there. Moving a folder takes its contents with it and is refused if the result would nest deeper than 4 levels or put a folder inside itself. ${STRUCTURE_NOTE}`,
|
|
3131
3148
|
z.object({
|
|
3132
3149
|
kind: z.enum(["page", "collection", "folder"]),
|
|
3133
3150
|
id: z.string().min(1).describe("the page id, collection (content model) id, or folder id"),
|
|
3134
|
-
folderId: z.string().min(1).nullable().describe("target folder id; 'root' pins a page or collection at the top level (for a folder, 'root' is the top level); null moves it back to its home (
|
|
3151
|
+
folderId: z.string().min(1).nullable().describe("target folder id; 'root' pins a page or collection at the top level (for a folder, 'root' is the top level); null moves it back to its home (Other pages, the Shared list, or the top level for a folder)")
|
|
3135
3152
|
}).shape,
|
|
3136
3153
|
async (c, a) => structureResult(await data(c, "POST", `/management/content-structure/move`, { kind: a.kind, id: a.id, folderId: a.folderId }))
|
|
3137
3154
|
),
|
|
@@ -3299,7 +3316,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3299
3316
|
if (framework === void 0) {
|
|
3300
3317
|
const asked = await askFramework(deps);
|
|
3301
3318
|
if ("prompt" in asked) return fail(asked.prompt);
|
|
3302
|
-
framework = asked.
|
|
3319
|
+
framework = asked.value;
|
|
3303
3320
|
}
|
|
3304
3321
|
return ok("Created project.", await data(c, "POST", `/management/projects`, { ...a, framework }));
|
|
3305
3322
|
}
|
|
@@ -3318,7 +3335,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3318
3335
|
if (preference === void 0) {
|
|
3319
3336
|
const asked = await askAuthoring(deps);
|
|
3320
3337
|
if ("prompt" in asked) return fail(asked.prompt);
|
|
3321
|
-
preference = asked.
|
|
3338
|
+
preference = asked.value;
|
|
3322
3339
|
}
|
|
3323
3340
|
return ok("Recorded the authoring architecture.", await data(c, "PATCH", `/management/projects/current/authoring-preference`, { preference }));
|
|
3324
3341
|
}
|
|
@@ -3330,6 +3347,24 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3330
3347
|
z.object({ declaredBindings: z.boolean().describe("true = trust the template's declared bindings; false = text-match (the default)") }).shape,
|
|
3331
3348
|
async (c, a) => ok("Recorded the binding mode.", await data(c, "PATCH", `/management/projects/current/binding-mode`, { declaredBindings: a.declaredBindings }))
|
|
3332
3349
|
),
|
|
3350
|
+
def(
|
|
3351
|
+
"set_media_delivery",
|
|
3352
|
+
"Choose where this site's media is served from",
|
|
3353
|
+
"Record whether this project's media (images, video, files) is served from the BetterCMS CDN or from the API origin. `useCdn: true` is the DEFAULT and the recommendation: URLs are minted on the CDN host, or on the project's own CDN base when it stores media in its own bucket (BYOK). `useCdn: false` mints them on the API origin instead \u2014 the same service serving the same bytes, only under a different hostname. ASK THE USER; do not pick for them. Called without `useCdn`, this tool asks them directly (or hands you the question to ask). \u{1F534} IT APPLIES TO URLs MINTED FROM NOW ON \u2014 new uploads, entry saves, get_media. It does NOT rewrite URLs already stored in published content, and it does not need to: the old URLs keep resolving. To move an existing image, re-save the field that holds it. \u{1F534} IT IS THE BACKEND HALF ONLY. A deployed frontend builds its own transform URLs with @bettercms-ai/image-url, which has its own media host \u2014 set `PUBLIC_BCMS_MEDIA_URL` (or the integration's `mediaUrl` option) to match, or that site's resized images stay on the CDN while everything else moves. Read the current answer from get_project (`useCdn`). Re-callable; the last answer wins.",
|
|
3354
|
+
// Optional in the schema for the same reason `framework` and `preference` are: a
|
|
3355
|
+
// required arg is rejected by the SDK before the handler runs, which would kill the
|
|
3356
|
+
// elicitation below and leave the model guessing.
|
|
3357
|
+
z.object({ useCdn: z.boolean().optional().describe("REQUIRED in effect \u2014 the USER's answer. true = CDN (the default), false = API origin. Ask them; never default it.") }).shape,
|
|
3358
|
+
async (c, a) => {
|
|
3359
|
+
let useCdn = a.useCdn;
|
|
3360
|
+
if (useCdn === void 0) {
|
|
3361
|
+
const asked = await askMediaDelivery(deps);
|
|
3362
|
+
if ("prompt" in asked) return fail(asked.prompt);
|
|
3363
|
+
useCdn = asked.value;
|
|
3364
|
+
}
|
|
3365
|
+
return ok("Recorded the media delivery origin.", await data(c, "PATCH", `/management/projects/current/media-delivery`, { useCdn }));
|
|
3366
|
+
}
|
|
3367
|
+
),
|
|
3333
3368
|
def(
|
|
3334
3369
|
"submit_conversion_receipt",
|
|
3335
3370
|
"Record what the conversion codemod could and could not do",
|
|
@@ -6007,7 +6042,7 @@ and \`ui\` only changes editor chrome, so there is nothing to flip.`
|
|
|
6007
6042
|
|
|
6008
6043
|
// src/server.ts
|
|
6009
6044
|
var SERVER_NAME = "bettercms";
|
|
6010
|
-
var SERVER_VERSION = "1.
|
|
6045
|
+
var SERVER_VERSION = "1.5.0";
|
|
6011
6046
|
var SERVER_DISPLAY = {
|
|
6012
6047
|
title: "BetterCMS",
|
|
6013
6048
|
websiteUrl: "https://bettercms.ai",
|
|
@@ -6034,7 +6069,7 @@ function buildServer(deps) {
|
|
|
6034
6069
|
STRUCTURE_PLAYBOOK_URI,
|
|
6035
6070
|
{
|
|
6036
6071
|
title: "BetterCMS Content structure playbook",
|
|
6037
|
-
description: "How to organise a project's Content sidebar: one place per document, the home page and list pages pinned, one folder per content family
|
|
6072
|
+
description: "How to organise a project's Content sidebar: one place per document, the home page and list pages pinned, one folder per content family named for the detail type its route holds and ordered by entry count, shared taxonomies in Shared, Other pages for the rest, Settings last, at most 2 levels. With a worked set_content_structure payload.",
|
|
6038
6073
|
mimeType: "text/markdown"
|
|
6039
6074
|
},
|
|
6040
6075
|
() => ({
|