@cmo-so/mcp 0.12.14 → 0.12.15

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 +66 -7
  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.14",
7202
+ version: "0.12.15",
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",
@@ -15165,6 +15165,47 @@ async function partSet(site, slug, change) {
15165
15165
  counts.social = (counts.social ?? 0) + links2.length;
15166
15166
  }
15167
15167
  }
15168
+ if (change.footer_links && slug === "footer") {
15169
+ const isRow = (b) => String(b.attrs.className ?? "").split(/\s+/).includes("maggie-footer-links");
15170
+ const items3 = change.footer_links.filter((l) => l?.label && l?.url).slice(0, 20).map(
15171
+ (l) => `<!-- wp:paragraph {"fontSize":"small"} -->
15172
+ <p class="has-small-font-size"><a href="${escAttr(l.url)}">${escAttr(l.label)}</a></p>
15173
+ <!-- /wp:paragraph -->`
15174
+ ).join("\n\n");
15175
+ const row = items3 ? blockOf(
15176
+ `<!-- wp:group {"className":"maggie-footer-links","style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40"}}},"layout":{"type":"flex","flexWrap":"wrap"}} -->
15177
+ <div class="wp-block-group maggie-footer-links" style="margin-bottom:var(--wp--preset--spacing--40)">${items3}</div>
15178
+ <!-- /wp:group -->`
15179
+ ) : null;
15180
+ const old = find(blocks, isRow);
15181
+ if (old) {
15182
+ const top = blocks.indexOf(old);
15183
+ if (top >= 0) blocks.splice(top, 1, ...row ? [row] : []);
15184
+ else {
15185
+ const parent = find(blocks, (b) => b.inner.includes(old));
15186
+ const k = parent.inner.indexOf(old);
15187
+ if (row) parent.inner[k] = row;
15188
+ else {
15189
+ parent.inner.splice(k, 1);
15190
+ let seen = -1;
15191
+ const at = parent.content.findIndex((c) => c === null && ++seen === k);
15192
+ parent.content.splice(at, parent.content[at + 1] === "\n\n" ? 2 : 1);
15193
+ }
15194
+ touchPath(blocks, parent);
15195
+ }
15196
+ } else if (row) {
15197
+ const outer = blocks.find((b) => b.name === "core/group");
15198
+ if (outer?.inner.length) {
15199
+ const k = outer.inner.length - 1;
15200
+ outer.inner.splice(k, 0, row);
15201
+ let seen = -1;
15202
+ const at = outer.content.findIndex((c) => c === null && ++seen === k);
15203
+ outer.content.splice(at, 0, null, "\n\n");
15204
+ touchPath(blocks, outer);
15205
+ } else blocks.push(row);
15206
+ } else throw new Error("footer_links: give at least one { label, url }.");
15207
+ counts.footer_links = change.footer_links.length;
15208
+ }
15168
15209
  const content = serialize(blocks);
15169
15210
  if (content === p.content) return { slug, changed: false, counts };
15170
15211
  await call(site, { method: "POST", path: `/wp/v2/template-parts/${p.id}`, body: { content } });
@@ -17316,6 +17357,11 @@ async function linksPlan(site, o = {}) {
17316
17357
  );
17317
17358
  const footer = await partGet(site, "footer").catch(() => null);
17318
17359
  const footerPaths = footer ? contentLinks(footer.content, base) : /* @__PURE__ */ new Set();
17360
+ const rowHtml = /maggie-footer-links[^>]*>([\s\S]*?)<\/div>\s*<!-- \/wp:group -->/.exec(footer?.content ?? "")?.[1] ?? "";
17361
+ const footerRow = [...rowHtml.matchAll(/<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g)].map((m) => ({
17362
+ url: m[1],
17363
+ label: strip(m[2])
17364
+ }));
17319
17365
  const header = await partGet(site, "header").catch(() => null);
17320
17366
  const headerPaths = header ? contentLinks(header.content, base) : /* @__PURE__ */ new Set();
17321
17367
  const chrome = /* @__PURE__ */ new Set([...menuPaths, ...footerPaths, ...headerPaths]);
