@cmo-so/cli 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.mjs +155 -9
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -14160,7 +14160,7 @@ var SECTIONS2 = [
|
|
|
14160
14160
|
[
|
|
14161
14161
|
S(s.heading) || S(s.intro) ? prose([...headed(s), para(S(s.intro))]) : "",
|
|
14162
14162
|
group(boxes, {
|
|
14163
|
-
cls: `maggie-stagger grid gap-5 ${boxes.length
|
|
14163
|
+
cls: `maggie-stagger grid gap-5 ${boxes.length === 3 ? "sm:grid-cols-3" : "sm:grid-cols-2"}`
|
|
14164
14164
|
})
|
|
14165
14165
|
],
|
|
14166
14166
|
"flex flex-col gap-8"
|
|
@@ -14383,6 +14383,7 @@ var SECTIONS2 = [
|
|
|
14383
14383
|
slots: {
|
|
14384
14384
|
title: str('e.g. "Book your HydraFacial".'),
|
|
14385
14385
|
text: str("One sentence."),
|
|
14386
|
+
button_label: str('Default "Book Now".'),
|
|
14386
14387
|
button_url: str("Default: the booking link.")
|
|
14387
14388
|
},
|
|
14388
14389
|
required: ["title"],
|
|
@@ -14393,7 +14394,7 @@ var SECTIONS2 = [
|
|
|
14393
14394
|
buttons(
|
|
14394
14395
|
[
|
|
14395
14396
|
button(
|
|
14396
|
-
"Book Now",
|
|
14397
|
+
S(s.button_label) || "Book Now",
|
|
14397
14398
|
S(s.button_url) || kit.bookUrl || "/contact-us/",
|
|
14398
14399
|
"w-full [&_a]:w-full [&_a]:bg-white [&_a]:text-ink hover:[&_a]:bg-panel"
|
|
14399
14400
|
)
|
|
@@ -15041,7 +15042,7 @@ async function menuSet(site, ops2, id) {
|
|
|
15041
15042
|
const blocks = parse(nav.content.raw);
|
|
15042
15043
|
const before = toItems(blocks);
|
|
15043
15044
|
const after = applyMenuOps(before, ops2);
|
|
15044
|
-
const rest = blocks.filter((b) => b.name && !["core/navigation-link", "core/navigation-submenu"].includes(b.name));
|
|
15045
|
+
const rest = ops2.items ? [] : blocks.filter((b) => b.name && !["core/navigation-link", "core/navigation-submenu"].includes(b.name));
|
|
15045
15046
|
const content = [fromItems(after), serialize(rest)].filter(Boolean).join("\n");
|
|
15046
15047
|
await call(site, {
|
|
15047
15048
|
method: "POST",
|
|
@@ -15612,7 +15613,11 @@ function composePage(sections, kit, aside = {}) {
|
|
|
15612
15613
|
const asideParts = [
|
|
15613
15614
|
kit.synced?.share ? group([syncedRef(kit.synced.share)], { cls: "px-2" }) : "",
|
|
15614
15615
|
serialize([
|
|
15615
|
-
buildSection(
|
|
15616
|
+
buildSection(
|
|
15617
|
+
"cta-card",
|
|
15618
|
+
{ title: aside.ctaTitle || "Book your visit", text: aside.ctaText ?? "", button_label: aside.ctaButton },
|
|
15619
|
+
kit
|
|
15620
|
+
)
|
|
15616
15621
|
])
|
|
15617
15622
|
];
|
|
15618
15623
|
const body = band(
|
|
@@ -16399,7 +16404,11 @@ var pageTools = [
|
|
|
16399
16404
|
description: str2(""),
|
|
16400
16405
|
keywords: str2("")
|
|
16401
16406
|
}),
|
|
16402
|
-
cta: obj("Sidebar booking card: title, text
|
|
16407
|
+
cta: obj("Sidebar booking card: title, text, button label (default Book Now).", {
|
|
16408
|
+
title: str2(""),
|
|
16409
|
+
text: str2(""),
|
|
16410
|
+
button: str2("")
|
|
16411
|
+
}),
|
|
16403
16412
|
related: bool(
|
|
16404
16413
|
"Add a related-services band from sibling pages (default true when the page has a reading column)."
|
|
16405
16414
|
),
|
|
@@ -16444,7 +16453,8 @@ var pageTools = [
|
|
|
16444
16453
|
const cta = a.cta ?? {};
|
|
16445
16454
|
const blocks = composePage(sections, kit, {
|
|
16446
16455
|
ctaTitle: cta.title || `Book ${String(a.title)}`,
|
|
16447
|
-
ctaText: cta.text
|
|
16456
|
+
ctaText: cta.text,
|
|
16457
|
+
ctaButton: cta.button
|
|
16448
16458
|
});
|
|
16449
16459
|
const outline = brief2(blocks, kit.syncedTitles);
|
|
16450
16460
|
if (a.preview) return { preview: true, title: a.title, sections: outline };
|
|
@@ -17108,6 +17118,17 @@ var childTools = [
|
|
|
17108
17118
|
slug: str2("The child theme (default the active one)."),
|
|
17109
17119
|
colors: obj('e.g. { "olive": "#4F5B48" }.'),
|
|
17110
17120
|
fonts: obj('Font families by slug: { "body": "Jost, sans-serif" } (files must already be in the child).'),
|
|
17121
|
+
font_files: arr(
|
|
17122
|
+
"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).",
|
|
17123
|
+
obj("", {
|
|
17124
|
+
slug: str2("Family slug in theme.json: display, body, \u2026"),
|
|
17125
|
+
family: str2('Family name, e.g. "Roboto Slab".'),
|
|
17126
|
+
fallback: str2('Fallback stack, e.g. "Georgia, serif".'),
|
|
17127
|
+
url: str2("https link to the font file."),
|
|
17128
|
+
weight: str2('e.g. "400", "800" or "100 900".'),
|
|
17129
|
+
style: str2('"normal" or "italic".')
|
|
17130
|
+
})
|
|
17131
|
+
),
|
|
17111
17132
|
styles: obj('theme.json styles to merge in (e.g. { "elements": { "button": { \u2026 } } }).'),
|
|
17112
17133
|
css: str2("Replace the child's CSS (plain CSS)."),
|
|
17113
17134
|
css_append: str2("Add CSS at the end."),
|
|
@@ -17134,9 +17155,54 @@ var childTools = [
|
|
|
17134
17155
|
const cur = info.files ?? {};
|
|
17135
17156
|
const files = {};
|
|
17136
17157
|
const notes = [];
|
|
17158
|
+
if (Array.isArray(a.font_files) && a.font_files.length) {
|
|
17159
|
+
const tj0 = JSON.parse(files["theme.json"] ?? cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17160
|
+
if (!tj0.settings || Array.isArray(tj0.settings)) tj0.settings = {};
|
|
17161
|
+
const typo = tj0.settings.typography ?? {};
|
|
17162
|
+
const fams = typo.fontFamilies ?? [];
|
|
17163
|
+
for (const f of a.font_files) {
|
|
17164
|
+
if (!/^[a-z0-9-]{2,30}$/.test(f.slug ?? ""))
|
|
17165
|
+
throw new Error(`Font slug "${f.slug}": lowercase letters, digits and dashes.`);
|
|
17166
|
+
if (!/^https:\/\//.test(f.url ?? "")) throw new Error(`${f.family}: the font's link must be https.`);
|
|
17167
|
+
const r = await fetch(f.url);
|
|
17168
|
+
if (!r.ok) throw new Error(`${f.family}: the font's link answered HTTP ${r.status}.`);
|
|
17169
|
+
const bytes = new Uint8Array(await r.arrayBuffer());
|
|
17170
|
+
const head = String.fromCharCode(...bytes.subarray(0, 4));
|
|
17171
|
+
const ext = head === "wOF2" ? "woff2" : head === "wOFF" ? "woff" : head === "OTTO" ? "otf" : head === "\0\0\0" || head === "true" ? "ttf" : "";
|
|
17172
|
+
if (!ext) throw new Error(`${f.family}: that link isn't a font file.`);
|
|
17173
|
+
if (bytes.length > 4 * 1024 * 1024) throw new Error(`${f.family}: the font file is larger than 4 MB.`);
|
|
17174
|
+
const weight = f.weight || "400";
|
|
17175
|
+
const style = f.style || "normal";
|
|
17176
|
+
const name = `${f.family.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${weight.replace(/\s+/g, "-")}${style === "italic" ? "-italic" : ""}.${ext}`;
|
|
17177
|
+
let bin = "";
|
|
17178
|
+
for (let i = 0; i < bytes.length; i += 32768) bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
17179
|
+
files[`assets/fonts/${name}`] = btoa(bin);
|
|
17180
|
+
let fam = fams.find((x) => x.slug === f.slug);
|
|
17181
|
+
const stack = `"${f.family}", ${f.fallback || "sans-serif"}`;
|
|
17182
|
+
if (!fam || !fam.fontFamily.includes(`"${f.family}"`)) {
|
|
17183
|
+
const keep = fam ? fams.indexOf(fam) : -1;
|
|
17184
|
+
fam = { slug: f.slug, name: f.family, fontFamily: stack, fontFace: [] };
|
|
17185
|
+
if (keep >= 0) fams[keep] = fam;
|
|
17186
|
+
else fams.push(fam);
|
|
17187
|
+
}
|
|
17188
|
+
fam.fontFace = [
|
|
17189
|
+
...(fam.fontFace ?? []).filter((x) => !(x.fontWeight === weight && x.fontStyle === style)),
|
|
17190
|
+
{
|
|
17191
|
+
fontFamily: f.family,
|
|
17192
|
+
fontWeight: weight,
|
|
17193
|
+
fontStyle: style,
|
|
17194
|
+
fontDisplay: "swap",
|
|
17195
|
+
src: [`file:./assets/fonts/${name}`]
|
|
17196
|
+
}
|
|
17197
|
+
];
|
|
17198
|
+
}
|
|
17199
|
+
tj0.settings.typography = { ...typo, fontFamilies: fams };
|
|
17200
|
+
files["theme.json"] = JSON.stringify(tj0, null, " ");
|
|
17201
|
+
}
|
|
17137
17202
|
if (a.colors || a.fonts || a.styles) {
|
|
17138
|
-
const tj = JSON.parse(cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17139
|
-
tj.settings
|
|
17203
|
+
const tj = JSON.parse(files["theme.json"] ?? cur["theme.json"] ?? '{"version":3,"settings":{}}');
|
|
17204
|
+
if (!tj.settings || Array.isArray(tj.settings)) tj.settings = {};
|
|
17205
|
+
if (Array.isArray(tj.styles)) tj.styles = {};
|
|
17140
17206
|
if (a.colors) {
|
|
17141
17207
|
let palette = tj.settings.color?.palette ?? [];
|
|
17142
17208
|
if (!palette.length) palette = ((await styleGet(site))._palette ?? []).map((c) => ({ ...c }));
|
|
@@ -17265,6 +17331,81 @@ var childTools = [
|
|
|
17265
17331
|
})
|
|
17266
17332
|
})
|
|
17267
17333
|
];
|
|
17334
|
+
var themeTools = [
|
|
17335
|
+
tool({
|
|
17336
|
+
name: "wp_theme_updates",
|
|
17337
|
+
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.",
|
|
17338
|
+
parameters: { type: "object", properties: {} },
|
|
17339
|
+
group: "site",
|
|
17340
|
+
need: READ,
|
|
17341
|
+
risk: "R0",
|
|
17342
|
+
gates: ["WP"],
|
|
17343
|
+
run: (ctx) => call(siteOf(ctx), { path: "/maggie-core/v1/update", query: { fresh: 1 } })
|
|
17344
|
+
}),
|
|
17345
|
+
tool({
|
|
17346
|
+
name: "wp_theme_update",
|
|
17347
|
+
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.",
|
|
17348
|
+
parameters: {
|
|
17349
|
+
type: "object",
|
|
17350
|
+
properties: { rollback_to: str2("Put this kept version back instead (from wp_theme_updates backups).") }
|
|
17351
|
+
},
|
|
17352
|
+
group: "site",
|
|
17353
|
+
need: WRITE,
|
|
17354
|
+
risk: "W3",
|
|
17355
|
+
gates: [...GATES],
|
|
17356
|
+
confirm: (a, lang) => a.rollback_to ? say(
|
|
17357
|
+
lang,
|
|
17358
|
+
`Put Maggie Core ${String(a.rollback_to)} back on your website.`,
|
|
17359
|
+
`\u628A\u7DB2\u7AD9\u7684 Maggie Core \u9084\u539F\u5230 ${String(a.rollback_to)}\u3002`
|
|
17360
|
+
) : say(
|
|
17361
|
+
lang,
|
|
17362
|
+
"Update Maggie Core on your website to the latest version (rolled back by itself if the site breaks).",
|
|
17363
|
+
"\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"
|
|
17364
|
+
),
|
|
17365
|
+
run: async (ctx, a) => {
|
|
17366
|
+
const site = siteOf(ctx);
|
|
17367
|
+
const before = await homeShape(site);
|
|
17368
|
+
if (a.rollback_to) {
|
|
17369
|
+
const r2 = await call(site, {
|
|
17370
|
+
method: "POST",
|
|
17371
|
+
path: "/maggie-core/v1/update/rollback",
|
|
17372
|
+
body: { version: a.rollback_to }
|
|
17373
|
+
});
|
|
17374
|
+
return { ...r2, check: compareShapes(before, await homeShape(site)) };
|
|
17375
|
+
}
|
|
17376
|
+
const info = await call(site, { path: "/maggie-core/v1/update", query: { fresh: 1 } });
|
|
17377
|
+
if (!info.available)
|
|
17378
|
+
return {
|
|
17379
|
+
updated: false,
|
|
17380
|
+
current: info.current,
|
|
17381
|
+
latest: info.latest,
|
|
17382
|
+
note: info.check_error ?? "Already the latest Maggie Core."
|
|
17383
|
+
};
|
|
17384
|
+
if (!info.can_update)
|
|
17385
|
+
throw new Error(info.update_issue ?? "This host doesn't let WordPress update themes itself.");
|
|
17386
|
+
const r = await call(site, {
|
|
17387
|
+
method: "POST",
|
|
17388
|
+
path: "/maggie-core/v1/update"
|
|
17389
|
+
});
|
|
17390
|
+
const check2 = compareShapes(before, await homeShape(site));
|
|
17391
|
+
const broken = check2.checked && (check2.problems.some((p) => p.startsWith("the homepage answers")) || (check2.same_text_percent ?? 100) < 60);
|
|
17392
|
+
if (broken) {
|
|
17393
|
+
const back = await call(site, {
|
|
17394
|
+
method: "POST",
|
|
17395
|
+
path: "/maggie-core/v1/update/rollback",
|
|
17396
|
+
body: { version: r.before }
|
|
17397
|
+
}).catch((e) => ({ error: e.message }));
|
|
17398
|
+
return {
|
|
17399
|
+
...r,
|
|
17400
|
+
check: check2,
|
|
17401
|
+
rolled_back: back,
|
|
17402
|
+
note: `The homepage looked broken after the update, so Maggie Core ${r.before} was put back.`
|
|
17403
|
+
};
|
|
17404
|
+
}
|
|
17405
|
+
return { ...r, check: check2, notes: info.notes, undo: `wp_theme_update rollback_to ${r.before}` };
|
|
17406
|
+
}
|
|
17407
|
+
})
|
|
17408
|
+
];
|
|
17268
17409
|
function deepMerge(a, b) {
|
|
17269
17410
|
const out = { ...a };
|
|
17270
17411
|
for (const [k, v] of Object.entries(b)) {
|
|
@@ -17534,6 +17675,7 @@ var wordpressTools = [
|
|
|
17534
17675
|
...sectionTools,
|
|
17535
17676
|
...mediaTools,
|
|
17536
17677
|
...childTools,
|
|
17678
|
+
...themeTools,
|
|
17537
17679
|
...seoTools,
|
|
17538
17680
|
...elementTools
|
|
17539
17681
|
];
|
|
@@ -20946,7 +21088,7 @@ var program = new Command();
|
|
|
20946
21088
|
// package.json
|
|
20947
21089
|
var package_default = {
|
|
20948
21090
|
name: "@cmo-so/cli",
|
|
20949
|
-
version: "0.12.
|
|
21091
|
+
version: "0.12.5",
|
|
20950
21092
|
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.",
|
|
20951
21093
|
type: "module",
|
|
20952
21094
|
license: "MIT",
|
|
@@ -32095,6 +32237,10 @@ ${table(r.issues, ["severity", "code", "path", "detail"])}`;
|
|
|
32095
32237
|
}
|
|
32096
32238
|
})
|
|
32097
32239
|
);
|
|
32240
|
+
const theme = wp.command("theme").description("Maggie Core updates");
|
|
32241
|
+
theme.command("status", { isDefault: true }).description("installed and latest version, rollback versions").action((_o, c) => run(g(c), "wp_theme_updates", {}));
|
|
32242
|
+
theme.command("update").description("update to the latest (homepage checked; rolled back by itself if it breaks)").action((_o, c) => run(g(c), "wp_theme_update", {}));
|
|
32243
|
+
theme.command("rollback <version>").description("put a kept earlier version back").action((version2, _o, c) => run(g(c), "wp_theme_update", { rollback_to: version2 }));
|
|
32098
32244
|
const child = wp.command("child").description("the site's Maggie child theme, on the site itself (data files only, backups kept)");
|
|
32099
32245
|
child.command("status [slug]", { isDefault: true }).description("child themes, their files and backups; with a slug, its files in full").action((slug, _o, c) => run(g(c), "wp_child_theme_status", { slug }));
|
|
32100
32246
|
child.command("create <slug>").description("create maggie-<name> from the site's current look").requiredOption("--name <name>").option("--empty", "start empty instead of from the site").option("--activate", "switch the site to it (checks the homepage looks the same)").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.5",
|
|
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",
|