@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.
- package/dist/cmo.mjs +55 -11
- 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 {
|
|
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
|
-
|
|
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)
|
|
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
|
|
17419
|
-
|
|
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.
|
|
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.
|
|
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",
|