@@ -17378,14 +17424,16 @@ async function linksPlan(site, o = {}) {
17378
17424
  type: "menu",
17379
17425
  why: "Main pages every visitor should reach in one click.",
17380
17426
  add: wantMenu.slice(0, 6).map((d) => ({ label: d.title, url: d.url })),
17381
- tool: "wp_menu_set"
17427
+ tool: "wp_menu_set",
17428
+ args: { add: wantMenu.slice(0, 6).map((d) => ({ label: d.title, url: d.path })) }
17382
17429
  });
17383
17430
  if (postDocs.length && blogPage && !chrome.has(blogPage.path))
17384
17431
  actions.push({
17385
17432
  type: "menu",
17386
17433
  why: "The blog isn't reachable from the menu.",
17387
17434
  add: [{ label: blogPage.title || "Blog", url: blogPage.url }],
17388
- tool: "wp_menu_set"
17435
+ tool: "wp_menu_set",
17436
+ args: { add: [{ label: blogPage.title || "Blog", url: blogPage.path }] }
17389
17437
  });
17390
17438
  const legal = pageDocs.filter(
17391
17439
  (d) => /privacy|terms|cookie|legal|impressum|datenschutz|accessib/i.test(`${d.path} ${d.title}`)
@@ -17397,7 +17445,14 @@ async function linksPlan(site, o = {}) {
17397
17445
  type: "footer",
17398
17446
  why: "Legal, contact and about pages belong in the footer of every page (trust, and Google's quality raters look for them).",
17399
17447
  add: footerMissing.slice(0, 8).map((d) => ({ label: d.title, url: d.url })),
17400
- tool: "wp_footer_set"
17448
+ tool: "wp_footer_set",
17449
+ // The footer's own row (if this tool made one) plus the missing pages, ready for wp_footer_set.
17450
+ args: {
17451
+ footer_links: [
17452
+ ...footerRow,
17453
+ ...footerMissing.slice(0, 8).filter((d) => !footerRow.some((r) => sitePath(r.url, base) === d.path)).map((d) => ({ label: d.title, url: d.path }))
17454
+ ]
17455
+ }
17401
17456
  });
17402
17457
  for (const hub of hubs) {
17403
17458
  const kids = pageDocs.filter((c) => c.parent === hub.id);
@@ -17520,8 +17575,8 @@ function relatedBlock(heading2, links2) {
17520
17575
  const items3 = links2.map((l) => `<!-- wp:list-item -->
17521
17576
  <li><a href="${esc(l.url)}">${esc(l.label)}</a></li>
17522
17577
  <!-- /wp:list-item -->`).join("\n");
17523
- return `<!-- wp:group {"metadata":{"name":"Related links","maggie":{"type":"related-links","version":1}},"align":"wide","className":"maggie-related-links","layout":{"type":"constrained"}} -->
17524
- <div class="wp-block-group alignwide maggie-related-links"><!-- wp:heading {"level":2} -->
17578
+ return `<!-- wp:group {"metadata":{"name":"Related links","maggie":{"type":"related-links","version":1}},"align":"wide","className":"maggie-related-links","style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained","contentSize":"var(--wp--style--global--wide-size)"}} -->
17579
+ <div class="wp-block-group alignwide maggie-related-links" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:heading {"level":2} -->
17525
17580
  <h2 class="wp-block-heading">${esc(heading2)}</h2>
17526
17581
  <!-- /wp:heading -->
17527
17582
 
@@ -19484,12 +19539,16 @@ var elementTools = [
19484
19539
  }),
19485
19540
  tool({
19486
19541
  name: "wp_footer_set",
19487
- description: "Change the footer: any text (address, hours, copyright), link or picture, social profile links by service; reset goes back to the theme's own footer.",
19542
+ description: "Change the footer: any text (address, hours, copyright), link or picture, social profile links by service, the row of page links (footer_links); reset goes back to the theme's own footer.",
19488
19543
  parameters: {
19489
19544
  type: "object",
19490
19545
  properties: {
19491
19546
  ...EDITS.properties ?? {},
19492
19547
  social: obj('Social links by service: { "facebook": "https://\u2026", "instagram": "" (remove) }.'),
19548
+ footer_links: arr(
19549
+ "The footer's row of page links, in order (replaces the row set before; [] removes it): legal, contact, about, main pages.",
19550
+ obj("", { label: str2(""), url: str2("Relative for own pages: /contact/.") })
19551
+ ),
19493
19552
  reset: bool("Drop the site's changes: back to the theme's footer.")
19494
19553
  }
19495
19554
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/mcp",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
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",