@cmo-so/cli 0.12.16 → 0.12.17

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 +34 -10
  2. package/package.json +1 -1
package/dist/cmo.mjs CHANGED
@@ -34705,30 +34705,54 @@ async function techAudit(site, o2 = {}) {
34705
34705
  ...urls.length > crawl.length ? { note: `Crawled the first ${crawl.length} of ${urls.length} sitemap addresses (limit up to 200).` } : {}
34706
34706
  };
34707
34707
  }
34708
- async function pageSpeed(url) {
34709
- const key = globalThis.process?.env?.PAGESPEED_API_KEY;
34708
+ async function pageSpeedRun(url, key) {
34710
34709
  const q4 = new URLSearchParams({ url, strategy: "mobile", category: "performance" });
34711
34710
  if (key) q4.set("key", key);
34712
34711
  const r = await fetch(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${q4}`, {
34713
34712
  signal: AbortSignal.timeout(9e4)
34714
34713
  }).catch(() => null);
34715
- if (!r || !r.ok)
34716
- return {
34717
- error: `PageSpeed Insights didn't answer (${r?.status ?? "timeout"})${r?.status === 429 && !key ? ": the server needs a PAGESPEED_API_KEY (Google Cloud, free)" : ""}.`
34718
- };
34714
+ if (!r || !r.ok) return { status: r?.status ?? 0 };
34719
34715
  const j2 = await r.json();
34720
34716
  const f2 = j2.loadingExperience?.metrics ?? {};
34721
34717
  const a3 = j2.lighthouseResult?.audits ?? {};
34722
34718
  const field = !!f2.LARGEST_CONTENTFUL_PAINT_MS;
34723
34719
  return {
34724
- source: field ? "real visitors (Chrome UX Report, 28 days)" : "lab test (no real-visitor data yet)",
34720
+ status: 200,
34721
+ field,
34725
34722
  score: Math.round((j2.lighthouseResult?.categories?.performance?.score ?? 0) * 100),
34726
- lcp_ms: field ? f2.LARGEST_CONTENTFUL_PAINT_MS?.percentile : Math.round(a3["largest-contentful-paint"]?.numericValue ?? 0),
34723
+ lcp_ms: field ? f2.LARGEST_CONTENTFUL_PAINT_MS?.percentile ?? 0 : Math.round(a3["largest-contentful-paint"]?.numericValue ?? 0),
34727
34724
  cls: field ? (f2.CUMULATIVE_LAYOUT_SHIFT_SCORE?.percentile ?? 0) / 100 : Number((a3["cumulative-layout-shift"]?.numericValue ?? 0).toFixed(3)),
34728
34725
  inp_ms: f2.INTERACTION_TO_NEXT_PAINT?.percentile ?? null,
34729
34726
  tbt_ms: Math.round(a3["total-blocking-time"]?.numericValue ?? 0)
34730
34727
  };
34731
34728
  }
34729
+ async function pageSpeed(url) {
34730
+ const key = globalThis.process?.env?.PAGESPEED_API_KEY;
34731
+ const runs = await Promise.all(
34732
+ [1, 2, 3].map((n2) => pageSpeedRun(`${url}${url.includes("?") ? "&" : "?"}cmo-speed=${Date.now()}-${n2}`, key))
34733
+ );
34734
+ const ok2 = runs.filter((r) => r.status === 200 && "lcp_ms" in r);
34735
+ if (!ok2.length) {
34736
+ const status = runs[0]?.status ?? 0;
34737
+ return {
34738
+ error: `PageSpeed Insights didn't answer (${status || "timeout"})${status === 429 && !key ? ": the server needs a PAGESPEED_API_KEY (Google Cloud, free)" : ""}.`
34739
+ };
34740
+ }
34741
+ const median = (xs) => {
34742
+ const s = [...xs].sort((a3, b2) => a3 - b2);
34743
+ return s[Math.floor((s.length - 1) / 2)];
34744
+ };
34745
+ const field = ok2.some((r) => r.field);
34746
+ return {
34747
+ source: field ? "real visitors (Chrome UX Report, 28 days)" : `lab test, median of ${ok2.length} runs (no real-visitor data yet)`,
34748
+ score: median(ok2.map((r) => r.score)),
34749
+ lcp_ms: median(ok2.map((r) => r.lcp_ms)),
34750
+ cls: median(ok2.map((r) => r.cls)),
34751
+ inp_ms: ok2.find((r) => r.inp_ms != null)?.inp_ms ?? null,
34752
+ tbt_ms: median(ok2.map((r) => r.tbt_ms)),
34753
+ runs: ok2.map((r) => ({ score: r.score, lcp_ms: r.lcp_ms }))
34754
+ };
34755
+ }
34732
34756
  var STOP = new Set(
34733
34757
  "the a an and or of to in for on with by at from is are be your our you we it this that how what why best guide near vs and/or my me us".split(
34734
34758
  " "
@@ -37711,7 +37735,7 @@ function withRelated(blocks, heading2, links2) {
37711
37735
  var seoTechTools = [
37712
37736
  tool({
37713
37737
  name: "wp_seo_tech_audit",
37714
- description: "Technical SEO check of the connected site, like a crawler: robots.txt (sitemap line, Google or AI crawlers blocked), the sitemap and every page it lists (status, redirects, noindex in the sitemap, canonicals pointing elsewhere, H1 count, missing or duplicate titles and descriptions, long titles, pictures without alt text, broken structured data, thin pages), broken and redirected internal links, generic link words, llms.txt; speed: true adds Core Web Vitals for the homepage (PageSpeed Insights). Findings ranked p0\u2026p4 with the fix and a tool when one can do it. Read-only. Internal-link layout: wp_internal_links_plan.",
37738
+ description: "Technical SEO check of the connected site, like a crawler: robots.txt (sitemap line, Google or AI crawlers blocked), the sitemap and every page it lists (status, redirects, noindex in the sitemap, canonicals pointing elsewhere, H1 count, missing or duplicate titles and descriptions, long titles, pictures without alt text, broken structured data, thin pages), broken and redirected internal links, generic link words, llms.txt; speed: true adds Core Web Vitals for the homepage (PageSpeed Insights: real visitors when Google has them, else the median of three lab runs). Findings ranked p0\u2026p4 with the fix and a tool when one can do it. Read-only. Internal-link layout: wp_internal_links_plan.",
37715
37739
  parameters: {
37716
37740
  type: "object",
37717
37741
  properties: {
@@ -41359,7 +41383,7 @@ var program = new Command();
41359
41383
  // package.json
41360
41384
  var package_default = {
41361
41385
  name: "@cmo-so/cli",
41362
- version: "0.12.16",
41386
+ version: "0.12.17",
41363
41387
  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.",
41364
41388
  type: "module",
41365
41389
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/cli",
3
- "version": "0.12.16",
3
+ "version": "0.12.17",
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",