@cmo-so/mcp 0.12.3 → 0.12.5
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 +151 -9
- 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.5",
|
|
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",
|
|
@@ -14220,7 +14220,7 @@ var SECTIONS2 = [
|
|
|
14220
14220
|
[
|
|
14221
14221
|
S(s.heading) || S(s.intro) ? prose([...headed(s), para(S(s.intro))]) : "",
|
|
14222
14222
|
group(boxes, {
|
|
14223
|
-
cls: `maggie-stagger grid gap-5 ${boxes.length
|
|
14223
|
+
cls: `maggie-stagger grid gap-5 ${boxes.length === 3 ? "sm:grid-cols-3" : "sm:grid-cols-2"}`
|
|
14224
14224
|
})
|
|
14225
14225
|
],
|
|
14226
14226
|
"flex flex-col gap-8"
|
|
@@ -14443,6 +14443,7 @@ var SECTIONS2 = [
|
|
|
14443
14443
|
slots: {
|
|
14444
14444
|
title: str('e.g. "Book your HydraFacial".'),
|
|
14445
14445
|
text: str("One sentence."),
|
|
14446
|
+
button_label: str('Default "Book Now".'),
|
|
14446
14447
|
button_url: str("Default: the booking link.")
|
|
14447
14448
|
},
|
|
14448
14449
|
required: ["title"],
|
|
@@ -14453,7 +14454,7 @@ var SECTIONS2 = [
|
|
|
14453
14454
|
buttons(
|
|
14454
14455
|
[
|
|
14455
14456
|
button(
|
|
14456
|
-
"Book Now",
|
|
14457
|
+
S(s.button_label) || "Book Now",
|
|
14457
14458
|
S(s.button_url) || kit.bookUrl || "/contact-us/",
|
|
14458
14459
|
"w-full [&_a]:w-full [&_a]:bg-white [&_a]:text-ink hover:[&_a]:bg-panel"
|
|
14459
14460
|
)
|
|
@@ -15001,7 +15002,7 @@ async function menuSet(site, ops, id) {
|
|
|
15001
15002
|
const blocks = parse(nav.content.raw);
|
|
15002
15003
|
const before = toItems(blocks);
|
|
15003
15004
|
const after = applyMenuOps(before, ops);
|
|
15004
|
-
const rest = blocks.filter((b) => b.name && !["core/navigation-link", "core/navigation-submenu"].includes(b.name));
|
|
15005
|
+
const rest = ops.items ? [] : blocks.filter((b) => b.name && !["core/navigation-link", "core/navigation-submenu"].includes(b.name));
|
|
15005
15006
|
const content = [fromItems(after), serialize(rest)].filter(Boolean).join("\n");
|
|
15006
15007
|
await call(site, {
|
|
15007
15008
|
method: "POST",
|
|
@@ -15495,7 +15496,11 @@ function composePage(sections, kit, aside = {}) {
|
|
|
15495
15496
|
const asideParts = [
|
|
15496
15497
|
kit.synced?.share ? group([syncedRef(kit.synced.share)], { cls: "px-2" }) : "",
|
|
15497
15498
|
serialize([
|
|
15498
|
-
buildSection(
|
|
15499
|
+
buildSection(
|
|
15500
|
+
"cta-card",
|
|
15501
|
+
{ title: aside.ctaTitle || "Book your visit", text: aside.ctaText ?? "", button_label: aside.ctaButton },
|
|
15502
|
+
kit
|
|
15503
|
+
)
|
|
15499
15504
|
])
|
|
15500
15505
|
];
|
|
15501
15506
|
const body = band(
|
|
@@ -16282,7 +16287,11 @@ var pageTools = [
|
|
|
16282
16287
|
description: str2(""),
|
|
16283
16288
|
keywords: str2("")
|
|
16284
16289
|
}),
|
|
16285
|
-
cta: obj("Sidebar booking card: title, text
|
|
16290
|
+
cta: obj("Sidebar booking card: title, text, button label (default Book Now).", {
|
|
16291
|
+
title: str2(""),
|
|
16292
|
+
text: str2(""),
|
|
16293
|
+
button: str2("")
|
|
16294
|
+
}),
|
|
16286
16295
|
related: bool(
|
|
16287
16296
|
"Add a related-services band from sibling pages (default true when the page has a reading column)."
|
|
16288
16297
|
),
|
|
@@ -16327,7 +16336,8 @@ var pageTools = [
|
|
|
16327
16336
|
const cta = a.cta ?? {};
|
|
16328
16337
|
const blocks = composePage(sections, kit, {
|
|
16329
16338
|
ctaTitle: cta.title || `Book ${String(a.title)}`,
|
|
16330
|
-
ctaText: cta.text
|
|
16339
|
+
ctaText: cta.text,
|
|
16340
|
+
ctaButton: cta.button
|
|
16331
16341
|
});
|
|
16332
16342
|
const outline = brief2(blocks, kit.syncedTitles);
|
|
16333
16343
|
if (a.preview) return { preview: true, title: a.title, sections: outline };
|
|
@@ -16991,6 +17001,17 @@ var childTools = [
|
|
|
16991
17001
|
slug: str2("The child theme (default the active one)."),
|
|
16992
17002
|
colors: obj('e.g. { "olive": "#4F5B48" }.'),
|
|
16993
17003
|
fonts: obj('Font families by slug: { "body": "Jost, sans-serif" } (files must already be in the child).'),
|
|
17004
|
+
font_files: arr(
|
|
17005
|
+
"Install fonts in the child from https links to font files (woff2/woff/ttf), e.g. Google Fonts' fonts.gstatic.com files: each becomes a fontFace of its family (slug display / body / any).",
|
|
17006
|
+
obj("", {
|
|
17007
|
+
slug: str2("Family slug in theme.json: display, body, \u2026"),
|
|
17008
|
+
family: str2('Family name, e.g. "Roboto Slab".'),
|
|
17009
|
+
fallback: str2('Fallback stack, e.g. "Georgia, serif".'),
|
|
17010
|
+
url: str2("https link to the font file."),
|
|
17011
|
+
weight: str2('e.g. "400", "800" or "100 900".'),
|
|
17012
|
+
style: str2('"normal" or "italic".')
|
|
17013
|
+
})
|
|
17014
|
+
),
|
|
16994
17015
|
styles: obj('theme.json styles to merge in (e.g. { "elements": { "button": { \u2026 } } }).'),
|
|
16995
17016
|
css: str2("Replace the child's CSS (plain CSS)."),
|
|
16996
17017
|
css_append: str2("Add CSS at the end."),
|
|
@@ -17017,9 +17038,54 @@ var childTools = [
|
|
|
17017
17038
|
const cur = info.files ?? {};
|
|
17018
17039
|
const files = {};
|
|
17019
17040
|
const notes = [];
|
|
17041
|
+
if (Array.isArray(a.font_files) && a.font_files.length) {
|
|
17042
|
+
const tj0 = JSON.parse(files["theme.json"] ?? cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17043
|
+
if (!tj0.settings || Array.isArray(tj0.settings)) tj0.settings = {};
|
|
17044
|
+
const typo = tj0.settings.typography ?? {};
|
|
17045
|
+
const fams = typo.fontFamilies ?? [];
|
|
17046
|
+
for (const f of a.font_files) {
|
|
17047
|
+
if (!/^[a-z0-9-]{2,30}$/.test(f.slug ?? ""))
|
|
17048
|
+
throw new Error(`Font slug "${f.slug}": lowercase letters, digits and dashes.`);
|
|
17049
|
+
if (!/^https:\/\//.test(f.url ?? "")) throw new Error(`${f.family}: the font's link must be https.`);
|
|
17050
|
+
const r = await fetch(f.url);
|
|
17051
|
+
if (!r.ok) throw new Error(`${f.family}: the font's link answered HTTP ${r.status}.`);
|
|
17052
|
+
const bytes = new Uint8Array(await r.arrayBuffer());
|
|
17053
|
+
const head = String.fromCharCode(...bytes.subarray(0, 4));
|
|
17054
|
+
const ext = head === "wOF2" ? "woff2" : head === "wOFF" ? "woff" : head === "OTTO" ? "otf" : head === "\0\0\0" || head === "true" ? "ttf" : "";
|
|
17055
|
+
if (!ext) throw new Error(`${f.family}: that link isn't a font file.`);
|
|
17056
|
+
if (bytes.length > 4 * 1024 * 1024) throw new Error(`${f.family}: the font file is larger than 4 MB.`);
|
|
17057
|
+
const weight = f.weight || "400";
|
|
17058
|
+
const style = f.style || "normal";
|
|
17059
|
+
const name = `${f.family.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${weight.replace(/\s+/g, "-")}${style === "italic" ? "-italic" : ""}.${ext}`;
|
|
17060
|
+
let bin = "";
|
|
17061
|
+
for (let i = 0; i < bytes.length; i += 32768) bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
17062
|
+
files[`assets/fonts/${name}`] = btoa(bin);
|
|
17063
|
+
let fam = fams.find((x) => x.slug === f.slug);
|
|
17064
|
+
const stack = `"${f.family}", ${f.fallback || "sans-serif"}`;
|
|
17065
|
+
if (!fam || !fam.fontFamily.includes(`"${f.family}"`)) {
|
|
17066
|
+
const keep = fam ? fams.indexOf(fam) : -1;
|
|
17067
|
+
fam = { slug: f.slug, name: f.family, fontFamily: stack, fontFace: [] };
|
|
17068
|
+
if (keep >= 0) fams[keep] = fam;
|
|
17069
|
+
else fams.push(fam);
|
|
17070
|
+
}
|
|
17071
|
+
fam.fontFace = [
|
|
17072
|
+
...(fam.fontFace ?? []).filter((x) => !(x.fontWeight === weight && x.fontStyle === style)),
|
|
17073
|
+
{
|
|
17074
|
+
fontFamily: f.family,
|
|
17075
|
+
fontWeight: weight,
|
|
17076
|
+
fontStyle: style,
|
|
17077
|
+
fontDisplay: "swap",
|
|
17078
|
+
src: [`file:./assets/fonts/${name}`]
|
|
17079
|
+
}
|
|
17080
|
+
];
|
|
17081
|
+
}
|
|
17082
|
+
tj0.settings.typography = { ...typo, fontFamilies: fams };
|
|
17083
|
+
files["theme.json"] = JSON.stringify(tj0, null, " ");
|
|
17084
|
+
}
|
|
17020
17085
|
if (a.colors || a.fonts || a.styles) {
|
|
17021
|
-
const tj = JSON.parse(cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17022
|
-
tj.settings
|
|
17086
|
+
const tj = JSON.parse(files["theme.json"] ?? cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17087
|
+
if (!tj.settings || Array.isArray(tj.settings)) tj.settings = {};
|
|
17088
|
+
if (Array.isArray(tj.styles)) tj.styles = {};
|
|
17023
17089
|
if (a.colors) {
|
|
17024
17090
|
let palette = tj.settings.color?.palette ?? [];
|
|
17025
17091
|
if (!palette.length) palette = ((await styleGet(site))._palette ?? []).map((c) => ({ ...c }));
|
|
@@ -17148,6 +17214,81 @@ var childTools = [
|
|
|
17148
17214
|
})
|
|
17149
17215
|
})
|
|
17150
17216
|
];
|
|
17217
|
+
var themeTools = [
|
|
17218
|
+
tool({
|
|
17219
|
+
name: "wp_theme_updates",
|
|
17220
|
+
description: "Is there a newer Maggie Core for this site? The installed and latest versions, release notes link, whether this host lets it update itself, auto-updates on or off, and the earlier versions kept for rollback.",
|
|
17221
|
+
parameters: { type: "object", properties: {} },
|
|
17222
|
+
group: "site",
|
|
17223
|
+
need: READ,
|
|
17224
|
+
risk: "R0",
|
|
17225
|
+
gates: ["WP"],
|
|
17226
|
+
run: (ctx) => call(siteOf(ctx), { path: "/maggie-core/v1/update", query: { fresh: 1 } })
|
|
17227
|
+
}),
|
|
17228
|
+
tool({
|
|
17229
|
+
name: "wp_theme_update",
|
|
17230
|
+
description: "Update Maggie Core on the site to the latest version (checked against its published checksum; the current version is kept for rollback), then compare the homepage before and after (words, fonts, brand colours). If the site breaks (an error page or most of the homepage gone) it rolls back by itself. rollback_to puts an earlier kept version back instead. The site's look lives in the database and its child theme, so an update doesn't change it.",
|
|
17231
|
+
parameters: {
|
|
17232
|
+
type: "object",
|
|
17233
|
+
properties: { rollback_to: str2("Put this kept version back instead (from wp_theme_updates backups).") }
|
|
17234
|
+
},
|
|
17235
|
+
group: "site",
|
|
17236
|
+
need: WRITE,
|
|
17237
|
+
risk: "W3",
|
|
17238
|
+
gates: [...GATES],
|
|
17239
|
+
confirm: (a, lang) => a.rollback_to ? say(
|
|
17240
|
+
lang,
|
|
17241
|
+
`Put Maggie Core ${String(a.rollback_to)} back on your website.`,
|
|
17242
|
+
`\u628A\u7DB2\u7AD9\u7684 Maggie Core \u9084\u539F\u5230 ${String(a.rollback_to)}\u3002`
|
|
17243
|
+
) : say(
|
|
17244
|
+
lang,
|
|
17245
|
+
"Update Maggie Core on your website to the latest version (rolled back by itself if the site breaks).",
|
|
17246
|
+
"\u628A\u7DB2\u7AD9\u7684 Maggie Core \u66F4\u65B0\u5230\u6700\u65B0\u7248\u672C\uFF08\u7DB2\u7AD9\u51FA\u932F\u6703\u81EA\u52D5\u9084\u539F\uFF09\u3002"
|
|
17247
|
+
),
|
|
17248
|
+
run: async (ctx, a) => {
|
|
17249
|
+
const site = siteOf(ctx);
|
|
17250
|
+
const before = await homeShape(site);
|
|
17251
|
+
if (a.rollback_to) {
|
|
17252
|
+
const r2 = await call(site, {
|
|
17253
|
+
method: "POST",
|
|
17254
|
+
path: "/maggie-core/v1/update/rollback",
|
|
17255
|
+
body: { version: a.rollback_to }
|
|
17256
|
+
});
|
|
17257
|
+
return { ...r2, check: compareShapes(before, await homeShape(site)) };
|
|
17258
|
+
}
|
|
17259
|
+
const info = await call(site, { path: "/maggie-core/v1/update", query: { fresh: 1 } });
|
|
17260
|
+
if (!info.available)
|
|
17261
|
+
return {
|
|
17262
|
+
updated: false,
|
|
17263
|
+
current: info.current,
|
|
17264
|
+
latest: info.latest,
|
|
17265
|
+
note: info.check_error ?? "Already the latest Maggie Core."
|
|
17266
|
+
};
|
|
17267
|
+
if (!info.can_update)
|
|
17268
|
+
throw new Error(info.update_issue ?? "This host doesn't let WordPress update themes itself.");
|
|
17269
|
+
const r = await call(site, {
|
|
17270
|
+
method: "POST",
|
|
17271
|
+
path: "/maggie-core/v1/update"
|
|
17272
|
+
});
|
|
17273
|
+
const check2 = compareShapes(before, await homeShape(site));
|
|
17274
|
+
const broken = check2.checked && (check2.problems.some((p) => p.startsWith("the homepage answers")) || (check2.same_text_percent ?? 100) < 60);
|
|
17275
|
+
if (broken) {
|
|
17276
|
+
const back = await call(site, {
|
|
17277
|
+
method: "POST",
|
|
17278
|
+
path: "/maggie-core/v1/update/rollback",
|
|
17279
|
+
body: { version: r.before }
|
|
17280
|
+
}).catch((e) => ({ error: e.message }));
|
|
17281
|
+
return {
|
|
17282
|
+
...r,
|
|
17283
|
+
check: check2,
|
|
17284
|
+
rolled_back: back,
|
|
17285
|
+
note: `The homepage looked broken after the update, so Maggie Core ${r.before} was put back.`
|
|
17286
|
+
};
|
|
17287
|
+
}
|
|
17288
|
+
return { ...r, check: check2, notes: info.notes, undo: `wp_theme_update rollback_to ${r.before}` };
|
|
17289
|
+
}
|
|
17290
|
+
})
|
|
17291
|
+
];
|
|
17151
17292
|
function deepMerge(a, b) {
|
|
17152
17293
|
const out = { ...a };
|
|
17153
17294
|
for (const [k, v] of Object.entries(b)) {
|
|
@@ -17417,6 +17558,7 @@ var wordpressTools = [
|
|
|
17417
17558
|
...sectionTools,
|
|
17418
17559
|
...mediaTools,
|
|
17419
17560
|
...childTools,
|
|
17561
|
+
...themeTools,
|
|
17420
17562
|
...seoTools,
|
|
17421
17563
|
...elementTools
|
|
17422
17564
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
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",
|