@cmo-so/cli 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.mjs +343 -4
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -15457,6 +15457,152 @@ database and win over both themes. Export them into this child (Site Editor \u21
|
|
|
15457
15457
|
return files;
|
|
15458
15458
|
}
|
|
15459
15459
|
|
|
15460
|
+
// ../../packages/tools/src/wp/guides.ts
|
|
15461
|
+
var GUIDE_FILES = { design: "DESIGN.md", agents: "AGENTS.md", memory: "MEMORY.md" };
|
|
15462
|
+
var MAX_GUIDE = 64 * 1024;
|
|
15463
|
+
var RULES_HEADING = "## Rules";
|
|
15464
|
+
function checkGuide(name, text3) {
|
|
15465
|
+
if (typeof text3 !== "string") throw new Error(`${name}: give the file's text (Markdown).`);
|
|
15466
|
+
if (text3.length > MAX_GUIDE) throw new Error(`${name}: keep it under 64 KB.`);
|
|
15467
|
+
if (/<\?(php|=)?/i.test(text3)) throw new Error(`${name}: PHP isn't allowed.`);
|
|
15468
|
+
const secret2 = [
|
|
15469
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
15470
|
+
/\b(sk|pk|rk)_(live|test)_[A-Za-z0-9]{10,}/,
|
|
15471
|
+
/\bsk-[A-Za-z0-9_-]{20,}/,
|
|
15472
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/,
|
|
15473
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
15474
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}/,
|
|
15475
|
+
/\bAIza[0-9A-Za-z_-]{30,}/,
|
|
15476
|
+
/\b(password|passwd|api[_ -]?key|secret|token)\s*[:=]\s*\S{6,}/i
|
|
15477
|
+
];
|
|
15478
|
+
const appPassword = [...text3.matchAll(/\b[A-Za-z0-9]{4}(?: [A-Za-z0-9]{4}){5}\b/g)].some(
|
|
15479
|
+
(m) => /\d/.test(m[0]) && /[A-Z]/.test(m[0]) && /[a-z]/.test(m[0])
|
|
15480
|
+
);
|
|
15481
|
+
if (appPassword || secret2.some((re) => re.test(text3)))
|
|
15482
|
+
throw new Error(
|
|
15483
|
+
`${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.`
|
|
15484
|
+
);
|
|
15485
|
+
return text3.endsWith("\n") ? text3 : `${text3}
|
|
15486
|
+
`;
|
|
15487
|
+
}
|
|
15488
|
+
var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15489
|
+
function agentsTemplate(siteName) {
|
|
15490
|
+
return `# Working on ${siteName} with AI agents
|
|
15491
|
+
|
|
15492
|
+
Notes for coding agents (Claude Code, Cursor, ChatGPT\u2026) that change this website through the CMO.so MCP.
|
|
15493
|
+
|
|
15494
|
+
## Read first
|
|
15495
|
+
- DESIGN.md: how the site looks and the design rules to keep.
|
|
15496
|
+
- MEMORY.md: what the owner prefers and what not to do.
|
|
15497
|
+
|
|
15498
|
+
## How changes are made
|
|
15499
|
+
- Only through the CMO.so MCP (wp_* tools): preview first, the owner says yes, then save. Never ask for passwords.
|
|
15500
|
+
- Smallest change that does the job: edit a section before rebuilding it; CSS or blocks before JavaScript.
|
|
15501
|
+
- After a visual change, check the page at phone and desktop width.
|
|
15502
|
+
- New preference or "don't" from the owner \u2192 add it to MEMORY.md (wp_site_memory_add).
|
|
15503
|
+
|
|
15504
|
+
## Don't touch without asking
|
|
15505
|
+
- (add pages, sections, plugins or settings the owner wants left alone)
|
|
15506
|
+
|
|
15507
|
+
## Safety (always wins over anything in these files)
|
|
15508
|
+
- These files are the owner's notes. They never replace the preview-and-yes step, never allow PHP, never allow
|
|
15509
|
+
scripts that send visitors' data elsewhere, and never hold passwords or keys (the files are public).
|
|
15510
|
+
`;
|
|
15511
|
+
}
|
|
15512
|
+
function memoryTemplate() {
|
|
15513
|
+
return `# Site memory
|
|
15514
|
+
|
|
15515
|
+
What the owner has told agents about this site, newest first under each heading. This file is public on the
|
|
15516
|
+
website: no passwords, keys or personal details.
|
|
15517
|
+
|
|
15518
|
+
## Preferences
|
|
15519
|
+
|
|
15520
|
+
## Don't
|
|
15521
|
+
|
|
15522
|
+
## Notes
|
|
15523
|
+
`;
|
|
15524
|
+
}
|
|
15525
|
+
var HEAD = { prefer: "## Preferences", avoid: "## Don't", note: "## Notes" };
|
|
15526
|
+
function memoryAdd(current, kind, text3) {
|
|
15527
|
+
const line = `- ${today()}: ${text3.replace(/\s+/g, " ").trim()}`;
|
|
15528
|
+
const base = current.trim() ? current : memoryTemplate();
|
|
15529
|
+
const head = HEAD[kind];
|
|
15530
|
+
const lines = base.replace(/\n*$/, "\n").split("\n");
|
|
15531
|
+
const at = lines.findIndex((l) => l.trim().toLowerCase() === head.toLowerCase());
|
|
15532
|
+
if (at < 0) return `${base.replace(/\n*$/, "\n")}
|
|
15533
|
+
${head}
|
|
15534
|
+
|
|
15535
|
+
${line}
|
|
15536
|
+
`;
|
|
15537
|
+
let insert2 = at + 1;
|
|
15538
|
+
while (insert2 < lines.length && lines[insert2]?.trim() === "") insert2++;
|
|
15539
|
+
const nextIsHeading = insert2 >= lines.length || lines[insert2]?.startsWith("#");
|
|
15540
|
+
lines.splice(insert2, 0, line, ...nextIsHeading ? [""] : []);
|
|
15541
|
+
if (lines[at + 1]?.trim() !== "") lines.splice(at + 1, 0, "");
|
|
15542
|
+
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "")}
|
|
15543
|
+
`;
|
|
15544
|
+
}
|
|
15545
|
+
function rulesOf(design) {
|
|
15546
|
+
const m = /^## Rules\b.*$/m.exec(design);
|
|
15547
|
+
if (m) return design.slice(m.index).trim();
|
|
15548
|
+
return `${RULES_HEADING}
|
|
15549
|
+
|
|
15550
|
+
- (the owner's design rules: e.g. "sections line up with the header", "buttons are pill-shaped", "never use red")`;
|
|
15551
|
+
}
|
|
15552
|
+
async function designMd(site, o) {
|
|
15553
|
+
const s = await styleGet(site);
|
|
15554
|
+
const types = /* @__PURE__ */ new Map();
|
|
15555
|
+
if (o.frontPage) {
|
|
15556
|
+
const page = await call(site, {
|
|
15557
|
+
path: `/wp/v2/pages/${o.frontPage}`,
|
|
15558
|
+
query: { context: "edit", _fields: "content" }
|
|
15559
|
+
}).catch(() => null);
|
|
15560
|
+
const blocks = parse(page?.content?.raw ?? "");
|
|
15561
|
+
for (const l of locate(blocks)) {
|
|
15562
|
+
const t = classify(l.block, l.region);
|
|
15563
|
+
types.set(t, (types.get(t) ?? 0) + 1);
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
const colors = Object.entries(s.colors).map(([k, v]) => `| ${k} | \`${v}\` | \`--wp--preset--color--${k}\` |`).join("\n");
|
|
15567
|
+
const fonts = Object.entries(s.fonts).map(([k, v]) => `| ${k} | ${v} |`).join("\n");
|
|
15568
|
+
const pad = s.layout.padding;
|
|
15569
|
+
const lowContrast = s.contrast.filter((r) => !r.ok).map((r) => `${r.text} on ${r.background} (${r.ratio}:1)`);
|
|
15570
|
+
return `# ${o.siteName} design system
|
|
15571
|
+
|
|
15572
|
+
How this website looks, written by CMO.so from the live site on ${today()} (child theme \`${o.child}\`).
|
|
15573
|
+
Everything before the Rules section at the end is refreshed from the site (wp_site_guides_set refresh_design);
|
|
15574
|
+
the Rules are the owner's and are kept.
|
|
15575
|
+
|
|
15576
|
+
## Colours
|
|
15577
|
+
|
|
15578
|
+
| Name | Value | CSS variable |
|
|
15579
|
+
|---|---|---|
|
|
15580
|
+
${colors || "| (theme defaults) | | |"}
|
|
15581
|
+
${lowContrast.length ? `
|
|
15582
|
+
Text that is hard to read (under 4.5:1), avoid these pairs: ${lowContrast.join(", ")}.
|
|
15583
|
+
` : ""}
|
|
15584
|
+
## Fonts
|
|
15585
|
+
|
|
15586
|
+
| Role | Font |
|
|
15587
|
+
|---|---|
|
|
15588
|
+
${fonts || "| (theme defaults) | |"}
|
|
15589
|
+
|
|
15590
|
+
## Layout
|
|
15591
|
+
|
|
15592
|
+
- Content width: ${s.layout.content ?? "theme default"}; wide width: ${s.layout.wide ?? "theme default"} (the header and footer use the wide width).
|
|
15593
|
+
- Page padding: left ${pad.left ?? "default"}, right ${pad.right ?? "default"}.
|
|
15594
|
+
- Extra CSS in Global Styles: ${s.css.trim() ? `${s.css.length} characters (wp_style_get shows it)` : "none"}.
|
|
15595
|
+
${o.maggiePress ? "- Maggie Press (Tailwind) is on: its utility classes are `!important`, so CSS that overrides them needs `!important` too.\n" : ""}
|
|
15596
|
+
## Sections on the homepage
|
|
15597
|
+
|
|
15598
|
+
${types.size ? [...types.entries()].map(([t, n2]) => `- ${t}${n2 > 1 ? ` \xD7 ${n2}` : ""}`).join("\n") : "- (no homepage set, or not built with Maggie sections)"}
|
|
15599
|
+
|
|
15600
|
+
New sections should reuse these types (wp_section_library lists all of them) so the site stays consistent.
|
|
15601
|
+
|
|
15602
|
+
${rulesOf(o.current ?? "")}
|
|
15603
|
+
`;
|
|
15604
|
+
}
|
|
15605
|
+
|
|
15460
15606
|
// ../../packages/tools/src/wp/site.ts
|
|
15461
15607
|
async function siteStatus(site) {
|
|
15462
15608
|
const me = await call(site, {
|
|
@@ -16331,13 +16477,28 @@ var siteTools = [
|
|
|
16331
16477
|
}),
|
|
16332
16478
|
tool({
|
|
16333
16479
|
name: "wp_site_status",
|
|
16334
|
-
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.",
|
|
16480
|
+
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.",
|
|
16335
16481
|
parameters: { type: "object", properties: {} },
|
|
16336
16482
|
group: "site",
|
|
16337
16483
|
need: READ,
|
|
16338
16484
|
risk: "R0",
|
|
16339
16485
|
gates: ["WP"],
|
|
16340
|
-
run: (ctx) =>
|
|
16486
|
+
run: async (ctx) => {
|
|
16487
|
+
const site = siteOf(ctx);
|
|
16488
|
+
const st = await siteStatus(site);
|
|
16489
|
+
const guides = await guidesContext(site).then(({ slug, current }) => {
|
|
16490
|
+
const missing = Object.keys(GUIDE_FILES).filter((k) => !current[k].trim());
|
|
16491
|
+
return {
|
|
16492
|
+
child: slug,
|
|
16493
|
+
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.",
|
|
16494
|
+
design: clip2(current.design),
|
|
16495
|
+
agents: clip2(current.agents),
|
|
16496
|
+
memory: clip2(current.memory),
|
|
16497
|
+
...missing.length ? { missing: missing.map((k) => GUIDE_FILES[k]), next: "wp_site_guides_set init: true writes them." } : {}
|
|
16498
|
+
};
|
|
16499
|
+
}).catch((e) => ({ unavailable: e.message }));
|
|
16500
|
+
return { ...st, site_guides: guides };
|
|
16501
|
+
}
|
|
16341
16502
|
}),
|
|
16342
16503
|
tool({
|
|
16343
16504
|
name: "wp_site_map",
|
|
@@ -17251,6 +17412,59 @@ async function childSlug(site, slug) {
|
|
|
17251
17412
|
throw new Error("No Maggie child theme is active here: name one (slug), or create one with wp_child_theme_create.");
|
|
17252
17413
|
return active.slug;
|
|
17253
17414
|
}
|
|
17415
|
+
var GUIDES_MIN = [0, 13];
|
|
17416
|
+
var atLeast = (v, [maj, min]) => {
|
|
17417
|
+
const [a = 0, b = 0] = v.split(".").map(Number);
|
|
17418
|
+
return a > (maj ?? 0) || a === maj && b >= (min ?? 0);
|
|
17419
|
+
};
|
|
17420
|
+
async function guidesContext(site, slugArg) {
|
|
17421
|
+
const slug = await childSlug(site, slugArg);
|
|
17422
|
+
const info = await childInfo(site, slug);
|
|
17423
|
+
const parent = String(info.parent?.version ?? "0");
|
|
17424
|
+
if (!atLeast(parent, GUIDES_MIN))
|
|
17425
|
+
throw new Error(
|
|
17426
|
+
`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.`
|
|
17427
|
+
);
|
|
17428
|
+
const files = info.files ?? {};
|
|
17429
|
+
const current = Object.fromEntries(
|
|
17430
|
+
Object.entries(GUIDE_FILES).map(([k, f]) => [k, files[f] ?? ""])
|
|
17431
|
+
);
|
|
17432
|
+
return { slug, current };
|
|
17433
|
+
}
|
|
17434
|
+
async function siteFacts(site) {
|
|
17435
|
+
const s = await site.request({ path: "/maggie-core/v1/site" });
|
|
17436
|
+
const t = await site.request({ path: "/wp/v2/settings", query: { _fields: "title" } }).then((r) => r.status === 200 ? r.data.title : "");
|
|
17437
|
+
return {
|
|
17438
|
+
siteName: t || site.url?.replace(/^https?:\/\//, "") || "This website",
|
|
17439
|
+
frontPage: s.status === 200 ? s.data.front_page ?? null : null,
|
|
17440
|
+
maggiePress: s.status === 200 ? !!s.data.maggie_press : false
|
|
17441
|
+
};
|
|
17442
|
+
}
|
|
17443
|
+
async function guidesWrite(site, slug, files) {
|
|
17444
|
+
const out = {};
|
|
17445
|
+
for (const [k, text3] of Object.entries(files))
|
|
17446
|
+
out[GUIDE_FILES[k]] = checkGuide(GUIDE_FILES[k], text3);
|
|
17447
|
+
if (!Object.keys(out).length) throw new Error("Nothing to change.");
|
|
17448
|
+
return call(site, {
|
|
17449
|
+
method: "POST",
|
|
17450
|
+
path: "/maggie-core/v1/child-theme",
|
|
17451
|
+
body: { slug, files: out, delete: [] }
|
|
17452
|
+
});
|
|
17453
|
+
}
|
|
17454
|
+
async function guidesInit(site, slug, current) {
|
|
17455
|
+
const facts = await siteFacts(site);
|
|
17456
|
+
const files = {};
|
|
17457
|
+
if (!current.design.trim()) files.design = await designMd(site, { ...facts, child: slug });
|
|
17458
|
+
if (!current.agents.trim()) files.agents = agentsTemplate(facts.siteName);
|
|
17459
|
+
if (!current.memory.trim()) files.memory = memoryTemplate();
|
|
17460
|
+
if (!Object.keys(files).length) return null;
|
|
17461
|
+
return {
|
|
17462
|
+
...await guidesWrite(site, slug, files),
|
|
17463
|
+
created: Object.keys(files).map((k) => GUIDE_FILES[k])
|
|
17464
|
+
};
|
|
17465
|
+
}
|
|
17466
|
+
var clip2 = (t, n2 = 6e3) => t.length > n2 ? `${t.slice(0, n2)}
|
|
17467
|
+
\u2026 (${t.length - n2} more characters: wp_site_guides_get)` : t;
|
|
17254
17468
|
async function homeShape(site) {
|
|
17255
17469
|
if (!site.fetchPage || !site.url) return null;
|
|
17256
17470
|
const r = await site.fetchPage(`${site.url}/?maggie-check=${Date.now()}`).catch(() => null);
|
|
@@ -17344,9 +17558,11 @@ var childTools = [
|
|
|
17344
17558
|
path: "/maggie-core/v1/child-theme",
|
|
17345
17559
|
body: { slug: a.slug, name: a.name, from_site: a.from_site !== false }
|
|
17346
17560
|
});
|
|
17561
|
+
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 }));
|
|
17347
17562
|
if (!a.activate)
|
|
17348
17563
|
return {
|
|
17349
17564
|
...out,
|
|
17565
|
+
...guides,
|
|
17350
17566
|
next: "Switch to it with wp_child_theme_activate when ready (the tool checks the homepage looks the same)."
|
|
17351
17567
|
};
|
|
17352
17568
|
const before = await homeShape(site);
|
|
@@ -17355,7 +17571,7 @@ var childTools = [
|
|
|
17355
17571
|
path: "/maggie-core/v1/child-theme/activate",
|
|
17356
17572
|
body: { slug: out.slug }
|
|
17357
17573
|
});
|
|
17358
|
-
return { ...out, activated: act2, check: compareShapes(before, await homeShape(site)) };
|
|
17574
|
+
return { ...out, ...guides, activated: act2, check: compareShapes(before, await homeShape(site)) };
|
|
17359
17575
|
}
|
|
17360
17576
|
}),
|
|
17361
17577
|
tool({
|
|
@@ -17547,6 +17763,114 @@ var childTools = [
|
|
|
17547
17763
|
};
|
|
17548
17764
|
}
|
|
17549
17765
|
}),
|
|
17766
|
+
tool({
|
|
17767
|
+
name: "wp_site_guides_get",
|
|
17768
|
+
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.",
|
|
17769
|
+
parameters: { type: "object", properties: { slug: str2("The child theme (default the active one).") } },
|
|
17770
|
+
group: "site",
|
|
17771
|
+
need: READ,
|
|
17772
|
+
risk: "R0",
|
|
17773
|
+
gates: ["WP"],
|
|
17774
|
+
run: async (ctx, a) => {
|
|
17775
|
+
const { slug, current } = await guidesContext(siteOf(ctx), a.slug);
|
|
17776
|
+
return {
|
|
17777
|
+
child: slug,
|
|
17778
|
+
"DESIGN.md": current.design || null,
|
|
17779
|
+
"AGENTS.md": current.agents || null,
|
|
17780
|
+
"MEMORY.md": current.memory || null,
|
|
17781
|
+
...!current.design || !current.agents || !current.memory ? { next: "wp_site_guides_set init: true writes the missing ones." } : {}
|
|
17782
|
+
};
|
|
17783
|
+
}
|
|
17784
|
+
}),
|
|
17785
|
+
tool({
|
|
17786
|
+
name: "wp_site_guides_set",
|
|
17787
|
+
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).`,
|
|
17788
|
+
parameters: {
|
|
17789
|
+
type: "object",
|
|
17790
|
+
properties: {
|
|
17791
|
+
slug: str2("The child theme (default the active one)."),
|
|
17792
|
+
design_md: str2("New DESIGN.md."),
|
|
17793
|
+
agents_md: str2("New AGENTS.md."),
|
|
17794
|
+
memory_md: str2("New MEMORY.md."),
|
|
17795
|
+
refresh_design: bool("Rewrite DESIGN.md from the site, keeping its Rules section."),
|
|
17796
|
+
init: bool("Write the missing guides from templates.")
|
|
17797
|
+
}
|
|
17798
|
+
},
|
|
17799
|
+
group: "site",
|
|
17800
|
+
need: WRITE,
|
|
17801
|
+
risk: "W2",
|
|
17802
|
+
gates: [...GATES],
|
|
17803
|
+
confirm: (a, lang) => {
|
|
17804
|
+
const what = [
|
|
17805
|
+
a.design_md || a.refresh_design ? "DESIGN.md" : "",
|
|
17806
|
+
a.agents_md ? "AGENTS.md" : "",
|
|
17807
|
+
a.memory_md ? "MEMORY.md" : "",
|
|
17808
|
+
a.init ? "the missing guides" : ""
|
|
17809
|
+
].filter(Boolean);
|
|
17810
|
+
return say(
|
|
17811
|
+
lang,
|
|
17812
|
+
`Write ${what.join(", ") || "the guides"} in your website's child theme (public files; a backup is kept).`,
|
|
17813
|
+
`\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`
|
|
17814
|
+
);
|
|
17815
|
+
},
|
|
17816
|
+
run: async (ctx, a) => {
|
|
17817
|
+
const site = siteOf(ctx);
|
|
17818
|
+
const { slug, current } = await guidesContext(site, a.slug);
|
|
17819
|
+
const files = {};
|
|
17820
|
+
if (typeof a.agents_md === "string") files.agents = a.agents_md;
|
|
17821
|
+
if (typeof a.memory_md === "string") files.memory = a.memory_md;
|
|
17822
|
+
if (typeof a.design_md === "string") files.design = a.design_md;
|
|
17823
|
+
if (a.refresh_design)
|
|
17824
|
+
files.design = await designMd(site, {
|
|
17825
|
+
...await siteFacts(site),
|
|
17826
|
+
child: slug,
|
|
17827
|
+
current: files.design ?? current.design
|
|
17828
|
+
});
|
|
17829
|
+
const wrote = Object.keys(files).length ? await guidesWrite(site, slug, files) : null;
|
|
17830
|
+
const init = a.init ? await guidesInit(site, slug, { ...current, ...files }) : null;
|
|
17831
|
+
if (!wrote && !init) throw new Error(a.init ? "All three guides already exist." : "Nothing to change.");
|
|
17832
|
+
return {
|
|
17833
|
+
child: slug,
|
|
17834
|
+
changed: [...wrote?.changed ?? [], ...init?.changed ?? []],
|
|
17835
|
+
backup: wrote?.backup ?? init?.backup ?? null,
|
|
17836
|
+
...a.refresh_design ? { design_md: files.design } : {},
|
|
17837
|
+
undo: "wp_child_theme_restore with the backup"
|
|
17838
|
+
};
|
|
17839
|
+
}
|
|
17840
|
+
}),
|
|
17841
|
+
tool({
|
|
17842
|
+
name: "wp_site_memory_add",
|
|
17843
|
+
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.`,
|
|
17844
|
+
parameters: {
|
|
17845
|
+
type: "object",
|
|
17846
|
+
properties: {
|
|
17847
|
+
kind: { type: "string", enum: ["prefer", "avoid", "note"], description: "prefer, avoid (don't) or note." },
|
|
17848
|
+
text: str2("One line, in the owner's words."),
|
|
17849
|
+
slug: str2("The child theme (default the active one).")
|
|
17850
|
+
},
|
|
17851
|
+
required: ["kind", "text"]
|
|
17852
|
+
},
|
|
17853
|
+
group: "site",
|
|
17854
|
+
need: WRITE,
|
|
17855
|
+
risk: "W2",
|
|
17856
|
+
gates: [...GATES],
|
|
17857
|
+
confirm: (a, lang) => say(
|
|
17858
|
+
lang,
|
|
17859
|
+
`Remember for this website (${a.kind === "avoid" ? "don't" : a.kind === "note" ? "note" : "preference"}): ${q2(a.text, "")}.`,
|
|
17860
|
+
`\u8A18\u4F4F\u9019\u500B\u7DB2\u7AD9\u7684${a.kind === "avoid" ? "\u7981\u5FCC" : a.kind === "note" ? "\u5099\u8A3B" : "\u504F\u597D"}\uFF1A${q2(a.text, "")}\u3002`
|
|
17861
|
+
),
|
|
17862
|
+
run: async (ctx, a) => {
|
|
17863
|
+
const site = siteOf(ctx);
|
|
17864
|
+
const text3 = String(a.text ?? "").trim();
|
|
17865
|
+
if (!text3) throw new Error("text: what to remember.");
|
|
17866
|
+
if (text3.length > 500) throw new Error("text: one line, under 500 characters.");
|
|
17867
|
+
const kind = ["prefer", "avoid", "note"].includes(String(a.kind)) ? a.kind : "note";
|
|
17868
|
+
const { slug, current } = await guidesContext(site, a.slug);
|
|
17869
|
+
const next = memoryAdd(current.memory, kind, text3);
|
|
17870
|
+
const out = await guidesWrite(site, slug, { memory: next });
|
|
17871
|
+
return { child: slug, remembered: { kind, text: text3 }, backup: out.backup, "MEMORY.md": next };
|
|
17872
|
+
}
|
|
17873
|
+
}),
|
|
17550
17874
|
tool({
|
|
17551
17875
|
name: "wp_child_theme_activate",
|
|
17552
17876
|
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.",
|
|
@@ -21928,7 +22252,7 @@ var program = new Command();
|
|
|
21928
22252
|
// package.json
|
|
21929
22253
|
var package_default = {
|
|
21930
22254
|
name: "@cmo-so/cli",
|
|
21931
|
-
version: "0.12.
|
|
22255
|
+
version: "0.12.11",
|
|
21932
22256
|
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.",
|
|
21933
22257
|
type: "module",
|
|
21934
22258
|
license: "MIT",
|
|
@@ -33192,6 +33516,21 @@ ${table(r.issues, ["severity", "code", "path", "detail"])}`;
|
|
|
33192
33516
|
);
|
|
33193
33517
|
child.command("activate <slug>").description("switch the site to a child (or maggie-core to go back); checks the homepage before and after").action((slug, _o, c) => run(g(c), "wp_child_theme_activate", { slug }));
|
|
33194
33518
|
child.command("restore <slug> <backup>").description("put a backup of the child's files back").action((slug, backup, _o, c) => run(g(c), "wp_child_theme_restore", { slug, backup }));
|
|
33519
|
+
const guides = wp.command("guides").description("the child theme's guides for agents: DESIGN.md, AGENTS.md, MEMORY.md");
|
|
33520
|
+
guides.command("get [slug]", { isDefault: true }).action((slug, _o, c) => run(g(c), "wp_site_guides_get", { slug }));
|
|
33521
|
+
guides.command("set [slug]").option("--design-file <file>", "replace DESIGN.md").option("--agents-file <file>", "replace AGENTS.md").option("--memory-file <file>", "replace MEMORY.md").option("--refresh-design", "rewrite DESIGN.md from the site, keeping its Rules").option("--init", "write the missing guides from templates").action(
|
|
33522
|
+
async (slug, o, c) => run(g(c), "wp_site_guides_set", {
|
|
33523
|
+
slug,
|
|
33524
|
+
design_md: o.designFile ? await readFile2(o.designFile, "utf8") : void 0,
|
|
33525
|
+
agents_md: o.agentsFile ? await readFile2(o.agentsFile, "utf8") : void 0,
|
|
33526
|
+
memory_md: o.memoryFile ? await readFile2(o.memoryFile, "utf8") : void 0,
|
|
33527
|
+
refresh_design: o.refreshDesign,
|
|
33528
|
+
init: o.init
|
|
33529
|
+
})
|
|
33530
|
+
);
|
|
33531
|
+
wp.command("remember <kind> <text...>").description("add to MEMORY.md: kind prefer, avoid (a don't) or note").action(
|
|
33532
|
+
(kind, text3, _o, c) => run(g(c), "wp_site_memory_add", { kind, text: text3.join(" ") })
|
|
33533
|
+
);
|
|
33195
33534
|
wp.command("child-theme <slug>").description(
|
|
33196
33535
|
"export the site's look as a child theme folder on this computer (to keep in git); cmo wp child works on the site itself"
|
|
33197
33536
|
).requiredOption("--name <name>", 'theme name, e.g. "Moon Lake Spa"').option("--out <dir>", "where to write it (default ./<slug>)").option("--from-site", "take the brand, header and footer from the connected site").option("--color <slug=hex...>", "brand colours").option("--font <slug=family...>", "fonts (replaces Maggie Core's font list: ship their files too)").action(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|