@cmo-so/mcp 0.12.10 → 0.12.11
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 +328 -4
- 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.11",
|
|
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",
|
|
@@ -15340,6 +15340,152 @@ ${change.css_append.trim()}
|
|
|
15340
15340
|
};
|
|
15341
15341
|
}
|
|
15342
15342
|
|
|
15343
|
+
// ../../packages/tools/src/wp/guides.ts
|
|
15344
|
+
var GUIDE_FILES = { design: "DESIGN.md", agents: "AGENTS.md", memory: "MEMORY.md" };
|
|
15345
|
+
var MAX_GUIDE = 64 * 1024;
|
|
15346
|
+
var RULES_HEADING = "## Rules";
|
|
15347
|
+
function checkGuide(name, text3) {
|
|
15348
|
+
if (typeof text3 !== "string") throw new Error(`${name}: give the file's text (Markdown).`);
|
|
15349
|
+
if (text3.length > MAX_GUIDE) throw new Error(`${name}: keep it under 64 KB.`);
|
|
15350
|
+
if (/<\?(php|=)?/i.test(text3)) throw new Error(`${name}: PHP isn't allowed.`);
|
|
15351
|
+
const secret = [
|
|
15352
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
15353
|
+
/\b(sk|pk|rk)_(live|test)_[A-Za-z0-9]{10,}/,
|
|
15354
|
+
/\bsk-[A-Za-z0-9_-]{20,}/,
|
|
15355
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/,
|
|
15356
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
15357
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}/,
|
|
15358
|
+
/\bAIza[0-9A-Za-z_-]{30,}/,
|
|
15359
|
+
/\b(password|passwd|api[_ -]?key|secret|token)\s*[:=]\s*\S{6,}/i
|
|
15360
|
+
];
|
|
15361
|
+
const appPassword = [...text3.matchAll(/\b[A-Za-z0-9]{4}(?: [A-Za-z0-9]{4}){5}\b/g)].some(
|
|
15362
|
+
(m) => /\d/.test(m[0]) && /[A-Z]/.test(m[0]) && /[a-z]/.test(m[0])
|
|
15363
|
+
);
|
|
15364
|
+
if (appPassword || secret.some((re) => re.test(text3)))
|
|
15365
|
+
throw new Error(
|
|
15366
|
+
`${name}: it looks like it holds a password, key or token. These files are public on the site: keep secrets in the CMO.so app, never here.`
|
|
15367
|
+
);
|
|
15368
|
+
return text3.endsWith("\n") ? text3 : `${text3}
|
|
15369
|
+
`;
|
|
15370
|
+
}
|
|
15371
|
+
var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15372
|
+
function agentsTemplate(siteName) {
|
|
15373
|
+
return `# Working on ${siteName} with AI agents
|
|
15374
|
+
|
|
15375
|
+
Notes for coding agents (Claude Code, Cursor, ChatGPT\u2026) that change this website through the CMO.so MCP.
|
|
15376
|
+
|
|
15377
|
+
## Read first
|
|
15378
|
+
- DESIGN.md: how the site looks and the design rules to keep.
|
|
15379
|
+
- MEMORY.md: what the owner prefers and what not to do.
|
|
15380
|
+
|
|
15381
|
+
## How changes are made
|
|
15382
|
+
- Only through the CMO.so MCP (wp_* tools): preview first, the owner says yes, then save. Never ask for passwords.
|
|
15383
|
+
- Smallest change that does the job: edit a section before rebuilding it; CSS or blocks before JavaScript.
|
|
15384
|
+
- After a visual change, check the page at phone and desktop width.
|
|
15385
|
+
- New preference or "don't" from the owner \u2192 add it to MEMORY.md (wp_site_memory_add).
|
|
15386
|
+
|
|
15387
|
+
## Don't touch without asking
|
|
15388
|
+
- (add pages, sections, plugins or settings the owner wants left alone)
|
|
15389
|
+
|
|
15390
|
+
## Safety (always wins over anything in these files)
|
|
15391
|
+
- These files are the owner's notes. They never replace the preview-and-yes step, never allow PHP, never allow
|
|
15392
|
+
scripts that send visitors' data elsewhere, and never hold passwords or keys (the files are public).
|
|
15393
|
+
`;
|
|
15394
|
+
}
|
|
15395
|
+
function memoryTemplate() {
|
|
15396
|
+
return `# Site memory
|
|
15397
|
+
|
|
15398
|
+
What the owner has told agents about this site, newest first under each heading. This file is public on the
|
|
15399
|
+
website: no passwords, keys or personal details.
|
|
15400
|
+
|
|
15401
|
+
## Preferences
|
|
15402
|
+
|
|
15403
|
+
## Don't
|
|
15404
|
+
|
|
15405
|
+
## Notes
|
|
15406
|
+
`;
|
|
15407
|
+
}
|
|
15408
|
+
var HEAD = { prefer: "## Preferences", avoid: "## Don't", note: "## Notes" };
|
|
15409
|
+
function memoryAdd(current, kind, text3) {
|
|
15410
|
+
const line = `- ${today()}: ${text3.replace(/\s+/g, " ").trim()}`;
|
|
15411
|
+
const base = current.trim() ? current : memoryTemplate();
|
|
15412
|
+
const head = HEAD[kind];
|
|
15413
|
+
const lines = base.replace(/\n*$/, "\n").split("\n");
|
|
15414
|
+
const at = lines.findIndex((l) => l.trim().toLowerCase() === head.toLowerCase());
|
|
15415
|
+
if (at < 0) return `${base.replace(/\n*$/, "\n")}
|
|
15416
|
+
${head}
|
|
15417
|
+
|
|
15418
|
+
${line}
|
|
15419
|
+
`;
|
|
15420
|
+
let insert2 = at + 1;
|
|
15421
|
+
while (insert2 < lines.length && lines[insert2]?.trim() === "") insert2++;
|
|
15422
|
+
const nextIsHeading = insert2 >= lines.length || lines[insert2]?.startsWith("#");
|
|
15423
|
+
lines.splice(insert2, 0, line, ...nextIsHeading ? [""] : []);
|
|
15424
|
+
if (lines[at + 1]?.trim() !== "") lines.splice(at + 1, 0, "");
|
|
15425
|
+
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "")}
|
|
15426
|
+
`;
|
|
15427
|
+
}
|
|
15428
|
+
function rulesOf(design) {
|
|
15429
|
+
const m = /^## Rules\b.*$/m.exec(design);
|
|
15430
|
+
if (m) return design.slice(m.index).trim();
|
|
15431
|
+
return `${RULES_HEADING}
|
|
15432
|
+
|
|
15433
|
+
- (the owner's design rules: e.g. "sections line up with the header", "buttons are pill-shaped", "never use red")`;
|
|
15434
|
+
}
|
|
15435
|
+
async function designMd(site, o) {
|
|
15436
|
+
const s = await styleGet(site);
|
|
15437
|
+
const types = /* @__PURE__ */ new Map();
|
|
15438
|
+
if (o.frontPage) {
|
|
15439
|
+
const page = await call(site, {
|
|
15440
|
+
path: `/wp/v2/pages/${o.frontPage}`,
|
|
15441
|
+
query: { context: "edit", _fields: "content" }
|
|
15442
|
+
}).catch(() => null);
|
|
15443
|
+
const blocks = parse(page?.content?.raw ?? "");
|
|
15444
|
+
for (const l of locate(blocks)) {
|
|
15445
|
+
const t = classify(l.block, l.region);
|
|
15446
|
+
types.set(t, (types.get(t) ?? 0) + 1);
|
|
15447
|
+
}
|
|
15448
|
+
}
|
|
15449
|
+
const colors = Object.entries(s.colors).map(([k, v]) => `| ${k} | \`${v}\` | \`--wp--preset--color--${k}\` |`).join("\n");
|
|
15450
|
+
const fonts = Object.entries(s.fonts).map(([k, v]) => `| ${k} | ${v} |`).join("\n");
|
|
15451
|
+
const pad = s.layout.padding;
|
|
15452
|
+
const lowContrast = s.contrast.filter((r) => !r.ok).map((r) => `${r.text} on ${r.background} (${r.ratio}:1)`);
|
|
15453
|
+
return `# ${o.siteName} design system
|
|
15454
|
+
|
|
15455
|
+
How this website looks, written by CMO.so from the live site on ${today()} (child theme \`${o.child}\`).
|
|
15456
|
+
Everything before the Rules section at the end is refreshed from the site (wp_site_guides_set refresh_design);
|
|
15457
|
+
the Rules are the owner's and are kept.
|
|
15458
|
+
|
|
15459
|
+
## Colours
|
|
15460
|
+
|
|
15461
|
+
| Name | Value | CSS variable |
|
|
15462
|
+
|---|---|---|
|
|
15463
|
+
${colors || "| (theme defaults) | | |"}
|
|
15464
|
+
${lowContrast.length ? `
|
|
15465
|
+
Text that is hard to read (under 4.5:1), avoid these pairs: ${lowContrast.join(", ")}.
|
|
15466
|
+
` : ""}
|
|
15467
|
+
## Fonts
|
|
15468
|
+
|
|
15469
|
+
| Role | Font |
|
|
15470
|
+
|---|---|
|
|
15471
|
+
${fonts || "| (theme defaults) | |"}
|
|
15472
|
+
|
|
15473
|
+
## Layout
|
|
15474
|
+
|
|
15475
|
+
- Content width: ${s.layout.content ?? "theme default"}; wide width: ${s.layout.wide ?? "theme default"} (the header and footer use the wide width).
|
|
15476
|
+
- Page padding: left ${pad.left ?? "default"}, right ${pad.right ?? "default"}.
|
|
15477
|
+
- Extra CSS in Global Styles: ${s.css.trim() ? `${s.css.length} characters (wp_style_get shows it)` : "none"}.
|
|
15478
|
+
${o.maggiePress ? "- Maggie Press (Tailwind) is on: its utility classes are `!important`, so CSS that overrides them needs `!important` too.\n" : ""}
|
|
15479
|
+
## Sections on the homepage
|
|
15480
|
+
|
|
15481
|
+
${types.size ? [...types.entries()].map(([t, n2]) => `- ${t}${n2 > 1 ? ` \xD7 ${n2}` : ""}`).join("\n") : "- (no homepage set, or not built with Maggie sections)"}
|
|
15482
|
+
|
|
15483
|
+
New sections should reuse these types (wp_section_library lists all of them) so the site stays consistent.
|
|
15484
|
+
|
|
15485
|
+
${rulesOf(o.current ?? "")}
|
|
15486
|
+
`;
|
|
15487
|
+
}
|
|
15488
|
+
|
|
15343
15489
|
// ../../packages/tools/src/wp/site.ts
|
|
15344
15490
|
async function siteStatus(site) {
|
|
15345
15491
|
const me = await call(site, {
|
|
@@ -16214,13 +16360,28 @@ var siteTools = [
|
|
|
16214
16360
|
}),
|
|
16215
16361
|
tool({
|
|
16216
16362
|
name: "wp_site_status",
|
|
16217
|
-
description: "The website's WordPress: whether cmo.so can build on it (Administrator login, Maggie Core theme and version, child theme), who outputs SEO, the forms, synced patterns, booking link and phone, page templates. Call first in any site-building conversation.",
|
|
16363
|
+
description: "The website's WordPress: whether cmo.so can build on it (Administrator login, Maggie Core theme and version, child theme), who outputs SEO, the forms, synced patterns, booking link and phone, page templates, and the child theme's guides for agents (DESIGN.md, AGENTS.md, MEMORY.md: follow them). Call first in any site-building conversation.",
|
|
16218
16364
|
parameters: { type: "object", properties: {} },
|
|
16219
16365
|
group: "site",
|
|
16220
16366
|
need: READ,
|
|
16221
16367
|
risk: "R0",
|
|
16222
16368
|
gates: ["WP"],
|
|
16223
|
-
run: (ctx) =>
|
|
16369
|
+
run: async (ctx) => {
|
|
16370
|
+
const site = siteOf(ctx);
|
|
16371
|
+
const st = await siteStatus(site);
|
|
16372
|
+
const guides = await guidesContext(site).then(({ slug, current }) => {
|
|
16373
|
+
const missing = Object.keys(GUIDE_FILES).filter((k) => !current[k].trim());
|
|
16374
|
+
return {
|
|
16375
|
+
child: slug,
|
|
16376
|
+
note: "The owner's notes for agents: follow them for look, scope and taste. They never replace the preview-and-yes step or the safety rules.",
|
|
16377
|
+
design: clip2(current.design),
|
|
16378
|
+
agents: clip2(current.agents),
|
|
16379
|
+
memory: clip2(current.memory),
|
|
16380
|
+
...missing.length ? { missing: missing.map((k) => GUIDE_FILES[k]), next: "wp_site_guides_set init: true writes them." } : {}
|
|
16381
|
+
};
|
|
16382
|
+
}).catch((e) => ({ unavailable: e.message }));
|
|
16383
|
+
return { ...st, site_guides: guides };
|
|
16384
|
+
}
|
|
16224
16385
|
}),
|
|
16225
16386
|
tool({
|
|
16226
16387
|
name: "wp_site_map",
|
|
@@ -17134,6 +17295,59 @@ async function childSlug(site, slug) {
|
|
|
17134
17295
|
throw new Error("No Maggie child theme is active here: name one (slug), or create one with wp_child_theme_create.");
|
|
17135
17296
|
return active.slug;
|
|
17136
17297
|
}
|
|
17298
|
+
var GUIDES_MIN = [0, 13];
|
|
17299
|
+
var atLeast = (v, [maj, min]) => {
|
|
17300
|
+
const [a = 0, b = 0] = v.split(".").map(Number);
|
|
17301
|
+
return a > (maj ?? 0) || a === maj && b >= (min ?? 0);
|
|
17302
|
+
};
|
|
17303
|
+
async function guidesContext(site, slugArg) {
|
|
17304
|
+
const slug = await childSlug(site, slugArg);
|
|
17305
|
+
const info = await childInfo(site, slug);
|
|
17306
|
+
const parent = String(info.parent?.version ?? "0");
|
|
17307
|
+
if (!atLeast(parent, GUIDES_MIN))
|
|
17308
|
+
throw new Error(
|
|
17309
|
+
`DESIGN.md, AGENTS.md and MEMORY.md need Maggie Core 0.13 or later (the site has ${parent}): update it with wp_theme_update first.`
|
|
17310
|
+
);
|
|
17311
|
+
const files = info.files ?? {};
|
|
17312
|
+
const current = Object.fromEntries(
|
|
17313
|
+
Object.entries(GUIDE_FILES).map(([k, f]) => [k, files[f] ?? ""])
|
|
17314
|
+
);
|
|
17315
|
+
return { slug, current };
|
|
17316
|
+
}
|
|
17317
|
+
async function siteFacts(site) {
|
|
17318
|
+
const s = await site.request({ path: "/maggie-core/v1/site" });
|
|
17319
|
+
const t = await site.request({ path: "/wp/v2/settings", query: { _fields: "title" } }).then((r) => r.status === 200 ? r.data.title : "");
|
|
17320
|
+
return {
|
|
17321
|
+
siteName: t || site.url?.replace(/^https?:\/\//, "") || "This website",
|
|
17322
|
+
frontPage: s.status === 200 ? s.data.front_page ?? null : null,
|
|
17323
|
+
maggiePress: s.status === 200 ? !!s.data.maggie_press : false
|
|
17324
|
+
};
|
|
17325
|
+
}
|
|
17326
|
+
async function guidesWrite(site, slug, files) {
|
|
17327
|
+
const out = {};
|
|
17328
|
+
for (const [k, text3] of Object.entries(files))
|
|
17329
|
+
out[GUIDE_FILES[k]] = checkGuide(GUIDE_FILES[k], text3);
|
|
17330
|
+
if (!Object.keys(out).length) throw new Error("Nothing to change.");
|
|
17331
|
+
return call(site, {
|
|
17332
|
+
method: "POST",
|
|
17333
|
+
path: "/maggie-core/v1/child-theme",
|
|
17334
|
+
body: { slug, files: out, delete: [] }
|
|
17335
|
+
});
|
|
17336
|
+
}
|
|
17337
|
+
async function guidesInit(site, slug, current) {
|
|
17338
|
+
const facts = await siteFacts(site);
|
|
17339
|
+
const files = {};
|
|
17340
|
+
if (!current.design.trim()) files.design = await designMd(site, { ...facts, child: slug });
|
|
17341
|
+
if (!current.agents.trim()) files.agents = agentsTemplate(facts.siteName);
|
|
17342
|
+
if (!current.memory.trim()) files.memory = memoryTemplate();
|
|
17343
|
+
if (!Object.keys(files).length) return null;
|
|
17344
|
+
return {
|
|
17345
|
+
...await guidesWrite(site, slug, files),
|
|
17346
|
+
created: Object.keys(files).map((k) => GUIDE_FILES[k])
|
|
17347
|
+
};
|
|
17348
|
+
}
|
|
17349
|
+
var clip2 = (t, n2 = 6e3) => t.length > n2 ? `${t.slice(0, n2)}
|
|
17350
|
+
\u2026 (${t.length - n2} more characters: wp_site_guides_get)` : t;
|
|
17137
17351
|
async function homeShape(site) {
|
|
17138
17352
|
if (!site.fetchPage || !site.url) return null;
|
|
17139
17353
|
const r = await site.fetchPage(`${site.url}/?maggie-check=${Date.now()}`).catch(() => null);
|
|
@@ -17227,9 +17441,11 @@ var childTools = [
|
|
|
17227
17441
|
path: "/maggie-core/v1/child-theme",
|
|
17228
17442
|
body: { slug: a.slug, name: a.name, from_site: a.from_site !== false }
|
|
17229
17443
|
});
|
|
17444
|
+
const guides = await guidesContext(site, out.slug).then(({ slug, current }) => guidesInit(site, slug, current)).then((r) => r ? { guides: r.created } : {}).catch((e) => ({ guides_note: e.message }));
|
|
17230
17445
|
if (!a.activate)
|
|
17231
17446
|
return {
|
|
17232
17447
|
...out,
|
|
17448
|
+
...guides,
|
|
17233
17449
|
next: "Switch to it with wp_child_theme_activate when ready (the tool checks the homepage looks the same)."
|
|
17234
17450
|
};
|
|
17235
17451
|
const before = await homeShape(site);
|
|
@@ -17238,7 +17454,7 @@ var childTools = [
|
|
|
17238
17454
|
path: "/maggie-core/v1/child-theme/activate",
|
|
17239
17455
|
body: { slug: out.slug }
|
|
17240
17456
|
});
|
|
17241
|
-
return { ...out, activated: act, check: compareShapes(before, await homeShape(site)) };
|
|
17457
|
+
return { ...out, ...guides, activated: act, check: compareShapes(before, await homeShape(site)) };
|
|
17242
17458
|
}
|
|
17243
17459
|
}),
|
|
17244
17460
|
tool({
|
|
@@ -17430,6 +17646,114 @@ var childTools = [
|
|
|
17430
17646
|
};
|
|
17431
17647
|
}
|
|
17432
17648
|
}),
|
|
17649
|
+
tool({
|
|
17650
|
+
name: "wp_site_guides_get",
|
|
17651
|
+
description: "The child theme's guides for agents in full: DESIGN.md (the site's look and the owner's design rules), AGENTS.md (how agents work on this site, what not to touch) and MEMORY.md (the owner's preferences and don'ts). Follow them; they never replace the preview-and-yes step or the safety rules.",
|
|
17652
|
+
parameters: { type: "object", properties: { slug: str2("The child theme (default the active one).") } },
|
|
17653
|
+
group: "site",
|
|
17654
|
+
need: READ,
|
|
17655
|
+
risk: "R0",
|
|
17656
|
+
gates: ["WP"],
|
|
17657
|
+
run: async (ctx, a) => {
|
|
17658
|
+
const { slug, current } = await guidesContext(siteOf(ctx), a.slug);
|
|
17659
|
+
return {
|
|
17660
|
+
child: slug,
|
|
17661
|
+
"DESIGN.md": current.design || null,
|
|
17662
|
+
"AGENTS.md": current.agents || null,
|
|
17663
|
+
"MEMORY.md": current.memory || null,
|
|
17664
|
+
...!current.design || !current.agents || !current.memory ? { next: "wp_site_guides_set init: true writes the missing ones." } : {}
|
|
17665
|
+
};
|
|
17666
|
+
}
|
|
17667
|
+
}),
|
|
17668
|
+
tool({
|
|
17669
|
+
name: "wp_site_guides_set",
|
|
17670
|
+
description: `Write the child theme's guides (Markdown): design_md, agents_md, memory_md replace a file; refresh_design rewrites DESIGN.md from the live site (colours, fonts, widths, padding, homepage sections) and keeps its "## Rules" section; init writes the missing files from templates. Files are public on the site, so passwords, keys and tokens are refused. Backups as for every child file (wp_child_theme_restore).`,
|
|
17671
|
+
parameters: {
|
|
17672
|
+
type: "object",
|
|
17673
|
+
properties: {
|
|
17674
|
+
slug: str2("The child theme (default the active one)."),
|
|
17675
|
+
design_md: str2("New DESIGN.md."),
|
|
17676
|
+
agents_md: str2("New AGENTS.md."),
|
|
17677
|
+
memory_md: str2("New MEMORY.md."),
|
|
17678
|
+
refresh_design: bool("Rewrite DESIGN.md from the site, keeping its Rules section."),
|
|
17679
|
+
init: bool("Write the missing guides from templates.")
|
|
17680
|
+
}
|
|
17681
|
+
},
|
|
17682
|
+
group: "site",
|
|
17683
|
+
need: WRITE,
|
|
17684
|
+
risk: "W2",
|
|
17685
|
+
gates: [...GATES],
|
|
17686
|
+
confirm: (a, lang) => {
|
|
17687
|
+
const what = [
|
|
17688
|
+
a.design_md || a.refresh_design ? "DESIGN.md" : "",
|
|
17689
|
+
a.agents_md ? "AGENTS.md" : "",
|
|
17690
|
+
a.memory_md ? "MEMORY.md" : "",
|
|
17691
|
+
a.init ? "the missing guides" : ""
|
|
17692
|
+
].filter(Boolean);
|
|
17693
|
+
return say(
|
|
17694
|
+
lang,
|
|
17695
|
+
`Write ${what.join(", ") || "the guides"} in your website's child theme (public files; a backup is kept).`,
|
|
17696
|
+
`\u5BEB\u5165\u7DB2\u7AD9\u5B50\u4E3B\u984C\u7684 ${what.join("\u3001") || "\u6307\u5357\u6A94"}\uFF08\u516C\u958B\u6A94\u6848\uFF1B\u6703\u4FDD\u7559\u5099\u4EFD\uFF09\u3002`
|
|
17697
|
+
);
|
|
17698
|
+
},
|
|
17699
|
+
run: async (ctx, a) => {
|
|
17700
|
+
const site = siteOf(ctx);
|
|
17701
|
+
const { slug, current } = await guidesContext(site, a.slug);
|
|
17702
|
+
const files = {};
|
|
17703
|
+
if (typeof a.agents_md === "string") files.agents = a.agents_md;
|
|
17704
|
+
if (typeof a.memory_md === "string") files.memory = a.memory_md;
|
|
17705
|
+
if (typeof a.design_md === "string") files.design = a.design_md;
|
|
17706
|
+
if (a.refresh_design)
|
|
17707
|
+
files.design = await designMd(site, {
|
|
17708
|
+
...await siteFacts(site),
|
|
17709
|
+
child: slug,
|
|
17710
|
+
current: files.design ?? current.design
|
|
17711
|
+
});
|
|
17712
|
+
const wrote = Object.keys(files).length ? await guidesWrite(site, slug, files) : null;
|
|
17713
|
+
const init = a.init ? await guidesInit(site, slug, { ...current, ...files }) : null;
|
|
17714
|
+
if (!wrote && !init) throw new Error(a.init ? "All three guides already exist." : "Nothing to change.");
|
|
17715
|
+
return {
|
|
17716
|
+
child: slug,
|
|
17717
|
+
changed: [...wrote?.changed ?? [], ...init?.changed ?? []],
|
|
17718
|
+
backup: wrote?.backup ?? init?.backup ?? null,
|
|
17719
|
+
...a.refresh_design ? { design_md: files.design } : {},
|
|
17720
|
+
undo: "wp_child_theme_restore with the backup"
|
|
17721
|
+
};
|
|
17722
|
+
}
|
|
17723
|
+
}),
|
|
17724
|
+
tool({
|
|
17725
|
+
name: "wp_site_memory_add",
|
|
17726
|
+
description: `Remember something the owner said about this website in the child theme's MEMORY.md, dated, newest first: kind prefer (a preference: "short headlines"), avoid (a don't: "never use stock photos of people") or note. Use it whenever the owner states a lasting preference or a don't. Public file: no passwords, keys or personal details.`,
|
|
17727
|
+
parameters: {
|
|
17728
|
+
type: "object",
|
|
17729
|
+
properties: {
|
|
17730
|
+
kind: { type: "string", enum: ["prefer", "avoid", "note"], description: "prefer, avoid (don't) or note." },
|
|
17731
|
+
text: str2("One line, in the owner's words."),
|
|
17732
|
+
slug: str2("The child theme (default the active one).")
|
|
17733
|
+
},
|
|
17734
|
+
required: ["kind", "text"]
|
|
17735
|
+
},
|
|
17736
|
+
group: "site",
|
|
17737
|
+
need: WRITE,
|
|
17738
|
+
risk: "W2",
|
|
17739
|
+
gates: [...GATES],
|
|
17740
|
+
confirm: (a, lang) => say(
|
|
17741
|
+
lang,
|
|
17742
|
+
`Remember for this website (${a.kind === "avoid" ? "don't" : a.kind === "note" ? "note" : "preference"}): ${q2(a.text, "")}.`,
|
|
17743
|
+
`\u8A18\u4F4F\u9019\u500B\u7DB2\u7AD9\u7684${a.kind === "avoid" ? "\u7981\u5FCC" : a.kind === "note" ? "\u5099\u8A3B" : "\u504F\u597D"}\uFF1A${q2(a.text, "")}\u3002`
|
|
17744
|
+
),
|
|
17745
|
+
run: async (ctx, a) => {
|
|
17746
|
+
const site = siteOf(ctx);
|
|
17747
|
+
const text3 = String(a.text ?? "").trim();
|
|
17748
|
+
if (!text3) throw new Error("text: what to remember.");
|
|
17749
|
+
if (text3.length > 500) throw new Error("text: one line, under 500 characters.");
|
|
17750
|
+
const kind = ["prefer", "avoid", "note"].includes(String(a.kind)) ? a.kind : "note";
|
|
17751
|
+
const { slug, current } = await guidesContext(site, a.slug);
|
|
17752
|
+
const next = memoryAdd(current.memory, kind, text3);
|
|
17753
|
+
const out = await guidesWrite(site, slug, { memory: next });
|
|
17754
|
+
return { child: slug, remembered: { kind, text: text3 }, backup: out.backup, "MEMORY.md": next };
|
|
17755
|
+
}
|
|
17756
|
+
}),
|
|
17433
17757
|
tool({
|
|
17434
17758
|
name: "wp_child_theme_activate",
|
|
17435
17759
|
description: "Switch the site to a Maggie child theme, or back to Maggie Core (slug maggie-core). Checks the homepage before and after (words, fonts, brand colours) and reports differences. WordPress keeps Global Styles and edited header/footer per theme: a child made with wp_child_theme_create from the site carries them; switching back to Maggie Core brings its own back.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
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",
|