@cmo-so/mcp 0.12.8 → 0.12.10

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.
Files changed (2) hide show
  1. package/dist/cmo-mcp.mjs +178 -16
  2. package/package.json +1 -1
package/dist/cmo-mcp.mjs CHANGED
@@ -7199,7 +7199,7 @@ var require_dist = __commonJS({
7199
7199
  // package.json
7200
7200
  var package_default = {
7201
7201
  name: "@cmo-so/cli",
7202
- version: "0.12.8",
7202
+ version: "0.12.10",
7203
7203
  description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
7204
7204
  type: "module",
7205
7205
  license: "MIT",
@@ -14718,6 +14718,54 @@ function replace(blocks, ref, next, synced = {}) {
14718
14718
  function sectionAt(blocks, ref, synced = {}) {
14719
14719
  return pick(locate(blocks), ref, synced);
14720
14720
  }
14721
+ function widthValue(width) {
14722
+ const w = String(width ?? "").trim();
14723
+ if (w === "content") return null;
14724
+ if (w === "wide") return "var(--wp--style--global--wide-size)";
14725
+ if (/^\d{2,4}(\.\d+)?(px|rem|em|vw|%)$/.test(w)) return w;
14726
+ throw new Error('width: "content", "wide" or a CSS length such as 1240px.');
14727
+ }
14728
+ var NARROW_PX = 700;
14729
+ var sizePx = (v) => {
14730
+ const m = /^(\d+(?:\.\d+)?)(px|rem|em)$/.exec(v);
14731
+ return m ? Number(m[1]) * (m[2] === "px" ? 1 : 16) : null;
14732
+ };
14733
+ function setSectionWidth(b, width) {
14734
+ const size = widthValue(width);
14735
+ const before = [];
14736
+ const visit = (x, top) => {
14737
+ let changed = false;
14738
+ for (const c of x.inner) if (visit(c, false)) changed = true;
14739
+ const lay = x.attrs.layout;
14740
+ const constrained = lay?.type === "constrained";
14741
+ const cur = lay?.contentSize;
14742
+ const px = cur ? sizePx(cur) : null;
14743
+ const eligible = constrained && (!cur || cur.includes("--wp--style--global") || px == null || px >= NARROW_PX);
14744
+ if (eligible || top && !lay && (x.name === "core/group" || x.name === "core/cover")) {
14745
+ if ((cur ?? null) !== size) {
14746
+ before.push(cur ?? "content");
14747
+ const next = { ...lay ?? {}, type: "constrained" };
14748
+ if (size) next.contentSize = size;
14749
+ else delete next.contentSize;
14750
+ x.attrs = { ...x.attrs, layout: next };
14751
+ changed = true;
14752
+ }
14753
+ }
14754
+ if (changed) delete x.raw;
14755
+ return changed;
14756
+ };
14757
+ visit(b, true);
14758
+ return { containers: before.length, before };
14759
+ }
14760
+ function refreshRaw(list3) {
14761
+ let any = false;
14762
+ for (const b of list3) {
14763
+ const inner = refreshRaw(b.inner);
14764
+ if (inner) delete b.raw;
14765
+ if (inner || b.raw == null) any = true;
14766
+ }
14767
+ return any;
14768
+ }
14721
14769
  var escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14722
14770
  function applyEdits(b, e) {
14723
14771
  let m = serializeBlock(b);
@@ -15155,6 +15203,15 @@ async function styleGet(site) {
15155
15203
  fonts: (user.settings?.typography?.fontFamilies?.theme ?? []).map((f) => f.slug)
15156
15204
  },
15157
15205
  contrast: contrastReport(Object.fromEntries([...palette.values()].map((c) => [c.slug, c.color]))),
15206
+ layout: {
15207
+ content: user.settings?.layout?.contentSize ?? base.settings?.layout?.contentSize ?? null,
15208
+ wide: user.settings?.layout?.wideSize ?? base.settings?.layout?.wideSize ?? null,
15209
+ padding: {
15210
+ left: user.styles?.spacing?.padding?.left ?? base.styles?.spacing?.padding?.left ?? null,
15211
+ right: user.styles?.spacing?.padding?.right ?? base.styles?.spacing?.padding?.right ?? null
15212
+ }
15213
+ },
15214
+ css: user.styles?.css ?? "",
15158
15215
  _palette: [...palette.values()],
15159
15216
  _fonts: [...fonts.values()],
15160
15217
  _user: user
@@ -15195,6 +15252,17 @@ function contrastReport(colors) {
15195
15252
  });
15196
15253
  }
15197
15254
  async function styleSet(site, change, force = false) {
15255
+ const len = (v, what) => {
15256
+ const t = String(v ?? "").trim();
15257
+ if (!/^(0|\d{1,4}(\.\d+)?(px|rem|em|vw|%)|var\(--wp--preset--spacing--[a-z0-9-]+\)|clamp\([0-9a-z.,%+\-\s()]+\))$/i.test(
15258
+ t
15259
+ ))
15260
+ throw new Error(`${what}: "${t}" isn't a CSS length like 1240px, 2rem or var(--wp--preset--spacing--50).`);
15261
+ return t;
15262
+ };
15263
+ for (const css of [change.css, change.css_append])
15264
+ if (css != null && /<\/?style|<\?|<script|@import\s+url\(\s*['"]?(?!https:)/i.test(css))
15265
+ throw new Error("css: plain CSS only (no <style>, <script>, PHP or non-https imports).");
15198
15266
  const cur = await styleGet(site);
15199
15267
  const palette = cur._palette.map((c) => ({ ...c }));
15200
15268
  for (const [slug, color] of Object.entries(change.colors ?? {})) {
@@ -15219,15 +15287,48 @@ async function styleSet(site, change, force = false) {
15219
15287
  );
15220
15288
  const user = cur._user;
15221
15289
  const settings = { ...user.settings ?? {} };
15290
+ const styles = { ...user.styles ?? {} };
15222
15291
  if (change.colors) settings.color = { ...settings.color ?? {}, palette: { theme: palette } };
15223
15292
  if (change.fonts)
15224
15293
  settings.typography = { ...settings.typography ?? {}, fontFamilies: { theme: fonts } };
15294
+ if (change.layout) {
15295
+ const layout = { ...settings.layout ?? {} };
15296
+ if (change.layout.content != null) layout.contentSize = len(change.layout.content, "layout.content");
15297
+ if (change.layout.wide != null) layout.wideSize = len(change.layout.wide, "layout.wide");
15298
+ settings.layout = layout;
15299
+ }
15300
+ if (change.padding) {
15301
+ const spacing = { ...styles.spacing ?? {} };
15302
+ const pad = { ...spacing.padding ?? {} };
15303
+ if (change.padding.left != null) pad.left = len(change.padding.left, "padding.left");
15304
+ if (change.padding.right != null) pad.right = len(change.padding.right, "padding.right");
15305
+ styles.spacing = { ...spacing, padding: pad };
15306
+ settings.useRootPaddingAwareAlignments = true;
15307
+ }
15308
+ if (change.css != null) styles.css = change.css;
15309
+ if (change.css_append)
15310
+ styles.css = `${String(styles.css ?? "").trimEnd()}
15311
+
15312
+ ${change.css_append.trim()}
15313
+ `.trimStart();
15225
15314
  await call(site, {
15226
15315
  method: "POST",
15227
15316
  path: `/wp/v2/global-styles/${cur.id}`,
15228
- body: { settings, styles: user.styles ?? {} }
15317
+ body: { settings, styles }
15229
15318
  });
15319
+ const changedLayout = change.layout || change.padding;
15230
15320
  return {
15321
+ ...changedLayout ? {
15322
+ layout: {
15323
+ before: cur.layout,
15324
+ after: {
15325
+ content: settings.layout?.contentSize ?? cur.layout.content,
15326
+ wide: settings.layout?.wideSize ?? cur.layout.wide,
15327
+ padding: change.padding ? { ...cur.layout.padding, ...change.padding } : cur.layout.padding
15328
+ }
15329
+ }
15330
+ } : {},
15331
+ ...change.css != null || change.css_append ? { css: { before: cur.css.length, after: String(styles.css ?? "").length, before_text: cur.css } } : {},
15231
15332
  colors: Object.fromEntries(
15232
15333
  Object.entries(change.colors ?? {}).map(([k, v]) => [k, { before: cur.colors[k], after: v }])
15233
15334
  ),
@@ -16269,7 +16370,7 @@ var siteTools = [
16269
16370
  }),
16270
16371
  tool({
16271
16372
  name: "wp_style_get",
16272
- description: "The brand: colours (by name: canvas, panel, soft, ink, muted, olive, accent\u2026) and fonts (display, body), and the text contrast of each colour pair.",
16373
+ description: "The site's look: colours (by name: canvas, panel, soft, ink, muted, olive, accent\u2026), fonts (display, body), layout widths (content, wide) and page padding, the extra CSS, and the text contrast of each colour pair.",
16273
16374
  parameters: { type: "object", properties: {} },
16274
16375
  group: "site",
16275
16376
  need: READ,
@@ -16277,7 +16378,14 @@ var siteTools = [
16277
16378
  gates: ["WP"],
16278
16379
  run: async (ctx) => {
16279
16380
  const s = await styleGet(siteOf(ctx));
16280
- return { colors: s.colors, fonts: s.fonts, customized: s.customized, contrast: s.contrast };
16381
+ return {
16382
+ colors: s.colors,
16383
+ fonts: s.fonts,
16384
+ layout: s.layout,
16385
+ css: s.css,
16386
+ customized: s.customized,
16387
+ contrast: s.contrast
16388
+ };
16281
16389
  }
16282
16390
  })
16283
16391
  ];
@@ -16719,12 +16827,15 @@ var sectionTools = [
16719
16827
  }),
16720
16828
  tool({
16721
16829
  name: "wp_section_update",
16722
- description: "Change one section. edits: change text, links or pictures inside it and keep its design (works on any section, the homepage too). slots: write the section again from new content (generated types; give the complete content). faq: add, remove (1-based numbers) or replace questions.",
16830
+ description: 'Change one section. width: line its content up with the header and footer ("wide") or another width, one section or all. edits: change text, links or pictures inside it and keep its design (works on any section, the homepage too). slots: write the section again from new content (generated types; give the complete content). faq: add, remove (1-based numbers) or replace questions.',
16723
16831
  parameters: {
16724
16832
  type: "object",
16725
16833
  properties: {
16726
16834
  page: PAGE,
16727
- section: str2("Section id from wp_page_get (s2, m4) or its type (faq)."),
16835
+ section: str2('Section id from wp_page_get (s2, m4) or its type (faq); "all" with width only.'),
16836
+ width: str2(
16837
+ `The section's inner width: "content" (the site's content width), "wide" (the site's wide width, as the header and footer) or a CSS length (1240px). Changes the section and its containers wider than 700px; with section "all", every section on the page.`
16838
+ ),
16728
16839
  edits: EDITS,
16729
16840
  slots: obj("Complete new content for the section (same type unless type is given)."),
16730
16841
  markup: str2(
@@ -16753,6 +16864,19 @@ var sectionTools = [
16753
16864
  ),
16754
16865
  run: (ctx, a) => changePage(ctx, a, (blocks, kit) => {
16755
16866
  const ref = String(a.section);
16867
+ if (a.width != null && a.width !== "") {
16868
+ const targets = ref === "all" ? locate(blocks) : [sectionAt(blocks, ref, kit.syncedTitles)];
16869
+ const changed = {};
16870
+ for (const t of targets) {
16871
+ if (t.block.name === "core/block") continue;
16872
+ const r = setSectionWidth(t.block, a.width);
16873
+ if (r.containers) changed[t.id] = r;
16874
+ }
16875
+ refreshRaw(blocks);
16876
+ if (!Object.keys(changed).length) throw new Error("Those sections already have that width.");
16877
+ return { blocks, note: { width: String(a.width), sections: changed } };
16878
+ }
16879
+ if (ref === "all") throw new Error('section "all" works with width only.');
16756
16880
  const target = sectionAt(blocks, ref, kit.syncedTitles);
16757
16881
  const type = classify(target.block, target.region, kit.syncedTitles);
16758
16882
  let next;
@@ -17119,7 +17243,7 @@ var childTools = [
17119
17243
  }),
17120
17244
  tool({
17121
17245
  name: "wp_child_theme_update",
17122
- description: "Change a Maggie child theme's files (default: the active child): brand colours (by name, hex) and fonts (by name, CSS font-family of fonts already in it) in theme.json, extra theme.json styles, plain CSS (replace or append), the header or footer (block markup), templates, and the site's own patterns (shown in the editor's inserter). Data only, never code. Each change keeps a backup (wp_child_theme_restore). Changes made with wp_style_set / wp_header_set are stored in the database and win over the child's files.",
17246
+ description: "Change a Maggie child theme's files (default: the active child): brand colours (by name, hex) and fonts (by name, CSS font-family of fonts already in it) in theme.json, extra theme.json styles, plain CSS (replace or append), the site's own JavaScript (assets/js/site.js, replace or append; runs for every visitor), the header or footer (block markup), templates, and the site's own patterns (shown in the editor's inserter). No PHP or server code. Each change keeps a backup (wp_child_theme_restore). Changes made with wp_style_set / wp_header_set are stored in the database and win over the child's files.",
17123
17247
  parameters: {
17124
17248
  type: "object",
17125
17249
  properties: {
@@ -17140,6 +17264,10 @@ var childTools = [
17140
17264
  styles: obj('theme.json styles to merge in (e.g. { "elements": { "button": { \u2026 } } }).'),
17141
17265
  css: str2("Replace the child's CSS (plain CSS)."),
17142
17266
  css_append: str2("Add CSS at the end."),
17267
+ js: str2(
17268
+ `Replace the site's own script (assets/js/site.js, plain JavaScript loaded deferred on every page; "" turns it off). Needs Maggie Core 0.12+ and a login allowed to add scripts. No eval or new Function; addresses only on the site or cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com.`
17269
+ ),
17270
+ js_append: str2("Add JavaScript at the end of the site's script."),
17143
17271
  header: str2("Header block markup (parts/header.html)."),
17144
17272
  footer: str2("Footer block markup (parts/footer.html)."),
17145
17273
  templates: obj('Templates by slug \u2192 block markup, e.g. { "page-landing": "\u2026" }.'),
@@ -17153,8 +17281,8 @@ var childTools = [
17153
17281
  gates: [...GATES],
17154
17282
  confirm: (a, lang) => say(
17155
17283
  lang,
17156
- `Change the child theme ${q2(a.slug, "(the active one)")} on your website.`,
17157
- `\u4FEE\u6539\u7DB2\u7AD9\u7684\u5B50\u4E3B\u984C ${q2(a.slug, "\uFF08\u76EE\u524D\u4F7F\u7528\u4E2D\u7684\uFF09")}\u3002`
17284
+ `Change the child theme ${q2(a.slug, "(the active one)")} on your website.${typeof a.js === "string" || typeof a.js_append === "string" ? a.js === "" ? " Turns off the site's own script." : " Includes JavaScript that runs in every visitor's browser on every page." : ""}`,
17285
+ `\u4FEE\u6539\u7DB2\u7AD9\u7684\u5B50\u4E3B\u984C ${q2(a.slug, "\uFF08\u76EE\u524D\u4F7F\u7528\u4E2D\u7684\uFF09")}\u3002${typeof a.js === "string" || typeof a.js_append === "string" ? a.js === "" ? "\u95DC\u9589\u7DB2\u7AD9\u81EA\u8A02\u8173\u672C\u3002" : "\u5305\u542B\u6703\u5728\u6BCF\u4F4D\u8A2A\u5BA2\u700F\u89BD\u5668\u3001\u6BCF\u500B\u9801\u9762\u57F7\u884C\u7684 JavaScript\u3002" : ""}`
17158
17286
  ),
17159
17287
  run: async (ctx, a) => {
17160
17288
  const site = siteOf(ctx);
@@ -17252,6 +17380,22 @@ var childTools = [
17252
17380
  if (typeof a.css_append === "string")
17253
17381
  files["style.css"] = `${(files["style.css"] ?? cur["style.css"] ?? "").replace(/\n*$/, "\n")}${a.css_append}
17254
17382
  `;
17383
+ if (typeof a.js === "string" || typeof a.js_append === "string") {
17384
+ const parentV = String(info.parent?.version ?? "0");
17385
+ const [maj, min] = parentV.split(".").map(Number);
17386
+ if ((maj ?? 0) === 0 && (min ?? 0) < 12)
17387
+ throw new Error(
17388
+ `The site's script needs Maggie Core 0.12 or later (the site has ${parentV}): update it with wp_theme_update first.`
17389
+ );
17390
+ const js = typeof a.js === "string" ? a.js : `${(cur["assets/js/site.js"] ?? "").replace(/\n*$/, "\n")}${String(a.js_append)}
17391
+ `.trimStart();
17392
+ if (/<\/?script\b/i.test(js)) throw new Error("js: plain JavaScript only, no <script> tags.");
17393
+ if (/<\?(php|=)?/i.test(js)) throw new Error("js: PHP isn't allowed.");
17394
+ files["assets/js/site.js"] = js;
17395
+ notes.push(
17396
+ js.trim() ? "assets/js/site.js runs deferred on every page; undo with wp_child_theme_restore." : "The site's own script is off."
17397
+ );
17398
+ }
17255
17399
  const markup = (path2, m) => {
17256
17400
  if (typeof m !== "string" || !m.trim()) return;
17257
17401
  if (!parse(m).some((b) => b.name)) throw new Error(`${path2}: give block markup (<!-- wp:\u2026 -->).`);
@@ -17658,23 +17802,41 @@ var elementTools = [
17658
17802
  }),
17659
17803
  tool({
17660
17804
  name: "wp_style_set",
17661
- description: "Change brand colours (by name, hex) and fonts (by name, CSS font-family) for the whole site. Refuses colours that would make text hard to read (contrast under 4.5:1).",
17805
+ description: "Change the whole site's look: brand colours (by name, hex), fonts (by name, CSS font-family), layout widths (content: the width sections and text use; wide: the header, footer and wide blocks), the left and right page padding, and extra CSS. Saved in the site's Global Styles (works on any block theme; returns before and after). Refuses colours that would make text hard to read (contrast under 4.5:1). One section's width: wp_section_update width.",
17662
17806
  parameters: {
17663
17807
  type: "object",
17664
17808
  properties: {
17665
17809
  colors: obj('e.g. { "olive": "#4F5B48", "accent": "#A8834F" }.'),
17666
- fonts: obj('e.g. { "display": "\\"Playfair Display\\", Georgia, serif" }.')
17810
+ fonts: obj('e.g. { "display": "\\"Playfair Display\\", Georgia, serif" }.'),
17811
+ layout: obj('Widths, e.g. { "content": "1240px", "wide": "1240px" }.', {
17812
+ content: str2("Content width (CSS length)."),
17813
+ wide: str2("Wide width (CSS length).")
17814
+ }),
17815
+ padding: obj('Left and right page padding, e.g. { "left": "24px", "right": "24px" }.', {
17816
+ left: str2(""),
17817
+ right: str2("")
17818
+ }),
17819
+ css: str2("Replace the site's extra CSS (Global Styles \u2192 Additional CSS); plain CSS only."),
17820
+ css_append: str2("Add this CSS after the site's extra CSS.")
17667
17821
  }
17668
17822
  },
17669
17823
  group: "site",
17670
17824
  need: WRITE,
17671
17825
  risk: "W3",
17672
17826
  gates: [...GATES],
17673
- confirm: (_a3, lang) => say(lang, "Change the website's brand colours or fonts (every page).", "\u4FEE\u6539\u7DB2\u7AD9\u7684\u54C1\u724C\u984F\u8272\u6216\u5B57\u9AD4\uFF08\u6240\u6709\u9801\u9762\uFF09\u3002"),
17674
- run: async (ctx, a) => {
17675
- const r = await styleSet(siteOf(ctx), { colors: a.colors, fonts: a.fonts });
17676
- return r;
17677
- }
17827
+ confirm: (_a3, lang) => say(
17828
+ lang,
17829
+ "Change the website's look for every page (colours, fonts, widths, padding or extra CSS); the result shows before and after.",
17830
+ "\u4FEE\u6539\u7DB2\u7AD9\u6240\u6709\u9801\u9762\u7684\u5916\u89C0\uFF08\u984F\u8272\u3001\u5B57\u9AD4\u3001\u5BEC\u5EA6\u3001\u908A\u8DDD\u6216\u984D\u5916 CSS\uFF09\uFF1B\u7D50\u679C\u6703\u5217\u51FA\u4FEE\u6539\u524D\u5F8C\u3002"
17831
+ ),
17832
+ run: async (ctx, a) => styleSet(siteOf(ctx), {
17833
+ colors: a.colors,
17834
+ fonts: a.fonts,
17835
+ layout: a.layout,
17836
+ padding: a.padding,
17837
+ css: typeof a.css === "string" ? a.css : void 0,
17838
+ css_append: typeof a.css_append === "string" ? a.css_append : void 0
17839
+ })
17678
17840
  })
17679
17841
  ];
17680
17842
  function checkMarkup(markup, what) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/mcp",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "description": "CMO.so's MCP server for Claude Desktop, Cursor, VS Code and other MCP clients: manage your websites, blog posts, LinkedIn queue and results with Maggie.",
5
5
  "type": "module",
6
6
  "license": "MIT",