@cmo-so/cli 0.12.5 → 0.12.6

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cmo.mjs +125 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,6 +36,7 @@ cmo wp status | map [--sections] | pages [--search …] [--missing-seo] | sectio
36
36
  cmo wp page <page> [--section s2] a page's sections (ids: m… bands, s… reading column, a… sidebar)
37
37
  cmo wp page create --title … --from sections.json [--parent …] [--publish] [--menu-parent …]
38
38
  cmo wp page update <page> title=… slug=… status=… | delete <page> [--redirect-to …] | undo <page>
39
+ cmo wp page publish <page> [--homepage] | cmo wp home <page> [--posts-page …] [--latest-posts]
39
40
  cmo wp page duplicate <page> --title … --replace Fairfax=Vienna
40
41
  cmo wp section add <page> <type> --from slots.json [--after s1] [--preview]
41
42
  cmo wp section update <page> <section> --from change.json [--preview] (edits | slots | faq)
package/dist/cmo.mjs CHANGED
@@ -16181,6 +16181,27 @@ async function changePage(ctx, a, fn) {
16181
16181
  undo: "wp_page_undo goes back to the version before this change."
16182
16182
  };
16183
16183
  }
16184
+ var findAllBlocks = (list3) => list3.flatMap((b) => [b, ...findAllBlocks(b.inner)]);
16185
+ async function setHome(site, id, postsPage) {
16186
+ const before = await call(site, {
16187
+ path: "/wp/v2/settings",
16188
+ query: { _fields: "show_on_front,page_on_front,page_for_posts" }
16189
+ });
16190
+ const body = { show_on_front: "page", page_on_front: id };
16191
+ if (postsPage !== void 0) body.page_for_posts = postsPage;
16192
+ if (postsPage === id) throw new Error("The homepage and the posts page can't be the same page.");
16193
+ const after = await call(site, {
16194
+ method: "POST",
16195
+ path: "/wp/v2/settings",
16196
+ query: { _fields: "show_on_front,page_on_front,page_for_posts" },
16197
+ body
16198
+ });
16199
+ return {
16200
+ before,
16201
+ after,
16202
+ undo: before.show_on_front === "page" && before.page_on_front ? `wp_site_home_set page ${before.page_on_front}` : "wp_site_home_set latest_posts true"
16203
+ };
16204
+ }
16184
16205
  function position(a) {
16185
16206
  return {
16186
16207
  after: a.after || void 0,
@@ -16553,6 +16574,103 @@ var pageTools = [
16553
16574
  };
16554
16575
  }
16555
16576
  }),
