@bettercms-ai/mcp 0.84.5 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +277 -60
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +149 -149
- package/dist/skills/bettercms/SKILL.md +1 -1
- package/dist/skills/bettercms/references/bindings.md +9 -0
- package/dist/skills/bettercms/references/components.md +1 -1
- package/dist/skills/bettercms/references/content-modeling.md +1 -1
- package/dist/skills/bettercms/references/dock-input.md +89 -1
- package/dist/skills/bettercms/references/editor-chrome.md +2 -2
- package/dist/skills/bettercms/references/finish-site.md +1 -1
- package/dist/skills/bettercms/references/forms-and-interactions.md +1 -1
- package/dist/skills/bettercms/references/integrations.md +13 -2
- package/dist/skills/bettercms/references/kit-catalog.md +117 -181
- package/package.json +1 -1
|
@@ -147,7 +147,7 @@ resources: `bettercms://skill/bettercms` for this file, and each reference under
|
|
|
147
147
|
| Nuxt, SvelteKit, React Router, Vite: no adapter | [other-frameworks.md](references/other-frameworks.md) | neither `astro.config.*` nor `next.config.*`; `nuxt.config.*`, `svelte.config.*`, `react-router.config.*` | Nuxt, SvelteKit, React Router, Vite, createClient |
|
|
148
148
|
| Rich text and field types | [portable-text.md](references/portable-text.md) | writing a richtext value; converting HTML or Markdown | rich text, Portable Text, richtext, HTML, Markdown |
|
|
149
149
|
| Brand kit, tokens, fonts, logo, images, icons | [media-and-brand.md](references/media-and-brand.md) | design fidelity, alt text, a logo or favicon change | brand, tokens, fonts, logo, image, alt text, favicon |
|
|
150
|
-
| Connect a vendor with a public API (jobs board, store, CRM, video): declare, approve, call, recipes | [integrations.md](references/integrations.md) | the user names Greenhouse, JobDiva, Shopify, HubSpot, Zoom, Apollo or any API; a 409
|
|
150
|
+
| Connect a vendor with a public API (jobs board, store, CRM, video): declare, approve, call, recipes | [integrations.md](references/integrations.md) | the user names Greenhouse, JobDiva, Shopify, HubSpot, Zoom, Apollo or any API; a 409 "integration_not_approved" | integration, API key, Greenhouse, JobDiva, Shopify, HubSpot, Zoom, Apollo, sync, vendor, token |
|
|
151
151
|
| Forms, menus, accordions, modals, consent | [forms-and-interactions.md](references/forms-and-interactions.md) | attaching or verifying a form; an interactive section | form, submission, Turnstile, modal, accordion, menu |
|
|
152
152
|
| Languages, locale prefixes, translation, hreflang | [localization.md](references/localization.md) | the build serves `/fr`-style prefixes; `locale-unvarianted` in a binding report | language, locale, /fr, translate, hreflang, i18n |
|
|
153
153
|
| SEO and AEO | [seo.md](references/seo.md) | meta, JSON-LD, sitemap, robots, redirects, AI crawlers, `list_seo_issues` | SEO, meta, JSON-LD, schema.org, sitemap, robots, OG image, redirect, 410, llms.txt |
|
|
@@ -9,6 +9,15 @@ Doctrine (what should be editable, the conversion loop): §11 and §13 of
|
|
|
9
9
|
`bettercms://playbook/schema`, and the `convert_site` prompt. This is the template side. Rich-text
|
|
10
10
|
values: [portable-text.md](portable-text.md). Adapter wiring and env: [frameworks.md](frameworks.md).
|
|
11
11
|
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [1. Why the attributes matter](#1-why-the-attributes-matter) · [2. Bind the leaf](#2-bind-the-leaf-and-know-which-vocabulary-you-are-writing-in)
|
|
15
|
+
- [3. Addresses](#3-addresses) · [4. Declared mode, publish, and the report](#4-declared-mode-publish-and-the-report)
|
|
16
|
+
- [5. Preview strings (stega)](#5-preview-strings-stega) · [6. Classify an unfamiliar codebase](#6-classify-an-unfamiliar-codebase-before-binding-it)
|
|
17
|
+
- [7. Edit-to-render test](#7-edit-to-render-test) · [Acceptance](#acceptance)
|
|
18
|
+
- [Reading the binding report and the conversion receipt](#reading-the-binding-report-and-the-conversion-receipt)
|
|
19
|
+
- [Editor setup, proposals and binding decisions](#editor-setup-proposals-and-binding-decisions)
|
|
20
|
+
|
|
12
21
|
## 1. Why the attributes matter
|
|
13
22
|
|
|
14
23
|
A `data-bcms-*` attribute is the only thing that locates a stored value on a rendered page. Five
|
|
@@ -47,7 +47,7 @@ defaultValue? }`.
|
|
|
47
47
|
a prop's help only for a format or unit, a place the canvas cannot show, a real constraint or
|
|
48
48
|
a fallback (most props get none). Write your own by the rules in
|
|
49
49
|
[content-modeling.md](content-modeling.md) ("Writing help text"): one short plain sentence,
|
|
50
|
-
|
|
50
|
+
of at most 160 characters, with no semicolon, no dash and no figure taken from the page.
|
|
51
51
|
- A prop takes `showIf` (`{ match, rules: [{ field, op, value? }] }`): hidden until SIBLING props match. `op` is `eq`, `neq`, `in`, `isSet`, `notEmpty` or `empty`; the last three take no `value`, the first three need one. Props and model fields share all six, and a sub-field's `showIf` names its sibling sub-fields.
|
|
52
52
|
- `object` holds ONE `{ childKey: value }` built from `config.fields` (required), like the model field `object`. Groups, tables and objects nest to 10 levels counting the prop; an 11th is refused `field_depth_exceeded`. Aim an `object` at `props` or at a key your component code reads, never at a text slot such as a heading's `props.text` (`prop_target_unrenderable`).
|
|
53
53
|
- `datetime`, `slug`, `email`, `phone`, `json`, `location` (`{ lat, lng }`), `tags` (`string[]`), `rating` (0..`config.max`) and `code` (`{ language?, code }` or a string) hold what the same-named model field holds, and a placement's value is checked by that field's rules (`prop_value_invalid`). Like `object`, a `tags` or `code` prop never targets a text slot.
|
|
@@ -204,7 +204,7 @@ stored field. Text you send is stored as yours (`helpSource: "agent"`) and is ne
|
|
|
204
204
|
|
|
205
205
|
Ask one question per field: does the editor need this line to type the right value? Write your
|
|
206
206
|
own `helpText` (on a field or a prop) only for one of four reasons, written as one short plain
|
|
207
|
-
sentence
|
|
207
|
+
sentence of at most 160 characters, with no semicolon, no dash and no figure taken from the page.
|
|
208
208
|
|
|
209
209
|
| Reason | Good |
|
|
210
210
|
|---|---|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Custom dock inputs"
|
|
3
|
-
description: "When a field earns its own input in the editor dock, how to ask the user first, the input contract and protocol, the writes allowlist, the file layout, and the
|
|
3
|
+
description: "When a field earns its own input in the editor dock, how to ask the user first, the input contract and protocol, the writes allowlist, the file layout, the fallback guarantee, and UI stored in the CMS (write/approve/validate/publish_dock_ui)."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Custom dock inputs
|
|
@@ -123,3 +123,91 @@ The stock control comes back whenever the input cannot run: an unknown name, inp
|
|
|
123
123
|
the project, a build that failed, a frame that throws or misses `ready`. The value is unchanged
|
|
124
124
|
and stays editable. So a custom input can only add to the dock, never take a field away from an
|
|
125
125
|
author. Removing `ui.input` returns the field to the stock control at once.
|
|
126
|
+
|
|
127
|
+
## 9. UI stored in the CMS (no repo push)
|
|
128
|
+
|
|
129
|
+
The same input, or a panel, can live in the CMS instead of `bettercms/inputs/`. Use it when the
|
|
130
|
+
site has no repo you can push to, or for a panel a section, a page or the Layout needs. The
|
|
131
|
+
restraint ladder (section 1) and the user's yes (section 2) still come first.
|
|
132
|
+
|
|
133
|
+
| Step | Tool | What happens |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| 1 | `write_dock_ui { name, scope, kind, target, source }` | saves a new DRAFT version; the same bytes again write nothing |
|
|
136
|
+
| 2 | show the user the source and what it is for | wait for an explicit yes |
|
|
137
|
+
| 3 | `approve_dock_ui { name, version, userApproved: true }` | approves that ONE version; a later write drops it |
|
|
138
|
+
| 4 | `validate_dock_ui { name }` | the import allow-list, the size caps, a name clash; the build receipt |
|
|
139
|
+
| 5 | `publish_dock_ui { name }` | validates again, then the dock serves that version |
|
|
140
|
+
| — | `list_dock_ui { scope?, status? }` | every source, its status and the project's usage |
|
|
141
|
+
|
|
142
|
+
- `name` is what `cms:<name>` imports resolve: a lowercase letter first, then letters, digits and
|
|
143
|
+
hyphens, at most 48 (`DOCK_UI_NAME_UNSERVABLE` otherwise: such a name could never build).
|
|
144
|
+
A name the repo also ships (`bettercms/inputs/<name>.tsx`) refuses `build.name_conflict`: rename one.
|
|
145
|
+
- `source` is ONE `.tsx` file, at most 256 KiB, whose default export is the component (section 5).
|
|
146
|
+
It imports only `react`, `react-dom`, `@bettercms-ai/kit/*`, `@bettercms-ai/dock-input` and its
|
|
147
|
+
siblings as `cms:<name>`, which resolve to their PUBLISHED version. Anything else, or a computed
|
|
148
|
+
`import()`, refuses `build.import_denied` naming the import.
|
|
149
|
+
- Limits per project: 64 sources and 4 MiB across their newest versions (`build.size_exceeded`).
|
|
150
|
+
- `approve_dock_ui` without `userApproved: true` refuses `policy.confirmation_required`. A version an
|
|
151
|
+
agent wrote is approved by an agent only on a connection allowed to publish; otherwise it refuses
|
|
152
|
+
`policy.not_approved` (detail "self_approval") and a person approves it in the dashboard.
|
|
153
|
+
- `publish_dock_ui` on a version nobody approved refuses `policy.not_approved`. On success it rebuilds
|
|
154
|
+
the project's dock UI release (the repo's inputs plus every published CMS source) and answers its
|
|
155
|
+
`release` key. A project with no site handle yet refuses `build.compile_error` (detail "no_site_handle"):
|
|
156
|
+
publish the site once, then publish again. A failed build leaves the stock control (section 8).
|
|
157
|
+
|
|
158
|
+
One minimal example per scope. The `target` says what the UI is for.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
// field: an input for one field (`kind: "input"`). Then set ui.input { name } as in section 2.
|
|
162
|
+
write_dock_ui { name: "brand-swatch", scope: "field", kind: "input",
|
|
163
|
+
target: { modelId: "<model id>", path: "accent" },
|
|
164
|
+
source: "import { Button } from '@bettercms-ai/kit/ui'\nconst SWATCHES = ['#0f172a', '#2563eb', '#16a34a']\nexport default function BrandSwatch({ value, onChange, readOnly }) {\n return <div>{SWATCHES.map((c) => <Button key={c} disabled={readOnly} aria-pressed={value === c} onClick={() => onChange(c)}>{c}</Button>)}</div>\n}" }
|
|
165
|
+
|
|
166
|
+
// section: a panel beside one component's props
|
|
167
|
+
write_dock_ui { name: "hero-checklist", scope: "section", kind: "panel",
|
|
168
|
+
target: { componentId: "<component id>" }, source: "export default function Panel({ document }) { … }" }
|
|
169
|
+
|
|
170
|
+
// page: a panel on one page
|
|
171
|
+
write_dock_ui { name: "launch-notes", scope: "page", kind: "panel", target: { pageId: "<page id>" }, source: "…" }
|
|
172
|
+
|
|
173
|
+
// layout: a panel on the global Layout (header, footer)
|
|
174
|
+
write_dock_ui { name: "nav-audit", scope: "layout", kind: "panel", target: {}, source: "…" }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### What each tool takes and answers
|
|
178
|
+
|
|
179
|
+
The shapes are `@bettercms-ai/types` `dock-ui.ts`. A `source` row is `DockUiSource`: `{ id, name, scope, kind,
|
|
180
|
+
target, headVersion, publishedVersion | null, status: draft | approved | published, approvedBy, approvedAt,
|
|
181
|
+
buildReceipt | null, createdAt, updatedAt }`. A version is `{ version, sourceSha, actorId, origin, createdAt }`.
|
|
182
|
+
|
|
183
|
+
| Tool | Takes | Answers |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `write_dock_ui` | `{ name, scope: field \| section \| page \| layout, kind: input \| panel, target, source }` | `{ source, version, created }` (`created: false` when the same bytes were already the newest) |
|
|
186
|
+
| `approve_dock_ui` | `{ name, version, userApproved? }` | `{ source }`, status `approved` |
|
|
187
|
+
| `validate_dock_ui` | `{ name, version? }` (default: the newest) | `{ name, receipt }`: `{ ok: true, version, bundleKey, bytes, builtAt }` or `{ ok: false, version, refusals, builtAt }` |
|
|
188
|
+
| `publish_dock_ui` | `{ name, version? }` | `{ ok: true, source, receipt, release: { K, built, cached } }` |
|
|
189
|
+
| `list_dock_ui` | `{ scope?, status? }` | `{ sources, usage: { sources, bytes, limits } }` |
|
|
190
|
+
|
|
191
|
+
**Who may approve.** A person in the dashboard approves any version (the route needs `content:publish`).
|
|
192
|
+
An agent approves only with the user's yes, sent as `userApproved: true` after they saw the source.
|
|
193
|
+
For a version an agent wrote, the connection must also hold the publish delegation (`content:publish`,
|
|
194
|
+
granted when the user connected the AI client), so an agent never approves its own write on a
|
|
195
|
+
connection that may not publish. A write after the approval drops it: approve the newest version.
|
|
196
|
+
|
|
197
|
+
### Every refusal, and its fix
|
|
198
|
+
|
|
199
|
+
| Code | When | Fix |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `DOCK_UI_NAME_INVALID` (400) | the name is not lowercase letters, digits and hyphens | rename it |
|
|
202
|
+
| `DOCK_UI_NAME_UNSERVABLE` (400) | it does not start with a letter, or is over 48 characters | rename it |
|
|
203
|
+
| `build.size_exceeded` (413 / 409) | one version over 256 KiB, a 65th source, or the project over 4 MiB (`detail` says which) | split the source, or remove a source you no longer need |
|
|
204
|
+
| `build.import_denied` (422) | an import off the allow-list, a computed `import()`, a `cms:` sibling that is not published, or a source importing itself | import only the allowed packages, as string literals; publish the sibling first |
|
|
205
|
+
| `build.name_conflict` (422) | the site's repo ships a dock UI of the same name | rename one of the two |
|
|
206
|
+
| `build.compile_error` (422 / 409) | the build failed (`detail` names it); "no_site_handle": the project has no site to build for | fix the source; for "no_site_handle", publish the site once, then publish again |
|
|
207
|
+
| `policy.confirmation_required` (403) | `approve_dock_ui` without the user's yes | show the user the source, then approve with `userApproved: true` |
|
|
208
|
+
| `policy.not_approved` (403 / 409) | publishing a version nobody approved; "self_approval": an agent approving its own write without the publish delegation | approve it first; for "self_approval", a person approves it in the dashboard |
|
|
209
|
+
| `DOCK_UI_VERSION_NOT_HEAD` (409) | approving an older version, or a newer one was saved meanwhile | `list_dock_ui`, then approve and publish the newest |
|
|
210
|
+
| `DOCK_UI_NOT_FOUND` / `DOCK_UI_VERSION_NOT_FOUND` (404) | no such name, or no such version | `list_dock_ui` lists them; `write_dock_ui` creates one |
|
|
211
|
+
| `DOCK_UI_REBUILD_FAILED` (502) | the version is published but the UI release failed | nothing to change: the next site release builds it; the stock control shows until then |
|
|
212
|
+
| `DOCK_UI_UNAVAILABLE` (503) | this server has no dock UI storage yet | try again after the server update |
|
|
213
|
+
|
|
@@ -30,7 +30,7 @@ Give a fieldset a `kind`:
|
|
|
30
30
|
|---|---|---|
|
|
31
31
|
| `group` | Fields folded under one header (`collapsed: true` starts it shut) | 12 rows inside one group |
|
|
32
32
|
| `row` | Short fields on one line | at most 3 fields |
|
|
33
|
-
| `tab` | A tab strip | at most
|
|
33
|
+
| `tab` | A tab strip | at most 8 tabs (the ones that do not fit fold into "More"), names of 40 characters or fewer, top-level fields only, tabs never nest |
|
|
34
34
|
|
|
35
35
|
A group joins a tab with `tab: "<tab id>"`. A fieldset with no `kind` stays flat. Props have no
|
|
36
36
|
fieldsets: the first prop of a group sends `ui.group: { id, label, kind }` and each later one
|
|
@@ -49,7 +49,7 @@ from the dock icon list the MCP serves as a resource (`resources/list`); any oth
|
|
|
49
49
|
|
|
50
50
|
## 4. Segmented, dropdown or slider
|
|
51
51
|
|
|
52
|
-
- `ui.control: "segmented"` on a single-choice select with 2–
|
|
52
|
+
- `ui.control: "segmented"` on a single-choice select with 2–8 short options: one click replaces
|
|
53
53
|
two. A multi-select or 5+ options is refused; keep the default dropdown there.
|
|
54
54
|
- `"slider"` needs a `number` with numeric `config.min` below `config.max` and at most 1000 steps.
|
|
55
55
|
|
|
@@ -203,7 +203,7 @@ and use what it calls things (`plan.cents`, `billingFrequency`, `StatsBand.astro
|
|
|
203
203
|
| A layout of that kind (a component's `name`) | how THIS layout differs from the kind's others; the kind alone when it has one layout | `Centered`, `Split with media`, `Pricing` | its heading's copy, the page it came from with a number (`Hero 2`) |
|
|
204
204
|
| One placed section's title | nothing you write. The outline shows the section's own heading, as written and read live, with its kind underneath | `Equip Your Projects with ScrewFast` over `Hero` | a `label` you invent, a shortened or reworded heading, the kind said again |
|
|
205
205
|
| A field or prop | what it HOLDS there, one to three words | `Dismiss key`, `Starter plan price`, `Dashboard screenshot`, `Billing frequency` | its own copy, a role with a number (`Text 4`), the section's name alone |
|
|
206
|
-
| Help text | most fields get none. One plain sentence
|
|
206
|
+
| Help text | most fields get none. One plain sentence of at most 160 characters, only for a format or unit, a place the canvas cannot show, a real constraint or a fallback. No semicolons, no dashes, no values from the page | `Leave empty to use the page title.` | the label or the value said again, writing advice, a figure copied from another page |
|
|
207
207
|
|
|
208
208
|
**Advice is not work left**, except the names. `gate.naming` is part of the job: rename every
|
|
209
209
|
row before you report, as above. `receipt.gate.advice`, `gate.families` and `gate.kit`
|
|
@@ -110,7 +110,7 @@ The pipeline with the code that enforces each hop.
|
|
|
110
110
|
|
|
111
111
|
| # | Hop | Code |
|
|
112
112
|
|---|---|---|
|
|
113
|
-
| 1 | Define: `create_form` / `update_form`, or the dashboard. Postgres `forms` row, `status` defaults to `draft` | `packages/db/src/schema/forms.ts:35` |
|
|
113
|
+
| 1 | Define: `create_form` / `update_form`, or the dashboard. Postgres `forms` row, `status` defaults to `published` (a `draft` when the creator may not publish) | `packages/db/src/schema/forms.ts:35` |
|
|
114
114
|
| 2 | Publish: `publish_form`, or the dashboard Forms tab. Both need publish permission | `src/routes/management/forms.ts:276`, `src/routes/admin/forms.ts:529`, `:530` |
|
|
115
115
|
| 3 | Place: a `form` block `{ formId }` inside a section | `packages/types/src/block.ts:253`, `:257` |
|
|
116
116
|
| 4 | Render: hosted renderer, Next `BcmsForm`, or Astro `BcmsForm.astro` | `packages/next/src/form.tsx:74` |
|
|
@@ -13,6 +13,17 @@ any vendor with a public HTTPS API and a key works the same way.
|
|
|
13
13
|
Everything here can also be done by a person in the dashboard (Site settings → Integrations), with the
|
|
14
14
|
same records. What you declare, they see and can edit.
|
|
15
15
|
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
- [1. The order of work](#1-the-order-of-work) · [2. Declaring one](#2-declaring-one)
|
|
19
|
+
- [3. Approval](#3-approval) · [4. Calling the vendor](#4-calling-the-vendor)
|
|
20
|
+
- [5. Token exchanges](#5-token-exchanges) · [6. Syncing a list into a collection](#6-syncing-a-list-into-a-collection)
|
|
21
|
+
- [7. A custom panel (always build one)](#7-a-custom-panel-always-build-one) · [8. Recipes](#8-recipes)
|
|
22
|
+
- [9. Webhooks: the vendor tells the site something changed](#9-webhooks-the-vendor-tells-the-site-something-changed) · [10. Functions: your own code, never your secrets](#10-functions-your-own-code-never-your-secrets)
|
|
23
|
+
- [11. Site endpoints: live reads for visitors, no key in the page](#11-site-endpoints-live-reads-for-visitors-no-key-in-the-page) · [12. Live references: point at a vendor item without copying it](#12-live-references-point-at-a-vendor-item-without-copying-it)
|
|
24
|
+
- [13. Sign in with OAuth (the customer's own OAuth app)](#13-sign-in-with-oauth-the-customers-own-oauth-app) · [14. Templates, the call log, alerts](#14-templates-the-call-log-alerts)
|
|
25
|
+
- [15. Two-way sync: BetterCMS edits go back to the vendor](#15-two-way-sync-bettercms-edits-go-back-to-the-vendor)
|
|
26
|
+
|
|
16
27
|
## 1. The order of work
|
|
17
28
|
|
|
18
29
|
1. **Model where the data lands.** A collection per vendor list (Jobs, Products), with fields for
|
|
@@ -52,7 +63,7 @@ create_integration {
|
|
|
52
63
|
## 3. Approval
|
|
53
64
|
|
|
54
65
|
Until an admin approves the integration, every vendor call and sync is refused (409
|
|
55
|
-
|
|
66
|
+
"integration_not_approved"). You cannot approve it: no tool and no header does. The create and
|
|
56
67
|
update answers carry `approval.changes` (in plain words: which hosts it will call, which env vars
|
|
57
68
|
it will read, where it sends them) and `approval.approveUrl`. Give the user both, and say why the
|
|
58
69
|
integration needs each host and secret. They (or an admin) press Approve on that page.
|
|
@@ -262,7 +273,7 @@ update_integration { functions: [{ name: "to-hubspot", triggers: ["form:<formId>
|
|
|
262
273
|
- Return `{ upsert: [{ sync, externalKey, data, slug? }] }` to write entries into a sync's
|
|
263
274
|
collection (drafts, like a sync). `run_integration_function { waitSeconds: 20 }` answers with the
|
|
264
275
|
output, the captured `console` lines and any error; runs are in `get_integration.functionRuns`.
|
|
265
|
-
- If the server answers
|
|
276
|
+
- If the server answers "functions_not_enabled", functions are not available yet on this
|
|
266
277
|
BetterCMS server: use a sync or an endpoint instead.
|
|
267
278
|
|
|
268
279
|
## 11. Site endpoints: live reads for visitors, no key in the page
|