@bettercms-ai/mcp 0.41.0 → 0.42.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 +79 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3107,7 +3107,7 @@ function buildToolDefs(deps) {
|
|
|
3107
3107
|
def(
|
|
3108
3108
|
"submit_conversion_receipt",
|
|
3109
3109
|
"Record what the conversion codemod could and could not do",
|
|
3110
|
-
"Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE BINDING RECEIPT, NOT THE `--forms` ONE. A run of `npx @bettercms-ai/convert --forms` writes a receipt whose `paths` are all zero and whose account is in a `forms` block (`{ wired, alreadyWired, pending[{ id, name, reason }], notes }`); it passes this endpoint's arithmetic and would overwrite the real coverage with zeros. Write it to its own file (`--receipt forms-receipt.json`), read `forms.pending` and publish every form `forms.notes` names in the Forms tab, and submit the binding run's receipt here. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
|
|
3110
|
+
"Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message, fix }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. \u{1F534} A RECEIPT WITH PENDING PATHS IS A PROGRESS REPORT, NOT A FINISH LINE. The response answers `complete` and `pendingTotal`, and echoes the first 40 pending rows WITH their `fix` \u2014 `{ action, file, line, col?, snippet, why? }`, where `action` is one of `wrap-span` (wrap the literal in a `<span data-bcms-field=\u2026>`), `declare-attr` (add `data-bcms-field=\u2026` to the element at file:line), `bind-expression` (replace the expression with the framework's bcmsField helper), `declare-richtext` (bind the container with the richtext helper), `bind-data` (the literal comes from the data file at file:line \u2014 bind that field) or `manual` (with a one-sentence `why`). Apply every fix in the source, rerun the codemod, resubmit. `complete: true` is the only receipt that ends a conversion \u2014 do not report a site converted on anything less. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE BINDING RECEIPT, NOT THE `--forms` ONE. A run of `npx @bettercms-ai/convert --forms` writes a receipt whose `paths` are all zero and whose account is in a `forms` block (`{ wired, alreadyWired, pending[{ id, name, reason }], notes }`); it passes this endpoint's arithmetic and would overwrite the real coverage with zeros. Write it to its own file (`--receipt forms-receipt.json`), read `forms.pending` and publish every form `forms.notes` names in the Forms tab, and submit the binding run's receipt here. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
|
|
3111
3111
|
z.object({
|
|
3112
3112
|
briefDigest: z.string().min(1).describe("The `briefDigest` get_conversion_brief { complete: true } returned. Must match the receipt's own."),
|
|
3113
3113
|
receipt: z.record(z.string(), z.unknown()).describe("The receipt `npx @bettercms-ai/convert --receipt out.json` wrote, verbatim.")
|
|
@@ -3171,7 +3171,20 @@ function buildToolDefs(deps) {
|
|
|
3171
3171
|
"Edit a page",
|
|
3172
3172
|
"Edit a page: title, slug, SEO metaTitle/metaDescription, publish status (draft|published), and `blockJson` (its block composition \u2014 passing it REPLACES the whole array, so read get_page first). It does NOT change the field SCHEMA \u2014 use add_page_field / set_page_content for that. Renaming the slug keeps content intact. Publishing copies the draft blocks live in the same call. " + SECTION_DOCTRINE,
|
|
3173
3173
|
z.object({ pageId: z.string().min(1), title: z.string().optional(), slug: z.string().optional(), blockJson: z.array(blockObject).optional().describe("REPLACES the page's block composition"), metaTitle: z.string().optional(), metaDescription: z.string().optional(), status: z.enum(["draft", "published"]).optional() }).shape,
|
|
3174
|
-
async (c, a) =>
|
|
3174
|
+
async (c, a) => {
|
|
3175
|
+
const res = await c.fetchJSON(
|
|
3176
|
+
c.url(`/management/pages/${s(a.pageId)}/meta`),
|
|
3177
|
+
{
|
|
3178
|
+
method: "PATCH",
|
|
3179
|
+
body: JSON.stringify({ title: a.title, slug: a.slug, blockJson: a.blockJson, metaTitle: a.metaTitle, metaDescription: a.metaDescription, status: a.status })
|
|
3180
|
+
}
|
|
3181
|
+
);
|
|
3182
|
+
const note = res.redirectNote;
|
|
3183
|
+
return ok(
|
|
3184
|
+
note ? `Updated page. An existing redirect from ${note.source} already points to ${note.existingDestination}; it wasn't changed, so the old address does not redirect to the new one.` : "Updated page.",
|
|
3185
|
+
res.data
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3175
3188
|
),
|
|
3176
3189
|
// ── Code + deploy (parity with remote /mcp; needs artifact:write) ──
|
|
3177
3190
|
// Both grant shapes carry that scope now; a workspace-wide one names its target per
|
|
@@ -4790,8 +4803,14 @@ an empty string on the live site.
|
|
|
4790
4803
|
|
|
4791
4804
|
## 13. Convert an imported repo into a CMS-backed, editable site
|
|
4792
4805
|
|
|
4793
|
-
\xA711 says a deploy does not make a site editable. This is the recipe that does
|
|
4794
|
-
|
|
4806
|
+
\xA711 says a deploy does not make a site editable. This is the recipe that does.
|
|
4807
|
+
|
|
4808
|
+
\u{1F534} **A CONVERSION ENDS ONLY WHEN NOTHING IS PENDING.** The codemod's first pass never
|
|
4809
|
+
declares every path \u2014 measured on a real site, one pass declared 282 of 767 \u2014 and every path it
|
|
4810
|
+
leaves in \`paths.pending\` carries a \`fix\` naming the file, the line and the one edit. Apply
|
|
4811
|
+
them, rerun, resubmit, and repeat until \`submit_conversion_receipt\` answers \`complete: true\`.
|
|
4812
|
+
That boolean is the finish line; a receipt with pending paths is a progress report. The
|
|
4813
|
+
\`convert_site\` MCP prompt walks exactly this loop.
|
|
4795
4814
|
|
|
4796
4815
|
**Scope, before you start.**
|
|
4797
4816
|
|
|
@@ -4865,21 +4884,30 @@ hand. THE ORDER, and every step of it matters:
|
|
|
4865
4884
|
could not match are in \`forms.pending\` with a reason (\`FORM_NOT_IN_SOURCE\`,
|
|
4866
4885
|
\`FIELD_UNMATCHED\`, \`FORM_AMBIGUOUS\`, \u2026) \u2014 it refuses rather than guessing, because a form
|
|
4867
4886
|
wired to the wrong id delivers the customer's leads into another form's inbox.
|
|
4868
|
-
3.
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
with
|
|
4887
|
+
3. APPLY EVERY PENDING FIX, THEN RERUN. Each entry of \`paths.pending\` carries its reason
|
|
4888
|
+
(\`IN_EXPRESSION\`, \`AMBIGUOUS_LITERAL\`, \`REPEATER_FIXED_LENGTH\`, \`PARSE_ERROR\`, \u2026) and,
|
|
4889
|
+
where the codemod could work one out, a \`fix\`: \`{ action, file, line, col?, snippet, why? }\`.
|
|
4890
|
+
Go to \`file\`:\`line\` and do what \`action\` says \u2014 \`wrap-span\` (wrap the literal in
|
|
4891
|
+
\`<span data-bcms-field="\u2026">\`), \`declare-attr\` (add \`data-bcms-field="\u2026"\` to that element),
|
|
4892
|
+
\`bind-expression\` (replace the expression with the framework's bcmsField helper),
|
|
4893
|
+
\`declare-richtext\` (bind the container with the richtext helper), \`bind-data\` (bind the field
|
|
4894
|
+
in the data file or collection entry it names) or \`manual\` (do what its \`why\` says). A row
|
|
4895
|
+
with no \`fix\` is still yours. Then RERUN step 2 and repeat until \`paths.pending\` is empty \u2014
|
|
4896
|
+
the codemod is idempotent, so a second run over converted source rewrites nothing.
|
|
4897
|
+
4. \`submit_conversion_receipt { briefDigest, receipt }\` with the receipt file, verbatim. It
|
|
4898
|
+
answers \`complete\`, \`pendingTotal\` and the first 40 pending rows with their \`fix\`. \`complete: true\` \u2014 nothing
|
|
4899
|
+
pending \u2014 is the ONLY receipt that ends a conversion; on anything less go back to step 3. It
|
|
4900
|
+
is also what lets the coverage meter say WHY a path is undeclared instead of only that it is.
|
|
4901
|
+
It records and releases nothing.
|
|
4875
4902
|
5. Push, or \`deploy_project\`. A PLAIN HTML repo \u2014 no \`package.json\` \u2014 deploys by PUSH ONLY:
|
|
4876
4903
|
\`deploy_project\` refuses such an archive by design, so pushing is the whole lane. Either way,
|
|
4877
4904
|
poll \`get_deploy_status\` until \`slot\` is \`current\` (or \`awaitingPromote\` is false) before you
|
|
4878
4905
|
read the report in step 6 \u2014 a report read against the previous release is the previous
|
|
4879
4906
|
release's verdict.
|
|
4880
4907
|
6. \`get_binding_report\`. Alongside \`unmatched\` it now carries \`coverage\` \u2014 \`declared\` (what the
|
|
4881
|
-
brief listed), \`bound\` (what the build declares) and \`pending\` with the reasons.
|
|
4882
|
-
|
|
4908
|
+
brief listed), \`bound\` (what the build declares) and \`pending\` with the reasons. It must read
|
|
4909
|
+
\`unmatched\` 0 and \`unaddressable\` under 2% of each route's visible text (\xA711). Fix what it
|
|
4910
|
+
names IN THE SOURCE and release again until \`coverage.pending\` is empty.
|
|
4883
4911
|
7. \`set_binding_mode { declaredBindings: true }\` and release ONE MORE TIME \u2014 the mode applies to
|
|
4884
4912
|
the next release, not to the one already out.
|
|
4885
4913
|
|
|
@@ -5108,6 +5136,31 @@ disagrees with the array the moment anyone drags a row in the builder.
|
|
|
5108
5136
|
// src/prompts.ts
|
|
5109
5137
|
var STRUCTURE_RULE = `### Page structure (non-negotiable)
|
|
5110
5138
|
${SECTION_DOCTRINE}`;
|
|
5139
|
+
var CONVERT_SITE_TEXT = (projectId) => `Convert my site so every visible element is editable in BetterCMS${projectId ? `, project ${projectId}` : ""}.
|
|
5140
|
+
Do not stop at the plan or at a partial receipt \u2014 finish the whole site.
|
|
5141
|
+
|
|
5142
|
+
1. \`list_projects\`; pass \`projectId\`${projectId ? ` (${projectId})` : ""} on every project tool from here on. If the project
|
|
5143
|
+
is not listed the grant is on another workspace \u2014 stop and say so.
|
|
5144
|
+
2. \`get_conversion_plan\`. On a 404 \`no-approved-plan\`, call
|
|
5145
|
+
\`get_conversion_brief { complete: true }\` and follow \`cursor\` to the LAST page, keeping every
|
|
5146
|
+
page \u2014 a codemod fed the capped brief converts a truncated site. Write them to \`brief.json\`.
|
|
5147
|
+
3. \`pull_project_source\` (or clone the \`github\` remote it names) and work from the SOURCE, never
|
|
5148
|
+
from the deployed HTML.
|
|
5149
|
+
4. \`npx @bettercms-ai/convert --brief brief.json --root . --receipt receipt.json\`; read \`git diff\`.
|
|
5150
|
+
5. For EVERY entry of \`paths.pending\`, go to its \`fix.file\`:\`fix.line\` and do what \`fix.action\`
|
|
5151
|
+
says: \`wrap-span\` wrap the literal in \`<span data-bcms-field="\u2026">\`; \`declare-attr\` add
|
|
5152
|
+
\`data-bcms-field="\u2026"\` to that element; \`bind-expression\` replace the expression with the
|
|
5153
|
+
framework's bcmsField helper; \`declare-richtext\` bind the container with the richtext helper;
|
|
5154
|
+
\`bind-data\` bind the field in the data file it names; \`manual\` do what its \`why\` says. A row
|
|
5155
|
+
with no \`fix\` is still yours. Then rerun step 4 until \`paths.pending\` is EMPTY.
|
|
5156
|
+
6. \`submit_conversion_receipt { briefDigest, receipt }\`. It must answer \`complete: true\`; if it
|
|
5157
|
+
does not, go back to step 5 \u2014 that is the only receipt that ends a conversion.
|
|
5158
|
+
7. Deploy (push, or \`deploy_project\`) and poll \`get_deploy_status\` until the release is live.
|
|
5159
|
+
8. \`get_binding_report\` must read \`unmatched\` 0 and \`unaddressable\` under 2% of each route's
|
|
5160
|
+
visible text. Fix what it names in the SOURCE, deploy, re-read (playbook section 11).
|
|
5161
|
+
9. \`set_binding_mode { declaredBindings: true }\`, deploy AGAIN and poll \u2014 the mode applies to the
|
|
5162
|
+
NEXT release \u2014 then confirm \`get_binding_report\` reads mode "declared", unmatched 0, bound
|
|
5163
|
+
above 0, \`coverage.pending\` empty. That report is your final answer to me.`;
|
|
5111
5164
|
var SCHEMA_PROPOSAL_FLOW = `### Whole-project design (confirm-first) \u2192 \`create_component\` / \`create_page\` / \`create_content_model\`
|
|
5112
5165
|
Design the WHOLE project from its brief or its code, and **confirm the shape with the user
|
|
5113
5166
|
BEFORE creating anything**. Never silently guess.
|
|
@@ -5487,6 +5540,19 @@ in the dashboard \u2014 those render as an empty string until they are published
|
|
|
5487
5540
|
]
|
|
5488
5541
|
})
|
|
5489
5542
|
);
|
|
5543
|
+
server.registerPrompt(
|
|
5544
|
+
"convert_site",
|
|
5545
|
+
{
|
|
5546
|
+
title: "Convert a site so nothing is left pending (guided)",
|
|
5547
|
+
description: "Run the conversion end to end: pull the source, run @bettercms-ai/convert, apply every fix it lists as pending, rerun until nothing is pending, and finish only when submit_conversion_receipt returns complete and get_binding_report reads unmatched 0.",
|
|
5548
|
+
argsSchema: {
|
|
5549
|
+
projectId: z2.string().optional().describe("the project to convert; omit if this connection is scoped to one")
|
|
5550
|
+
}
|
|
5551
|
+
},
|
|
5552
|
+
({ projectId }) => ({
|
|
5553
|
+
messages: [{ role: "user", content: { type: "text", text: CONVERT_SITE_TEXT(projectId) } }]
|
|
5554
|
+
})
|
|
5555
|
+
);
|
|
5490
5556
|
server.registerPrompt(
|
|
5491
5557
|
"build_site",
|
|
5492
5558
|
{
|