@cmo-so/cli 0.12.12 → 0.12.13

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 (2) hide show
  1. package/dist/cmo.mjs +55 -11
  2. package/package.json +1 -1
package/dist/cmo.mjs CHANGED
@@ -17013,7 +17013,29 @@ async function techAudit(site, o = {}) {
17013
17013
  const add = (f) => findings.push(f);
17014
17014
  const robotsR = await get(`${base}/robots.txt`, "follow");
17015
17015
  const robots = robotsR?.status === 200 ? robotsR.text : "";
17016
- const { urls, sitemap } = await sitemapUrls(base, robots);
17016
+ const visible = await site.request({ path: "/wp/v2/settings", query: { _fields: "blog_public" } }).then((r) => r.status === 200 && r.data.blog_public != null ? !!Number(r.data.blog_public) : null).catch(() => null);
17017
+ const engagedOff = visible === false;
17018
+ let { urls, sitemap } = await sitemapUrls(base, robots);
17019
+ let discovered = "sitemap";
17020
+ if (!urls.length) {
17021
+ const rows = await Promise.all(
17022
+ ["/wp/v2/pages", "/wp/v2/posts"].map(
17023
+ (path3) => all(site, path3, { status: "publish", _fields: "link", per_page: 100 }, limit).catch(() => [])
17024
+ )
17025
+ );
17026
+ urls = [...new Set(rows.flat().map((r) => r.link))];
17027
+ discovered = "wordpress";
17028
+ }
17029
+ if (engagedOff)
17030
+ add({
17031
+ id: "search-engines-off",
17032
+ priority: "p1",
17033
+ area: "settings",
17034
+ title: "Search engines are switched off for this site",
17035
+ why: "WordPress tells every search engine not to index it (noindex on every page, no sitemap). Right for a test or unfinished site; check MEMORY.md before changing it.",
17036
+ fix: "When the site is ready: wp_settings_set search_engines true.",
17037
+ tool: { name: "wp_settings_set", args: { search_engines: true } }
17038
+ });
17017
17039
  if (!robotsR || robotsR.status !== 200)
17018
17040
  add({
17019
17041
  id: "robots-missing",
@@ -17033,7 +17055,7 @@ async function techAudit(site, o = {}) {
17033
17055
  fix: "Turn search engines on (wp_settings_set search_engines true) or fix the robots.txt file.",
17034
17056
  tool: { name: "wp_settings_set", args: { search_engines: true } }
17035
17057
  });
17036
- if (robots && !/^sitemap:/im.test(robots))
17058
+ if (robots && !engagedOff && !/^sitemap:/im.test(robots))
17037
17059
  add({
17038
17060
  id: "robots-sitemap",
17039
17061
  priority: "p3",
@@ -17052,7 +17074,7 @@ async function techAudit(site, o = {}) {
17052
17074
  why: "ChatGPT, Claude, Perplexity and Google's AI answers can't read or cite the site.",
17053
17075
  fix: "Allow them in robots.txt unless the owner chose to block AI."
17054
17076
  });
17055
- if (!sitemap)
17077
+ if (!sitemap && !engagedOff)
17056
17078
  add({
17057
17079
  id: "sitemap-missing",
17058
17080
  priority: "p1",
@@ -17074,6 +17096,7 @@ async function techAudit(site, o = {}) {
17074
17096
  tool: { name: "wp_llms_txt_set", args: { generate: true } }
17075
17097
  });
17076
17098
  const crawl = urls.length ? urls.slice(0, limit) : [`${base}/`];
17099
+ if (!crawl.some((u) => sitePath(u, base) === "/")) crawl.unshift(`${base}/`);
17077
17100
  const scans = await pool3(crawl, 6, async (u) => {
17078
17101
  const r = await get(u);
17079
17102
  if (!r) return null;
@@ -17093,7 +17116,7 @@ async function techAudit(site, o = {}) {
17093
17116
  fix: p.status >= 400 ? "Publish the page again, or remove it (wp_post_delete / wp_page_publish)." : "List the final address; the redirect is fine for visitors."
17094
17117
  });
17095
17118
  for (const p of ok2) {
17096
- if (/noindex/.test(p.robots))
17119
+ if (/noindex/.test(p.robots) && !engagedOff && discovered === "sitemap")
17097
17120
  add({
17098
17121
  id: `noindex-in-sitemap:${p.path}`,
17099
17122
  priority: "p1",
@@ -17270,7 +17293,9 @@ async function techAudit(site, o = {}) {
17270
17293
  summary: {
17271
17294
  ...Object.fromEntries(order2.map((p) => [p, findings.filter((f) => f.priority === p).length])),
17272
17295
  sitemap,
17273
- sitemap_urls: urls.length,
17296
+ pages_from: discovered,
17297
+ search_engines: visible,
17298
+ sitemap_urls: discovered === "sitemap" ? urls.length : 0,
17274
17299
  crawled: pages.length,
17275
17300
  live: ok2.length,
17276
17301
  llms_txt: hasLlms,
@@ -17298,7 +17323,10 @@ async function pageSpeed(url) {
17298
17323
  const r = await fetch(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${q3}`, {
17299
17324
  signal: AbortSignal.timeout(9e4)
17300
17325
  }).catch(() => null);
17301
- if (!r || !r.ok) return { error: `PageSpeed Insights didn't answer (${r?.status ?? "timeout"}).` };
17326
+ if (!r || !r.ok)
17327
+ return {
17328
+ error: `PageSpeed Insights didn't answer (${r?.status ?? "timeout"})${r?.status === 429 && !key ? ": the server needs a PAGESPEED_API_KEY (Google Cloud, free)" : ""}.`
17329
+ };
17302
17330
  const j = await r.json();
17303
17331
  const f = j.loadingExperience?.metrics ?? {};
17304
17332
  const a = j.lighthouseResult?.audits ?? {};
@@ -17415,8 +17443,24 @@ async function linksPlan(site, o = {}) {
17415
17443
  const blogPage = docs.find((d) => d.id === settings2.page_for_posts && d.kind === "page");
17416
17444
  const actions = [];
17417
17445
  const findings = [];
17418
- const pageDocs = docs.filter((d) => d.kind === "page" && d !== home);
17419
- const postDocs = docs.filter((d) => d.kind === "post");
17446
+ const sample = docs.filter(
17447
+ (d) => d.kind === "page" && (/\/sample-page\/$/.test(d.path) || d.title === "Sample Page") || d.kind === "post" && (/\/hello-world\/$/.test(d.path) || d.title === "Hello world!")
17448
+ );
17449
+ for (const d of sample)
17450
+ findings.push({
17451
+ id: `sample:${d.path}`,
17452
+ priority: "p2",
17453
+ area: "outside",
17454
+ title: `WordPress's sample ${d.kind === "page" ? "page" : "article"} \u201C${d.title}\u201D is still published`,
17455
+ why: "It tells visitors and search engines the site isn't finished, and dilutes the real pages.",
17456
+ fix: "Move it to the trash.",
17457
+ tool: {
17458
+ name: d.kind === "page" ? "wp_page_delete" : "wp_post_delete",
17459
+ args: d.kind === "page" ? { page: d.id } : { post: d.id }
17460
+ }
17461
+ });
17462
+ const pageDocs = docs.filter((d) => d.kind === "page" && d !== home && !sample.includes(d));
17463
+ const postDocs = docs.filter((d) => d.kind === "post" && !sample.includes(d));
17420
17464
  const menuItems = flatMenu(menu?.items ?? []);
17421
17465
  const top = (menu?.items ?? []).length;
17422
17466
  for (const i of menuItems) {
@@ -17504,7 +17548,7 @@ async function linksPlan(site, o = {}) {
17504
17548
  for (const post of postDocs) {
17505
17549
  let best = null;
17506
17550
  let score = 0;
17507
- for (const pg of pageDocs) {
17551
+ for (const pg of home ? [...pageDocs, home] : pageDocs) {
17508
17552
  if (pg === blogPage) continue;
17509
17553
  const s = overlap(post.words, pg.words, idf);
17510
17554
  if (s > score) [best, score] = [pg, s];
@@ -17536,7 +17580,7 @@ async function linksPlan(site, o = {}) {
17536
17580
  }
17537
17581
  const lonelyPosts = [];
17538
17582
  for (const d of docs) {
17539
- if (d === home || d === blogPage) continue;
17583
+ if (d === home || d === blogPage || sample.includes(d)) continue;
17540
17584
  const ins = inbound.get(d.path);
17541
17585
  if (ins && ins.size || chrome.has(d.path)) continue;
17542
17586
  if (d.kind === "post") lonelyPosts.push(d);
@@ -23844,7 +23888,7 @@ var program = new Command();
23844
23888
  // package.json
23845
23889
  var package_default = {
23846
23890
  name: "@cmo-so/cli",
23847
- version: "0.12.12",
23891
+ version: "0.12.13",
23848
23892
  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.",
23849
23893
  type: "module",
23850
23894
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/cli",
3
- "version": "0.12.12",
3
+ "version": "0.12.13",
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",