@caelo-cms/mcp-server 0.10.72 → 0.11.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/package.json +1 -1
- package/power-catalogue.json +785 -232
package/power-catalogue.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.11.0",
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "edit_module",
|
|
6
|
-
"description": "Edit ONE module's structure: HTML template, fields, CSS, JS, displayName, description, or kind. **AUTHOR EXPLICITLY (v0.12.0+):** pass `html` with `{{fieldName}}` placeholders + an explicit `fields[]` array with semantic snake_case names (`hero_title`, `primary_cta_href`, `nav_items`), NOT a literal-content fallback. The operator says 'fix the homepage hero copy'; YOU translate that to the right field names. Field names like `cta2label2` or `spanText` are wrong — they leak the extractor heuristic. **Field kinds
|
|
6
|
+
"description": "Edit ONE module's structure: HTML template, fields, CSS, JS, displayName, description, or kind. **AUTHOR EXPLICITLY (v0.12.0+):** pass `html` with `{{fieldName}}` placeholders + an explicit `fields[]` array with semantic snake_case names (`hero_title`, `primary_cta_href`, `nav_items`), NOT a literal-content fallback. The operator says 'fix the homepage hero copy'; YOU translate that to the right field names. Field names like `cta2label2` or `spanText` are wrong — they leak the extractor heuristic. **Field kinds:** `text`, `richtext`, `url`, `image`, `number`, `boolean`, `link` (primitives); `list` (entries of a list type — see List fields below); `module` (single nested module, slot `{{>field}}`); `module-list` (array of nested modules, slot `{{#field}}{{/field}}`). **Nested fields may declare `allowedModuleTypes`** — a whitelist of stable module `type`s (e.g. `[\"button\"]`), NOT slugs. It matches the referenced module's `type`; set it to constrain what can nest in a `module`/`module-list` slot, and widen it here if a valid module is being wrongly rejected. **Lists are lists, not numbered scalars.** A menu with 10 items is ONE `list` field with 10 entries — never `label1`, `label2`, …, `label10`. A tag cloud is ONE `list` (listType \"texts\"). Cards with several parts per item (image + heading + text) use a list type with those fields (upsert_list_types_many) or a `module-list` of a card module. **Update description + kind when the module's purpose drifts.** `list_modules` exposes them to your future self; stale descriptions hurt your own decision-making. **Legacy fallback only:** if you pass HTML with literal content and NO `fields[]`, a server-side extractor mints heuristic field names — useful for one-shot drafts but the result hurts the `list_modules` catalog. Treat it as a fallback, not the default path. **Scope module CSS under the module's own root class** - bare global selectors (`body`, `h1`, `.card`) bleed into every other module on the page (issue #158); the tool result flags unscoped selectors. **Module JS shares ONE page-level script** - no implicit `root`/`el` binding exists; wrap in an IIFE and query via document.querySelectorAll scoped to your module's root class (see the `js` input description). **Accordion, tabs, carousel need NO module JS** - mark the markup with `data-caelo-ui=\"accordion|tabs|carousel\"` and Caelo ships vetted, accessible behaviour (markup contract in the `js` input description). Edits are CHAT-BRANCHED until publish. **For a TARGETED change to existing html/css/js, PREFER `edit_content`** (surgical string-replace: far cheaper than re-emitting the whole body here, and the diff is reviewable). Use `edit_module` for a WHOLESALE rewrite, a brand-new module body, or `fields`/`displayName`/`kind`/`type`/`description` changes. Use this for structure, styling, fields list, or `description`/`kind` updates. DO NOT use this to change what one page shows — use `set_page_module_content` (per-page content) or `set_content_instance_values` (shared content) for that. Prefer `update_modules_many` when targeting > 1 module. DO NOT use for page metadata (`update_pages_many`) or template-level edits (`propose_update_template`). **List fields** (`kind:'list'`, `listType`): repeating content is ONE list field, `{{#field}}…{{/field}}` (system prompt: lists). local (default): the placement value / chrome `default` is the entries, `[{node:'link', values:{label:'Home', link:{href:'/'}}}]`; shared: `bind:'shared'`, `list:'caelo:list/<id>'` — menus, navigation, footer links, categories, tags (list_lists first; entries change via patch_list_items_many; promote_local_list when a second place needs a local list); source: `bind:'source'` with a query / system / plugin `source`. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
7
7
|
"inputSchema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"additionalProperties": false,
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"js": {
|
|
52
52
|
"type": "string",
|
|
53
|
-
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav`
|
|
53
|
+
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class=\"caelo-nav-menu\" data-caelo-ui=\"mobile-nav\" data-nav-open=\"false\"><button type=\"button\" class=\"caelo-nav-toggle\" aria-expanded=\"false\" aria-label=\"Menu\"><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span></button><ul>{{#menu}}<li>…</li>{{/menu}}</ul></nav>` around the module's menu `list` field. Write module JS only for behaviour the kit does not cover."
|
|
54
54
|
},
|
|
55
55
|
"fields": {
|
|
56
56
|
"type": "array",
|
|
@@ -78,8 +78,7 @@
|
|
|
78
78
|
"number",
|
|
79
79
|
"boolean",
|
|
80
80
|
"link",
|
|
81
|
-
"
|
|
82
|
-
"link-list",
|
|
81
|
+
"list",
|
|
83
82
|
"module",
|
|
84
83
|
"module-list"
|
|
85
84
|
]
|
|
@@ -90,7 +89,7 @@
|
|
|
90
89
|
"maxLength": 128
|
|
91
90
|
},
|
|
92
91
|
"default": {
|
|
93
|
-
"description": "The ORIGINAL value(s) this field replaced
|
|
92
|
+
"description": "The ORIGINAL value(s) this field replaced, never a placeholder: scalar kinds → a string; list → its entries (local) or \"caelo:list/<id>\" (shared). Omit for module / module-list and bind \"source\".",
|
|
94
93
|
"anyOf": [
|
|
95
94
|
{
|
|
96
95
|
"type": "string"
|
|
@@ -101,27 +100,31 @@
|
|
|
101
100
|
{
|
|
102
101
|
"type": "boolean"
|
|
103
102
|
},
|
|
104
|
-
{
|
|
105
|
-
"type": "array",
|
|
106
|
-
"items": {
|
|
107
|
-
"type": "string"
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
103
|
{
|
|
111
104
|
"type": "array",
|
|
112
105
|
"items": {
|
|
113
106
|
"type": "object",
|
|
114
|
-
"additionalProperties": false,
|
|
115
107
|
"required": [
|
|
116
|
-
"
|
|
117
|
-
"
|
|
108
|
+
"node",
|
|
109
|
+
"values"
|
|
118
110
|
],
|
|
111
|
+
"description": "texts {node:\"text\", values:{text}} · links {node:\"link\", values:{label, link:{href}}} · menu {node:\"entry\", …, children}; keep `id` on resend.",
|
|
119
112
|
"properties": {
|
|
120
|
-
"
|
|
113
|
+
"id": {
|
|
121
114
|
"type": "string"
|
|
122
115
|
},
|
|
123
|
-
"
|
|
124
|
-
"type": "string"
|
|
116
|
+
"node": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "The NODE kind, not the type slug: \"text\" (texts), \"link\" (links), \"entry\" (menu)."
|
|
119
|
+
},
|
|
120
|
+
"values": {
|
|
121
|
+
"type": "object"
|
|
122
|
+
},
|
|
123
|
+
"children": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"items": {
|
|
126
|
+
"type": "object"
|
|
127
|
+
}
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
}
|
|
@@ -144,6 +147,29 @@
|
|
|
144
147
|
}
|
|
145
148
|
]
|
|
146
149
|
},
|
|
150
|
+
"listType": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
153
|
+
"description": "list: type slug — \"texts\", \"links\", \"menu\", \"categories\", \"tags\" or custom."
|
|
154
|
+
},
|
|
155
|
+
"bind": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"enum": [
|
|
158
|
+
"local",
|
|
159
|
+
"shared",
|
|
160
|
+
"source"
|
|
161
|
+
],
|
|
162
|
+
"description": "list: local (default; entries live with the placement) | shared (a site list: menus, navigation, footer links, categories, tags) | source (generated per page)."
|
|
163
|
+
},
|
|
164
|
+
"list": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"pattern": "^caelo:list/[0-9a-f-]{36}$",
|
|
167
|
+
"description": "list, shared: caelo:list/<id>."
|
|
168
|
+
},
|
|
169
|
+
"source": {
|
|
170
|
+
"type": "object",
|
|
171
|
+
"description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
|
|
172
|
+
},
|
|
147
173
|
"allowedModuleTypes": {
|
|
148
174
|
"type": "array",
|
|
149
175
|
"maxItems": 32,
|
|
@@ -216,7 +242,7 @@
|
|
|
216
242
|
},
|
|
217
243
|
{
|
|
218
244
|
"name": "edit_content",
|
|
219
|
-
"description": "Surgically edit a module's, template's or layout's body (html/css/js) by string replacement — the cheap, minimal-diff way to change existing code. Pass `entityKind` + `entityId` + `field` + an `edits` array of {oldString, newString, replaceAll?}. Each `oldString` must be UNIQUE in the body (add surrounding context if not) unless `replaceAll:true`; edits apply in order, atomically (all-or-nothing). Read the body first with read_content and copy exact text (incl. whitespace) into `oldString`; optionally pass its `sha=` as `expectedSha` to guard against a concurrent change. To fill an EMPTY field (e.g. a module with no js yet), pass one edit with `oldString: \"\"` and the new content as `newString`; an empty oldString on a field that has content is refused. On success it returns the NEW `sha=` plus a line-numbered snippet of each changed region — chain another edit_content in the same turn using that sha and the fresh line numbers, WITHOUT re-reading. PREFER this over edit_module for targeted changes to existing html/css/js — it is far cheaper and the diff is reviewable. Use edit_module instead for a wholesale rewrite, a new module, or field-schema / displayName / kind changes. TEMPLATES AND LAYOUTS ARE GATED: an edit_content on a template or layout is a TWO-STEP §11.A flow — you propose, the Owner clicks Approve on the proposal card. Do not claim such an edit was applied. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
245
|
+
"description": "Surgically edit a module's, template's or layout's body (html/css/js) by string replacement — the cheap, minimal-diff way to change existing code. Pass `entityKind` + `entityId` + `field` + an `edits` array of {oldString, newString, replaceAll?}. Each `oldString` must be UNIQUE in the body (add surrounding context if not) unless `replaceAll:true`; edits apply in order, atomically (all-or-nothing). Read the body first with read_content and copy exact text (incl. whitespace) into `oldString`; optionally pass its `sha=` as `expectedSha` to guard against a concurrent change. To fill an EMPTY field (e.g. a module with no js yet), pass one edit with `oldString: \"\"` and the new content as `newString`; an empty oldString on a field that has content is refused. On success it returns the NEW `sha=` plus a line-numbered snippet of each changed region — chain another edit_content in the same turn using that sha and the fresh line numbers, WITHOUT re-reading. PREFER this over edit_module for targeted changes to existing html/css/js — it is far cheaper and the diff is reviewable. Use edit_module instead for a wholesale rewrite, a new module, or field-schema / displayName / kind changes. TEMPLATES AND LAYOUTS ARE GATED: an edit_content on a template or layout is a TWO-STEP §11.A flow — you propose, the Owner clicks Approve on the proposal card. Do not claim such an edit was applied. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
220
246
|
"inputSchema": {
|
|
221
247
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
222
248
|
"type": "object",
|
|
@@ -323,7 +349,7 @@
|
|
|
323
349
|
},
|
|
324
350
|
{
|
|
325
351
|
"name": "set_page_module_content",
|
|
326
|
-
"description": "Set the content values for ONE module placement on a specific page. **Prefer `set_page_module_content_many` when a content pass touches more than one placement** (a whole page's text in one transaction — §11 bulk-first), and `build_page` when the modules don't exist yet (it places modules WITH content in one call). v0.12.0 — page CONTENT lives in a content_instance bound to the placement. For UNSYNCED placements (the default) this tool routes through content_instances.set_values and edits stay local to this page. For SYNCED placements (sharing content with other pages) this tool refuses and points you at fork_placement_content (to detach first) or set_content_instance_values (to commit to the propagate-everywhere blast radius). Use this when the operator says 'change the hero heading on /home to X' or 'set the about page subtitle to Y'. Use `edit_module` (NOT this tool) when the change should affect every page using the module (structural / styling / field-schema change). `contentValues` is keyed by the module's declared field names (`{{fieldName}}` placeholders). For nested fields (kind `module` / `module-list`) the value is `{ moduleId, contentInstanceId }` (or an array of them); the referenced module's stable `type` must be in the field's `allowedModuleTypes` when set (reuse a module of an allowed type — `list_modules` shows each module's `type` and each nested field's `allowedModuleTypes`). Missing fields fall back to the module field's `default`. Pass an empty object to reset a placement to all defaults. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
352
|
+
"description": "Set the content values for ONE module placement on a specific page. **Prefer `set_page_module_content_many` when a content pass touches more than one placement** (a whole page's text in one transaction — §11 bulk-first), and `build_page` when the modules don't exist yet (it places modules WITH content in one call). v0.12.0 — page CONTENT lives in a content_instance bound to the placement. For UNSYNCED placements (the default) this tool routes through content_instances.set_values and edits stay local to this page. For SYNCED placements (sharing content with other pages) this tool refuses and points you at fork_placement_content (to detach first) or set_content_instance_values (to commit to the propagate-everywhere blast radius). Use this when the operator says 'change the hero heading on /home to X' or 'set the about page subtitle to Y'. Use `edit_module` (NOT this tool) when the change should affect every page using the module (structural / styling / field-schema change). `contentValues` is keyed by the module's declared field names (`{{fieldName}}` placeholders). For a `list` field: a local list's value is its entry array (`[{node:'text', values:{text:'Fast'}}]`; existing entries keep their `id` — for one entry's text prefer patch_page_module_fields); a shared list field takes `caelo:list/<id>` to switch which list it shows (the list's own entries change with patch_list_items_many, on every page at once); a source field takes the knobs its source marks editable (`{limit:5}`). For nested fields (kind `module` / `module-list`) the value is `{ moduleId, contentInstanceId }` (or an array of them); the referenced module's stable `type` must be in the field's `allowedModuleTypes` when set (reuse a module of an allowed type — `list_modules` shows each module's `type` and each nested field's `allowedModuleTypes`). Missing fields fall back to the module field's `default`. Pass an empty object to reset a placement to all defaults. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
327
353
|
"inputSchema": {
|
|
328
354
|
"type": "object",
|
|
329
355
|
"additionalProperties": false,
|
|
@@ -356,7 +382,7 @@
|
|
|
356
382
|
},
|
|
357
383
|
{
|
|
358
384
|
"name": "patch_page_module_fields",
|
|
359
|
-
"description": "Change individual values of ONE module placement — a heading, one
|
|
385
|
+
"description": "Change individual values of ONE module placement — a heading, one entry's text or link target in a local `list` field, a `url` field, an `image` field — and leave every other value of that placement untouched. Use this for targeted wording fixes ('the hero says Welcom, fix it', 'rename the second menu item to Pricing', or a reference chip naming `placement=(blockName, position), field=…[entry]`): you send only the changed values instead of the placement's whole contentValues. `text` is the new value: plain text for text fields (it is HTML-escaped for you — never send markup), a path / https URL for `url` / `image` fields and link targets. For a local `list` field, `item` is the entry id and `key` the entry field (e.g. `label`, `text`, `link`); `part: \"href\"` sets a link field's target. Several edits of one placement go in one call (all or none). Use `set_page_module_content` (NOT this tool) to replace a placement's values wholesale or to set nested modules; `set_page_module_content_many` for passes across several placements; `edit_module` for structure. SYNCED placements (content shared with other pages) need `scope`: `\"everywhere\"` changes it on every page that shows it, `\"here\"` detaches this page first and changes only it — ask the operator which they mean when it is not clear. Typical call: `{pageId: \"…\", blockName: \"content\", position: 0, edits: [{field: \"hero_title\", text: \"Welcome\"}, {field: \"nav_items\", item: \"<entry id>\", key: \"label\", text: \"Pricing\"}]}`.",
|
|
360
386
|
"inputSchema": {
|
|
361
387
|
"type": "object",
|
|
362
388
|
"additionalProperties": false,
|
|
@@ -396,14 +422,17 @@
|
|
|
396
422
|
"minLength": 1,
|
|
397
423
|
"maxLength": 200
|
|
398
424
|
},
|
|
399
|
-
"
|
|
400
|
-
"type": "
|
|
401
|
-
"
|
|
425
|
+
"item": {
|
|
426
|
+
"type": "string",
|
|
427
|
+
"description": "Entry id of a local list field."
|
|
428
|
+
},
|
|
429
|
+
"key": {
|
|
430
|
+
"type": "string",
|
|
431
|
+
"pattern": "^[a-z][a-z0-9_]{0,63}$"
|
|
402
432
|
},
|
|
403
433
|
"part": {
|
|
404
434
|
"type": "string",
|
|
405
435
|
"enum": [
|
|
406
|
-
"label",
|
|
407
436
|
"href"
|
|
408
437
|
]
|
|
409
438
|
},
|
|
@@ -426,7 +455,7 @@
|
|
|
426
455
|
},
|
|
427
456
|
{
|
|
428
457
|
"name": "build_page",
|
|
429
|
-
"description": "Build a WHOLE page in ONE call: the page (create new via `page: {slug, title}` or target existing via `page: {pageId}`) plus the FULL ordered module list — each entry places one module into a template block, top to bottom (on an existing page entries append; an entry's optional `position` — 'top' or an index, as in add_module — inserts it instead). ONE transaction: any invalid entry aborts the whole call (the error names `modules[i]` and the failing field) and nothing is written. This is THE page-creation tool: when creating, pass `page.metaDescription` (and `status:'published'` for a page that belongs on the site) — a Stage refuses published pages without a description, and builds only published ones; the result says when something still blocks a Stage. `modules: []` (empty) creates an intentionally empty page shell; a populated `modules` array builds the whole page in one transaction. **Prefer it over add_module / create_content_instance chains whenever a page needs more than one module** — one build_page call replaces that whole round-trip chain (§11 bulk-first). Use add_module (target='page') only for a single incremental module on an existing page. **Per module, two modes** (same contract as add_module): pass `moduleId` to place an EXISTING module from `list_modules` (reuse first — shared modules keep pages consistent), or `displayName` + `html` (+ required `description`, `kind`, explicit `fields[]`) to mint a new one. Field names are semantic snake_case (`hero_title`, `primary_cta_href`);
|
|
458
|
+
"description": "Build a WHOLE page in ONE call: the page (create new via `page: {slug, title}` or target existing via `page: {pageId}`) plus the FULL ordered module list — each entry places one module into a template block, top to bottom (on an existing page entries append; an entry's optional `position` — 'top' or an index, as in add_module — inserts it instead). ONE transaction: any invalid entry aborts the whole call (the error names `modules[i]` and the failing field) and nothing is written. This is THE page-creation tool: when creating a typed page (an article is `news`), name its `page.pageType` from list_page_types with its `properties` / `terms` (an ordinary page omits pageType and gets the site's default type), and pass `page.metaDescription` (and `status:'published'` for a page that belongs on the site) — a Stage refuses published pages without a description, and builds only published ones; the result says when something still blocks a Stage. `modules: []` (empty) creates an intentionally empty page shell; a populated `modules` array builds the whole page in one transaction. **Prefer it over add_module / create_content_instance chains whenever a page needs more than one module** — one build_page call replaces that whole round-trip chain (§11 bulk-first). Use add_module (target='page') only for a single incremental module on an existing page. **Per module, two modes** (same contract as add_module): pass `moduleId` to place an EXISTING module from `list_modules` (reuse first — shared modules keep pages consistent), or `displayName` + `html` (+ required `description`, `kind`, explicit `fields[]`) to mint a new one. Field names are semantic snake_case (`hero_title`, `primary_cta_href`); repeating content is ONE `list` field (entries of a list type, iterated with `{{#field}}…{{/field}}`) or a `module-list`, never numbered scalars. A `list` field is local (entries in `content.values`, e.g. `{features:[{node:'text', values:{text:'Fast'}}]}`), shared (bind 'shared', value or field `list` = caelo:list/<id> — menus, footer links) or a source (bind 'source': a page query such as the latest news, breadcrumbs; `content.values` may override the knobs the source marks editable). **Per module, `content` fills the placement**: `{source:'inline', values:{hero_title:'…'}}` for page-local content (the routine case); `{source:'shared', purpose:'…', values:{…}}` to mint a REUSABLE instance other pages can bind (synced by default); `{source:'existing', contentInstanceId}` to bind a row from `## Content Library`. Omit `content` for an empty private instance you fill later. Typical call: `{page:{slug:'pricing', title:'Pricing', status:'published', metaDescription:'…'}, modules:[{blockName:'content', displayName:'Pricing Hero', kind:'hero', description:'…', html:'<section><h1>{{hero_title}}</h1></section>', fields:[{name:'hero_title', kind:'text', label:'Hero title'}], content:{source:'inline', values:{hero_title:'Fair pricing'}}}, …]}`. **Nested modules in the SAME call**: give the inner module `ref` and NO blockName (detached — minted + content-bound but not placed), then reference it from the outer module's `module`/`module-list` field value as `{\"$ref\": \"<ref>\"}` — the server resolves it to the real {moduleId, contentInstanceId}, in any array order. Example: `modules:[{ref:'btn', displayName:'Button', html:'<button>{{label}}</button>', fields:[…], content:{source:'inline', values:{label:'Go'}}}, {blockName:'content', displayName:'CTA', html:'<section>{{>cta}}</section>', fields:[{name:'cta', kind:'module', label:'CTA', allowedModuleTypes:['button']}], content:{source:'inline', values:{cta:{'$ref':'btn'}}}}]` — the whole nested composition is ONE call, never an add_module → remove_module_from → create_content_instance chain. For site-wide chrome (header/footer) use add_module (target='layout') — chrome is layout-owned and never rides in a page body. Each entry's `blockName` must be a block on the page's template — see `# Current page` (exhaustive list for the focused page); a mismatch error names the valid set. A block name is a template slot, NOT a module `kind`. With zero templates on the site, bootstrap first (create_layout → create_template). **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
430
459
|
"inputSchema": {
|
|
431
460
|
"type": "object",
|
|
432
461
|
"additionalProperties": false,
|
|
@@ -438,7 +467,7 @@
|
|
|
438
467
|
"page": {
|
|
439
468
|
"type": "object",
|
|
440
469
|
"additionalProperties": false,
|
|
441
|
-
"description": "EITHER { pageId, metaDescription? } to build onto an existing page (modules are appended), OR { slug, title, metaDescription, name?, templateId?, status?, parentPageId? } to create the page in the same call. Never both.",
|
|
470
|
+
"description": "EITHER { pageId, metaDescription? } to build onto an existing page (modules are appended), OR { slug, title, metaDescription, name?, pageType?, properties?, terms?, templateId?, status?, parentPageId? } to create the page in the same call. Never both.",
|
|
442
471
|
"properties": {
|
|
443
472
|
"pageId": {
|
|
444
473
|
"type": "string",
|
|
@@ -462,7 +491,27 @@
|
|
|
462
491
|
},
|
|
463
492
|
"templateId": {
|
|
464
493
|
"type": "string",
|
|
465
|
-
"format": "uuid"
|
|
494
|
+
"format": "uuid",
|
|
495
|
+
"description": "Omit to take the page type's default template (or, without pageType, the site default)."
|
|
496
|
+
},
|
|
497
|
+
"pageType": {
|
|
498
|
+
"type": "string",
|
|
499
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
500
|
+
"description": "What the page IS — a page type slug list_page_types shows for this site (e.g. 'news' for an article): page queries ('latest news', 'articles in this category') find pages by it. Omit it for an ordinary page — it then gets the type that lists templateId, else the site's default type. Never guess a slug."
|
|
501
|
+
},
|
|
502
|
+
"properties": {
|
|
503
|
+
"type": "object",
|
|
504
|
+
"additionalProperties": true,
|
|
505
|
+
"description": "Values of the page type's properties (list_page_types shows them, `*` = required), e.g. {author:'Ada Lovelace', event_date:'2026-11-03'}."
|
|
506
|
+
},
|
|
507
|
+
"terms": {
|
|
508
|
+
"type": "array",
|
|
509
|
+
"maxItems": 100,
|
|
510
|
+
"items": {
|
|
511
|
+
"type": "string",
|
|
512
|
+
"pattern": "^caelo:term/[0-9a-f-]{36}/[0-9a-f-]{36}$"
|
|
513
|
+
},
|
|
514
|
+
"description": "The page's categories / tags: caelo:term/<listId>/<itemId> from the page type's vocabularies (get_list on the vocabulary shows the entry ids)."
|
|
466
515
|
},
|
|
467
516
|
"status": {
|
|
468
517
|
"type": "string",
|
|
@@ -523,7 +572,7 @@
|
|
|
523
572
|
},
|
|
524
573
|
"ref": {
|
|
525
574
|
"type": "string",
|
|
526
|
-
"pattern": "^[
|
|
575
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]{0,31}$",
|
|
527
576
|
"description": "Local handle: another entry's module/module-list field value (or moduleId) {\"$ref\": \"<ref>\"} resolves to this entry's {moduleId, contentInstanceId}. Array order does not matter — referenced entries are created first automatically."
|
|
528
577
|
},
|
|
529
578
|
"moduleId": {
|
|
@@ -586,7 +635,7 @@
|
|
|
586
635
|
"js": {
|
|
587
636
|
"type": "string",
|
|
588
637
|
"maxLength": 50000,
|
|
589
|
-
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav`
|
|
638
|
+
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class=\"caelo-nav-menu\" data-caelo-ui=\"mobile-nav\" data-nav-open=\"false\"><button type=\"button\" class=\"caelo-nav-toggle\" aria-expanded=\"false\" aria-label=\"Menu\"><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span></button><ul>{{#menu}}<li>…</li>{{/menu}}</ul></nav>` around the module's menu `list` field. Write module JS only for behaviour the kit does not cover."
|
|
590
639
|
},
|
|
591
640
|
"fields": {
|
|
592
641
|
"type": "array",
|
|
@@ -614,8 +663,7 @@
|
|
|
614
663
|
"number",
|
|
615
664
|
"boolean",
|
|
616
665
|
"link",
|
|
617
|
-
"
|
|
618
|
-
"link-list",
|
|
666
|
+
"list",
|
|
619
667
|
"module",
|
|
620
668
|
"module-list"
|
|
621
669
|
]
|
|
@@ -626,7 +674,7 @@
|
|
|
626
674
|
"maxLength": 128
|
|
627
675
|
},
|
|
628
676
|
"default": {
|
|
629
|
-
"description": "The ORIGINAL value(s) this field replaced
|
|
677
|
+
"description": "The ORIGINAL value(s) this field replaced, never a placeholder: scalar kinds → a string; list → its entries (local) or \"caelo:list/<id>\" (shared). Omit for module / module-list and bind \"source\".",
|
|
630
678
|
"anyOf": [
|
|
631
679
|
{
|
|
632
680
|
"type": "string"
|
|
@@ -637,27 +685,31 @@
|
|
|
637
685
|
{
|
|
638
686
|
"type": "boolean"
|
|
639
687
|
},
|
|
640
|
-
{
|
|
641
|
-
"type": "array",
|
|
642
|
-
"items": {
|
|
643
|
-
"type": "string"
|
|
644
|
-
}
|
|
645
|
-
},
|
|
646
688
|
{
|
|
647
689
|
"type": "array",
|
|
648
690
|
"items": {
|
|
649
691
|
"type": "object",
|
|
650
|
-
"additionalProperties": false,
|
|
651
692
|
"required": [
|
|
652
|
-
"
|
|
653
|
-
"
|
|
693
|
+
"node",
|
|
694
|
+
"values"
|
|
654
695
|
],
|
|
696
|
+
"description": "texts {node:\"text\", values:{text}} · links {node:\"link\", values:{label, link:{href}}} · menu {node:\"entry\", …, children}; keep `id` on resend.",
|
|
655
697
|
"properties": {
|
|
656
|
-
"
|
|
698
|
+
"id": {
|
|
657
699
|
"type": "string"
|
|
658
700
|
},
|
|
659
|
-
"
|
|
660
|
-
"type": "string"
|
|
701
|
+
"node": {
|
|
702
|
+
"type": "string",
|
|
703
|
+
"description": "The NODE kind, not the type slug: \"text\" (texts), \"link\" (links), \"entry\" (menu)."
|
|
704
|
+
},
|
|
705
|
+
"values": {
|
|
706
|
+
"type": "object"
|
|
707
|
+
},
|
|
708
|
+
"children": {
|
|
709
|
+
"type": "array",
|
|
710
|
+
"items": {
|
|
711
|
+
"type": "object"
|
|
712
|
+
}
|
|
661
713
|
}
|
|
662
714
|
}
|
|
663
715
|
}
|
|
@@ -680,6 +732,29 @@
|
|
|
680
732
|
}
|
|
681
733
|
]
|
|
682
734
|
},
|
|
735
|
+
"listType": {
|
|
736
|
+
"type": "string",
|
|
737
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
738
|
+
"description": "list: type slug — \"texts\", \"links\", \"menu\", \"categories\", \"tags\" or custom."
|
|
739
|
+
},
|
|
740
|
+
"bind": {
|
|
741
|
+
"type": "string",
|
|
742
|
+
"enum": [
|
|
743
|
+
"local",
|
|
744
|
+
"shared",
|
|
745
|
+
"source"
|
|
746
|
+
],
|
|
747
|
+
"description": "list: local (default; entries live with the placement) | shared (a site list: menus, navigation, footer links, categories, tags) | source (generated per page)."
|
|
748
|
+
},
|
|
749
|
+
"list": {
|
|
750
|
+
"type": "string",
|
|
751
|
+
"pattern": "^caelo:list/[0-9a-f-]{36}$",
|
|
752
|
+
"description": "list, shared: caelo:list/<id>."
|
|
753
|
+
},
|
|
754
|
+
"source": {
|
|
755
|
+
"type": "object",
|
|
756
|
+
"description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
|
|
757
|
+
},
|
|
683
758
|
"allowedModuleTypes": {
|
|
684
759
|
"type": "array",
|
|
685
760
|
"maxItems": 32,
|
|
@@ -805,7 +880,7 @@
|
|
|
805
880
|
},
|
|
806
881
|
{
|
|
807
882
|
"name": "set_page_module_content_many",
|
|
808
|
-
"description": "Set content values for SEVERAL placements in ONE transaction — prefer this over multiple `set_page_module_content` calls whenever a content pass touches more than one placement (a whole page's text, the same fix across pages; §11 bulk-first). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`, the placement, and for value problems the failing field) and nothing is written. Each item is the exact `set_page_module_content` shape: `{pageId, blockName, position, contentValues}` with `contentValues` keyed by the module's declared field names; each fully replaces that placement's values. Items may span multiple pages. Same UNSYNCED-only contract as the singular tool: a SYNCED placement in the batch aborts it with the fork/commit guidance — fork_placement_content that placement (or set_content_instance_values deliberately) and resend. **When NOT to use:** when the page and its modules don't exist yet, `build_page` places modules WITH their content in one call — never build a page as add_module + set_page_module_content_many when one build_page call does both. For one placement the singular tool is fine. Typical call: `{items: [{pageId: \"…\", blockName: \"content\", position: 0, contentValues: {hero_title: \"…\"}}, {pageId: \"…\", blockName: \"content\", position: 1, contentValues: {body: \"…\"}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
883
|
+
"description": "Set content values for SEVERAL placements in ONE transaction — prefer this over multiple `set_page_module_content` calls whenever a content pass touches more than one placement (a whole page's text, the same fix across pages; §11 bulk-first). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`, the placement, and for value problems the failing field) and nothing is written. Each item is the exact `set_page_module_content` shape: `{pageId, blockName, position, contentValues}` with `contentValues` keyed by the module's declared field names; each fully replaces that placement's values. Items may span multiple pages. Same UNSYNCED-only contract as the singular tool: a SYNCED placement in the batch aborts it with the fork/commit guidance — fork_placement_content that placement (or set_content_instance_values deliberately) and resend. **When NOT to use:** when the page and its modules don't exist yet, `build_page` places modules WITH their content in one call — never build a page as add_module + set_page_module_content_many when one build_page call does both. For one placement the singular tool is fine. Typical call: `{items: [{pageId: \"…\", blockName: \"content\", position: 0, contentValues: {hero_title: \"…\"}}, {pageId: \"…\", blockName: \"content\", position: 1, contentValues: {body: \"…\"}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
809
884
|
"inputSchema": {
|
|
810
885
|
"type": "object",
|
|
811
886
|
"additionalProperties": false,
|
|
@@ -852,7 +927,7 @@
|
|
|
852
927
|
},
|
|
853
928
|
{
|
|
854
929
|
"name": "create_content_instances",
|
|
855
|
-
"description": "Mint SEVERAL content_instances in ONE transaction — prefer this over multiple `create_content_instance` calls whenever you need more than one instance (§11 bulk-first: one round-trip instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `instances[i]` and, for value problems, the failing field) and nothing is written. Each item is the exact `create_content_instance` shape: `{moduleId, values, purpose?, slug?, displayName?}` — `purpose` required for SHARED rows (why this row exists as a reusable instance; surfaced in `## Content Library`), `values` keyed by the module's declared field names. **When NOT to use:** if you're also creating the page and its modules, `build_page` does the whole assembly (modules + instances + placements) in one call — use that instead. For a single instance the singular tool is fine. After minting, bind rows to placements via `set_placement_content` (or let `build_page` content.source='existing' bind them). Typical call: `{instances: [{moduleId: \"…\", purpose: \"Footer CTA shared across product pages\", values: {cta_label: \"Start free\", cta_href: \"/signup\"}}, {moduleId: \"…\", values: {…}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
930
|
+
"description": "Mint SEVERAL content_instances in ONE transaction — prefer this over multiple `create_content_instance` calls whenever you need more than one instance (§11 bulk-first: one round-trip instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `instances[i]` and, for value problems, the failing field) and nothing is written. Each item is the exact `create_content_instance` shape: `{moduleId, values, purpose?, slug?, displayName?}` — `purpose` required for SHARED rows (why this row exists as a reusable instance; surfaced in `## Content Library`), `values` keyed by the module's declared field names. **When NOT to use:** if you're also creating the page and its modules, `build_page` does the whole assembly (modules + instances + placements) in one call — use that instead. For a single instance the singular tool is fine. After minting, bind rows to placements via `set_placement_content` (or let `build_page` content.source='existing' bind them). Typical call: `{instances: [{moduleId: \"…\", purpose: \"Footer CTA shared across product pages\", values: {cta_label: \"Start free\", cta_href: \"/signup\"}}, {moduleId: \"…\", values: {…}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
856
931
|
"inputSchema": {
|
|
857
932
|
"type": "object",
|
|
858
933
|
"additionalProperties": false,
|
|
@@ -966,7 +1041,7 @@
|
|
|
966
1041
|
},
|
|
967
1042
|
{
|
|
968
1043
|
"name": "create_content_instance",
|
|
969
|
-
"description": "Mint ONE shared, reusable content_instance for a module — used when the same content should appear identically on N pages (the site footer's copyright, a brand banner across product pages, a repeated CTA in the blog). **Prefer `create_content_instances` (plural) when minting more than one instance** — one transaction instead of N round-trips (§11 bulk-first) — and `build_page` when you're also creating the page/modules (its per-module `content` payload mints and binds in the same call). The new row is then bindable to N placements via `set_placement_content({syncMode:'synced'})` so editing it propagates everywhere bound. **Required v0.12.0 input:** `purpose` — a one-line rationale for why this is a shared row (\"The brand footer used across the whole marketing site\", \"Pricing CTA — appears at the bottom of every pricing-adjacent page\"). The `## Content Library` block surfaces this so your future self can decide reuse vs fork without asking the operator. **When NOT to use:** for one-off per-page content, prefer `set_page_module_content` — that path auto-mints an unsynced (private) instance per placement, no shared semantics, no decision needed. Only mint a shared instance when reuse is the actual intent. **Decision rule before calling:** check `## Content Library` first. If a row with the matching purpose + module + appropriate placement pattern already exists, BIND to it via `set_placement_content` instead of minting a new one. Duplicate shared rows defeat the point. `values` keys are the module's declared field names (`{{fieldName}}` placeholders); `slug` is an optional kebab-case handle (must be unique per module) for easy AI-side reference. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
1044
|
+
"description": "Mint ONE shared, reusable content_instance for a module — used when the same content should appear identically on N pages (the site footer's copyright, a brand banner across product pages, a repeated CTA in the blog). **Prefer `create_content_instances` (plural) when minting more than one instance** — one transaction instead of N round-trips (§11 bulk-first) — and `build_page` when you're also creating the page/modules (its per-module `content` payload mints and binds in the same call). The new row is then bindable to N placements via `set_placement_content({syncMode:'synced'})` so editing it propagates everywhere bound. **Required v0.12.0 input:** `purpose` — a one-line rationale for why this is a shared row (\"The brand footer used across the whole marketing site\", \"Pricing CTA — appears at the bottom of every pricing-adjacent page\"). The `## Content Library` block surfaces this so your future self can decide reuse vs fork without asking the operator. **When NOT to use:** for one-off per-page content, prefer `set_page_module_content` — that path auto-mints an unsynced (private) instance per placement, no shared semantics, no decision needed. Only mint a shared instance when reuse is the actual intent. **Decision rule before calling:** check `## Content Library` first. If a row with the matching purpose + module + appropriate placement pattern already exists, BIND to it via `set_placement_content` instead of minting a new one. Duplicate shared rows defeat the point. `values` keys are the module's declared field names (`{{fieldName}}` placeholders); `slug` is an optional kebab-case handle (must be unique per module) for easy AI-side reference. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
970
1045
|
"inputSchema": {
|
|
971
1046
|
"type": "object",
|
|
972
1047
|
"additionalProperties": false,
|
|
@@ -1000,7 +1075,7 @@
|
|
|
1000
1075
|
},
|
|
1001
1076
|
{
|
|
1002
1077
|
"name": "set_content_instance_values",
|
|
1003
|
-
"description": "Edit a content_instance's values. **BLAST RADIUS = placementCount in `## Content Library`** — this propagates to EVERY placement bound with sync_mode='synced'. **Read `## Content Library` first** to see the row's `purpose` + sample pages + placement count before editing. If the operator described an edit that should ONLY affect one page, this is the WRONG tool — use `set_page_module_content` (unsynced placements) or `fork_placement_content` first (synced placements that need to be detached). `values` fully replaces existing values (zero-merge); read first via `get_content_instance` if you need to preserve other fields. **Nested fields (kind `module` / `module-list`):** the value is `{ moduleId, contentInstanceId }` (single) or an array of them (list). The referenced module's stable `type` MUST be in the field's `allowedModuleTypes` when that whitelist is set — see each module's `type` and each nested field's `allowedModuleTypes` in `list_modules`. Reuse an existing module of an allowed type rather than minting a duplicate; if none fits, create one with `type` set to an allowed value. Optional metadata edits in the same write: `slug` + `displayName` (pass `null` to clear), and v0.12.0 `purpose` (rewrite the rationale when the operator's intent for this shared row has shifted — keeps `## Content Library` accurate for your future self). Prefer `set_content_instance_values_many` over multiple `set_content_instance_values` calls when changing more than one. **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
1078
|
+
"description": "Edit a content_instance's values. **BLAST RADIUS = placementCount in `## Content Library`** — this propagates to EVERY placement bound with sync_mode='synced'. **Read `## Content Library` first** to see the row's `purpose` + sample pages + placement count before editing. If the operator described an edit that should ONLY affect one page, this is the WRONG tool — use `set_page_module_content` (unsynced placements) or `fork_placement_content` first (synced placements that need to be detached). `values` fully replaces existing values (zero-merge); read first via `get_content_instance` if you need to preserve other fields. **Nested fields (kind `module` / `module-list`):** the value is `{ moduleId, contentInstanceId }` (single) or an array of them (list). The referenced module's stable `type` MUST be in the field's `allowedModuleTypes` when that whitelist is set — see each module's `type` and each nested field's `allowedModuleTypes` in `list_modules`. Reuse an existing module of an allowed type rather than minting a duplicate; if none fits, create one with `type` set to an allowed value. **List fields:** a local list's value is its entry array (`[{node:'text', values:{text:'Fast'}}]`, existing entries keep their `id`); a shared list field takes `caelo:list/<id>` (switch which list it shows) — edit the shared list's entries with patch_list_items_many, not here; a source field takes the knobs its source marks editable (`{limit:5}`, `{terms:[…]}` for term_pages). Optional metadata edits in the same write: `slug` + `displayName` (pass `null` to clear), and v0.12.0 `purpose` (rewrite the rationale when the operator's intent for this shared row has shifted — keeps `## Content Library` accurate for your future self). Prefer `set_content_instance_values_many` over multiple `set_content_instance_values` calls when changing more than one. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
1004
1079
|
"inputSchema": {
|
|
1005
1080
|
"type": "object",
|
|
1006
1081
|
"properties": {
|
|
@@ -1082,7 +1157,7 @@
|
|
|
1082
1157
|
},
|
|
1083
1158
|
{
|
|
1084
1159
|
"name": "set_content_instance_values_many",
|
|
1085
|
-
"description": "Edit SEVERAL content_instances in ONE transaction — prefer this over multiple `set_content_instance_values` calls whenever you change more than one (§11 bulk-first: one round-trip + one snapshot instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`) and nothing is written. **Same per-item BLAST RADIUS caveat as the singular tool:** each item propagates to EVERY placement bound to that instance with sync_mode='synced' — read `## Content Library` first. `values` fully replaces existing values (zero-merge) per item. Each item is the exact `set_content_instance_values` shape: `{id, values, expectedVersion?, slug?, displayName?, purpose?}
|
|
1160
|
+
"description": "Edit SEVERAL content_instances in ONE transaction — prefer this over multiple `set_content_instance_values` calls whenever you change more than one (§11 bulk-first: one round-trip + one snapshot instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`) and nothing is written. **Same per-item BLAST RADIUS caveat as the singular tool:** each item propagates to EVERY placement bound to that instance with sync_mode='synced' — read `## Content Library` first. `values` fully replaces existing values (zero-merge) per item. Each item is the exact `set_content_instance_values` shape: `{id, values, expectedVersion?, slug?, displayName?, purpose?}` (list field values as there: local entries, `caelo:list/<id>` for a shared field, editable knobs for a source field). **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
1086
1161
|
"inputSchema": {
|
|
1087
1162
|
"type": "object",
|
|
1088
1163
|
"additionalProperties": false,
|
|
@@ -1242,7 +1317,7 @@
|
|
|
1242
1317
|
},
|
|
1243
1318
|
{
|
|
1244
1319
|
"name": "list_layouts",
|
|
1245
|
-
"description": "List every layout with UUID, slug, display name, and its LAYOUT blocks INCLUDING the chrome modules placed in each (`header(
|
|
1320
|
+
"description": "List every layout with UUID, slug, display name, and its LAYOUT blocks INCLUDING the chrome modules placed in each (`header(main-menu)|content|footer(site-footer)`). One read answers \"is module X already on the layout?\". Layout blocks are site chrome: place a header / footer / nav into them ONCE for every page with add_module target='layout'. They are NOT build_page blockNames — a page places into its TEMPLATE's blocks (list_templates `pageBlocks`, usually just `content`); the layout's `content` block is where the template renders. Standard list params: `filter`, `limit`/`offset`, `full: true`. Use when you need a layout UUID or slug (e.g. right after an Owner approved a layout-create proposal). DO NOT ask the operator to paste a UUID.",
|
|
1246
1321
|
"inputSchema": {
|
|
1247
1322
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1248
1323
|
"type": "object",
|
|
@@ -1335,7 +1410,7 @@
|
|
|
1335
1410
|
},
|
|
1336
1411
|
{
|
|
1337
1412
|
"name": "list_templates",
|
|
1338
|
-
"description": "List every template with UUID, slug, display name, bound layout UUID, and `
|
|
1413
|
+
"description": "List every template with UUID, slug, display name, bound layout UUID, `pageBlocks` and `pageTypes` (the page types whose pages may use it — list_page_types) (TOON rows). `pageBlocks` are the blocks a page on that template places modules into — the ONLY valid `blockName` values for build_page and add_module target='page' (usually just `content`). Header / footer / nav are NOT page blocks: they are site chrome on the template's LAYOUT (list_layouts), placed once with add_module target='layout'. Standard list params: `filter`, `limit`/`offset`, `full: true`. Use when you need a template UUID or its blocks before building a page. DO NOT ask the operator to paste a UUID.",
|
|
1339
1414
|
"inputSchema": {
|
|
1340
1415
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1341
1416
|
"type": "object",
|
|
@@ -1374,7 +1449,7 @@
|
|
|
1374
1449
|
},
|
|
1375
1450
|
{
|
|
1376
1451
|
"name": "list_pages",
|
|
1377
|
-
"description": "List every page with its URL path, UUID, slug, parent page UUID, nesting depth, title, and template UUID (TOON rows). Pages nest: a page's URL is its parent's URL + '/' + its slug (`/seo-monitoring/alerts` = slug 'alerts' under the '/seo-monitoring' page); depth 1 = top level. Pass `underPageId` to list one section's subtree. Inside a chat, paths show the chat's pending moves. Standard list params: `filter` (substring across all columns), `limit`/`offset` (pagination), `full: true` (no truncation). Use when you need a page UUID that isn't in the `# All pages` context block — the block is a turn-start snapshot, this is live. DO NOT ask the operator to paste a UUID.",
|
|
1452
|
+
"description": "List every page with its URL path, UUID, slug, parent page UUID, nesting depth, title, page type (what the page IS — `news`, `page`, …; list_page_types) and template UUID (TOON rows). Pages nest: a page's URL is its parent's URL + '/' + its slug (`/seo-monitoring/alerts` = slug 'alerts' under the '/seo-monitoring' page); depth 1 = top level. Pass `underPageId` to list one section's subtree. Inside a chat, paths show the chat's pending moves. Standard list params: `filter` (substring across all columns), `limit`/`offset` (pagination), `full: true` (no truncation). Use when you need a page UUID that isn't in the `# All pages` context block — the block is a turn-start snapshot, this is live. DO NOT ask the operator to paste a UUID.",
|
|
1378
1453
|
"inputSchema": {
|
|
1379
1454
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1380
1455
|
"type": "object",
|
|
@@ -1442,7 +1517,7 @@
|
|
|
1442
1517
|
},
|
|
1443
1518
|
{
|
|
1444
1519
|
"name": "list_modules",
|
|
1445
|
-
"description": "List the FULL module catalog with decision-support metadata: id, slug, type, kind, description, fields, and placement usage. Call it before placing or nesting an existing module: to find a reuse candidate, to find modules by `kind`/`filter` (plus `limit`/`offset`/`full` as in every list tool), and before filling a `module`/`module-list` field — the fields column shows each nested field's accepted types (`blocks:module-list allowed=faq/cta`; `allowed=any` = no restriction), and a child's `type` must be one of them. Returns metadata only (no HTML/CSS/JS bodies). Prefer placing an existing module (`add_module` with `moduleId`) over minting a near-duplicate.",
|
|
1520
|
+
"description": "List the FULL module catalog with decision-support metadata: id, slug, type, kind, description, fields, and placement usage. Call it before placing or nesting an existing module: to find a reuse candidate, to find modules by `kind`/`filter` (plus `limit`/`offset`/`full` as in every list tool), and before filling a `module`/`module-list` field — the fields column shows each nested field's accepted types (`blocks:module-list allowed=faq/cta`; `allowed=any` = no restriction), and a child's `type` must be one of them. `list` fields show their list type and binding: `nav:list(menu) shared=main-nav` (the shared list it shows — edit its entries with patch_list_items_many), `items:list(texts) local` (entries in each placement), `latest:list(links) source=query(page_type=news …)`. Returns metadata only (no HTML/CSS/JS bodies). Prefer placing an existing module (`add_module` with `moduleId`) over minting a near-duplicate.",
|
|
1446
1521
|
"inputSchema": {
|
|
1447
1522
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1448
1523
|
"type": "object",
|
|
@@ -1968,7 +2043,7 @@
|
|
|
1968
2043
|
},
|
|
1969
2044
|
{
|
|
1970
2045
|
"name": "get_link_migration_report",
|
|
1971
|
-
"description": "Internal links are stored as page references (they follow a page when it is renamed or moved). Content written before that was converted once by the link migration; this tool reports it: links converted per place, and every link it could NOT convert (it matched no page or media asset — never guessed), with the module / content
|
|
2046
|
+
"description": "Internal links are stored as page references (they follow a page when it is renamed or moved). Content written before that was converted once by the link migration; this tool reports it: links converted per place, and every link it could NOT convert (it matched no page or media asset — never guessed), with the module / content it sits in. Use when the operator asks about broken or old links after an update, or before fixing links site-wide. Fix each listed link with the normal edit tools (edit_module, set_content_instance_values) by pointing it at an existing page's path (list_pages) or removing it; the edit stores the reference. A link in a layout's own HTML (the page frame) is the Owner's to fix under Settings → Layouts: name the layout and the path to use. Filter with `search` (matches the link, the label and the field). Running or reverting the migration is the Owner's (Settings → Internal links); you only read.",
|
|
1972
2047
|
"inputSchema": {
|
|
1973
2048
|
"type": "object",
|
|
1974
2049
|
"additionalProperties": false,
|
|
@@ -2520,7 +2595,7 @@
|
|
|
2520
2595
|
},
|
|
2521
2596
|
{
|
|
2522
2597
|
"name": "add_module",
|
|
2523
|
-
"description": "Add ONE module to a page, a layout (site-wide chrome on every page), or a template (fans out to every page of that page-type). **Prefer `build_page` when a PAGE needs more than one module** — it places the whole ordered list WITH content in one transaction. `target` = 'page' | 'layout' | 'template'. `targetRef` is that page's id or URL path ('/docs/setup'), or the layout's / template's slug or id. A bare page slug works only while it names one page — nested pages can share a slug. Two modes, on ALL three targets. **Reuse (check the catalog first):** pass `moduleId` of an existing module from `list_modules` to place/fan-out a SHARED module without minting a duplicate — a later edit_module then updates it everywhere at once. **Mint:** pass `html` (+ `displayName`, `kind`, and semantic snake_case `fields`) to author a new module and place it; raw HTML is fine, it is parametrised into {{fields}} automatically. `moduleId` is placement-only: if you also pass authoring fields (a different label, new html, …) the call still succeeds but those fields are NOT applied and the result says so — to actually change a shared module, place it by `moduleId` and then call edit_module. **The initial content comes IN THIS CALL, always via `values`** ({fieldName: value, …}): on 'page' it fills the placement's content_instance, on 'template' every fanned-out placement gets it, on 'layout' it is stored as the minted module's field defaults (chrome has no content_instance — so layout `values` require explicit `fields`, and are rejected with `moduleId` reuse: a shared module renders its stored defaults; edit_module changes them). A mint with fields but neither `values` nor field defaults is REJECTED — it would render empty placeholders until a second call. `blockName` must be a real block on the target; the handler returns the available block set if it isn't. NOTE on `position`: pass the literal string \"top\" or \"bottom\", OR a bare integer (0, 1, 2…). Prefer a bare integer (`0`, not `\"0\"`). **Internal links** (`href`s in HTML, `url`/`link` fields,
|
|
2598
|
+
"description": "Add ONE module to a page, a layout (site-wide chrome on every page), or a template (fans out to every page of that page-type). **Prefer `build_page` when a PAGE needs more than one module** — it places the whole ordered list WITH content in one transaction. `target` = 'page' | 'layout' | 'template'. `targetRef` is that page's id or URL path ('/docs/setup'), or the layout's / template's slug or id. A bare page slug works only while it names one page — nested pages can share a slug. Two modes, on ALL three targets. **Reuse (check the catalog first):** pass `moduleId` of an existing module from `list_modules` to place/fan-out a SHARED module without minting a duplicate — a later edit_module then updates it everywhere at once. **Mint:** pass `html` (+ `displayName`, `kind`, and semantic snake_case `fields`) to author a new module and place it; raw HTML is fine, it is parametrised into {{fields}} automatically. `moduleId` is placement-only: if you also pass authoring fields (a different label, new html, …) the call still succeeds but those fields are NOT applied and the result says so — to actually change a shared module, place it by `moduleId` and then call edit_module. **The initial content comes IN THIS CALL, always via `values`** ({fieldName: value, …}): on 'page' it fills the placement's content_instance, on 'template' every fanned-out placement gets it, on 'layout' it is stored as the minted module's field defaults (chrome has no content_instance — so layout `values` require explicit `fields`, and are rejected with `moduleId` reuse: a shared module renders its stored defaults; edit_module changes them). A mint with fields but neither `values` nor field defaults is REJECTED — it would render empty placeholders until a second call. `blockName` must be a real block on the target; the handler returns the available block set if it isn't. NOTE on `position`: pass the literal string \"top\" or \"bottom\", OR a bare integer (0, 1, 2…). Prefer a bare integer (`0`, not `\"0\"`). **List fields** (`kind:'list'`, `listType`): repeating content is ONE list field, `{{#field}}…{{/field}}` (system prompt: lists). local (default): the placement value / chrome `default` is the entries, `[{node:'link', values:{label:'Home', link:{href:'/'}}}]`; shared: `bind:'shared'`, `list:'caelo:list/<id>'` — menus, navigation, footer links, categories, tags (list_lists first; entries change via patch_list_items_many; promote_local_list when a second place needs a local list); source: `bind:'source'` with a query / system / plugin `source`. **Internal links** (`href`s in HTML, `url`/`link` fields, the links of list entries): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.\n\nOver this connection the install's AI is not used: the AI part comes back to you — a work item to answer with your own model and send back through the tool it names, or an `AiDelegated:` answer that says what to do instead (see caelo_get_context).",
|
|
2524
2599
|
"inputSchema": {
|
|
2525
2600
|
"type": "object",
|
|
2526
2601
|
"additionalProperties": false,
|
|
@@ -2609,7 +2684,7 @@
|
|
|
2609
2684
|
"js": {
|
|
2610
2685
|
"type": "string",
|
|
2611
2686
|
"maxLength": 50000,
|
|
2612
|
-
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav`
|
|
2687
|
+
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class=\"caelo-nav-menu\" data-caelo-ui=\"mobile-nav\" data-nav-open=\"false\"><button type=\"button\" class=\"caelo-nav-toggle\" aria-expanded=\"false\" aria-label=\"Menu\"><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span></button><ul>{{#menu}}<li>…</li>{{/menu}}</ul></nav>` around the module's menu `list` field. Write module JS only for behaviour the kit does not cover."
|
|
2613
2688
|
},
|
|
2614
2689
|
"bindThemeLiterals": {
|
|
2615
2690
|
"type": "boolean",
|
|
@@ -2641,8 +2716,7 @@
|
|
|
2641
2716
|
"number",
|
|
2642
2717
|
"boolean",
|
|
2643
2718
|
"link",
|
|
2644
|
-
"
|
|
2645
|
-
"link-list",
|
|
2719
|
+
"list",
|
|
2646
2720
|
"module",
|
|
2647
2721
|
"module-list"
|
|
2648
2722
|
]
|
|
@@ -2653,7 +2727,7 @@
|
|
|
2653
2727
|
"maxLength": 128
|
|
2654
2728
|
},
|
|
2655
2729
|
"default": {
|
|
2656
|
-
"description": "The ORIGINAL value(s) this field replaced
|
|
2730
|
+
"description": "The ORIGINAL value(s) this field replaced, never a placeholder: scalar kinds → a string; list → its entries (local) or \"caelo:list/<id>\" (shared). Omit for module / module-list and bind \"source\".",
|
|
2657
2731
|
"anyOf": [
|
|
2658
2732
|
{
|
|
2659
2733
|
"type": "string"
|
|
@@ -2664,27 +2738,31 @@
|
|
|
2664
2738
|
{
|
|
2665
2739
|
"type": "boolean"
|
|
2666
2740
|
},
|
|
2667
|
-
{
|
|
2668
|
-
"type": "array",
|
|
2669
|
-
"items": {
|
|
2670
|
-
"type": "string"
|
|
2671
|
-
}
|
|
2672
|
-
},
|
|
2673
2741
|
{
|
|
2674
2742
|
"type": "array",
|
|
2675
2743
|
"items": {
|
|
2676
2744
|
"type": "object",
|
|
2677
|
-
"additionalProperties": false,
|
|
2678
2745
|
"required": [
|
|
2679
|
-
"
|
|
2680
|
-
"
|
|
2746
|
+
"node",
|
|
2747
|
+
"values"
|
|
2681
2748
|
],
|
|
2749
|
+
"description": "texts {node:\"text\", values:{text}} · links {node:\"link\", values:{label, link:{href}}} · menu {node:\"entry\", …, children}; keep `id` on resend.",
|
|
2682
2750
|
"properties": {
|
|
2683
|
-
"
|
|
2751
|
+
"id": {
|
|
2684
2752
|
"type": "string"
|
|
2685
2753
|
},
|
|
2686
|
-
"
|
|
2687
|
-
"type": "string"
|
|
2754
|
+
"node": {
|
|
2755
|
+
"type": "string",
|
|
2756
|
+
"description": "The NODE kind, not the type slug: \"text\" (texts), \"link\" (links), \"entry\" (menu)."
|
|
2757
|
+
},
|
|
2758
|
+
"values": {
|
|
2759
|
+
"type": "object"
|
|
2760
|
+
},
|
|
2761
|
+
"children": {
|
|
2762
|
+
"type": "array",
|
|
2763
|
+
"items": {
|
|
2764
|
+
"type": "object"
|
|
2765
|
+
}
|
|
2688
2766
|
}
|
|
2689
2767
|
}
|
|
2690
2768
|
}
|
|
@@ -2707,6 +2785,29 @@
|
|
|
2707
2785
|
}
|
|
2708
2786
|
]
|
|
2709
2787
|
},
|
|
2788
|
+
"listType": {
|
|
2789
|
+
"type": "string",
|
|
2790
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
2791
|
+
"description": "list: type slug — \"texts\", \"links\", \"menu\", \"categories\", \"tags\" or custom."
|
|
2792
|
+
},
|
|
2793
|
+
"bind": {
|
|
2794
|
+
"type": "string",
|
|
2795
|
+
"enum": [
|
|
2796
|
+
"local",
|
|
2797
|
+
"shared",
|
|
2798
|
+
"source"
|
|
2799
|
+
],
|
|
2800
|
+
"description": "list: local (default; entries live with the placement) | shared (a site list: menus, navigation, footer links, categories, tags) | source (generated per page)."
|
|
2801
|
+
},
|
|
2802
|
+
"list": {
|
|
2803
|
+
"type": "string",
|
|
2804
|
+
"pattern": "^caelo:list/[0-9a-f-]{36}$",
|
|
2805
|
+
"description": "list, shared: caelo:list/<id>."
|
|
2806
|
+
},
|
|
2807
|
+
"source": {
|
|
2808
|
+
"type": "object",
|
|
2809
|
+
"description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
|
|
2810
|
+
},
|
|
2710
2811
|
"allowedModuleTypes": {
|
|
2711
2812
|
"type": "array",
|
|
2712
2813
|
"maxItems": 32,
|
|
@@ -2783,7 +2884,7 @@
|
|
|
2783
2884
|
},
|
|
2784
2885
|
{
|
|
2785
2886
|
"name": "create_template",
|
|
2786
|
-
"description": "Create a new
|
|
2887
|
+
"description": "Create a new template (a reusable HTML+CSS shell pages bind to) for a page type. Use when pages need a new shape ('a blog-post template', 'a landing-page layout'). `pageType` = the page type (slug, list_page_types) whose pages may use it — the template is added to that type's templates; omit it for the site's default page type. A new KIND of content (news, products) is a page type first: upsert_page_types_many needs a template id, so create the template with the page type it belongs to, or create the type afterwards listing this template. Do NOT use to create a one-off page — use `build_page` instead. `layoutId` is OPTIONAL when site defaults define a default layout (see `## Site defaults` / get_site_defaults) — then omitting it binds to that default. Without a configured default, `layoutId` is REQUIRED: pick a UUID from `## Layouts on this site` / list_layouts (omitting it fails with a structured 'no defaults' error, no silent fallback). With ZERO layouts on the site, call create_layout first. CRITICAL — block syntax: render slots in `html` MUST be <caelo-slot name=\"X\"></caelo-slot> tags, NOT HTML comments like <!-- block:X -->. The composer ignores comment-style markers and the page renders empty. Define block rows separately via the templates editor or propose_update_template's `blocks` field — every block name needs a matching <caelo-slot> in html.",
|
|
2787
2888
|
"inputSchema": {
|
|
2788
2889
|
"type": "object",
|
|
2789
2890
|
"additionalProperties": false,
|
|
@@ -2814,6 +2915,11 @@
|
|
|
2814
2915
|
"type": "string",
|
|
2815
2916
|
"format": "uuid"
|
|
2816
2917
|
},
|
|
2918
|
+
"pageType": {
|
|
2919
|
+
"type": "string",
|
|
2920
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
2921
|
+
"description": "Page type slug (list_page_types) this template serves; omit for the site's default page type."
|
|
2922
|
+
},
|
|
2817
2923
|
"blocks": {
|
|
2818
2924
|
"type": "array",
|
|
2819
2925
|
"description": "Optional block-set metadata (same shape as create_layout). Omit it to auto-derive one block per <caelo-slot name=\"X\"> in html (displayName = name). Pass it only to give a block a nicer displayName or an explicit position; every `name` MUST match a <caelo-slot> in html.",
|
|
@@ -2951,143 +3057,6 @@
|
|
|
2951
3057
|
}
|
|
2952
3058
|
}
|
|
2953
3059
|
},
|
|
2954
|
-
{
|
|
2955
|
-
"name": "set_structured_set",
|
|
2956
|
-
"description": "Upsert a structured-data set. Creates the set if `(kind, slug)` doesn't exist yet; REPLACES the items array if it does (NOT append — pass the full desired list). Kinds: nav-menu, tags, taxonomy, link-list. The per-item shape is enforced by Zod at the tool boundary; a mismatch is rejected with a structured error naming the offending field. Read before you write: sets are NOT inlined in the system prompt — call `get_structured_set({kind, slug})` first and modify its items. Visibility: writing a set does NOT by itself change the site. The only built-in binding renders a set through a module whose slug is exactly `nav-menu-<set-slug>` placed directly in a layout/page block — that module's own HTML is replaced by the built-in renderer (`children` render as nested submenus). No AI tool can mint such a slug today (admin UI only — issue #414); if no bound module exists, render nav in the chrome module via a `link-list` field instead. Other kinds render only where module HTML/JS consumes them. Common slugs: nav-menu/header-main, nav-menu/footer-main, tags/blog. Theme tokens are NOT structured sets anymore (v0.11.0) — use `set_theme_tokens` for theme edits and `propose_create_theme` to mint a new theme. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
2957
|
-
"inputSchema": {
|
|
2958
|
-
"type": "object",
|
|
2959
|
-
"additionalProperties": false,
|
|
2960
|
-
"required": [
|
|
2961
|
-
"kind",
|
|
2962
|
-
"slug",
|
|
2963
|
-
"displayName",
|
|
2964
|
-
"items"
|
|
2965
|
-
],
|
|
2966
|
-
"properties": {
|
|
2967
|
-
"kind": {
|
|
2968
|
-
"enum": [
|
|
2969
|
-
"nav-menu",
|
|
2970
|
-
"tags",
|
|
2971
|
-
"taxonomy",
|
|
2972
|
-
"link-list"
|
|
2973
|
-
]
|
|
2974
|
-
},
|
|
2975
|
-
"slug": {
|
|
2976
|
-
"type": "string",
|
|
2977
|
-
"minLength": 1,
|
|
2978
|
-
"maxLength": 120
|
|
2979
|
-
},
|
|
2980
|
-
"displayName": {
|
|
2981
|
-
"type": "string",
|
|
2982
|
-
"minLength": 1,
|
|
2983
|
-
"maxLength": 200
|
|
2984
|
-
},
|
|
2985
|
-
"items": {
|
|
2986
|
-
"type": "array",
|
|
2987
|
-
"items": {
|
|
2988
|
-
"type": "object"
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
},
|
|
2994
|
-
{
|
|
2995
|
-
"name": "list_structured_sets",
|
|
2996
|
-
"description": "List structured-data sets (nav menus, tags, taxonomies, link-lists) with item counts (TOON rows). Optional `kind`, plus the standard list params: `filter`, `limit`/`offset`, `full: true`. Sets are NOT inlined in the system prompt — call this to discover what exists, then `get_structured_set` for one set's items.",
|
|
2997
|
-
"inputSchema": {
|
|
2998
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2999
|
-
"type": "object",
|
|
3000
|
-
"properties": {
|
|
3001
|
-
"kind": {
|
|
3002
|
-
"type": "string",
|
|
3003
|
-
"enum": [
|
|
3004
|
-
"nav-menu",
|
|
3005
|
-
"taxonomy",
|
|
3006
|
-
"tags",
|
|
3007
|
-
"link-list"
|
|
3008
|
-
]
|
|
3009
|
-
},
|
|
3010
|
-
"filter": {
|
|
3011
|
-
"description": "Case-insensitive substring filter applied across all output columns.",
|
|
3012
|
-
"type": "string",
|
|
3013
|
-
"maxLength": 200
|
|
3014
|
-
},
|
|
3015
|
-
"limit": {
|
|
3016
|
-
"description": "Max rows to return (default 50).",
|
|
3017
|
-
"type": "integer",
|
|
3018
|
-
"minimum": 1,
|
|
3019
|
-
"maximum": 500
|
|
3020
|
-
},
|
|
3021
|
-
"offset": {
|
|
3022
|
-
"description": "Rows to skip (pagination).",
|
|
3023
|
-
"type": "integer",
|
|
3024
|
-
"minimum": 0,
|
|
3025
|
-
"maximum": 9007199254740991
|
|
3026
|
-
},
|
|
3027
|
-
"full": {
|
|
3028
|
-
"description": "true disables truncation entirely: all rows, no char cap.",
|
|
3029
|
-
"type": "boolean"
|
|
3030
|
-
}
|
|
3031
|
-
},
|
|
3032
|
-
"additionalProperties": false
|
|
3033
|
-
}
|
|
3034
|
-
},
|
|
3035
|
-
{
|
|
3036
|
-
"name": "get_structured_set",
|
|
3037
|
-
"description": "Fetch one structured-data set's current items by `kind` + `slug`. Sets are NOT inlined in the system prompt — ALWAYS fetch the current items with this tool before `set_structured_set` (which REPLACES the whole list: merge in JS, then write the complete result). Theme tokens are NOT a structured-set kind (v0.11.0+) — use `get_theme` / `set_theme_tokens` instead. Returns null in the set field if no row exists for that kind+slug.",
|
|
3038
|
-
"inputSchema": {
|
|
3039
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3040
|
-
"type": "object",
|
|
3041
|
-
"properties": {
|
|
3042
|
-
"kind": {
|
|
3043
|
-
"type": "string",
|
|
3044
|
-
"enum": [
|
|
3045
|
-
"nav-menu",
|
|
3046
|
-
"taxonomy",
|
|
3047
|
-
"tags",
|
|
3048
|
-
"link-list"
|
|
3049
|
-
]
|
|
3050
|
-
},
|
|
3051
|
-
"slug": {
|
|
3052
|
-
"type": "string",
|
|
3053
|
-
"minLength": 1,
|
|
3054
|
-
"maxLength": 120
|
|
3055
|
-
}
|
|
3056
|
-
},
|
|
3057
|
-
"required": [
|
|
3058
|
-
"kind",
|
|
3059
|
-
"slug"
|
|
3060
|
-
],
|
|
3061
|
-
"additionalProperties": false
|
|
3062
|
-
}
|
|
3063
|
-
},
|
|
3064
|
-
{
|
|
3065
|
-
"name": "delete_structured_set",
|
|
3066
|
-
"description": "Delete a structured-data set by `kind` + `slug`. Reversible by calling `set_structured_set` with the same kind+slug. If a module with slug `<kind>-<slug>` references this set, that module's render falls back to empty after delete — remove or rewrite the module separately if needed. Returns an error if no set exists for that kind+slug.",
|
|
3067
|
-
"inputSchema": {
|
|
3068
|
-
"type": "object",
|
|
3069
|
-
"additionalProperties": false,
|
|
3070
|
-
"required": [
|
|
3071
|
-
"kind",
|
|
3072
|
-
"slug"
|
|
3073
|
-
],
|
|
3074
|
-
"properties": {
|
|
3075
|
-
"kind": {
|
|
3076
|
-
"enum": [
|
|
3077
|
-
"nav-menu",
|
|
3078
|
-
"tags",
|
|
3079
|
-
"taxonomy",
|
|
3080
|
-
"link-list"
|
|
3081
|
-
]
|
|
3082
|
-
},
|
|
3083
|
-
"slug": {
|
|
3084
|
-
"type": "string",
|
|
3085
|
-
"minLength": 1,
|
|
3086
|
-
"maxLength": 120
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
},
|
|
3091
3060
|
{
|
|
3092
3061
|
"name": "set_template_layout",
|
|
3093
3062
|
"description": "Re-point a template to a different layout. Every page using the template adopts the new chrome on the next render. Use when the user wants a page-type to switch between chrome variants (e.g. \"make landing pages use the bare layout\").",
|
|
@@ -3358,7 +3327,7 @@
|
|
|
3358
3327
|
},
|
|
3359
3328
|
{
|
|
3360
3329
|
"name": "duplicate_page",
|
|
3361
|
-
"description": "Clone an existing page under a new slug. Modules are shared by reference (edits propagate to both pages). The copy sits next to the source (same parent page, so `/docs/a` copies to `/docs/<newSlug>`); pass `newParentPageId` to place it under another page, or null for the top level. Use when the user says 'make a copy of this page' or 'duplicate the about page'. If cloning to a different page-type via `targetTemplateId`, block names must align — if they don't, modules in unmatched blocks orphan immediately. Follow up with `repoint_page_template` to migrate or drop those orphans. Soft-deleted modules on the source are filtered out at clone time; the audit log surfaces the dropped count.",
|
|
3330
|
+
"description": "Clone an existing page under a new slug. Modules are shared by reference (edits propagate to both pages). The copy sits next to the source (same parent page, so `/docs/a` copies to `/docs/<newSlug>`); pass `newParentPageId` to place it under another page, or null for the top level. Use when the user says 'make a copy of this page' or 'duplicate the about page'. If cloning to a different page-type via `targetTemplateId`, block names must align — if they don't, modules in unmatched blocks orphan immediately. Follow up with `repoint_page_template` to migrate or drop those orphans. The copy is the same kind of page: it keeps the source's page type, properties and terms (categories / tags); with a `targetTemplateId` of another page type it takes that type — change type, properties or terms afterwards with update_pages_many. Soft-deleted modules on the source are filtered out at clone time; the audit log surfaces the dropped count.",
|
|
3362
3331
|
"inputSchema": {
|
|
3363
3332
|
"type": "object",
|
|
3364
3333
|
"additionalProperties": false,
|
|
@@ -4256,7 +4225,7 @@
|
|
|
4256
4225
|
},
|
|
4257
4226
|
{
|
|
4258
4227
|
"name": "check_links",
|
|
4259
|
-
"description": "Check the site's internal links (links between its own pages and media) in the draft you see — every place a link is stored: page content, module HTML, field defaults, layouts, templates,
|
|
4228
|
+
"description": "Check the site's internal links (links between its own pages and media) in the draft you see — every place a link is stored: page content, module HTML, field defaults, layouts, templates, lists (menus, vocabularies), page terms and properties, and plugin data. Reports links to deleted pages or media, links without a page in the linking page's language, and references to a deleted list, a removed vocabulary entry (term) or a deleted module embedded in a list entry (all BLOCK Stage and Publish live), links to unpublished pages (BLOCK Publish live, a warning at Stage), and old plain-path links (warnings). Each problem names the page, where the link is stored and the tool that fixes it. Use it before stage_changes when you deleted, unpublished or translated pages, when a Stage was refused for links, or when the operator asks about broken links. Not for external links or what the built site serves (get_quality_audit has the staged-build link check).",
|
|
4260
4229
|
"inputSchema": {
|
|
4261
4230
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4262
4231
|
"type": "object",
|
|
@@ -4941,7 +4910,7 @@
|
|
|
4941
4910
|
},
|
|
4942
4911
|
{
|
|
4943
4912
|
"name": "update_pages_many",
|
|
4944
|
-
"description": "Edit page metadata — the ONE tool for it, for 1 page or 200 (pass a single-item `updates` array for one page; there is no separate rename/retitle/reslug tool). Use for 'rename this page', 'change the browser-tab title', 'move this to /pricing', 'archive all draft posts', 'move these pages to the {tpl} template'. **A page has THREE independently-editable identifiers — never substitute one for another:** `name` = the internal editor label (page picker / breadcrumbs; not public), `title` = the HTML <title> tag (browser tab + search results), `slug` = the URL path. If the operator just says 'rename' without saying which, ASK before guessing — 'rename' usually means `name`. **Changing `slug` moves the page's public URL — and the URL of every page nested below it**: a 301 from each old URL is created automatically in the same transaction, for links from outside the site. Links on this site (nav menus, link lists, modules, content) follow the page on their own — do not update them after a rename. A rename that moves 10+ URLs (the page plus its subtree) needs one Owner click in chat before it runs. To put a page under another page (or back at the top level) use move_pages, not this tool. Pass `redirectFromOld: 'skip'` ONLY when the operator explicitly says the old URL should 404 — the default keeps inbound links alive. Per-item failures (not-found, version conflict) are reported; the rest of the batch still applies. DO NOT use for SEO sidecar (`pages_seo.set_many`) or modules (`update_modules_many`) — those have their own bulk tools.\n\nOver MCP: when this call needs the operator's approval, it queues an approval card in their Caelo admin (Live Edit chat and the pending-approvals page) and returns the outcome once they decide — tell them to approve the card; never ask them to paste a prompt into the Caelo chat.",
|
|
4913
|
+
"description": "Edit page metadata — the ONE tool for it, for 1 page or 200 (pass a single-item `updates` array for one page; there is no separate rename/retitle/reslug tool). Use for 'rename this page', 'change the browser-tab title', 'move this to /pricing', 'archive all draft posts', 'move these pages to the {tpl} template', 'these posts are news articles', 'file this article under Product updates'. **What a page IS:** `pageType` (slug from list_page_types — e.g. turn a plain page into a `news` article; pass `templateId` too when the current template is not one of the new type's templates), `properties` (the page type's fields; merged, null removes one) and `terms` (REPLACES the page's terms: caelo:term/<listId>/<itemId> from the type's vocabularies — get_list on the categories / tags list shows the entry ids). Page queries ('latest news', 'articles in this category') filter on these. **A page has THREE independently-editable identifiers — never substitute one for another:** `name` = the internal editor label (page picker / breadcrumbs; not public), `title` = the HTML <title> tag (browser tab + search results), `slug` = the URL path. If the operator just says 'rename' without saying which, ASK before guessing — 'rename' usually means `name`. **Changing `slug` moves the page's public URL — and the URL of every page nested below it**: a 301 from each old URL is created automatically in the same transaction, for links from outside the site. Links on this site (nav menus, link lists, modules, content) follow the page on their own — do not update them after a rename. A rename that moves 10+ URLs (the page plus its subtree) needs one Owner click in chat before it runs. To put a page under another page (or back at the top level) use move_pages, not this tool. Pass `redirectFromOld: 'skip'` ONLY when the operator explicitly says the old URL should 404 — the default keeps inbound links alive. Per-item failures (not-found, version conflict) are reported; the rest of the batch still applies. DO NOT use for SEO sidecar (`pages_seo.set_many`) or modules (`update_modules_many`) — those have their own bulk tools.\n\nOver MCP: when this call needs the operator's approval, it queues an approval card in their Caelo admin (Live Edit chat and the pending-approvals page) and returns the outcome once they decide — tell them to approve the card; never ask them to paste a prompt into the Caelo chat.",
|
|
4945
4914
|
"inputSchema": {
|
|
4946
4915
|
"type": "object",
|
|
4947
4916
|
"additionalProperties": false,
|
|
@@ -4998,6 +4967,25 @@
|
|
|
4998
4967
|
"archived"
|
|
4999
4968
|
]
|
|
5000
4969
|
},
|
|
4970
|
+
"pageType": {
|
|
4971
|
+
"type": "string",
|
|
4972
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
4973
|
+
"description": "Page type slug (list_page_types). The template must be one of the type's templates — pass templateId too when it is not."
|
|
4974
|
+
},
|
|
4975
|
+
"properties": {
|
|
4976
|
+
"type": "object",
|
|
4977
|
+
"description": "Page type properties, merged into the page's (null removes one), e.g. {author: 'Ada', event_date: '2026-11-03'}.",
|
|
4978
|
+
"additionalProperties": true
|
|
4979
|
+
},
|
|
4980
|
+
"terms": {
|
|
4981
|
+
"type": "array",
|
|
4982
|
+
"maxItems": 100,
|
|
4983
|
+
"items": {
|
|
4984
|
+
"type": "string",
|
|
4985
|
+
"pattern": "^caelo:term/[0-9a-f-]{36}/[0-9a-f-]{36}$"
|
|
4986
|
+
},
|
|
4987
|
+
"description": "REPLACES the page's terms: caelo:term/<listId>/<itemId> of the page type's vocabularies (categories, tags). [] clears them."
|
|
4988
|
+
},
|
|
5001
4989
|
"redirectFromOld": {
|
|
5002
4990
|
"type": "string",
|
|
5003
4991
|
"enum": [
|
|
@@ -5066,7 +5054,7 @@
|
|
|
5066
5054
|
},
|
|
5067
5055
|
{
|
|
5068
5056
|
"name": "update_modules_many",
|
|
5069
|
-
"description": "Bulk module body edits across 1–200 modules in one tool call. Use when the operator says 'change the CTA in these 5 hero modules', 'fix the typo across these footer modules', 'apply this CSS tweak to all card modules'. Each item is the FULL `edit_module` shape (moduleId + optional displayName/description/kind/type/html/css/js/fields) — including `fields` and `kind`/`description`, so a bulk edit can restructure a module's field schema, not just its body. Per-item failures are reported but the rest of the batch still applies. Prefer this over multiple `edit_module` calls when targeting > 1 module. Each updated module emits its own snapshot, so revert_module (or revert_site) can undo the lot.",
|
|
5057
|
+
"description": "Bulk module body edits across 1–200 modules in one tool call. Use when the operator says 'change the CTA in these 5 hero modules', 'fix the typo across these footer modules', 'apply this CSS tweak to all card modules'. Each item is the FULL `edit_module` shape (moduleId + optional displayName/description/kind/type/html/css/js/fields) — including `fields` and `kind`/`description`, so a bulk edit can restructure a module's field schema, not just its body. Per-item failures are reported but the rest of the batch still applies. Prefer this over multiple `edit_module` calls when targeting > 1 module. Each updated module emits its own snapshot, so revert_module (or revert_site) can undo the lot. `list` fields take the same shape as in edit_module (listType, bind local | shared | source, list, source, default).",
|
|
5070
5058
|
"inputSchema": {
|
|
5071
5059
|
"type": "object",
|
|
5072
5060
|
"additionalProperties": false,
|
|
@@ -5124,7 +5112,7 @@
|
|
|
5124
5112
|
},
|
|
5125
5113
|
"js": {
|
|
5126
5114
|
"type": "string",
|
|
5127
|
-
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav`
|
|
5115
|
+
"description": "Vanilla JS. Emitted ONCE per page into a single shared <script defer> concatenated with every other module's JS — top-level scope is SHARED and there is NO implicit binding: `root`, `el`, `module`, and `this` are NOT defined. Wrap everything in an IIFE, select elements with document.querySelectorAll('.your-module-root-class …'), and iterate the matches so multiple placements of the module on one page all work. Omit entirely for purely presentational modules. Plain source text exactly as it is served: no `<![CDATA[ … ]]>` wrapper and no escaping of the whole value (the tool input is JSON — the string needs nothing beyond JSON escaping). **Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui=\"<id>\"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. `accordion` (FAQ, collapsible sections): `<div data-caelo-ui=\"accordion\" data-single>` containing `<details id=\"...\"><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. `tabs`: `<div data-caelo-ui=\"tabs\"><div role=\"tablist\" aria-label=\"...\"><button type=\"button\" role=\"tab\" id=\"t1\" aria-controls=\"p1\" aria-selected=\"true\">...</button><button type=\"button\" role=\"tab\" id=\"t2\" aria-controls=\"p2\" aria-selected=\"false\" tabindex=\"-1\">...</button></div><div role=\"tabpanel\" id=\"p1\" aria-labelledby=\"t1\">...</div><div role=\"tabpanel\" id=\"p2\" aria-labelledby=\"t2\" hidden>...</div></div>` - ids must be unique on the page. `carousel` (slider, testimonials): `<section data-caelo-ui=\"carousel\" aria-roledescription=\"carousel\" aria-label=\"...\"><div data-caelo-carousel-track tabindex=\"0\"><div role=\"group\" aria-roledescription=\"slide\" aria-label=\"1 of 3\">...</div>...</div><button type=\"button\" data-caelo-carousel-prev aria-label=\"Previous slide\"><span aria-hidden=\"true\">‹</span></button><button type=\"button\" data-caelo-carousel-next aria-label=\"Next slide\"><span aria-hidden=\"true\">›</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides' `flex-basis` in module CSS to show more. `mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class=\"caelo-nav-menu\" data-caelo-ui=\"mobile-nav\" data-nav-open=\"false\"><button type=\"button\" class=\"caelo-nav-toggle\" aria-expanded=\"false\" aria-label=\"Menu\"><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span><span class=\"caelo-nav-bar\"></span></button><ul>{{#menu}}<li>…</li>{{/menu}}</ul></nav>` around the module's menu `list` field. Write module JS only for behaviour the kit does not cover."
|
|
5128
5116
|
},
|
|
5129
5117
|
"fields": {
|
|
5130
5118
|
"type": "array",
|
|
@@ -5152,8 +5140,7 @@
|
|
|
5152
5140
|
"number",
|
|
5153
5141
|
"boolean",
|
|
5154
5142
|
"link",
|
|
5155
|
-
"
|
|
5156
|
-
"link-list",
|
|
5143
|
+
"list",
|
|
5157
5144
|
"module",
|
|
5158
5145
|
"module-list"
|
|
5159
5146
|
]
|
|
@@ -5164,7 +5151,7 @@
|
|
|
5164
5151
|
"maxLength": 128
|
|
5165
5152
|
},
|
|
5166
5153
|
"default": {
|
|
5167
|
-
"description": "The ORIGINAL value(s) this field replaced
|
|
5154
|
+
"description": "The ORIGINAL value(s) this field replaced, never a placeholder: scalar kinds → a string; list → its entries (local) or \"caelo:list/<id>\" (shared). Omit for module / module-list and bind \"source\".",
|
|
5168
5155
|
"anyOf": [
|
|
5169
5156
|
{
|
|
5170
5157
|
"type": "string"
|
|
@@ -5175,27 +5162,31 @@
|
|
|
5175
5162
|
{
|
|
5176
5163
|
"type": "boolean"
|
|
5177
5164
|
},
|
|
5178
|
-
{
|
|
5179
|
-
"type": "array",
|
|
5180
|
-
"items": {
|
|
5181
|
-
"type": "string"
|
|
5182
|
-
}
|
|
5183
|
-
},
|
|
5184
5165
|
{
|
|
5185
5166
|
"type": "array",
|
|
5186
5167
|
"items": {
|
|
5187
5168
|
"type": "object",
|
|
5188
|
-
"additionalProperties": false,
|
|
5189
5169
|
"required": [
|
|
5190
|
-
"
|
|
5191
|
-
"
|
|
5170
|
+
"node",
|
|
5171
|
+
"values"
|
|
5192
5172
|
],
|
|
5173
|
+
"description": "texts {node:\"text\", values:{text}} · links {node:\"link\", values:{label, link:{href}}} · menu {node:\"entry\", …, children}; keep `id` on resend.",
|
|
5193
5174
|
"properties": {
|
|
5194
|
-
"
|
|
5175
|
+
"id": {
|
|
5195
5176
|
"type": "string"
|
|
5196
5177
|
},
|
|
5197
|
-
"
|
|
5198
|
-
"type": "string"
|
|
5178
|
+
"node": {
|
|
5179
|
+
"type": "string",
|
|
5180
|
+
"description": "The NODE kind, not the type slug: \"text\" (texts), \"link\" (links), \"entry\" (menu)."
|
|
5181
|
+
},
|
|
5182
|
+
"values": {
|
|
5183
|
+
"type": "object"
|
|
5184
|
+
},
|
|
5185
|
+
"children": {
|
|
5186
|
+
"type": "array",
|
|
5187
|
+
"items": {
|
|
5188
|
+
"type": "object"
|
|
5189
|
+
}
|
|
5199
5190
|
}
|
|
5200
5191
|
}
|
|
5201
5192
|
}
|
|
@@ -5218,6 +5209,29 @@
|
|
|
5218
5209
|
}
|
|
5219
5210
|
]
|
|
5220
5211
|
},
|
|
5212
|
+
"listType": {
|
|
5213
|
+
"type": "string",
|
|
5214
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
5215
|
+
"description": "list: type slug — \"texts\", \"links\", \"menu\", \"categories\", \"tags\" or custom."
|
|
5216
|
+
},
|
|
5217
|
+
"bind": {
|
|
5218
|
+
"type": "string",
|
|
5219
|
+
"enum": [
|
|
5220
|
+
"local",
|
|
5221
|
+
"shared",
|
|
5222
|
+
"source"
|
|
5223
|
+
],
|
|
5224
|
+
"description": "list: local (default; entries live with the placement) | shared (a site list: menus, navigation, footer links, categories, tags) | source (generated per page)."
|
|
5225
|
+
},
|
|
5226
|
+
"list": {
|
|
5227
|
+
"type": "string",
|
|
5228
|
+
"pattern": "^caelo:list/[0-9a-f-]{36}$",
|
|
5229
|
+
"description": "list, shared: caelo:list/<id>."
|
|
5230
|
+
},
|
|
5231
|
+
"source": {
|
|
5232
|
+
"type": "object",
|
|
5233
|
+
"description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
|
|
5234
|
+
},
|
|
5221
5235
|
"allowedModuleTypes": {
|
|
5222
5236
|
"type": "array",
|
|
5223
5237
|
"maxItems": 32,
|
|
@@ -5703,7 +5717,9 @@
|
|
|
5703
5717
|
"head_contributions",
|
|
5704
5718
|
"url_slots",
|
|
5705
5719
|
"client_assets",
|
|
5706
|
-
"
|
|
5720
|
+
"list_sources",
|
|
5721
|
+
"list_types",
|
|
5722
|
+
"list_resolve",
|
|
5707
5723
|
"companion_skills",
|
|
5708
5724
|
"font_assets",
|
|
5709
5725
|
"private_files",
|
|
@@ -6193,7 +6209,7 @@
|
|
|
6193
6209
|
},
|
|
6194
6210
|
{
|
|
6195
6211
|
"name": "propose_update_template",
|
|
6196
|
-
"description": "Propose updating a template's HTML/CSS/displayName/layoutId AND/OR the block-set (template_blocks). Re-renders every page bound to the template — preview shows the count. CRITICAL — block syntax: in `html`, render-slot markers MUST be `<caelo-slot name=\"X\"></caelo-slot>` tags, NOT HTML comments. Every block declared in `blocks` needs a matching `<caelo-slot name=\"<blockName>\"></caelo-slot>` in `html` for the renderer to inject the page's modules into it. Without the caelo-slot tag the modules attach to the right block name in the DB but the renderer has nowhere to put them and the page renders empty between header and footer. Send html + blocks together in ONE proposal so they apply atomically. APPROVAL-GATED: calling this PAUSES for the operator's Approve/Reject right in the chat before anything changes. Do not claim the change is live until it is approved.\n\nOver MCP: call this tool yourself — it works here. The call queues an approval card in the operator's Caelo admin (their Live Edit chat and the pending-approvals page), waits for their Approve/Reject and returns the outcome (if the wait times out, continue with caelo_await_approval). Tell the operator you requested it and that they approve the card in Caelo; never ask them to paste a prompt into the Caelo chat to run it.",
|
|
6212
|
+
"description": "Propose updating a template's HTML/CSS/displayName/layoutId/pageType AND/OR the block-set (template_blocks). Re-renders every page bound to the template — preview shows the count. CRITICAL — block syntax: in `html`, render-slot markers MUST be `<caelo-slot name=\"X\"></caelo-slot>` tags, NOT HTML comments. Every block declared in `blocks` needs a matching `<caelo-slot name=\"<blockName>\"></caelo-slot>` in `html` for the renderer to inject the page's modules into it. Without the caelo-slot tag the modules attach to the right block name in the DB but the renderer has nowhere to put them and the page renders empty between header and footer. Send html + blocks together in ONE proposal so they apply atomically. APPROVAL-GATED: calling this PAUSES for the operator's Approve/Reject right in the chat before anything changes. Do not claim the change is live until it is approved.\n\nOver MCP: call this tool yourself — it works here. The call queues an approval card in the operator's Caelo admin (their Live Edit chat and the pending-approvals page), waits for their Approve/Reject and returns the outcome (if the wait times out, continue with caelo_await_approval). Tell the operator you requested it and that they approve the card in Caelo; never ask them to paste a prompt into the Caelo chat to run it.",
|
|
6197
6213
|
"inputSchema": {
|
|
6198
6214
|
"type": "object",
|
|
6199
6215
|
"additionalProperties": false,
|
|
@@ -6221,6 +6237,11 @@
|
|
|
6221
6237
|
"type": "string",
|
|
6222
6238
|
"format": "uuid"
|
|
6223
6239
|
},
|
|
6240
|
+
"pageType": {
|
|
6241
|
+
"type": "string",
|
|
6242
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
6243
|
+
"description": "Also allow this template for the page type (slug, list_page_types) — it is added to that type's templates."
|
|
6244
|
+
},
|
|
6224
6245
|
"blocks": {
|
|
6225
6246
|
"type": "array",
|
|
6226
6247
|
"description": "Block-set definition. Each block name must have a matching <caelo-slot name=\"<name>\"></caelo-slot> in `html`. Replaces the existing block set atomically (DELETE-then-INSERT). Omit to leave blocks unchanged.",
|
|
@@ -8263,6 +8284,538 @@
|
|
|
8263
8284
|
],
|
|
8264
8285
|
"additionalProperties": false
|
|
8265
8286
|
}
|
|
8287
|
+
},
|
|
8288
|
+
{
|
|
8289
|
+
"name": "list_list_types",
|
|
8290
|
+
"description": "The list types of this site — the shapes lists take: which node kinds exist, their fields (`*` = required), what may hang under what, and which nodes generate entries from a source (a page query, breadcrumbs, a plugin). Shipped types: `texts` (plain strings), `links` (label + link), `menu` (nested entries, 3 levels, marks the current page), `categories` / `tags` (vocabularies pages are assigned to). A type that ships with a plugin (marked 'ships with plugin') is read-only: its lists are yours to fill, its definition is not to change. 'Inert plugin settings' are extensions of a plugin that is not active — kept, not applied. Read this before writing a list's entries or a module's `{{#field}}…{{/field}}` template (the node fields are the keys inside the section). Pass `slug` for one type in detail (lists of the type + modules with local lists of it). When no type fits (a mega menu with columns and a latest-news block, a team list with photo + role), create one with upsert_list_types_many instead of bending `links`.",
|
|
8291
|
+
"inputSchema": {
|
|
8292
|
+
"type": "object",
|
|
8293
|
+
"properties": {
|
|
8294
|
+
"slug": {
|
|
8295
|
+
"description": "One type in detail: its definition plus every list and module using it.",
|
|
8296
|
+
"type": "string",
|
|
8297
|
+
"minLength": 1,
|
|
8298
|
+
"maxLength": 64
|
|
8299
|
+
},
|
|
8300
|
+
"role": {
|
|
8301
|
+
"type": "string",
|
|
8302
|
+
"enum": [
|
|
8303
|
+
"navigation",
|
|
8304
|
+
"vocabulary",
|
|
8305
|
+
"content"
|
|
8306
|
+
]
|
|
8307
|
+
},
|
|
8308
|
+
"query": {
|
|
8309
|
+
"description": "Free text over slug, name, purpose.",
|
|
8310
|
+
"type": "string",
|
|
8311
|
+
"minLength": 1,
|
|
8312
|
+
"maxLength": 128
|
|
8313
|
+
}
|
|
8314
|
+
},
|
|
8315
|
+
"additionalProperties": false
|
|
8316
|
+
}
|
|
8317
|
+
},
|
|
8318
|
+
{
|
|
8319
|
+
"name": "upsert_list_types_many",
|
|
8320
|
+
"description": "Create or replace list types (1–50 in one call, one transaction). A list type is the SHAPE of a list: `nodes` (node kind → {label, fields[], children?: {name, allow[]}, source?, current_from?}), `root.allow` (node kinds at the top level), `role` (navigation | vocabulary | content), `purpose` (why it exists — later reads decide by it), `max_depth` (required when a node allows itself). Field types: text, richtext, link ({href}), media, select (options), boolean, number, date, icon, module (an embedded module), term (entries of a vocabulary list, `list: <slug>`). Vocabulary types need exactly one node kind with text fields `name` and `slug`. A node with `source` gets generated entries instead of stored children: {kind:'query', editable:[knobs the entry may change], defaults:{page_type:'news', order:'published_desc', limit:3}} for 'latest news', {kind:'system', name:'breadcrumbs'|'children'|'siblings'|'page_terms'|'term_pages'}, or {kind:'plugin', name}. Mega menu: root.allow [\"column\"]; `column` {fields:[heading text], children:{name:\"links\", allow:[\"link\",\"news\"]}}; `link` {fields:[label text*, link link*], current_from:\"link\"}; `news` {fields:[heading text], source:{kind:\"query\", editable:[\"limit\"], defaults:{page_type:\"news\", order:\"published_desc\", limit:3}}}. Changing a type that has lists is checked against every list of the type (local ones in modules too): a new required field needs a `default`; removing a node kind or field that still has values is refused with the lists named — pass `dropValues: true` only when the operator agreed to lose those values. Pass `expectedVersion` from list_list_types to avoid overwriting a concurrent change. A type that ships with a plugin cannot be changed (by you or the operator) — create your own type under another slug instead. Do NOT create a near-duplicate of a shipped type — reuse `links` / `menu` when they fit.",
|
|
8321
|
+
"inputSchema": {
|
|
8322
|
+
"type": "object",
|
|
8323
|
+
"additionalProperties": false,
|
|
8324
|
+
"required": [
|
|
8325
|
+
"listTypes"
|
|
8326
|
+
],
|
|
8327
|
+
"properties": {
|
|
8328
|
+
"listTypes": {
|
|
8329
|
+
"type": "array",
|
|
8330
|
+
"minItems": 1,
|
|
8331
|
+
"items": {
|
|
8332
|
+
"type": "object",
|
|
8333
|
+
"additionalProperties": false,
|
|
8334
|
+
"required": [
|
|
8335
|
+
"slug",
|
|
8336
|
+
"name",
|
|
8337
|
+
"purpose",
|
|
8338
|
+
"role",
|
|
8339
|
+
"root",
|
|
8340
|
+
"nodes"
|
|
8341
|
+
],
|
|
8342
|
+
"properties": {
|
|
8343
|
+
"slug": {
|
|
8344
|
+
"type": "string"
|
|
8345
|
+
},
|
|
8346
|
+
"name": {
|
|
8347
|
+
"type": "string"
|
|
8348
|
+
},
|
|
8349
|
+
"purpose": {
|
|
8350
|
+
"type": "string"
|
|
8351
|
+
},
|
|
8352
|
+
"role": {
|
|
8353
|
+
"type": "string",
|
|
8354
|
+
"enum": [
|
|
8355
|
+
"navigation",
|
|
8356
|
+
"vocabulary",
|
|
8357
|
+
"content"
|
|
8358
|
+
]
|
|
8359
|
+
},
|
|
8360
|
+
"root": {
|
|
8361
|
+
"type": "object",
|
|
8362
|
+
"required": [
|
|
8363
|
+
"allow"
|
|
8364
|
+
],
|
|
8365
|
+
"properties": {
|
|
8366
|
+
"allow": {
|
|
8367
|
+
"type": "array",
|
|
8368
|
+
"items": {
|
|
8369
|
+
"type": "string"
|
|
8370
|
+
}
|
|
8371
|
+
},
|
|
8372
|
+
"max": {
|
|
8373
|
+
"type": "integer"
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
},
|
|
8377
|
+
"nodes": {
|
|
8378
|
+
"type": "object",
|
|
8379
|
+
"description": "{<kind>: {label, fields:[{name, label, type, required?, default?, options?, list?, multiple?, accepts?, ext?}], children?:{name, allow, max?}, current_from?, source?}}"
|
|
8380
|
+
},
|
|
8381
|
+
"max_depth": {
|
|
8382
|
+
"type": "integer"
|
|
8383
|
+
},
|
|
8384
|
+
"expectedVersion": {
|
|
8385
|
+
"type": "integer"
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
}
|
|
8389
|
+
},
|
|
8390
|
+
"dropValues": {
|
|
8391
|
+
"type": "boolean"
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
},
|
|
8396
|
+
{
|
|
8397
|
+
"name": "delete_list_types_many",
|
|
8398
|
+
"description": "Delete list types by slug (1–50). Refused for a type that ships with a plugin, and while any list (shared, or local in a module field) still uses the type — the error names them; delete or convert those lists first. Undoable like every draft change. Do NOT use to empty a list (that is patch_list_items_many / upsert_lists_many) or to drop a list (delete_lists_many).",
|
|
8399
|
+
"inputSchema": {
|
|
8400
|
+
"type": "object",
|
|
8401
|
+
"properties": {
|
|
8402
|
+
"slugs": {
|
|
8403
|
+
"minItems": 1,
|
|
8404
|
+
"maxItems": 50,
|
|
8405
|
+
"type": "array",
|
|
8406
|
+
"items": {
|
|
8407
|
+
"type": "string",
|
|
8408
|
+
"minLength": 1,
|
|
8409
|
+
"maxLength": 64
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
},
|
|
8413
|
+
"required": [
|
|
8414
|
+
"slugs"
|
|
8415
|
+
],
|
|
8416
|
+
"additionalProperties": false
|
|
8417
|
+
}
|
|
8418
|
+
},
|
|
8419
|
+
{
|
|
8420
|
+
"name": "list_lists",
|
|
8421
|
+
"description": "The shared lists of this site (TOON rows): slug, type, role, entry count, purpose, which module fields bind it (module.field), placements, sample pages, vocabulary use (page types + tagged pages), last edit. Menus, navigation, footer links, categories and tags are SHARED LISTS of a fitting list type (menu, links, categories, tags, or a custom type such as a mega menu) — there are no menu-specific tools. A list only ONE module shows stays LOCAL (its entries live in that module's `list` field); a list needed in more than one place, the site navigation, or a vocabulary pages are assigned to is SHARED. promote_local_list switches a local list to shared in one call. Call it BEFORE creating a list — when the operator says 'add Careers to the menu', the header's list is here (usedBy shows the header module), so you patch it instead of minting a second menu. Filters: `type` (list type slug), `role` (navigation | vocabulary | content), `query` (free text over slug, name, purpose and entry text), plus the standard list params. Entries are in get_list; local lists (inside one module) are not listed here — list_modules shows them.",
|
|
8422
|
+
"inputSchema": {
|
|
8423
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
8424
|
+
"type": "object",
|
|
8425
|
+
"properties": {
|
|
8426
|
+
"type": {
|
|
8427
|
+
"type": "string",
|
|
8428
|
+
"minLength": 1,
|
|
8429
|
+
"maxLength": 64
|
|
8430
|
+
},
|
|
8431
|
+
"role": {
|
|
8432
|
+
"type": "string",
|
|
8433
|
+
"enum": [
|
|
8434
|
+
"navigation",
|
|
8435
|
+
"vocabulary",
|
|
8436
|
+
"content"
|
|
8437
|
+
]
|
|
8438
|
+
},
|
|
8439
|
+
"query": {
|
|
8440
|
+
"type": "string",
|
|
8441
|
+
"minLength": 1,
|
|
8442
|
+
"maxLength": 128
|
|
8443
|
+
},
|
|
8444
|
+
"filter": {
|
|
8445
|
+
"description": "Case-insensitive substring filter applied across all output columns.",
|
|
8446
|
+
"type": "string",
|
|
8447
|
+
"maxLength": 200
|
|
8448
|
+
},
|
|
8449
|
+
"limit": {
|
|
8450
|
+
"description": "Max rows to return (default 50).",
|
|
8451
|
+
"type": "integer",
|
|
8452
|
+
"minimum": 1,
|
|
8453
|
+
"maximum": 500
|
|
8454
|
+
},
|
|
8455
|
+
"offset": {
|
|
8456
|
+
"description": "Rows to skip (pagination).",
|
|
8457
|
+
"type": "integer",
|
|
8458
|
+
"minimum": 0,
|
|
8459
|
+
"maximum": 9007199254740991
|
|
8460
|
+
},
|
|
8461
|
+
"full": {
|
|
8462
|
+
"description": "true disables truncation entirely: all rows, no char cap.",
|
|
8463
|
+
"type": "boolean"
|
|
8464
|
+
}
|
|
8465
|
+
},
|
|
8466
|
+
"additionalProperties": false
|
|
8467
|
+
}
|
|
8468
|
+
},
|
|
8469
|
+
{
|
|
8470
|
+
"name": "get_list",
|
|
8471
|
+
"description": "One shared list with its whole entry tree (by `slug` or `id`): every entry's id, node kind and values (links as paths), its version, and where the list is used. Read it before patch_list_items_many — patches address entries by id (`parentId` of 'Company' to add 'Careers' under it) — and pass `version` back as `expectedVersion`.",
|
|
8472
|
+
"inputSchema": {
|
|
8473
|
+
"type": "object",
|
|
8474
|
+
"properties": {
|
|
8475
|
+
"slug": {
|
|
8476
|
+
"type": "string",
|
|
8477
|
+
"minLength": 1,
|
|
8478
|
+
"maxLength": 64
|
|
8479
|
+
},
|
|
8480
|
+
"id": {
|
|
8481
|
+
"type": "string",
|
|
8482
|
+
"format": "uuid",
|
|
8483
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
8484
|
+
}
|
|
8485
|
+
},
|
|
8486
|
+
"additionalProperties": false
|
|
8487
|
+
}
|
|
8488
|
+
},
|
|
8489
|
+
{
|
|
8490
|
+
"name": "upsert_lists_many",
|
|
8491
|
+
"description": "Create shared lists (menus, footer links, vocabularies), or replace a list's WHOLE entry tree (1–50 lists, one transaction). Each list: `slug`, `name`, `purpose` (why it exists and where it shows — e.g. 'Main navigation in the site header'), `type` (list type slug from list_list_types), `items` (the entry tree) — nothing else: the tree is `items` (not `entries`), and the role comes from the type. An entry is {node, values, children?, query?}: `values` keyed by the node's fields (links as {\"href\": \"/about\"} or an external URL; terms as caelo:term/<listId>/<itemId>), `children` under nodes that allow them, `query` only on source nodes (the knobs the type marks editable). Keep the `id` of entries that already exist; omit it for new ones. Example (type menu): [{\"node\":\"entry\",\"values\":{\"label\":\"Company\",\"link\":{\"href\":\"/about\"}},\"children\":[{\"node\":\"entry\",\"values\":{\"label\":\"Team\",\"link\":{\"href\":\"/about/team\"}}}]}]. Links must name pages that exist (build them first). To change a few entries of an existing list prefer patch_list_items_many — a replace drops every entry you leave out. After creating a list, bind it: a module `list` field with bind 'shared' (add_module / edit_module), e.g. the header module on the layout.",
|
|
8492
|
+
"inputSchema": {
|
|
8493
|
+
"type": "object",
|
|
8494
|
+
"additionalProperties": false,
|
|
8495
|
+
"required": [
|
|
8496
|
+
"lists"
|
|
8497
|
+
],
|
|
8498
|
+
"properties": {
|
|
8499
|
+
"lists": {
|
|
8500
|
+
"type": "array",
|
|
8501
|
+
"minItems": 1,
|
|
8502
|
+
"maxItems": 50,
|
|
8503
|
+
"items": {
|
|
8504
|
+
"type": "object",
|
|
8505
|
+
"additionalProperties": false,
|
|
8506
|
+
"required": [
|
|
8507
|
+
"slug",
|
|
8508
|
+
"name",
|
|
8509
|
+
"purpose",
|
|
8510
|
+
"type",
|
|
8511
|
+
"items"
|
|
8512
|
+
],
|
|
8513
|
+
"properties": {
|
|
8514
|
+
"slug": {
|
|
8515
|
+
"type": "string"
|
|
8516
|
+
},
|
|
8517
|
+
"name": {
|
|
8518
|
+
"type": "string"
|
|
8519
|
+
},
|
|
8520
|
+
"purpose": {
|
|
8521
|
+
"type": "string"
|
|
8522
|
+
},
|
|
8523
|
+
"type": {
|
|
8524
|
+
"type": "string",
|
|
8525
|
+
"description": "list type slug"
|
|
8526
|
+
},
|
|
8527
|
+
"items": {
|
|
8528
|
+
"type": "array",
|
|
8529
|
+
"items": {
|
|
8530
|
+
"type": "object",
|
|
8531
|
+
"description": "{node, values, children?, query?, id?} — node = the node KIND (list_list_types); values keyed by its fields; keep `id` of existing entries."
|
|
8532
|
+
}
|
|
8533
|
+
},
|
|
8534
|
+
"expectedVersion": {
|
|
8535
|
+
"type": "integer"
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8542
|
+
},
|
|
8543
|
+
{
|
|
8544
|
+
"name": "patch_list_items_many",
|
|
8545
|
+
"description": "Edit entries of shared lists by id — the routine way to maintain a menu, footer links or a vocabulary (1–50 lists, up to 200 patches each, one transaction). Patches: {op:'add', parentId?, position?, item:{node, values, children?}} (omit parentId for the top level, position to append), {op:'update', itemId, values} (merged; null removes a value), {op:'move', itemId, parentId?, position}, {op:'remove', itemId} (with its children). 'Add Careers under Company' = get_list → one add patch with parentId = Company's id. Pass `expectedVersion` from get_list; a conflict means someone changed the list — re-read and redo. The result names the ids of added entries. Every page that shows the list updates with it — there is nothing to rewire.",
|
|
8546
|
+
"inputSchema": {
|
|
8547
|
+
"type": "object",
|
|
8548
|
+
"additionalProperties": false,
|
|
8549
|
+
"required": [
|
|
8550
|
+
"lists"
|
|
8551
|
+
],
|
|
8552
|
+
"properties": {
|
|
8553
|
+
"lists": {
|
|
8554
|
+
"type": "array",
|
|
8555
|
+
"minItems": 1,
|
|
8556
|
+
"maxItems": 50,
|
|
8557
|
+
"items": {
|
|
8558
|
+
"type": "object",
|
|
8559
|
+
"additionalProperties": false,
|
|
8560
|
+
"required": [
|
|
8561
|
+
"slug",
|
|
8562
|
+
"patches"
|
|
8563
|
+
],
|
|
8564
|
+
"properties": {
|
|
8565
|
+
"slug": {
|
|
8566
|
+
"type": "string"
|
|
8567
|
+
},
|
|
8568
|
+
"expectedVersion": {
|
|
8569
|
+
"type": "integer"
|
|
8570
|
+
},
|
|
8571
|
+
"patches": {
|
|
8572
|
+
"type": "array",
|
|
8573
|
+
"items": {
|
|
8574
|
+
"type": "object",
|
|
8575
|
+
"required": [
|
|
8576
|
+
"op"
|
|
8577
|
+
],
|
|
8578
|
+
"description": "{op:\"add\", parentId?, position?, item} | {op:\"update\", itemId, values?, query?} | {op:\"move\", itemId, parentId?, position} | {op:\"remove\", itemId}",
|
|
8579
|
+
"properties": {
|
|
8580
|
+
"op": {
|
|
8581
|
+
"type": "string",
|
|
8582
|
+
"enum": [
|
|
8583
|
+
"add",
|
|
8584
|
+
"update",
|
|
8585
|
+
"move",
|
|
8586
|
+
"remove"
|
|
8587
|
+
]
|
|
8588
|
+
}
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
},
|
|
8598
|
+
{
|
|
8599
|
+
"name": "delete_lists_many",
|
|
8600
|
+
"description": "Delete shared lists by slug (1–50). Refused while a module field or placement still binds the list (the error names them) — rebind or remove those first. Undoable like every draft change. Do NOT use to remove entries (patch_list_items_many) or a list type (delete_list_types_many).",
|
|
8601
|
+
"inputSchema": {
|
|
8602
|
+
"type": "object",
|
|
8603
|
+
"properties": {
|
|
8604
|
+
"slugs": {
|
|
8605
|
+
"minItems": 1,
|
|
8606
|
+
"maxItems": 50,
|
|
8607
|
+
"type": "array",
|
|
8608
|
+
"items": {
|
|
8609
|
+
"type": "string",
|
|
8610
|
+
"minLength": 1,
|
|
8611
|
+
"maxLength": 64
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
},
|
|
8615
|
+
"required": [
|
|
8616
|
+
"slugs"
|
|
8617
|
+
],
|
|
8618
|
+
"additionalProperties": false
|
|
8619
|
+
}
|
|
8620
|
+
},
|
|
8621
|
+
{
|
|
8622
|
+
"name": "promote_local_list",
|
|
8623
|
+
"description": "Turn a module's LOCAL list into a SHARED list and bind the field to it, in one call — when a second place needs the same entries ('show the footer links in the mobile menu too', 'the editors should maintain these links without opening the module'). Input: `moduleId` + `field` (the module's `list` field), and the new list's `slug`, `name`, `purpose`. Refused when the module's placements show different entries (it names them) — then decide which entries the shared list keeps and use upsert_lists_many + edit_module instead. Result: the list id and its ref (caelo:list/<id>) for binding other modules.",
|
|
8624
|
+
"inputSchema": {
|
|
8625
|
+
"type": "object",
|
|
8626
|
+
"properties": {
|
|
8627
|
+
"moduleId": {
|
|
8628
|
+
"type": "string",
|
|
8629
|
+
"format": "uuid",
|
|
8630
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
8631
|
+
},
|
|
8632
|
+
"field": {
|
|
8633
|
+
"type": "string",
|
|
8634
|
+
"minLength": 1,
|
|
8635
|
+
"maxLength": 64
|
|
8636
|
+
},
|
|
8637
|
+
"slug": {
|
|
8638
|
+
"type": "string",
|
|
8639
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
|
|
8640
|
+
},
|
|
8641
|
+
"name": {
|
|
8642
|
+
"type": "string",
|
|
8643
|
+
"minLength": 1,
|
|
8644
|
+
"maxLength": 200
|
|
8645
|
+
},
|
|
8646
|
+
"purpose": {
|
|
8647
|
+
"type": "string",
|
|
8648
|
+
"minLength": 1,
|
|
8649
|
+
"maxLength": 1000
|
|
8650
|
+
}
|
|
8651
|
+
},
|
|
8652
|
+
"required": [
|
|
8653
|
+
"moduleId",
|
|
8654
|
+
"field",
|
|
8655
|
+
"slug",
|
|
8656
|
+
"name",
|
|
8657
|
+
"purpose"
|
|
8658
|
+
],
|
|
8659
|
+
"additionalProperties": false
|
|
8660
|
+
}
|
|
8661
|
+
},
|
|
8662
|
+
{
|
|
8663
|
+
"name": "preview_list_source",
|
|
8664
|
+
"description": "Show what a generated list returns for a page BEFORE you bind or save it: a page query ('the 3 latest news'), a system source (breadcrumbs, children, siblings, page_terms, term_pages) or a plugin source — the same resolver the preview and the build use, in this chat's draft view. Name the source ONE way (see the input schema). `pageId` = the page it renders on (needed for breadcrumbs / children / siblings / page_terms; a query leaves that page out by default). Only published pages appear. An empty result usually means the filter is wrong (page type slug, terms) or nothing is published yet.",
|
|
8665
|
+
"inputSchema": {
|
|
8666
|
+
"type": "object",
|
|
8667
|
+
"additionalProperties": false,
|
|
8668
|
+
"description": "Name the source ONE way: `source` (+ value, vocabulary) | `list` + `itemId` (a saved source entry) | `list` + `node` (+ value) | `moduleId` + `field` (+ contentInstanceId).",
|
|
8669
|
+
"properties": {
|
|
8670
|
+
"pageId": {
|
|
8671
|
+
"type": "string",
|
|
8672
|
+
"description": "the page the entries are generated for"
|
|
8673
|
+
},
|
|
8674
|
+
"source": {
|
|
8675
|
+
"type": "object",
|
|
8676
|
+
"description": "a source definition (query / system / plugin)"
|
|
8677
|
+
},
|
|
8678
|
+
"value": {
|
|
8679
|
+
"type": "object",
|
|
8680
|
+
"description": "query knob overrides, or {terms} for term_pages"
|
|
8681
|
+
},
|
|
8682
|
+
"vocabulary": {
|
|
8683
|
+
"type": "string"
|
|
8684
|
+
},
|
|
8685
|
+
"list": {
|
|
8686
|
+
"type": "string",
|
|
8687
|
+
"description": "slug or caelo:list/<id>"
|
|
8688
|
+
},
|
|
8689
|
+
"itemId": {
|
|
8690
|
+
"type": "string"
|
|
8691
|
+
},
|
|
8692
|
+
"node": {
|
|
8693
|
+
"type": "string"
|
|
8694
|
+
},
|
|
8695
|
+
"moduleId": {
|
|
8696
|
+
"type": "string"
|
|
8697
|
+
},
|
|
8698
|
+
"field": {
|
|
8699
|
+
"type": "string"
|
|
8700
|
+
},
|
|
8701
|
+
"contentInstanceId": {
|
|
8702
|
+
"type": "string"
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
},
|
|
8707
|
+
{
|
|
8708
|
+
"name": "list_page_types",
|
|
8709
|
+
"description": "The page types of this site — what each page IS (e.g. `content`, `news`, `product`): purpose, page count, allowed templates (ids) and default template, properties (`*` = required) and vocabularies (categories / tags lists whose terms its pages carry). Read it before build_page / update_pages_many so a new article gets `pageType: 'news'` (and its properties and terms), and before writing a page query (`page_type` filters on the slug). Pass `slug` for one type with sample pages. A new KIND of content (a blog, a job board, products) is a new page type: upsert_page_types_many.",
|
|
8710
|
+
"inputSchema": {
|
|
8711
|
+
"type": "object",
|
|
8712
|
+
"properties": {
|
|
8713
|
+
"slug": {
|
|
8714
|
+
"description": "One page type in detail, with sample pages.",
|
|
8715
|
+
"type": "string",
|
|
8716
|
+
"minLength": 1,
|
|
8717
|
+
"maxLength": 64
|
|
8718
|
+
},
|
|
8719
|
+
"query": {
|
|
8720
|
+
"description": "Free text over slug, name, purpose.",
|
|
8721
|
+
"type": "string",
|
|
8722
|
+
"minLength": 1,
|
|
8723
|
+
"maxLength": 128
|
|
8724
|
+
}
|
|
8725
|
+
},
|
|
8726
|
+
"additionalProperties": false
|
|
8727
|
+
}
|
|
8728
|
+
},
|
|
8729
|
+
{
|
|
8730
|
+
"name": "upsert_page_types_many",
|
|
8731
|
+
"description": "Create or replace page types (1–50, one transaction) — when the operator starts a new kind of content ('I need a news section with categories', 'a job board'). Each: `slug`, `name`, `purpose`, `templates` (template ids from list_templates, at least one; create one with create_template pageType first when none fits), `defaultTemplate` (one of them), `properties` (fields every page of the type carries, e.g. {name:'author', type:'text', label:'Author'}, {name:'event_date', type:'date', label:'Date'}; no `module` fields), `vocabularies` ([{list: <vocabulary list id from list_lists>, cardinality:'one'|'many', required?}]). Order for 'news with categories': upsert_lists_many (a `categories` list) → create_template (pageType 'news') or reuse a template → upsert_page_types_many → build_page / update_pages_many with pageType, properties, terms. Changing a type that pages use re-checks them: a new property's default is written into them; dropping a template they use is refused (move them first); dropping a property or vocabulary they hold values for needs `dropValues: true` (only with the operator's agreement). Pass `expectedVersion` from list_page_types.",
|
|
8732
|
+
"inputSchema": {
|
|
8733
|
+
"type": "object",
|
|
8734
|
+
"additionalProperties": false,
|
|
8735
|
+
"required": [
|
|
8736
|
+
"pageTypes"
|
|
8737
|
+
],
|
|
8738
|
+
"properties": {
|
|
8739
|
+
"pageTypes": {
|
|
8740
|
+
"type": "array",
|
|
8741
|
+
"minItems": 1,
|
|
8742
|
+
"items": {
|
|
8743
|
+
"type": "object",
|
|
8744
|
+
"additionalProperties": false,
|
|
8745
|
+
"required": [
|
|
8746
|
+
"slug",
|
|
8747
|
+
"name",
|
|
8748
|
+
"purpose",
|
|
8749
|
+
"templates",
|
|
8750
|
+
"defaultTemplate"
|
|
8751
|
+
],
|
|
8752
|
+
"properties": {
|
|
8753
|
+
"slug": {
|
|
8754
|
+
"type": "string"
|
|
8755
|
+
},
|
|
8756
|
+
"name": {
|
|
8757
|
+
"type": "string"
|
|
8758
|
+
},
|
|
8759
|
+
"purpose": {
|
|
8760
|
+
"type": "string"
|
|
8761
|
+
},
|
|
8762
|
+
"templates": {
|
|
8763
|
+
"type": "array",
|
|
8764
|
+
"items": {
|
|
8765
|
+
"type": "string"
|
|
8766
|
+
}
|
|
8767
|
+
},
|
|
8768
|
+
"defaultTemplate": {
|
|
8769
|
+
"type": "string"
|
|
8770
|
+
},
|
|
8771
|
+
"properties": {
|
|
8772
|
+
"type": "array",
|
|
8773
|
+
"items": {
|
|
8774
|
+
"type": "object",
|
|
8775
|
+
"description": "a field: {name, label, type, required?, default?, options?, …}"
|
|
8776
|
+
}
|
|
8777
|
+
},
|
|
8778
|
+
"vocabularies": {
|
|
8779
|
+
"type": "array",
|
|
8780
|
+
"items": {
|
|
8781
|
+
"type": "object",
|
|
8782
|
+
"description": "{list: <vocabulary list id>, cardinality: one|many, required?}"
|
|
8783
|
+
}
|
|
8784
|
+
},
|
|
8785
|
+
"expectedVersion": {
|
|
8786
|
+
"type": "integer"
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8790
|
+
},
|
|
8791
|
+
"dropValues": {
|
|
8792
|
+
"type": "boolean"
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
},
|
|
8797
|
+
{
|
|
8798
|
+
"name": "delete_page_types_many",
|
|
8799
|
+
"description": "Delete page types by slug (1–50). Refused while pages use the type (move them to another type with update_pages_many pageType first) and for the site's default page type. Undoable like every draft change.",
|
|
8800
|
+
"inputSchema": {
|
|
8801
|
+
"type": "object",
|
|
8802
|
+
"properties": {
|
|
8803
|
+
"slugs": {
|
|
8804
|
+
"minItems": 1,
|
|
8805
|
+
"maxItems": 50,
|
|
8806
|
+
"type": "array",
|
|
8807
|
+
"items": {
|
|
8808
|
+
"type": "string",
|
|
8809
|
+
"minLength": 1,
|
|
8810
|
+
"maxLength": 64
|
|
8811
|
+
}
|
|
8812
|
+
}
|
|
8813
|
+
},
|
|
8814
|
+
"required": [
|
|
8815
|
+
"slugs"
|
|
8816
|
+
],
|
|
8817
|
+
"additionalProperties": false
|
|
8818
|
+
}
|
|
8266
8819
|
}
|
|
8267
8820
|
]
|
|
8268
8821
|
}
|