16577
+ tool({
16578
+ name: "wp_page_publish",
16579
+ description: "Publish a page (a draft or private page) so visitors and search engines see it. Checks it first: one H1, an SEO title and description (warns when missing). homepage: true also makes it the site's homepage. Afterwards the live page is opened to confirm it works. To take it down again, wp_page_update status draft.",
16580
+ parameters: {
16581
+ type: "object",
16582
+ properties: {
16583
+ page: PAGE,
16584
+ homepage: bool("Also make it the site's homepage."),
16585
+ expect_modified: EXPECT
16586
+ },
16587
+ required: ["page"]
16588
+ },
16589
+ group: "site",
16590
+ need: WRITE,
16591
+ risk: "W3",
16592
+ gates: [...GATES],
16593
+ confirm: (a, lang) => say(
16594
+ lang,
16595
+ `Publish page ${q2(a.page, "")} on your website${a.homepage ? " and make it the homepage" : ""}.`,
16596
+ `\u767C\u4F48\u7DB2\u7AD9\u4E0A\u7684\u9801\u9762 ${q2(a.page, "")}${a.homepage ? "\uFF0C\u4E26\u8A2D\u70BA\u9996\u9801" : ""}\u3002`
16597
+ ),
16598
+ run: async (ctx, a) => {
16599
+ const site = siteOf(ctx);
16600
+ const p = await findPage(site, a.page, true);
16601
+ checkFresh(p, a.expect_modified);
16602
+ const m = p.meta ?? {};
16603
+ const blocks = parse(p.content?.raw ?? "");
16604
+ const h1 = findAllBlocks(blocks).filter((b) => b.name === "core/heading" && Number(b.attrs.level) === 1).length;
16605
+ const warnings = [
16606
+ h1 === 1 ? "" : h1 === 0 ? "The page has no H1 in its content (a template may add one)." : `The page has ${h1} H1 headings.`,
16607
+ m.maggie_seo_title ? "" : "No SEO title: the page title is used.",
16608
+ m.maggie_seo_description ? "" : "No meta description: set one with wp_seo_set."
16609
+ ].filter(Boolean);
16610
+ const wasStatus = p.status;
16611
+ const saved = p.status === "publish" ? p : await savePage(site, p.id, null, { status: "publish" });
16612
+ let home = null;
16613
+ if (a.homepage) home = await setHome(site, p.id);
16614
+ const live = await check(site, saved.link, "publish");
16615
+ return {
16616
+ published: true,
16617
+ id: p.id,
16618
+ path: path(saved.link),
16619
+ before: wasStatus,
16620
+ homepage: home,
16621
+ check: live,
16622
+ warnings
16623
+ };
16624
+ }
16625
+ }),
16626
+ tool({
16627
+ name: "wp_site_home_set",
16628
+ description: "Choose the site's homepage (a published page) and, optionally, the page that lists the blog posts. Returns the previous setting so it can be put back. latest_posts: true shows the newest posts as the homepage instead.",
16629
+ parameters: {
16630
+ type: "object",
16631
+ properties: {
16632
+ page: str2("The page to show as the homepage (path, slug or id)."),
16633
+ posts_page: str2('The page that lists the blog posts (optional; "" to clear).'),
16634
+ latest_posts: bool("Show the newest posts as the homepage instead of a page.")
16635
+ }
16636
+ },
16637
+ group: "site",
16638
+ need: WRITE,
16639
+ risk: "W3",
16640
+ gates: [...GATES],
16641
+ confirm: (a, lang) => a.latest_posts ? say(lang, "Show your newest posts as your website's homepage.", "\u628A\u7DB2\u7AD9\u9996\u9801\u6539\u70BA\u986F\u793A\u6700\u65B0\u6587\u7AE0\u3002") : say(
16642
+ lang,
16643
+ `Make ${q2(a.page, "that page")} your website's homepage.`,
16644
+ `\u628A ${q2(a.page, "\u8A72\u9801\u9762")} \u8A2D\u70BA\u7DB2\u7AD9\u9996\u9801\u3002`
16645
+ ),
16646
+ run: async (ctx, a) => {
16647
+ const site = siteOf(ctx);
16648
+ if (a.latest_posts) {
16649
+ const before = await call(site, {
16650
+ path: "/wp/v2/settings",
16651
+ query: { _fields: "show_on_front,page_on_front,page_for_posts" }
16652
+ });
16653
+ await call(site, {
16654
+ method: "POST",
16655
+ path: "/wp/v2/settings",
16656
+ query: { _fields: "show_on_front" },
16657
+ body: { show_on_front: "posts" }
16658
+ });
16659
+ return { before, after: { show_on_front: "posts" } };
16660
+ }
16661
+ if (!a.page) throw new Error("Which page should be the homepage?");
16662
+ const p = await findPage(site, a.page);
16663
+ if (p.status !== "publish")
16664
+ throw new Error(
16665
+ `${path(p.link)} isn't published yet: publish it first (wp_page_publish homepage: true does both).`
16666
+ );
16667
+ let postsPage;
16668
+ if (typeof a.posts_page === "string")
16669
+ postsPage = a.posts_page === "" ? 0 : (await findPage(site, a.posts_page)).id;
16670
+ const r = await setHome(site, p.id, postsPage);
16671
+ return { ...r, check: await check(site, `${site.url ?? ""}/`, "publish") };
16672
+ }
16673
+ }),
16556
16674
  tool({
16557
16675
  name: "wp_page_delete",
16558
16676
  description: "Move a page to the WordPress trash (restorable for 30 days) and redirect its address (301) to another page, by default its parent or the homepage.",
@@ -21088,7 +21206,7 @@ var program = new Command();
21088
21206
  // package.json
21089
21207
  var package_default = {
21090
21208
  name: "@cmo-so/cli",
21091
- version: "0.12.5",
21209
+ version: "0.12.6",
21092
21210
  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.",
21093
21211
  type: "module",
21094
21212
  license: "MIT",
@@ -32059,6 +32177,9 @@ function registerWp(program3) {
32059
32177
  wp.command("pages").description("find pages").option("--search <words>", "words in the title").option("--missing-seo", "only pages without their own SEO title or description").option("--template <slug>", "only this template").action(
32060
32178
  (o, c) => run(g(c), "wp_pages_list", { search: o.search, missing_seo: !!o.missingSeo, template: o.template })
32061
32179
  );
32180
+ wp.command("home [page]").description("choose the homepage (a published page), the posts page, or the latest posts").option("--posts-page <page>", 'the page that lists the blog posts ("" to clear)').option("--latest-posts", "show the newest posts as the homepage").action(
32181
+ (p, o, c) => run(g(c), "wp_site_home_set", { page: p, posts_page: o.postsPage, latest_posts: !!o.latestPosts })
32182
+ );
32062
32183
  wp.command("sections").description("the section library and page blueprints").action(
32063
32184
  (_o, c) => run(
32064
32185
  g(c),
@@ -32103,6 +32224,9 @@ function registerWp(program3) {
32103
32224
  replace: pairs(o.replace).map(([find2, replace2]) => ({ find: find2, replace: replace2 }))
32104
32225
  })
32105
32226
  );
32227
+ page.command("publish <page>").description("publish a page (checks it first); --homepage also makes it the homepage").option("--homepage", "make it the site's homepage").action(
32228
+ (p, o, c) => run(g(c), "wp_page_publish", { page: p, homepage: !!o.homepage })
32229
+ );
32106
32230
  page.command("undo <page>").description("back to the version before the last change").action((p, _o, c) => run(g(c), "wp_page_undo", { page: p }));
32107
32231
  const section = wp.command("section").description("add, change, move or remove a page's sections");
32108
32232
  const pos = (cmd) => cmd.option("--after <section>", "after this section (id or type)").option("--before <section>", "before this section").option("--region <region>", "main, story or aside").option("--index <n>", "position in the region");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/cli",
3
- "version": "0.12.5",
3
+ "version": "0.12.6",
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",