@cmo-so/mcp 0.12.13 → 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.
- package/dist/cmo-mcp.mjs +89 -18
- 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.
|
|
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
|
|
|
@@ -17539,7 +17594,7 @@ async function llmsTxt(site, o = {}) {
|
|
|
17539
17594
|
const base = (site.url ?? "").replace(/\/+$/, "");
|
|
17540
17595
|
const s = await site.request({
|
|
17541
17596
|
path: "/wp/v2/settings",
|
|
17542
|
-
query: { _fields: "title,description,page_on_front,page_for_posts" }
|
|
17597
|
+
query: { _fields: "title,description,page_on_front,page_for_posts,blog_public" }
|
|
17543
17598
|
}).then((r) => r.status === 200 ? r.data : {});
|
|
17544
17599
|
const pages = await listPages(site, { status: "publish" });
|
|
17545
17600
|
const posts = await all(
|
|
@@ -17548,10 +17603,20 @@ async function llmsTxt(site, o = {}) {
|
|
|
17548
17603
|
{ status: "publish", per_page: 100, _fields: "link,title,excerpt,meta", orderby: "date", order: "desc" },
|
|
17549
17604
|
o.posts ?? 25
|
|
17550
17605
|
);
|
|
17551
|
-
const
|
|
17606
|
+
const clip3 = (t, n2 = 200) => {
|
|
17607
|
+
const x = t.replace(/\s+/g, " ").trim();
|
|
17608
|
+
if (x.length <= n2) return x;
|
|
17609
|
+
const cut = x.slice(0, n2);
|
|
17610
|
+
return `${cut.slice(0, Math.max(cut.lastIndexOf(" "), n2 - 30)).replace(/[\s,;:–-]+$/, "")}\u2026`;
|
|
17611
|
+
};
|
|
17612
|
+
const desc = (m, fallback = "") => clip3(String(m?.maggie_seo_description || fallback));
|
|
17613
|
+
const isSample = (link, title) => /\/(sample-page|hello-world)\/?$/.test(link) || title === "Sample Page" || title === "Hello world!";
|
|
17552
17614
|
const home = pages.find((p) => p.id === s.page_on_front);
|
|
17553
17615
|
const noise = /privacy|terms|cookie|thank|cart|checkout|my-account|login/i;
|
|
17554
|
-
const visible = pages.filter(
|
|
17616
|
+
const visible = pages.filter(
|
|
17617
|
+
(p) => p.id !== s.page_on_front && p.id !== s.page_for_posts && !noise.test(p.link) && !isSample(p.link, titleOf(p.title))
|
|
17618
|
+
);
|
|
17619
|
+
const realPosts = posts.filter((p) => !isSample(p.link, strip(p.title.rendered ?? "")));
|
|
17555
17620
|
const byParent = /* @__PURE__ */ new Map();
|
|
17556
17621
|
for (const p of visible) byParent.set(p.parent, [...byParent.get(p.parent) ?? [], p]);
|
|
17557
17622
|
const line = (title, url, d) => `- [${title.replace(/[[\]]/g, "")}](${url})${d ? `: ${d}` : ""}`;
|
|
@@ -17559,25 +17624,27 @@ async function llmsTxt(site, o = {}) {
|
|
|
17559
17624
|
const summary = desc(home?.meta, s.description ?? "");
|
|
17560
17625
|
if (summary) out.push(`> ${summary}`, "");
|
|
17561
17626
|
const top = byParent.get(0) ?? [];
|
|
17562
|
-
if (top.length) {
|
|
17627
|
+
if (top.length || home) {
|
|
17563
17628
|
out.push("## Pages", "");
|
|
17629
|
+
if (home) out.push(line(titleOf(home.title) || "Home", home.link, summary));
|
|
17564
17630
|
for (const p of top) {
|
|
17565
17631
|
out.push(line(titleOf(p.title), p.link, desc(p.meta)));
|
|
17566
17632
|
for (const c of byParent.get(p.id) ?? []) out.push(` ${line(titleOf(c.title), c.link, desc(c.meta))}`);
|
|
17567
17633
|
}
|
|
17568
17634
|
out.push("");
|
|
17569
17635
|
}
|
|
17570
|
-
if (
|
|
17636
|
+
if (realPosts.length) {
|
|
17571
17637
|
out.push("## Articles", "");
|
|
17572
|
-
for (const p of
|
|
17573
|
-
out.push(
|
|
17574
|
-
line(strip(p.title.rendered ?? ""), p.link, desc(p.meta, strip(p.excerpt?.rendered ?? "").slice(0, 160)))
|
|
17575
|
-
);
|
|
17638
|
+
for (const p of realPosts)
|
|
17639
|
+
out.push(line(strip(p.title.rendered ?? ""), p.link, desc(p.meta, strip(p.excerpt?.rendered ?? ""))));
|
|
17576
17640
|
out.push("");
|
|
17577
17641
|
}
|
|
17578
17642
|
const legal = pages.filter((p) => /privacy|terms/i.test(p.link));
|
|
17579
|
-
|
|
17580
|
-
|
|
17643
|
+
const optional2 = [
|
|
17644
|
+
...s.blog_public === 0 ? [] : [line("Sitemap", `${base}/wp-sitemap.xml`, "every page and article")],
|
|
17645
|
+
...legal.map((p) => line(titleOf(p.title), p.link, ""))
|
|
17646
|
+
];
|
|
17647
|
+
if (optional2.length) out.push("## Optional", "", ...optional2);
|
|
17581
17648
|
return `${out.join("\n").replace(/\n{3,}/g, "\n\n").trim()}
|
|
17582
17649
|
`;
|
|
17583
17650
|
}
|
|
@@ -19472,12 +19539,16 @@ var elementTools = [
|
|
|
19472
19539
|
}),
|
|
19473
19540
|
tool({
|
|
19474
19541
|
name: "wp_footer_set",
|
|
19475
|
-
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.",
|
|
19476
19543
|
parameters: {
|
|
19477
19544
|
type: "object",
|
|
19478
19545
|
properties: {
|
|
19479
19546
|
...EDITS.properties ?? {},
|
|
19480
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
|
+
),
|
|
19481
19552
|
reset: bool("Drop the site's changes: back to the theme's footer.")
|
|
19482
19553
|
}
|
|
19483
19554
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.12.
|
|
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",
|