@bettercms-ai/mcp 0.53.1 → 0.54.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 CHANGED
@@ -2424,7 +2424,7 @@ function toField(f) {
2424
2424
  ...f.config ? { config: f.config } : {}
2425
2425
  };
2426
2426
  }
2427
- var BRAND_KIT_NOTE = "`brandKit` is the project's brand kit, read-only: colors, typography, radius and any typeScale/spacing/elevation/motion. It is edited in the BetterCMS dashboard; style sections with their `style` tokens rather than copying its values into props.";
2427
+ var BRAND_KIT_NOTE = "`brandKit` is the project's brand kit: colors, typography, radius and any typeScale/spacing/elevation/motion/graphics. It is edited in the BetterCMS dashboard AND by set_brand_assets and set_brand_graphics below; every write is a version a person can restore. ASSETS: point `mark`/`favicon` at a media asset OF THIS PROJECT by id (another project's id is refused); null clears one; a slot a person chose needs `replace: true`. GRAPHICS: gradients are STRUCTURED \u2014 `kind`, optional `angle`, 2-8 stops naming kit colour keys (`colors.*` or an `extras` key) \u2014 never a CSS string and never a hex; a colour the kit lacks is added to `extras` first. CONSUME, DO NOT COPY: on a hosted page the kit is `--brand-color-<key>`, `--brand-shadow-<key>` and `--brand-gradient-<key>`; style sections with their `style` tokens (`nav.backgroundGradient` and `footer.backgroundGradient` take a gradient key) rather than copying its values into props. Never invent brand facts: read them here.";
2428
2428
  var MODEL_IF_MATCH_NOTE = "optimisticVersion from get_content_model. When sent, the update applies only if the model is still at that version; a 409 means someone changed it since \u2014 read it again and re-apply, never retry blindly. Omitted, the last write wins.";
2429
2429
  var ENTRY_META_NOTE = "SEO is native: set this entry's metaTitle, metaDescription, noindex, canonical, og, twitter, schemaType or schema in `meta` (never as model fields). `meta` MERGES into what is stored: a key you leave out is kept, null or an empty string clears it.";
2430
2430
  var PAGE_HEAD_NOTE = "noindex, canonical, og and twitter set the page's head extras; each MERGES into what is stored (a key you leave out is kept, null or an empty string clears it).";
@@ -3237,6 +3237,36 @@ ${d.outline}` : "Proposed Content structure.", d);
3237
3237
  }).shape,
3238
3238
  async (c, a) => structureResult(await data(c, "POST", `/management/content-structure/move`, { kind: a.kind, id: a.id, folderId: a.folderId }))
3239
3239
  ),
3240
+ def(
3241
+ "set_brand_assets",
3242
+ "Point the brand's logo or favicon at a media asset",
3243
+ `Point the brand's logo (mark) or favicon at a media asset of this project. The asset must already be in this project's media library \u2014 upload it first if it is not. null clears a slot. A slot a PERSON chose is refused unless you pass replace: true, and the refusal says so. Every write is a brand version the dashboard can restore. ${BRAND_KIT_NOTE}`,
3244
+ z.object({
3245
+ mark: z.string().min(1).nullable().optional().describe("media asset id for the logo; null clears it"),
3246
+ favicon: z.string().min(1).nullable().optional().describe("media asset id for the favicon; null clears it"),
3247
+ replace: z.boolean().optional().describe("override a slot a person chose (default false)")
3248
+ }).shape,
3249
+ async (c, a) => ok("Brand assets.", await data(c, "POST", `/management/projects/current/brand-kit/assets`, { mark: a.mark, favicon: a.favicon, replace: a.replace }))
3250
+ ),
3251
+ def(
3252
+ "set_brand_graphics",
3253
+ "Add, replace or remove the brand's gradients",
3254
+ `Add, replace or remove the brand's gradients \u2014 the blobs and washes a site uses as backgrounds. They are TOKENS, not media: a gradient is a value, so it is stored as a kind, an angle and stops that name the kit's own colour keys, never as CSS and never as a hex. A stop naming a colour the kit does not have is refused; add it to extras first. Upsert matches BY KEY. ${BRAND_KIT_NOTE}`,
3255
+ z.object({
3256
+ upsert: z.array(z.object({
3257
+ key: z.string().min(1).describe("token key: lowercase letters, digits and hyphens. Emitted as --brand-gradient-<key>"),
3258
+ label: z.string().optional(),
3259
+ kind: z.enum(["linear", "radial", "conic"]),
3260
+ angle: z.number().optional().describe("degrees 0-360; ignored for radial"),
3261
+ stops: z.array(z.object({
3262
+ color: z.string().min(1).describe("a kit colour KEY \u2014 a colors.* role name or an extras key. Never a hex."),
3263
+ at: z.number().describe("position 0-100")
3264
+ })).min(2).max(8)
3265
+ })).max(12).optional().describe("gradients to add or replace, matched BY KEY"),
3266
+ remove: z.array(z.string().min(1)).max(12).optional().describe("gradient keys to remove")
3267
+ }).shape,
3268
+ async (c, a) => ok("Brand graphics.", await data(c, "POST", `/management/projects/current/brand-kit/graphics`, { upsert: a.upsert, remove: a.remove }))
3269
+ ),
3240
3270
  def(
3241
3271
  "set_icon",
3242
3272
  "Set a Content sidebar icon",
@@ -3453,7 +3483,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3453
3483
  def(
3454
3484
  "submit_conversion_receipt",
3455
3485
  "Record what the conversion codemod could and could not do",
3456
- "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 `--forms` RECEIPT TOO, AS A SECOND CALL. 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, wiredForms[{ id, file }] }`). Write it to its own file (`--receipt forms-receipt.json`) and submit it here under the same `briefDigest`: it is stored BESIDE the binding receipt and never touches coverage, and the next release reads `wiredForms` to say which component renders each form. Then read `forms.pending` and publish every form `forms.notes` names in the Forms tab. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
3486
+ "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?, kind? }`, 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 \u2014 and when it carries `kind: document` the container is the ONE element wrapping every block of a Body, never the paragraph holding its first block), `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 `--forms` RECEIPT TOO, AS A SECOND CALL. 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, wiredForms[{ id, file }] }`). Write it to its own file (`--receipt forms-receipt.json`) and submit it here under the same `briefDigest`: it is stored BESIDE the binding receipt and never touches coverage, and the next release reads `wiredForms` to say which component renders each form. Then read `forms.pending` and publish every form `forms.notes` names in the Forms tab. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
3457
3487
  z.object({
3458
3488
  briefDigest: z.string().min(1).describe("The `briefDigest` get_conversion_brief { complete: true } returned. Must match the receipt's own."),
3459
3489
  receipt: z.record(z.string(), z.unknown()).describe("The receipt `npx @bettercms-ai/convert --receipt out.json` wrote, verbatim.")
@@ -6093,7 +6123,7 @@ in the dashboard \u2014 those render as an empty string until they are published
6093
6123
  "convert_site",
6094
6124
  {
6095
6125
  title: "Convert a site so nothing is left pending (guided)",
6096
- 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.",
6126
+ 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, coverage.pending empty and every route's unaddressable under 2%.",
6097
6127
  argsSchema: {
6098
6128
  projectId: z2.string().optional().describe("the project to convert; omit if this connection is scoped to one")
6099
6129
  }