seo 0.3.0-beta.2 → 0.3.0
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/PRIVACY.md +62 -25
- package/README.md +49 -18
- package/dist/cli.js +136 -130
- package/dist/cli.js.map +1 -1
- package/dist/{dist-D41t9hLG.js → dist-C9P8dY80.js} +1 -1
- package/dist/{dist-STeaS3Hh.js → dist-wSAqXmM1.js} +58 -58
- package/dist/dist-wSAqXmM1.js.map +1 -0
- package/dist/index.d.ts +24 -4
- package/dist/index.js +74 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +473 -38
- package/dist/mcp.js.map +1 -1
- package/dist/{package-1f8VcMU8.js → package-7H_jIpAz.js} +2 -2
- package/dist/{package-1f8VcMU8.js.map → package-7H_jIpAz.js.map} +1 -1
- package/evals/research.json +2 -2
- package/package.json +10 -2
- package/skills/seo/SKILL.md +21 -11
- package/dist/dist-STeaS3Hh.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
3
|
-
import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,
|
|
4
|
-
<h2>${
|
|
5
|
-
${
|
|
6
|
-
</section>`).join(``)}function
|
|
7
|
-
<h2>${
|
|
8
|
-
${e.summary?`<p>${
|
|
9
|
-
${
|
|
10
|
-
</section>`).join(``)}function
|
|
3
|
+
import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Cr as d,Ct as f,D as p,Dn as m,Dt as h,E as g,En as _,Et as v,F as y,Fn as b,Ft as x,G as S,Gn as ee,Gt as te,H as ne,Hn as C,Ht as w,I as T,In as re,It as ie,J as ae,Jn as oe,Jt as se,K as ce,Kn as le,Kt as ue,L as de,Ln as fe,Lt as pe,M as me,Mn as he,Mt as ge,N as _e,Nn as ve,Nt as ye,O as be,On as xe,Ot as Se,P as Ce,Pn as we,Pt as Te,Q as Ee,Qn as De,Qt as Oe,R as ke,Rn as Ae,Rt as je,S as Me,Sn as Ne,Sr as Pe,St as Fe,T as Ie,Tn as Le,Tt as Re,U as ze,Un as Be,Ut as Ve,V as He,Vn as Ue,Vt as We,W as Ge,Wn as Ke,Wt as qe,X as Je,Xn as Ye,Xt as Xe,Y as Ze,Yn as Qe,Yt as $e,Z as et,Zn as tt,Zt as nt,_ as rt,_n as it,_r as at,_t as ot,a as st,an as ct,ar as lt,at as ut,b as dt,bn as ft,br as pt,bt as mt,c as ht,cn as gt,cr as _t,ct as vt,d as yt,dn as bt,dr as xt,dt as St,en as Ct,er as wt,et as Tt,f as Et,fn as Dt,fr as Ot,ft as kt,g as At,gn as jt,gr as E,gt as Mt,h as Nt,hn as Pt,hr as Ft,ht as It,i as Lt,in as Rt,ir as zt,it as Bt,j as Vt,jn as Ht,jt as Ut,k as Wt,kn as Gt,kt as Kt,l as qt,ln as Jt,lr as Yt,lt as Xt,m as Zt,mn as Qt,mr as $t,mt as en,n as tn,nn,nr as rn,nt as an,o as on,on as sn,or as cn,ot as ln,p as un,pn as dn,pr as fn,pt as pn,q as mn,qn as hn,qt as gn,rn as _n,rr as vn,rt as yn,sn as bn,sr as xn,st as Sn,t as Cn,tn as wn,tr as Tn,tt as En,u as Dn,un as On,ur as kn,ut as An,v as jn,vn as Mn,vr as Nn,vt as Pn,w as Fn,wn as In,wt as Ln,x as Rn,xn as zn,xr as Bn,xt as Vn,y as Hn,yn as Un,yr as Wn,yt as Gn,z as Kn,zn as qn,zt as Jn}from"./dist-wSAqXmM1.js";import{createHash as Yn}from"node:crypto";import{constants as Xn,copyFileSync as Zn,createWriteStream as Qn,existsSync as $n,mkdirSync as er,readFileSync as tr,readdirSync as nr,realpathSync as rr,renameSync as ir,statSync as ar,writeFileSync as or}from"node:fs";import{arch as sr,homedir as cr,platform as lr}from"node:os";import{basename as ur,dirname as dr,extname as fr,join as pr,posix as mr,resolve as hr,win32 as gr}from"node:path";import{fileURLToPath as _r}from"node:url";import{mkdir as vr,readFile as yr,readdir as br,stat as xr,writeFile as Sr}from"node:fs/promises";import{isDeepStrictEqual as Cr,stripVTControlCharacters as wr}from"node:util";import{spawn as Tr,spawnSync as Er}from"node:child_process";import{defineCommand as D,runCommand as Dr,runMain as Or}from"citty";import{cancel as kr,confirm as Ar,intro as jr,isCancel as Mr,log as Nr,multiselect as Pr,note as O,outro as Fr,password as Ir,select as Lr,text as Rr}from"@clack/prompts";import zr from"picocolors";import Br from"string-width";import Vr from"cli-table3";import{applyEdits as Hr,modify as Ur,parse as Wr,printParseErrorCode as Gr}from"jsonc-parser";import{finished as Kr}from"node:stream/promises";import qr from"update-notifier";function k(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function Jr(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function Yr(e){let t=Jr(e),n=k(e);return t?`<a href="${k(t)}" rel="noreferrer">${n}</a>`:n}function Xr(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${k(e)}</li>`).join(``)}</ul>`}function Zr(e){return e.sections.map(e=>`<section class="panel">
|
|
4
|
+
<h2>${k(e.title)}</h2>
|
|
5
|
+
${Xr(e.bullets)}
|
|
6
|
+
</section>`).join(``)}function Qr(e){return e.map(e=>`<section class="panel">
|
|
7
|
+
<h2>${k(e.title)}</h2>
|
|
8
|
+
${e.summary?`<p>${k(e.summary)}</p>`:``}
|
|
9
|
+
${Xr(e.items)}
|
|
10
|
+
</section>`).join(``)}function $r(e){return e.priorities.length===0?`<p class="empty">No evidence-backed priorities were found.</p>`:`<ol class="priorities">${e.priorities.map(e=>`<li>
|
|
11
11
|
<div class="priority-heading">
|
|
12
|
-
<strong>${
|
|
13
|
-
<span class="confidence">${
|
|
12
|
+
<strong>${k(e.title)}</strong>
|
|
13
|
+
<span class="confidence">${k(e.confidence)} confidence</span>
|
|
14
14
|
</div>
|
|
15
|
-
<p>${
|
|
16
|
-
</li>`).join(``)}</ol>`}function
|
|
15
|
+
<p>${k(e.action)}</p>
|
|
16
|
+
</li>`).join(``)}</ol>`}function ei(e){let t=e.diagnosis.skippedSections??[],n=e.changeMeasurementAttempts;return`<section class="panel analyst-only">
|
|
17
17
|
<h2>Evidence coverage</h2>
|
|
18
18
|
<dl class="facts">
|
|
19
|
-
<div><dt>Data status</dt><dd>${
|
|
20
|
-
<div><dt>Generated</dt><dd>${
|
|
19
|
+
<div><dt>Data status</dt><dd>${k(e.dataStatus)}</dd></div>
|
|
20
|
+
<div><dt>Generated</dt><dd>${k(e.generatedAt)}</dd></div>
|
|
21
21
|
<div><dt>Saved changes checked</dt><dd>${n.length}</dd></div>
|
|
22
22
|
<div><dt>Unavailable sections</dt><dd>${t.length}</dd></div>
|
|
23
23
|
</dl>
|
|
24
|
-
${t.length?`<h3>Unavailable sections</h3>${
|
|
25
|
-
</section>`}function
|
|
24
|
+
${t.length?`<h3>Unavailable sections</h3>${Xr(t.map(e=>`reason`in e&&typeof e.reason==`string`?`${e.section}: ${e.reason}`:e.section))}`:``}
|
|
25
|
+
</section>`}function ti(e){let t=e.report,n=e.view??`client`,r=e.title??`SEO report`,i=t.dataStatus===`complete`?``:` open`,a=n===`analyst`?ei(t):``;return`<!doctype html>
|
|
26
26
|
<html lang="en">
|
|
27
27
|
<head>
|
|
28
28
|
<meta charset="utf-8">
|
|
29
29
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
30
30
|
<meta name="robots" content="noindex,nofollow">
|
|
31
|
-
<title>${
|
|
31
|
+
<title>${k(r)}: ${k(t.site)}</title>
|
|
32
32
|
<style>
|
|
33
33
|
:root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #17211b; background: #f2f4ef; }
|
|
34
34
|
* { box-sizing: border-box; }
|
|
@@ -69,196 +69,202 @@ import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as
|
|
|
69
69
|
<body>
|
|
70
70
|
<main>
|
|
71
71
|
<header>
|
|
72
|
-
<p class="eyebrow">${
|
|
73
|
-
<h1>${
|
|
74
|
-
<p class="headline">${
|
|
72
|
+
<p class="eyebrow">${k(r)}</p>
|
|
73
|
+
<h1>${Yr(t.site)}</h1>
|
|
74
|
+
<p class="headline">${k(t.headline)}</p>
|
|
75
75
|
<div class="meta">
|
|
76
|
-
<span>${
|
|
77
|
-
<span>Generated ${
|
|
78
|
-
<span>${
|
|
76
|
+
<span>${k(t.period.startDate)} to ${k(t.period.endDate)}</span>
|
|
77
|
+
<span>Generated ${k(t.generatedAt.slice(0,10))}</span>
|
|
78
|
+
<span>${k(n)} view</span>
|
|
79
79
|
</div>
|
|
80
|
-
<span class="status">${
|
|
80
|
+
<span class="status">${k(t.dataStatus)} data</span>
|
|
81
81
|
</header>
|
|
82
82
|
|
|
83
83
|
<section class="panel">
|
|
84
84
|
<h2>Priorities</h2>
|
|
85
|
-
${
|
|
85
|
+
${$r(t)}
|
|
86
86
|
</section>
|
|
87
87
|
|
|
88
|
-
${
|
|
89
|
-
${
|
|
88
|
+
${Zr(t)}
|
|
89
|
+
${Qr(e.additionalSections??[])}
|
|
90
90
|
${a}
|
|
91
91
|
|
|
92
92
|
<section class="panel">
|
|
93
93
|
<details${i}>
|
|
94
94
|
<summary>Evidence notes and limitations</summary>
|
|
95
|
-
${
|
|
95
|
+
${Xr(t.caveats)}
|
|
96
96
|
</details>
|
|
97
97
|
</section>
|
|
98
98
|
|
|
99
99
|
<footer>Generated locally from observed report evidence.</footer>
|
|
100
100
|
</main>
|
|
101
101
|
</body>
|
|
102
|
-
</html>`}const Xr={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function Zr(e,t){return[...e.output.segments[t].winners,...e.output.segments[t].losers].map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))}function Qr(e,t){return e.output.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}function $r(e){return[{filename:`postmortem-summary.csv`,headers:[...Xr.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...Xr.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...Xr.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...Xr.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...Xr.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...Xr.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...Xr.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...Xr.segment],rows:Zr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...Xr.unmatchedSegment],rows:Qr(e,t)}))]}function ei(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function ti(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function ni(e,t){let n=ti(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:ei(e)===t?`hostname`:void 0}function ri(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=ei(n);if(!r)return[];let i=ni(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function ii(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched Google Analytics web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}const ai=`Search Console export`,oi=new Set([`top queries`,`query`,`queries`]),si=new Set([`top pages`,`page`,`pages`,`url`]),ci=[`clicks`,`url clicks`],li=[`impressions`],ui=[`ctr`,`url ctr`,`site ctr`],di=[`position`,`average position`,`avg position`,`avg. position`],fi=[`Query and page tables are separate aggregates from a Search Console export; no query-to-page mapping exists and none was created.`,`Search Console exports are partial: anonymised queries are withheld and export row caps apply, so missing rows are not zeros.`];function pi(e){let t=e??5e3;if(!Number.isSafeInteger(t)||t<1||t>1e5)throw new T(`INVALID_INPUT`,`Search Console export row limit must be between 1 and 100000.`);return t}async function mi(e){let t=lr(e),n=await hr(t).catch(()=>void 0);if(!n)throw new T(`INVALID_INPUT`,`Search Console export path was not found: ${t}`);if(n.isFile())return[t];if(!n.isDirectory())throw new T(`INVALID_INPUT`,`Search Console export path must be a CSV file or a directory: ${t}`);let r=(await mr(t,{withFileTypes:!0})).filter(e=>{if(!e.isFile())return!1;let t=or(e.name).toLowerCase();return t===`.csv`||t===`.tsv`}).map(e=>sr(t,e.name)).sort(xi);if(r.length===0)throw new T(`INVALID_INPUT`,`Search Console export directory contains no CSV files: ${t}`);if(r.length>20)throw new T(`INVALID_INPUT`,`Search Console export directories can contain at most 20 CSV files.`);return r}function hi(e){return e.replace(/^\uFEFF/u,``).trim().toLowerCase()}function gi(e,t){for(let n of t){let t=e.indexOf(n);if(t>0)return t}return null}function _i(e){let t=(e??``).trim();if(!t||!/^\d+(?:,\d{3})*$/u.test(t))return null;let n=Number(t.replaceAll(`,`,``));return Number.isSafeInteger(n)?n:null}function vi(e){let t=(e??``).trim();if(!t)return null;if(t.endsWith(`%`)){let e=Number(t.slice(0,-1).trim());if(!Number.isFinite(e))return null;let n=e/100;return n>=0&&n<=1?n:null}let n=Number(t);return Number.isFinite(n)&&n>=0&&n<=1?n:null}function yi(e){let t=(e??``).trim();if(!t)return null;let n=Number(t);return Number.isFinite(n)&&n>0?n:null}function bi(e){let t;try{t=new URL(e)}catch{return null}return t.protocol===`http:`||t.protocol===`https:`?e:null}function xi(e,t){return e<t?-1:+(e>t)}function Si(e,t){return e.impressions===t.impressions?e.clicks>t.clicks:e.impressions>t.impressions}function Ci(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||xi(e.query,t.query)}function wi(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||xi(e.url,t.url)}function Ti(e,t,n){let r=[...e.byKey.values()].sort(t),i=e.capped||r.length>n;return{rows:r.slice(0,n),totalRows:r.length,capped:i}}async function Ei(e){let t=pi(e.rowLimit),n=await mi(e.path),r=[],i=[],a={byKey:new Map,suppliedRows:0,capped:!1},o={byKey:new Map,suppliedRows:0,capped:!1};for(let e of n){let n=await Ye({file:e,format:`csv`,label:ai}),s=Un(`sha256`),c={bytesRead:0,hash:s},l=Qe({path:n.path,byteLimit:se,progress:c,label:ai}),u=await l.next(),d=(Array.isArray(u.value)?u.value:[]).map((e,t)=>(t===0?e.replace(/^\uFEFF/u,``):e).trim()),f=d.map(hi),p=f[0]??``,m=oi.has(p)?`queries`:si.has(p)?`pages`:`unrecognized`,h=m===`unrecognized`?d.length===0?`The file has no header row.`:`The first header "${d[0]}" is not a recognized Search Console query or page column.`:void 0,g=gi(f,ci),_=gi(f,li),v=gi(f,ui),y=gi(f,di),b=[...g===null?[`Clicks`]:[],..._===null?[`Impressions`]:[]],ee=h??(b.length?`The recognized ${m} table is missing required ${b.join(` and `)} columns.`:void 0),x=m!==`unrecognized`&&ee===void 0,te=0,S=0,ne=0,C=0,w=0,re=m===`queries`?a:o;for await(let e of l){if(te+=1,!x||re.suppliedRows>=t)continue;if(S+=1,re.suppliedRows+=1,e.length!==d.length){C+=1;continue}let n=_i(e[g??-1]),r=_i(e[_??-1]);if(n===null||r===null){C+=1;continue}let i=v===null?``:(e[v]??``).trim(),s=y===null?``:(e[y]??``).trim(),c=v===null?null:vi(i),l=y===null?null:yi(s);if(i&&c===null||s&&l===null){C+=1;continue}let u={clicks:n,impressions:r,ctr:c,position:l},f=(e[0]??``).trim();if(m===`queries`){if(!f){C+=1;continue}let e=a.byKey.get(f);if(e){w+=1,Si(u,e)&&a.byKey.set(f,{query:f,...u});continue}ne+=1,a.byKey.set(f,{query:f,...u});continue}let p=bi(f);if(!p){C+=1;continue}let h=o.byKey.get(p);if(h){w+=1,Si(u,h)&&o.byKey.set(p,{url:p,...u});continue}ne+=1,o.byKey.set(p,{url:p,...u})}let ie=x&&te>S;ie&&(m===`queries`?a.capped=!0:o.capped=!0,i.push(`Only ${S} of ${te} rows in ${n.path} were processed within the shared ${m} row limit of ${t}.`)),ee&&i.push(`Skipped ${n.path}: ${ee??`the file was not recognized.`}`),C>0&&i.push(`${C} row${C===1?``:`s`} in ${n.path} ${C===1?`was`:`were`} invalid and skipped.`),r.push({path:n.path,table:m,sha256:s.digest(`hex`),fileBytes:n.fileBytes,bytesRead:c.bytesRead,encoding:c.encoding??`utf-8`,delimiter:c.delimiter??null,includedFields:[...d].sort(xi),fileRows:te,suppliedRows:S,validRows:ne,invalidRows:C,duplicateRows:w,capped:ie,rowLimit:t,...ee===void 0?{}:{reason:ee}})}return r.every(e=>e.table===`unrecognized`)&&i.push(`No file matched the Search Console query or page table headers, so no rows were imported.`),{source:`search-console-export`,exportedAt:e.exportedAt?.toISOString()??null,importedAt:(e.now??new Date).toISOString(),files:r,queries:Ti(a,Ci,t),pages:Ti(o,wi,t),warnings:i,caveats:[...fi,e.exportedAt?`exportedAt was supplied by the caller and is not verified from the file; importedAt records when the local files were loaded.`:`The export time is unavailable unless supplied separately; importedAt records when the local files were loaded, not when Search Console produced them.`]}}function Di(e){let t=e??100;if(!Number.isSafeInteger(t)||t<1)throw new T(`INVALID_INPUT`,`Unreached-page limit must be a positive integer.`);return t}function Oi(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}function ki(e,t){return e<t?-1:+(e>t)}function Ai(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||ki(e.url,t.url)}function ji(e){let t=Di(e.limit),n=new Set;for(let t of e.crawledUrls){let e=Oi(t);e!==null&&n.add(e)}let r=new Set,i=0,a=[];for(let t of e.pages){let e;try{e=new URL(t.url)}catch{continue}r.add(e.origin);let o=Oi(t.url);o!==null&&n.has(o)?i+=1:a.push(t)}a.sort(Ai);let o=[...r].sort(ki);return{joinBasis:`path`,crawlOrigin:e.crawlOrigin,exportOrigins:o,originMismatch:o.some(t=>t!==e.crawlOrigin),matchedPages:i,unreachedPages:a.slice(0,t),unreachedCount:a.length,capped:a.length>t}}function Mi(e){let t=e-Date.now();if(t<=0)return`expired`;let n=Math.round(t/6e4);if(n<60)return`in ${n}m`;let r=Math.floor(n/60),i=n%60;return i?`in ${r}h ${i}m`:`in ${r}h`}function Ni(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function k(e){return typeof e==`string`?e:void 0}function A(e){let t=k(e.project),n=k(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function j(e){return typeof e==`boolean`?e:void 0}function Pi(e){let t=k(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new T(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(j(e.js)===!0&&t&&t!==`on`)throw new T(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(j(e.js)?`on`:`auto`)}function Fi(e,t){let n=j(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=j(e[Ii(t)]);if(r===!1)return!0;if(r===!0)return!1;if(j(e[`no-${t}`])===!0||j(e[`no${Li(t)}`])===!0)return!0}function Ii(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Li(e){let t=Ii(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function M(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function N(e,t){if(e===void 0||e===``)return;let n=M(e);if(n===void 0)throw new T(`INVALID_INPUT`,`${t} must be a number.`);return n}function P(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function F(e){return P(e)??[]}function Ri(e){let t={concurrency:M(e[`fetch-concurrency`]),intervalCap:M(e[`fetch-interval-cap`]),intervalMs:M(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function zi(e){let t={concurrency:N(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:N(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:N(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function I(e){return e.json===!0}async function Bi(e,t){let n=k(e),r=k(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await pr(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function Vi(e){if(typeof e==`number`)return Number.isFinite(e)?Math.round(e):void 0;if(!e)return;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:void 0}function Hi(e={}){let t=e.env??process.env,n=Vi(e.columns)??Vi(e.streamColumns)??Vi(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function Ui(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Hi({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Nr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function Wi(e){return Pr(vr(e))}function Gi(e,t){let n=[],r=``;for(let i of e)r&&Wi(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Ki(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=Wi(e)>t?Gi(e,t):[e];for(let e of n)i?Wi(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function L(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
|
|
102
|
+
</html>`}const ni={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function ri(e,t){return[...e.output.segments[t].winners,...e.output.segments[t].losers].map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))}function ii(e,t){return e.output.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}function ai(e){return[{filename:`postmortem-summary.csv`,headers:[...ni.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...ni.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...ni.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...ni.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...ni.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...ni.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...ni.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...ni.segment],rows:ri(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...ni.unmatchedSegment],rows:ii(e,t)}))]}function oi(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function si(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function ci(e,t){let n=si(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:oi(e)===t?`hostname`:void 0}function li(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=oi(n);if(!r)return[];let i=ci(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function ui(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched Google Analytics web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}const di=`Search Console export`,fi=new Set([`top queries`,`query`,`queries`]),pi=new Set([`top pages`,`page`,`pages`,`url`]),mi=[`clicks`,`url clicks`],hi=[`impressions`],gi=[`ctr`,`url ctr`,`site ctr`],_i=[`position`,`average position`,`avg position`,`avg. position`],vi=[`Query and page tables are separate aggregates from a Search Console export; no query-to-page mapping exists and none was created.`,`Search Console exports are partial: anonymised queries are withheld and export row caps apply, so missing rows are not zeros.`];function yi(e){let t=e??5e3;if(!Number.isSafeInteger(t)||t<1||t>1e5)throw new E(`INVALID_INPUT`,`Search Console export row limit must be between 1 and 100000.`);return t}async function bi(e){let t=hr(e),n=await xr(t).catch(()=>void 0);if(!n)throw new E(`INVALID_INPUT`,`Search Console export path was not found: ${t}`);if(n.isFile())return[t];if(!n.isDirectory())throw new E(`INVALID_INPUT`,`Search Console export path must be a CSV file or a directory: ${t}`);let r=(await br(t,{withFileTypes:!0})).filter(e=>{if(!e.isFile())return!1;let t=fr(e.name).toLowerCase();return t===`.csv`||t===`.tsv`}).map(e=>pr(t,e.name)).sort(Di);if(r.length===0)throw new E(`INVALID_INPUT`,`Search Console export directory contains no CSV files: ${t}`);if(r.length>20)throw new E(`INVALID_INPUT`,`Search Console export directories can contain at most 20 CSV files.`);return r}function xi(e){return e.replace(/^\uFEFF/u,``).trim().toLowerCase()}function Si(e,t){for(let n of t){let t=e.indexOf(n);if(t>0)return t}return null}function Ci(e){let t=(e??``).trim();if(!t||!/^\d+(?:,\d{3})*$/u.test(t))return null;let n=Number(t.replaceAll(`,`,``));return Number.isSafeInteger(n)?n:null}function wi(e){let t=(e??``).trim();if(!t)return null;if(t.endsWith(`%`)){let e=Number(t.slice(0,-1).trim());if(!Number.isFinite(e))return null;let n=e/100;return n>=0&&n<=1?n:null}let n=Number(t);return Number.isFinite(n)&&n>=0&&n<=1?n:null}function Ti(e){let t=(e??``).trim();if(!t)return null;let n=Number(t);return Number.isFinite(n)&&n>0?n:null}function Ei(e){let t;try{t=new URL(e)}catch{return null}return t.protocol===`http:`||t.protocol===`https:`?e:null}function Di(e,t){return e<t?-1:+(e>t)}function Oi(e,t){return e.impressions===t.impressions?e.clicks>t.clicks:e.impressions>t.impressions}function ki(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||Di(e.query,t.query)}function Ai(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||Di(e.url,t.url)}function ji(e,t,n){let r=[...e.byKey.values()].sort(t),i=e.capped||r.length>n;return{rows:r.slice(0,n),totalRows:r.length,capped:i}}async function Mi(e){let t=yi(e.rowLimit),n=await bi(e.path),r=[],i=[],a={byKey:new Map,suppliedRows:0,capped:!1},o={byKey:new Map,suppliedRows:0,capped:!1};for(let e of n){let n=await _n({file:e,format:`csv`,label:di}),s=Yn(`sha256`),c={bytesRead:0,hash:s},l=nn({path:n.path,byteLimit:wn,progress:c,label:di}),u=await l.next(),d=(Array.isArray(u.value)?u.value:[]).map((e,t)=>(t===0?e.replace(/^\uFEFF/u,``):e).trim()),f=d.map(xi),p=f[0]??``,m=fi.has(p)?`queries`:pi.has(p)?`pages`:`unrecognized`,h=m===`unrecognized`?d.length===0?`The file has no header row.`:`The first header "${d[0]}" is not a recognized Search Console query or page column.`:void 0,g=Si(f,mi),_=Si(f,hi),v=Si(f,gi),y=Si(f,_i),b=[...g===null?[`Clicks`]:[],..._===null?[`Impressions`]:[]],x=h??(b.length?`The recognized ${m} table is missing required ${b.join(` and `)} columns.`:void 0),S=m!==`unrecognized`&&x===void 0,ee=0,te=0,ne=0,C=0,w=0,T=m===`queries`?a:o;for await(let e of l){if(ee+=1,!S||T.suppliedRows>=t)continue;if(te+=1,T.suppliedRows+=1,e.length!==d.length){C+=1;continue}let n=Ci(e[g??-1]),r=Ci(e[_??-1]);if(n===null||r===null){C+=1;continue}let i=v===null?``:(e[v]??``).trim(),s=y===null?``:(e[y]??``).trim(),c=v===null?null:wi(i),l=y===null?null:Ti(s);if(i&&c===null||s&&l===null){C+=1;continue}let u={clicks:n,impressions:r,ctr:c,position:l},f=(e[0]??``).trim();if(m===`queries`){if(!f){C+=1;continue}let e=a.byKey.get(f);if(e){w+=1,Oi(u,e)&&a.byKey.set(f,{query:f,...u});continue}ne+=1,a.byKey.set(f,{query:f,...u});continue}let p=Ei(f);if(!p){C+=1;continue}let h=o.byKey.get(p);if(h){w+=1,Oi(u,h)&&o.byKey.set(p,{url:p,...u});continue}ne+=1,o.byKey.set(p,{url:p,...u})}let re=S&&ee>te;re&&(m===`queries`?a.capped=!0:o.capped=!0,i.push(`Only ${te} of ${ee} rows in ${n.path} were processed within the shared ${m} row limit of ${t}.`)),x&&i.push(`Skipped ${n.path}: ${x??`the file was not recognized.`}`),C>0&&i.push(`${C} row${C===1?``:`s`} in ${n.path} ${C===1?`was`:`were`} invalid and skipped.`),r.push({path:n.path,table:m,sha256:s.digest(`hex`),fileBytes:n.fileBytes,bytesRead:c.bytesRead,encoding:c.encoding??`utf-8`,delimiter:c.delimiter??null,includedFields:[...d].sort(Di),fileRows:ee,suppliedRows:te,validRows:ne,invalidRows:C,duplicateRows:w,capped:re,rowLimit:t,...x===void 0?{}:{reason:x}})}return r.every(e=>e.table===`unrecognized`)&&i.push(`No file matched the Search Console query or page table headers, so no rows were imported.`),{source:`search-console-export`,exportedAt:e.exportedAt?.toISOString()??null,importedAt:(e.now??new Date).toISOString(),files:r,queries:ji(a,ki,t),pages:ji(o,Ai,t),warnings:i,caveats:[...vi,e.exportedAt?`exportedAt was supplied by the caller and is not verified from the file; importedAt records when the local files were loaded.`:`The export time is unavailable unless supplied separately; importedAt records when the local files were loaded, not when Search Console produced them.`]}}function Ni(e){let t=e??100;if(!Number.isSafeInteger(t)||t<1)throw new E(`INVALID_INPUT`,`Unreached-page limit must be a positive integer.`);return t}function Pi(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}function Fi(e,t){return e<t?-1:+(e>t)}function Ii(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||Fi(e.url,t.url)}function Li(e){let t=Ni(e.limit),n=new Set;for(let t of e.crawledUrls){let e=Pi(t);e!==null&&n.add(e)}let r=new Set,i=0,a=[];for(let t of e.pages){let e;try{e=new URL(t.url)}catch{continue}r.add(e.origin);let o=Pi(t.url);o!==null&&n.has(o)?i+=1:a.push(t)}a.sort(Ii);let o=[...r].sort(Fi);return{joinBasis:`path`,crawlOrigin:e.crawlOrigin,exportOrigins:o,originMismatch:o.some(t=>t!==e.crawlOrigin),matchedPages:i,unreachedPages:a.slice(0,t),unreachedCount:a.length,capped:a.length>t}}function Ri(e){let t=e-Date.now();if(t<=0)return`expired`;let n=Math.round(t/6e4);if(n<60)return`in ${n}m`;let r=Math.floor(n/60),i=n%60;return i?`in ${r}h ${i}m`:`in ${r}h`}function zi(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function A(e){return typeof e==`string`?e:void 0}function j(e){let t=A(e.project),n=A(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function M(e){return typeof e==`boolean`?e:void 0}function Bi(e){let t=A(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new E(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(M(e.js)===!0&&t&&t!==`on`)throw new E(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(M(e.js)?`on`:`auto`)}function Vi(e,t){let n=M(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=M(e[Hi(t)]);if(r===!1)return!0;if(r===!0)return!1;if(M(e[`no-${t}`])===!0||M(e[`no${Ui(t)}`])===!0)return!0}function Hi(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Ui(e){let t=Hi(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function N(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function P(e,t){if(e===void 0||e===``)return;let n=N(e);if(n===void 0)throw new E(`INVALID_INPUT`,`${t} must be a number.`);return n}function F(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function I(e){return F(e)??[]}function Wi(e){let t={concurrency:N(e[`fetch-concurrency`]),intervalCap:N(e[`fetch-interval-cap`]),intervalMs:N(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function Gi(e){let t={concurrency:P(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:P(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:P(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function L(e){return e.json===!0}async function Ki(e,t){let n=A(e),r=A(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await yr(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function qi(e){if(typeof e==`number`)return Number.isFinite(e)?Math.round(e):void 0;if(!e)return;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:void 0}function Ji(e={}){let t=e.env??process.env,n=qi(e.columns)??qi(e.streamColumns)??qi(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function Yi(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Ji({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:zr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function Xi(e){return Br(wr(e))}function Zi(e,t){let n=[],r=``;for(let i of e)r&&Xi(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Qi(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=Xi(e)>t?Zi(e,t):[e];for(let e of n)i?Xi(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function R(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
|
|
103
103
|
`,`
|
|
104
104
|
`).split(`
|
|
105
|
-
`).flatMap(e=>
|
|
106
|
-
`)));return i.toString()}function
|
|
105
|
+
`).flatMap(e=>Qi(e,n))}function $i(e,t){if(Xi(e)<=t)return e;let n=Math.max(1,t-Xi(`...`)),r=``;for(let t of e){if(Xi(`${r}${t}`)>n)break;r+=t}return`${r}...`}const ea={top:``,"top-mid":``,"top-left":``,"top-right":``,bottom:``,"bottom-mid":``,"bottom-left":``,"bottom-right":``,left:``,"left-mid":``,mid:``,"mid-mid":``,right:``,"right-mid":``,middle:``};function ta(e){return e===void 0?``:String(e)}function na(e,t){return e.map((e,n)=>{let r=t.map(e=>ta(e[n]));return Math.min(42,Math.max(Xi(e),...r.map(Xi),4))})}function ra(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(ta(e[n]).trim()))).some(Boolean)?!1:na(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function ia(e,t,n){if(!/(?:status|state|assessment|data|allowed)/iu.test(e))return t;let r=t.trim().toLowerCase();return[`pass`,`passed`,`completed`,`allowed`,`yes`].includes(r)?n.colors.green(t):[`warn`,`warning`,`review`,`partial`].includes(r)?n.colors.yellow(t):[`fail`,`failed`,`blocked`,`no`].includes(r)?n.colors.red(t):[`unknown`,`unavailable`].includes(r)?n.colors.blue(t):t}function aa(e,t,n){let r=na(e,t).map(e=>e+2),i=new Vr({chars:ea,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let a of t)i.push(e.map((e,t)=>R(ia(e,ta(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
|
|
106
|
+
`)));return i.toString()}function oa(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...R(t,r.columns-2).map(e=>` ${e}`)]:R(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function sa(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>Xi(e)),0)),a=r.rowNumbers??!1,o=String(t.length).length;return t.map((t,r)=>{let s=[];a&&s.push(n.colors.bold(String(r+1).padStart(o,`0`)));for(let[r,a]of e.entries())s.push(...oa(a,ia(a,ta(t[r]),n),i,n));return s.join(`
|
|
107
107
|
`)}).join(`
|
|
108
108
|
|
|
109
|
-
`)}function
|
|
110
|
-
`)}function
|
|
111
|
-
`)}function
|
|
112
|
-
`)}function
|
|
113
|
-
`)}function
|
|
114
|
-
`)}`),e.metrics?.length&&r.push(
|
|
109
|
+
`)}function ca(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&ra(e,t,n)?aa(e,t,n):sa(e,t,n,r)}function la(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>Xi(e))));return e.flatMap(([e,r])=>{let i=oa(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
|
|
110
|
+
`)}function ua(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...R(e.body,t.columns)),e.command&&n.push(t.colors.cyan($i(e.command,t.columns))),n.join(`
|
|
111
|
+
`)}function da(e,t){return e===`pass`?t.colors.green:e===`warning`?t.colors.yellow:e===`fail`?t.colors.red:e===`unknown`?t.colors.blue:e===`info`?t.colors.cyan:t.colors.dim}function fa(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function pa(e,t,n){let r=` `.repeat(t);return R(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ma(e,t){let n=da(e.status,t)(`[${fa(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=R(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...pa(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=R(n,Math.max(1,t.columns-20));i.push(...e.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.dim(`Evidence`)} `:` `}${e}`))}if(e.fix){let n=R(e.fix,Math.max(1,t.columns-15));i.push(...n.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.cyan(t.colors.bold(`Fix`))} `:` `}${e}`))}return i.join(`
|
|
112
|
+
`)}function ha(e,t){return R(e.map(e=>{let n=String(e.value),r=e.status?da(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
|
|
113
|
+
`)}function ga(e,t){let n=da(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(fa(e.status))} ${R(e.summary,Math.max(1,t.columns-6)).join(`
|
|
114
|
+
`)}`),e.metrics?.length&&r.push(ha(e.metrics,t));for(let n of e.sections.filter(e=>e.diagnostics.length>0))r.push(``,t.colors.bold(n.title)),r.push(n.diagnostics.map(e=>ma(e,t)).join(`
|
|
115
115
|
|
|
116
|
-
`));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=
|
|
117
|
-
`)}function
|
|
118
|
-
`)}function
|
|
116
|
+
`));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=R(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
|
|
117
|
+
`)}function _a(e,t,n=`${t}s`){return e===1?t:n}function va(e,t,n){let r=` `.repeat(t);return R(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ya(e,t,n){return[t.colors.bold(e),...n?R(n,t.columns):[]].join(`
|
|
118
|
+
`)}function ba(e,t){let n={pass:t.colors.green,warn:t.colors.yellow,fail:t.colors.red};return e.map(e=>{let r=[`${n[e.status](e.status.toUpperCase().padEnd(4))} ${t.colors.bold(e.label)}`];if(r.push(...va(e.detail,6,t.columns)),e.fix){let n=R(e.fix,Math.max(1,t.columns-11));r.push(...n.map((e,n)=>`${` `.repeat(6)}${n===0?`${t.colors.bold(`Fix`)} `:` `}${e}`))}return r.join(`
|
|
119
119
|
`)}).join(`
|
|
120
120
|
|
|
121
|
-
`)}function
|
|
122
|
-
`):
|
|
123
|
-
`)}function
|
|
121
|
+
`)}function xa(e){let t=e.filter(e=>e.status===`pass`).length,n=e.filter(e=>e.status===`warn`).length,r=e.filter(e=>e.status===`fail`).length;return`${t} ${_a(t,`check`)} passed, ${n} ${_a(n,`warning`)}, ${r} failed.`}function Sa(e,t,n){let r=n.columns-t-2-2;return r<24||Xi(e.id)>t?[...va(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...va(e.name,4,n.columns)].join(`
|
|
122
|
+
`):R(e.name,r).map((r,i)=>{let a=i===0?e.id.padEnd(t):``.padEnd(t);return` ${n.colors.cyan(a)}${` `.repeat(2)}${r}`}).join(`
|
|
123
|
+
`)}function Ca(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function wa(e,t,n){let r=new Map;for(let t of e){let e=r.get(t.category)??[];e.push(t),r.set(t.category,e)}let i=`${e.length} ${_a(e.length,n.noun)} across ${r.size} ${_a(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>Xi(e.id)),0)),o=Object.keys(n.categoryLabels??{}).filter(e=>r.has(e)),s=[...r.keys()].filter(e=>!o.includes(e));return[i,...[...o,...s].map(e=>{let i=r.get(e)??[],o=n.categoryLabels?.[e]??Ca(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>Sa(e,a,t))].join(`
|
|
124
124
|
`)})].join(`
|
|
125
125
|
|
|
126
|
-
`)}function
|
|
126
|
+
`)}function Ta(e,t,n){return e.length===0?t.colors.dim(n.empty):e.map(e=>{let n=[t.colors.bold(e.title)];e.description&&n.push(...va(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...va(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
|
|
127
127
|
`)}).join(`
|
|
128
128
|
|
|
129
|
-
`)}function
|
|
130
|
-
`)}function
|
|
131
|
-
`)}function
|
|
132
|
-
`),`Clicky package`),
|
|
133
|
-
`)}}),
|
|
134
|
-
`),
|
|
135
|
-
`);return}if(
|
|
136
|
-
`);return}let t=await
|
|
137
|
-
`);return}
|
|
138
|
-
`),`Advanced`),
|
|
139
|
-
`};function
|
|
140
|
-
`,config:{}};let t=Yn(e.path,`utf8`),n=[],r=Rr(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${zr(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!Po(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!Po(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Io(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Lo(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!Po(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Ro(e){if(!Po(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Io(t)||Lo(e,t)}function zo(e){let t=Ir(e.source,Lr(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:Ao}));qn(e.target.path)&&No(e.target.path),Mo(e.target.path,t)}function Bo(e,t){let n=br(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Vo(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Ho(e){if(!e||!qn(e))return;let t=Zn(e);return Vo(t)?void 0:t}function Uo(e={}){let t=Ho(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??Ae}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function Wo(e=!1,t=Uo()){let n=mt(),r={command:t.command,args:t.args,env:{SEO_CONFIG_DIR:n.configDir,SEO_MCP_CONFIGURED_BY:`seo`}};return e&&(r.type=`stdio`),r}function Go(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function Ko(e,t,n){let r=Do(e.executable),i=n.runCommand(r,[`mcp`,`get`,`seo`]);if(i.error?.code===`ENOENT`)throw Error("Cannot install SEO MCP for Codex: the `codex` command is not available on PATH. Install Codex CLI or choose another MCP client.");let a=i.status===0;if(a&&!Go(i.stdout))throw Error(`Cannot ${t} SEO MCP: ${e.path} already has an unmanaged mcp_servers.seo entry.`);if(t===`install`&&a&&!n.reinstall||t===`uninstall`&&!a)return{client:e.client,path:e.path,changed:!1};if(t===`install`&&a){let e=n.runCommand(r,[`mcp`,`remove`,`seo`]);if(e.error||e.status!==0){let t=e.error?.message??e.stderr.trim()??`unknown error`;throw Error(`Cannot reinstall SEO MCP for Codex: ${t}`)}}let o=mt(),s=t===`install`?[`mcp`,`add`,`seo`,`--env`,`SEO_CONFIG_DIR=${o.configDir}`,`--env`,`SEO_MCP_CONFIGURED_BY=seo`,`--`,n.serverCommand.command,...n.serverCommand.args]:[`mcp`,`remove`,`seo`],c=n.runCommand(r,s);if(c.error||c.status!==0){let e=c.error?.message??c.stderr.trim()??`unknown error`;throw Error(`Cannot ${t} SEO MCP for Codex: ${e}`)}return{client:e.client,path:e.path,changed:!0}}function qo(e,t={}){let n={runCommand:t.runCommand??Bo,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Uo()};if(e.kind===`native`)return Ko(e,`install`,n);let{source:r,config:i}=Fo(e),a=i.mcpServers?.seo,o=Wo(e.includeType,n.serverCommand);if(_r(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Ro(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return zo({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function Jo(e,t={}){if(e.kind===`native`)return Ko(e,`uninstall`,{runCommand:t.runCommand??Bo,reinstall:!1,serverCommand:Uo()});if(!qn(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Fo(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Ro(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return zo({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function Yo(e={}){let t=br(`npx`,[`-y`,`skills`,`add`,`iannuttall/seo`],{stdio:e.quiet?`ignore`:`inherit`});return t.error?{status:`failed`,error:t.error.message}:t.status===0?{status:`installed`}:{status:`failed`,error:`npx skills add exited with status ${t.status??`unknown`}.`}}function Xo(e,t=[]){let n=[{value:`google`,label:`Google Analytics`,hint:`Use a property available to your Google login`},...t.filter(e=>e.capabilities.some(e=>e.id===`landing-page-visits`)&&e.id!==`google`).map(e=>({value:`extension:${e.id}`,label:e.displayName,hint:`Installed provider from ${e.package}`}))];return e?[{value:`keep`,label:`Keep ${kt(e)}`,hint:`Leave this project connection unchanged`},...n,{value:`remove`,label:`Remove traffic analytics`}]:[...n,{value:`skip`,label:`Skip traffic analytics`}]}async function Zo(e,t={},n=!0){if(!e.capabilities.some(e=>e.id===`landing-page-visits`))throw new T(`INVALID_INPUT`,`Provider ${e.id} does not supply landing-page visits.`);let r={...t},i={};for(let t of e.connection.fields.filter(e=>e.kind===`account`)){if(r[t.id]?.trim())continue;if(!n){if(t.required!==!1)throw new T(`INVALID_INPUT`,`${t.label} is required.`);continue}let e=H(await Mr({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();e&&(r[t.id]=e)}let a=e.connection.normalizeAccount?e.connection.normalizeAccount(r):r;try{Object.assign(i,await Rn({providerId:e.id,account:a,fields:e.connection.fields}))}catch{}if(e.id===`clicky`&&!i.sitekey){let e=await Mn(a.siteId??``);e&&(i.sitekey=e.siteKey)}for(let t of e.connection.fields.filter(e=>e.kind===`secret`)){let r=t.envVar?process.env[t.envVar]?.trim():void 0;if(r&&(i[t.id]=r),i[t.id])continue;if(!n)throw new T(`AUTH_REQUIRED`,t.envVar?`Set ${t.envVar} to connect ${e.displayName}.`:`${t.label} is required to connect ${e.displayName}.`);let a=H(await Ar({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();a&&(i[t.id]=a)}return e.connection.verificationNotice&&D(e.connection.verificationNotice,`Connection check`),await Yt({providerId:e.id,account:a,credentials:i}),Object.keys(i).length>0&&await ut({providerId:e.id,account:a,credentials:i}),{provider:`extension`,extension:{providerId:e.id,account:a}}}function Qo(e){let t=e.canSkip?[{value:`skip`,label:`Skip for now`}]:[];return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},...t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},...t]}async function $o(e,t){let n=[],r=!0,i=0;async function a(){for(;i<e.length;){let a=e[i];if(i+=1,a)try{let e=await ze(a.property,t);n.push(...e.filter(e=>e.webStreamData).map(e=>({account:a.account,property:a.property,propertyName:a.label,stream:e})))}catch{r=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>a())),{candidates:n,complete:r}}async function es(e){let t=await oe();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};let n=typeof e[`search-console-account`]==`string`?e[`search-console-account`]:void 0,r=xt();if(n){let e=r.find(e=>e.accountEmail.toLowerCase()===n.toLowerCase());if(!e)throw new T(`AUTH_REQUIRED`,`Google account ${n} is not connected. Run \`seo auth login\` first.`);return{status:`already-connected`,accountEmail:e.accountEmail}}if(e[`skip-auth`])return{status:`skipped`};if(!R({json:e.json===!0})){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new T(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.")}if(r.length>0){let n=H(await jr({message:`Google account for Search Console`,options:[...r.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`},...typeof e.site==`string`?[{value:{type:`skip`},label:`Skip for now`}]:[]]}));if(n.type===`account`)return hn(n.accountEmail),{status:`already-connected`,accountEmail:n.accountEmail};if(n.type===`skip`)return{status:`skipped`};if(t.sharedConfigured||t.byoConfigured){let e=await io();return D(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}}let i=H(await jr({message:`Connect Google now?`,options:Qo({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));if(i===`skip`)return{status:`skipped`};i===`setup`&&an({clientId:H(await Mr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:H(await Ar({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))});let a=await io();return D(`Connected as ${a.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:a.account_email}}async function ts(e){if(e.property)return{propertyId:e.property,accountEmail:e.accountEmail,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??R()))return;let t=(await ne(e.accountEmail).catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:s(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await $o(t,e.accountEmail),i=r?ri(e.site,n):[],a=new Map;for(let e of i)a.set(e.property,e);let o=[...a.values()];if(o.length===1){let t=o[0];return t?{propertyId:t.property,accountEmail:e.accountEmail,selection:`matched`,reason:ii(t,e.site)}:void 0}let c=o.length?o.map(e=>({property:e.property,label:e.propertyName,account:e.account,match:e})):t,l=H(await jr({message:o.length?`Several Google Analytics properties match this site. Which property should seo use?`:r?`No Google Analytics web stream clearly matches this site. Attach a property?`:`Some Google Analytics web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip Google Analytics for now`},...c.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?ii(t.match,e.site):t.account}))]}));if(l)return{propertyId:l.property,accountEmail:e.accountEmail,selection:`manual`,reason:l.match?ii(l.match,e.site):r?`Selected ${l.label} during setup. Its web stream did not clearly match ${e.site}.`:`Selected ${l.label} during setup. seo could not read every Google Analytics web stream, so it did not guess a match.`}}async function ns(e){if(e.googleProperty&&e.clickySiteId)throw new T(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);let t=e.interactive??R();if(e.clickySiteId){let n=(await Mt()).registry.get(`clicky`);if(!n)throw new T(`PROVIDER_UNAVAILABLE`,"The Clicky provider package is not installed. Run `seo providers install @seoskill/clicky-provider`.");return Zo(n,{siteId:e.clickySiteId},t)}if(e.googleProperty){let n=await rs({requested:e.googleAccount,interactive:t}),r=await ts({property:e.googleProperty,accountEmail:n,site:e.site,interactive:t});return r?{provider:`google`,google:r}:void 0}if(!t)return;let n=await Mt(),r=H(await jr({message:`Traffic analytics`,options:Xo(e.current,n.registry.list())}));if(r===`keep`)return;if(r===`remove`)return{provider:`none`};if(r===`skip`)return;if(r.startsWith(`extension:`)){let e=r.slice(10),t=n.registry.get(e);if(!t)throw new T(`INVALID_INPUT`,`Provider ${e} is installed but could not be loaded. Run \`seo providers doctor\`.`);return Zo(t)}let i=await ts({accountEmail:await rs({interactive:t}),site:e.site,interactive:t});return i||D(`No Google Analytics property was available for this Google login. You can connect one later.`,`Traffic analytics skipped`),i?{provider:`google`,google:i}:void 0}async function rs(e){let t=await oe();if(t.activeMode===`service-account`)return t.identity;let n=xt();if(e.requested){let t=n.find(t=>t.accountEmail.toLowerCase()===e.requested?.toLowerCase());if(!t)throw new T(`AUTH_REQUIRED`,`Google account ${e.requested} is not connected. Run \`seo auth login\` first.`);return t.accountEmail}if(!e.interactive)return n.find(e=>e.active)?.accountEmail;if(n.length===0)return(await io()).account_email;let r=H(await jr({message:`Google account for Google Analytics`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(r.type===`account`)return r.accountEmail;let i=await io();return D(`Connected as ${i.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),i.account_email}async function is(e){return e[`skip-skill`]||!R({json:e.json===!0})?{status:`skipped`}:H(await wr({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?Yo():{status:`declined`}}async function as(e){if(e[`skip-mcp`]||!R({json:e.json===!0}))return[];let t=ko();if(t.length===0||!H(await wr({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=H(await Or({message:`Which MCP clients?`,options:t.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:t.map(e=>e.client)}));return t.filter(e=>n.includes(e.client)).map(e=>{try{return qo(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function os(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function ss(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function cs(){process.stdout.write(`
|
|
141
|
-
`),
|
|
142
|
-
`)}async function
|
|
143
|
-
`),`This setup will guide you through`),
|
|
144
|
-
`),`Try next`),
|
|
145
|
-
`),`Try next`),kr(`Setup complete.`),cs()}const fs=E({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional Clicky site ID. Set SEO_CLICKY_SITEKEY outside a terminal.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>ds(e)}),ps=E({...fs,meta:{name:`start`,description:`Start here: connect first-party data, save a profile, and get the first report commands`}}),ms=E({...fs,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),hs=E({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:E({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=I(e),n=yn(A(e)),r=await Pt({site:await Ia({site:k(e.site)??n?.siteUrl,options:{json:t,refresh:j(e.refresh)}}),id:n?.id,name:n?.name,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),refresh:j(e.refresh)}),i=j(e.save)&&n?yt({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(j(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){z({...r,saved:i});return}B([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),V([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function gs(e){Oa(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function _s(e){B([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Search Console account`,e.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Google Analytics account`,e.googleAccounts?.googleAnalytics??`default Google account`],[`Clicky site`,e.analytics.clicky?.siteId??`not set`],[`Selected analytics`,e.analytics.selected??`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const vs=E({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional legacy Clicky site ID. New connections use seo start.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=k(e.id),r=n?yn(n)??yn(_o(n)):void 0,i=k(e[`google-analytics-property`]),a=k(e[`google-analytics-account`]),o=k(e[`search-console-account`]),s=k(e[`clicky-site-id`]);if(i&&s)throw new T(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(a&&!i&&!r?.analytics.google?.propertyId)throw new T(`INVALID_INPUT`,`--google-analytics-account needs a Google Analytics property.`);let c=await Ia({site:k(e.site)??r?.siteUrl,options:{json:t,account:o}}),l={name:k(e.name),siteUrl:c,startUrl:k(e.url),watchUrls:e.urls===void 0?void 0:F(e.urls),brandTerms:e.brand===void 0?void 0:F(e.brand),analytics:i||a?{...r?.analytics,selected:`google`,google:{propertyId:i??r?.analytics.google?.propertyId??``}}:s?{...r?.analytics,selected:`clicky`,clicky:{siteId:s}}:void 0,googleAccounts:o||a?{...r?.googleAccounts,...o?{searchConsole:o}:{},...a?{googleAnalytics:a}:{}}:void 0,technicalWeekday:M(e.weekday),isDefault:j(e.default)},u=r?Zt(r.id,l):yt({id:n,...l});if(t){z(u);return}_s(u)}}),ys=E({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=un(k(e.id)??``);if(I(e)){z(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),bs=E({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.id)??``,n=on(t);if(I(e)){z({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),xs=E({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:ms,brand:hs,list:E({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=qt();if(I(e)){z({clients:t});return}gs(t)}}),add:vs,show:E({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=yn(k(e.id));if(!t)throw Error(`Project not found.`);if(I(e)){z(t);return}_s(t)}}),default:ys,delete:bs}}),Ss=E({...xs,meta:{name:`projects`,description:`Manage saved project profiles`}}),Cs=E({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:E({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":Ni(`Fetch the page for content checks. Defaults to true.`,`Skip fetching the page for content checks.`),js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for page extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC and HTTP cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await Bt({site:n.site,url:k(e.url)??``,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`])!==!1,js:j(e.js)?!0:`auto`,refresh:j(e.refresh)});if(t){z(r);return}q({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:W(r.summary.queries)},{label:`Opportunities`,value:W(r.summary.opportunities)},{label:`Estimated lift`,value:W(r.summary.estimatedClickLift)}]}),Y(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
|
|
129
|
+
`)}function Ea(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>R(e,n.columns));return[n.colors.bold(e),...r].join(`
|
|
130
|
+
`)}function Da(e,t,n){let r=t.flatMap(e=>R(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
|
|
131
|
+
`)}function Oa(e,t){return e.length===0?t.colors.dim(`No parameters.`):Ta(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function z(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function B(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function V(e){let t=la(e,Yi());t&&process.stdout.write(`${t}\n`)}function H(e,t){process.stdout.write(`${ca(e,t,Yi())}\n`)}function ka(e){process.stdout.write(`${ua(e,Yi())}\n`)}function Aa(e,t){process.stdout.write(`${ya(e,Yi(),t)}\n`)}function ja(e){process.stdout.write(`${ba(e,Yi())}\n`)}function Ma(e){process.stdout.write(`${ga(e,Yi())}\n`)}function Na(e,t){process.stdout.write(`${wa(e,Yi(),t)}\n`)}function Pa(e,t){process.stdout.write(`${Ta(e,Yi(),t)}\n`)}function Fa(e,...t){process.stdout.write(`${Ea(e,t,Yi())}\n`)}function Ia(e,t){process.stdout.write(`${Da(e,t,Yi())}\n`)}function La(e){process.stdout.write(`${Oa(e,Yi())}\n`)}function Ra(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function U(e){return typeof e==`symbol`&&process.exit(1),e}function za(e){vn(e?.googleAccounts)}function Ba(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function Va(e){let t=e.choices;if(e.choices.length>8){let n=U(await Rr({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>Ba(e.searchValues(t),n)),!t.length)throw new E(`INVALID_INPUT`,e.emptyMessage)}return U(await Lr({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function Ha(e){if(e.site)return e.site;let t=_t(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(e.options?.allowDefault!==!1&&t.defaultSite&&n)return za(t.clients.find(e=>e.isDefault&&e.siteUrl===t.defaultSite)),t.defaultSite;if(e.options?.json||!z())throw new E(`INVALID_INPUT`,t.defaultSite?`Saved default site ${t.defaultSite} does not match any saved project. Pass --site or --project, or run \`seo start\` to save a new default.`:"No site selected. Pass --site, use --project on supported commands, or run `seo start` in a terminal.");let r=await C(e.options?.refresh,e.options?.account);if(!r.length)throw new E(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await Va({message:`Choose a Search Console property`,searchMessage:`Search Search Console properties`,emptyMessage:`No Search Console properties matched that search.`,choices:r,label:e=>e.siteUrl,hint:e=>e.permissionLevel,searchValues:e=>[e.siteUrl,e.permissionLevel??``]})).siteUrl}async function W(e){if(e.client&&e.project&&e.client!==e.project)throw new E(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=en(t);if(!e)throw new E(`INVALID_INPUT`,`Project not found: ${t}`);return za(e),{client:e,site:e.siteUrl}}let n=en();return!e.site&&n?(za(n),{client:n,site:n.siteUrl}):{site:await Ha({site:e.site,options:e.options})}}async function Ua(e){if(e.client&&e.project&&e.client!==e.project)throw new E(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=en(t);if(!e)throw new E(`INVALID_INPUT`,`Project not found: ${t}`);return za(e),e}let n=Mt();if(!n.length)return;let r=en();if(r)return za(r),r;if(e.options?.json||!z())return;let i=await Va({message:`Choose a project`,searchMessage:`Search projects`,emptyMessage:`No projects matched that search.`,choices:n,label:e=>e.name,hint:e=>e.siteUrl,searchValues:e=>[e.id,e.name,e.siteUrl]});return za(i),i}async function Wa(e){if(e.property)return e.property;let t=_t();if(t.analytics.google.defaultPropertyId)return t.analytics.google.defaultPropertyId;if(e.options?.json||!z())throw new E(`INVALID_INPUT`,`No Google Analytics property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await Qe(e.options?.account)).flatMap(e=>e.propertySummaries.map(t=>({property:hn(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new E(`PROPERTY_NOT_FOUND`,`No Google Analytics properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await Va({message:`Choose a Google Analytics property`,searchMessage:`Search Google Analytics properties`,emptyMessage:`No Google Analytics properties matched that search.`,choices:n,label:e=>`${e.displayName} (${e.property})`,hint:e=>e.account,searchValues:e=>[e.property,e.displayName,e.account]})).property}const Ga=`@seoskill/clicky-provider`;function Ka(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`provider-store`?`provider credential store`:e??`missing`}async function qa(e){let t=await In(),n=t.registry.get(`clicky`);if(n)return n;let r=t.failures.find(e=>e.id===`clicky`);if(r)throw new E(`PROVIDER_UNAVAILABLE`,`The Clicky provider package could not load: ${r.message}`);if(!z({json:e.json}))throw new E(`PROVIDER_UNAVAILABLE`,`Clicky is now an optional provider package. Run \`seo providers install ${Ga} --yes\`, then run this command again.`);let i;try{i=await l(Ga)}catch(e){throw new E(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}if(O([`${i.package}@${i.version}`,`Publisher: ${i.publisher??`not supplied by npm`}`,`Repository: ${i.repository??`not supplied by npm`}`,`Integrity: ${i.integrity}`].join(`
|
|
132
|
+
`),`Clicky package`),O(`This package can read files, use the network, and run with the same local permissions as seo.`,`Local permissions`),!U(await Ar({message:`Install the Clicky provider package?`,initialValue:!0})))throw new E(`PROVIDER_UNAVAILABLE`,`The Clicky provider package is required for this command.`);await Fn(i),t=await In();let a=t.registry.get(`clicky`);if(!a)throw new E(`PROVIDER_UNAVAILABLE`,"The Clicky provider package was installed but could not load. Run `seo providers doctor`.");return a}async function Ja(e){if(e.siteId)return e.siteId;let t=kt(await Ua({client:e.project,options:{json:e.json}}));if(t?.provider===`clicky`)return t.siteId;if(t?.provider===`extension`&&t.providerId===`clicky`&&t.account.siteId)return t.account.siteId;throw new E(`INVALID_INPUT`,`No Clicky site is selected. Pass --site-id or use a project connected to Clicky.`)}async function Ya(e,t){let n=await Ht(t);if(n)return{siteKey:n.siteKey,source:n.source};try{let n=await _({providerId:`clicky`,account:{siteId:t},fields:e.connection.fields});return n.sitekey?{siteKey:n.sitekey,source:`provider-store`}:{}}catch{return{}}}const Xa=D({meta:{name:`connect`,description:`Validate and save a Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await qa({json:t}),i=z({json:t}),a=j(e),o=a?await Ua({project:a,options:{json:t}}):void 0;i&&jr(`Connect Clicky`);let s=kt(o),c=s?.provider===`clicky`?s.siteId:s?.provider===`extension`&&s.providerId===`clicky`?s.account.siteId:void 0,l=A(e[`site-id`])??c??(i?U(await Rr({message:`Clicky site ID`,validate:e=>/^\d{1,30}$/u.test(e?.trim()??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0);if(!l)throw new E(`INVALID_INPUT`,`Pass --site-id when the selected project is not already connected to Clicky.`);let u=await Ya(n,l),d=u.siteKey??(i?U(await Ir({message:`Clicky sitekey`,validate:e=>/^[A-Za-z0-9]{12,64}$/u.test(e?.trim()??``)?void 0:`Enter the sitekey from Clicky`})):void 0);if(!d)throw new E(`AUTH_REQUIRED`,`Run this command in a terminal or set ${xe}.`);await zn({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}});let f=u.source??await he(l,d);await m({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}}),r(`clicky`,{siteId:l});let p=o?Gn(o.id,{provider:`extension`,providerId:`clicky`,account:{siteId:l}}):void 0,h={siteId:l,connected:!0,credentialSource:f,project:p?{id:p.id,name:p.name}:void 0};if(t){B(h);return}let g=p?`Connection verified and attached to ${p.name}. Saved in the ${Ka(f)}.`:`Connection verified. Saved in the ${Ka(f)}.`;i?Fr(g):process.stdout.write(`${g}\n`)}}),Za=D({meta:{name:`status`,description:`Show the local Clicky connection`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},check:{type:`boolean`,default:!1,description:`Verify the saved sitekey with Clicky.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await qa({json:t}),r=await Ja({siteId:A(e[`site-id`]),project:j(e),json:t}),i=await Ya(n,r),a=M(e.check);a&&i.siteKey&&await zn({providerId:`clicky`,account:{siteId:r},credentials:{sitekey:i.siteKey}});let o={siteId:r,connected:!!i.siteKey,credentialSource:i.source,liveCheck:a?i.siteKey?`passed`:`unavailable`:`not-requested`};if(t){B(o);return}V([[`Site ID`,r],[`Connected`,o.connected?`yes`:`no`],[`Credential`,Ka(o.credentialSource)],[`Live check`,o.liveCheck]])}}),Qa=D({meta:{name:`report`,description:`Run a bounded Clicky analytics report`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},type:{type:`string`,default:`pages-entrance`,description:`Clicky report type. Defaults to pages-entrance.`},"start-date":{type:`string`,required:!0,description:`First site calendar date in YYYY-MM-DD format.`},"end-date":{type:`string`,required:!0,description:`Last site calendar date in YYYY-MM-DD format.`},limit:{type:`string`,default:`100`,description:`Maximum retained rows. Defaults to 100 and caps at 5000.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Clicky cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await qa({json:t}),r=await Ja({siteId:A(e[`site-id`]),project:j(e),json:t}),i=await Ya(n,r),a=await Vt({providerId:`clicky`,actionId:`report`,account:{siteId:r},...i.siteKey?{credentials:{sitekey:i.siteKey}}:{},params:{type:A(e.type)??`pages-entrance`,startDate:A(e[`start-date`])??``,endDate:A(e[`end-date`])??``,limit:N(e.limit)??100},refresh:M(e.refresh)});if(!a.data||typeof a.data!=`object`)throw new E(`PROVIDER_UNAVAILABLE`,`The Clicky provider returned an invalid report.`);let o={...a.data,cache:a.cache};if(t){B(o);return}V([[`Site ID`,o.siteId],[`Report`,o.type],[`Rows`,String(o.returnedRows)],[`Cache`,o.cache]]),H([`Value`,`Title`,`URL`],o.rows.slice(0,25).map(e=>[String(e.value??``),String(e.title??``),String(e.url??``)]))}}),$a=D({meta:{name:`disconnect`,description:`Remove a saved Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e);await qa({json:t});let n=await Ja({siteId:A(e[`site-id`]),project:j(e),json:t});await Gt(n),await Le({providerId:`clicky`,account:{siteId:n}});let r=!!process.env[xe];t?B({siteId:n,savedCredentialRemoved:!0,environmentCredential:r?`active`:`missing`}):process.stdout.write(r?`Saved sitekey removed. ${xe} is still active.\n`:`Clicky is disconnected for this site.
|
|
133
|
+
`)}}),eo=D({meta:{name:`clicky`,description:`Connect Clicky and read bounded analytics reports`},subCommands:{connect:Xa,status:Za,report:Qa,detach:D({meta:{name:`detach`,description:`Remove Clicky from one project without deleting its sitekey`},args:{project:{type:`string`,required:!0,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await Ua({project:j(e),options:{json:t}}),r=kt(n),i=r?.provider===`extension`&&r.providerId===`clicky`?`extension:clicky`:r?.provider===`clicky`?`clicky`:void 0;if(!n||!i)throw new E(`INVALID_INPUT`,`The selected project is not attached to Clicky.`);let a=ot(n.id,i),o={project:{id:a.id,name:a.name},detached:!0,selectedAnalytics:a.analytics.selected,savedSitekey:!0};t?B(o):process.stdout.write(`Clicky detached from ${a.name}. The saved sitekey was kept.\n`)}}),disconnect:$a}}),to=D({meta:{name:`properties`,description:`List Google Analytics accounts and properties`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},save:{type:`string`,description:`Save this numeric Google Analytics property ID as the default.`},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await Qe(A(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:hn(t.property),displayName:t.displayName??t.property}))),r=A(e.save);if(r){let e=_t();e.analytics.google.defaultPropertyId=r,Ot(e)}if(L(e)){B({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default Google Analytics property ${r}.\n`),H([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}});function G(e){return Math.round(e).toLocaleString(`en-GB`)}function no(e){return e.toFixed(1)}function ro(e){return`${(e*100).toFixed(1)}%`}function K(e,t=72){return $i(e,t)}function io(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function ao(e){return{title:e.title,target:e.target,status:e.status,summary:e.summary,metrics:e.metrics,sections:(e.diagnostics??[]).map(e=>({title:e.title,diagnostics:e.items})),notes:e.notes}}function q(e){Ma(ao(e))}function J(e,t){H(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function Y(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=oo(e,r);i&&(process.stdout.write(`
|
|
134
|
+
`),Ma(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function oo(e,t){if(t.length)return{title:e,status:`info`,summary:`${t.length} ${t.length===1?`action`:`actions`} shown.`,sections:[{title:`Actions`,diagnostics:t.map(e=>({status:`info`,title:e.label,explanation:e.context??`Review the evidence before making changes.`,fix:e.action}))}]}}function so(e){process.stdout.write(`\nNext: ${e}\n`)}function X(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}async function co(e,t={resolveClient:Ua,resolveGoogleAnalyticsProperty:Wa}){if(e.property)return t.resolveGoogleAnalyticsProperty({property:e.property,options:e.options});let n=await t.resolveClient({client:e.project,options:e.options});return t.resolveGoogleAnalyticsProperty({property:It(n),options:e.options})}const lo=D({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:eo,google:D({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:to,report:D({meta:{name:`report`,description:`Run a Google Analytics report`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},dimensions:{type:`string`,default:`landingPage`},metrics:{type:`string`,default:`sessions,totalUsers,eventCount`},limit:{type:`string`,default:`25`},body:{type:`string`},"body-file":{type:`string`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Google Analytics cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await co({property:A(e.property),project:j(e),options:{json:t,account:A(e.account)}}),r=await tt(n,await Ki(e.body,e[`body-file`])??{dateRanges:[{startDate:A(e[`start-date`]),endDate:A(e[`end-date`])}],dimensions:(F(e.dimensions)??[]).map(e=>({name:e})),metrics:(F(e.metrics)??[]).map(e=>({name:e})),limit:A(e.limit)},{refresh:M(e.refresh),accountEmail:A(e.account)});if(t){B(r);return}let i=oe(r);if(q({title:`Google Analytics report`,target:n,status:`info`,summary:`${r.rowCount??i.length} rows returned for the requested dimensions and metrics.`,metrics:[{label:`Rows`,value:r.rowCount??i.length},{label:`Displayed`,value:Math.min(i.length,25)}]}),i.length){let e=Object.keys(i[0]??{});H(e,i.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}})}})}});function uo(){return`Your browser will open. Choose your Google account and select all permission boxes for read-only access. Keep this terminal open and finish the browser steps within five minutes.`}async function fo(){return O(uo(),`Google sign-in`),wt()}function po(e){return e?e===`environment-json`?`SEO_GOOGLE_SERVICE_ACCOUNT_JSON`:e===`environment-file`?`SEO_GOOGLE_SERVICE_ACCOUNT_FILE`:`GOOGLE_APPLICATION_CREDENTIALS`:`invalid or conflicting configuration`}function mo(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function ho(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function go(e){let t=[[`Account`,e.accountEmail]],n=new Set(e.scope.split(/\s+/u).filter(Boolean));return n.has(`https://www.googleapis.com/auth/webmasters.readonly`)?t.push([`Search Console`,`Read only`]):t.push([`Search Console`,`Not granted`]),n.has(`https://www.googleapis.com/auth/analytics.readonly`)?t.push([`Google Analytics`,`Read only`]):t.push([`Google Analytics`,`Not granted`]),t.push([`OAuth app`,ho(e.clientSource)],[`Expires`,Ri(e.expiresAt)]),t}const _o=D({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:D({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await Tn()).activeMode===`service-account`)throw new E(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await fo();V(go({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:D({meta:{name:`logout`,description:`Delete a local Google login`},args:{account:{type:`string`,description:`Delete the login for this Google account.`},all:{type:`boolean`,default:!1,description:`Delete every saved Google login.`}},run:async({args:e})=>{let t=await Tn(),n=A(e.account);if(n&&e.all)throw new E(`INVALID_INPUT`,`Use either --account or --all, not both.`);let r=e.all?void 0:n??t.accounts.find(e=>e.active)?.accountEmail;if(await zt(r),t.activeMode===`service-account`){process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`} Service account credentials remain available through the environment.\n`);return}process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`}\nRevoke at https://myaccount.google.com/permissions if you also want Google to forget the grant.\n`)}}),whoami:D({meta:{name:`whoami`,description:`Show the signed-in Google account`},args:{account:{type:`string`,description:`Show this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Tn();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:po(t.serviceAccount.source)};if(L(e)){B(n);return}V([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=A(e.account),r=n?await Yt(n):t.tokens;if(n&&!r)throw new E(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(!r){if(L(e)){B({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
|
|
135
|
+
`);return}if(L(e)){B({mode:`oauth`,account:r.account_email,scopes:r.scope.split(/\s+/u).filter(Boolean),clientSource:r.client_source,expiresAt:r.expires_at});return}V(go({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}))}}),status:D({meta:{name:`status`,description:`Show local Google auth status`},args:{account:{type:`string`,description:`Show status for this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Tn(),n=A(e.account),r=n?await Yt(n):t.tokens;if(n&&!r&&t.activeMode!==`service-account`)throw new E(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);let i=t.activeMode===`oauth`?await cn():void 0;if(L(e)){B({authenticated:t.activeMode===`oauth`?!!r:t.serviceAccount.configured,mode:t.activeMode,identity:t.identity,account:t.activeMode===`oauth`?r?.account_email:void 0,scopes:t.activeMode===`oauth`?r?.scope:void 0,clientSource:t.activeMode===`oauth`?r?.client_source:void 0,expiresAt:t.activeMode===`oauth`?r?.expires_at:void 0,sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,serviceAccount:{configured:t.serviceAccount.configured,identity:t.serviceAccount.identity,source:t.serviceAccount.source,error:t.serviceAccount.error},accounts:t.accounts,...i?{tokenStorage:i}:{}});return}if(t.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,t.identity??`unavailable`],[`Source`,po(t.serviceAccount.source)],[`Scopes`,`Search Console readonly, Google Analytics readonly`],[`Storage`,`credentials stay in the environment`]];t.serviceAccount.error&&e.push([`Status`,t.serviceAccount.error]),V(e);return}if(!r){let e=t.serviceAccount.error?t.serviceAccount.error:t.sharedConfigured?`Shared seo app available`:t.byoConfigured?`Local OAuth config available`:`No OAuth client configured`;process.stdout.write(`Not logged in. ${e}. Run \`seo auth login\` to connect Google.\n`);return}let a=i??await cn();V([[`Account`,r.account_email],[`Connected accounts`,String(t.accounts.length)],[`Scopes`,r.scope],[`Client`,ho(r.client_source)],[`Expires`,Ri(r.expires_at)],[`Tokens file`,Ft().tokensFile],[`Token storage`,mo(a)],...a.reason?[[`Storage note`,a.reason]]:[],[`OAuth mode`,t.sharedConfigured?`SEO Skill app available`:t.byoConfigured?`Local OAuth config available`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:D({meta:{name:`storage`,description:`Show or change local OAuth token storage`},args:{keychain:{type:`boolean`,default:!1,description:`Store OAuth tokens in the system keychain when available.`},file:{type:`boolean`,default:!1,description:`Store OAuth tokens in the private local file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(e.keychain&&e.file)throw new E(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await xt(e.keychain?`keychain`:`file`):await cn();if(L(e)){B(t);return}V([[`Configured storage`,t.configured],[`Active storage`,mo(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:D({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},args:{account:{type:`string`,description:`Refresh this saved Google account.`}},run:async({args:e})=>{if((await Tn()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
|
|
136
|
+
`);return}let t=await rn(A(e.account));process.stdout.write(`Refreshed. New expiry ${new Date(t.expires_at).toISOString()}.\n`)}}),accounts:D({meta:{name:`accounts`,description:`List saved Google accounts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=xn();if(L(e)){B({accounts:t});return}if(t.length===0){process.stdout.write(`No Google accounts are connected.
|
|
137
|
+
`);return}V(t.map(e=>[e.active?`Active account`:`Account`,e.accountEmail]))}}),use:D({meta:{name:`use`,description:`Select the default Google account`},args:{account:{type:`positional`,required:!0,description:`Saved Google account email.`}},run:({args:e})=>{let t=A(e.account);if(!t)throw new E(`INVALID_INPUT`,`Google account is required.`);kn(t),process.stdout.write(`Default Google account: ${t}\n`)}}),"setup-client":D({meta:{name:`setup-client`,description:`Save your own Google Desktop OAuth client`},args:{json:{type:`boolean`,default:!1,description:`Return a structured error instead of prompting.`}},run:async({args:e})=>{if(!z({json:L(e)}))throw new E(`INVALID_INPUT`,"`seo auth setup-client` needs an interactive terminal. Run it without --json, or set SEO_GOOGLE_CLIENT_ID and SEO_GOOGLE_CLIENT_SECRET.");jr(`Local Google OAuth client`),O([`This is the advanced path.`,`Most users should use the shared seo Google app.`,`Your actual sensitive data is the local refresh token, not the desktop app client secret.`].join(`
|
|
138
|
+
`),`Advanced`),fn({clientId:U(await Rr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:U(await Ir({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),Fr(`Saved local OAuth config to ${Ft().oauthClientFile}`)}})}}),vo=[`gsc`,`google-analytics`,`dataforseo`,`ahrefs`,`clicky`,`http`];function yo(e){if(e!==void 0){if(typeof e==`string`&&vo.includes(e))return e;throw new E(`INVALID_INPUT`,`--provider must be one of: ${vo.join(`, `)}.`)}}const bo=D({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:D({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=t();V([[`DB`,e.dbPath],[`Size`,Ra(e.sizeBytes)],[`Cached data`,`${Ra(e.logicalSizeBytes)} of ${Ra(e.maxSizeBytes)} automatic limit`],[`Sites`,String(e.counts.sites??0)],[`Search Console`,String(e.counts.gsc_cache??0)],[`Google Analytics`,String(e.counts.google_analytics_cache??0)],[`DataForSEO`,String(e.counts.provider_cache??0)],[`Ahrefs`,String(e.counts.ahrefs_cache??0)],[`Clicky`,String(e.counts.clicky_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:D({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, dataforseo, ahrefs, clicky, or http`}},run:async({args:e})=>{let t=De(yo(e.provider));process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function xo(e,t={}){return(await W({client:e.client,project:e.project,site:e.site,options:t})).site}function So(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function Co(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function wo(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function To(e){return e?e.backpressure?.status===`stopped`?`stopped: ${e.backpressure.reason??`origin cooldown`}`:e.backpressure?.status===`slowed`?`slowed ${e.backpressure.delayMs}ms`:e.rendering?.status===`unavailable`?`raw HTML; rendering unavailable`:e.rendering?.status===`failed`?`raw HTML; rendering failed`:e.blocked?`blocked`:e.rendered?`rendered`:e.cache===`hit`?`cached`:e.fetched?`fetched`:e.source:`-`}function Eo(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}const Do=`__create_project__`,Oo=`__skip_project__`;function ko(e){return[{value:Do,label:`Create a new project`,hint:`Save a separate site and its data sources`},...e.map(e=>({value:e.id,label:`Update ${e.name}`,hint:`${e.id}, ${e.siteUrl}`})),{value:Oo,label:`Continue without a project profile`,hint:`Pass --site or --url when you run reports`}]}function Ao(e,t){let n=wo(e);if(!n)throw Error(`Project id could not be derived.`);let r=new Set(t.map(e=>e.id.toLowerCase()));if(!r.has(n.toLowerCase()))return n;let i=2;for(;r.has(`${n}-${i}`.toLowerCase());)i+=1;return`${n}-${i}`}async function jo(e){if(e.skipProfile)return{mode:`skip`};if(e.requestedId){let t=e.requestedId.toLowerCase(),n=wo(e.requestedId).toLowerCase(),r=e.clients.find(e=>e.id.toLowerCase()===n||e.name.toLowerCase()===t);return r?{mode:`update`,client:r}:{mode:`create`,requestedId:e.requestedId}}if(!e.interactive)return{mode:`create`};let t=ko(e.clients),n=U(await(e.prompt?e.prompt(t):Lr({message:`Create a project or update an existing one?`,options:t})));if(n===Do)return{mode:`create`};if(n===Oo)return{mode:`skip`};let r=e.clients.find(e=>e.id===n);if(!r)throw Error(`Project not found: ${n}`);return{mode:`update`,client:r}}function Mo(e){let t=e.platform===`win32`?gr:mr;return e.platform===`win32`?e.env.APPDATA??t.resolve(e.home,`AppData/Roaming`):e.platform===`darwin`?t.resolve(e.home,`Library/Application Support`):e.env.XDG_CONFIG_HOME??t.resolve(e.home,`.config`)}function No(e={}){let t=e.home??cr(),n=e.platform??process.platform,r=Mo({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?gr:mr,a=[];if(n===`darwin`||n===`win32`){let e=i.resolve(r,`Claude`);a.push({kind:`json`,client:`claude-desktop`,label:`Claude Desktop`,path:i.resolve(e,`claude_desktop_config.json`),commandNames:[],detectionPaths:n===`darwin`?[e,`/Applications/Claude.app`]:[e]})}return a.push({kind:`json`,client:`claude-code`,label:`Claude Code`,path:i.resolve(t,`.claude.json`),includeType:!0,commandNames:[`claude`],detectionPaths:[i.resolve(t,`.claude`)]},{kind:`native`,client:`codex`,label:`Codex`,path:i.resolve(t,`.codex/config.toml`),executable:`codex`,commandNames:[`codex`],detectionPaths:[i.resolve(t,`.codex`)]},{kind:`json`,client:`cursor`,label:`Cursor`,path:i.resolve(t,`.cursor/mcp.json`),commandNames:[`cursor`,`cursor-agent`],detectionPaths:[i.resolve(t,`.cursor`),...n===`darwin`?[`/Applications/Cursor.app`]:[]]}),a}function Po(e,t={}){let n=t.platform??process.platform,r=t.exists??$n,i=t.nodePath??process.execPath,a=n===`win32`?[`${e}.cmd`,`${e}.exe`,e]:[e],o=(t.path??process.env.PATH??``).split(n===`win32`?`;`:`:`).filter(Boolean);for(let e of o)for(let t of a){let n=pr(e,t);if(r(n))return n}for(let e of a){let t=pr(dr(i),e);if(r(t))return t}return e}function Fo(e){return Po(e)!==e}function Io(e={}){let t=e.targets??No(),n=e.hasCommand??Fo,r=e.hasPath??$n;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const Lo={insertSpaces:!0,tabSize:2,eol:`
|
|
139
|
+
`};function Ro(){return new Date().toISOString().replace(/[:.]/g,`-`)}function zo(e,t){er(dr(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;or(n,t,{encoding:`utf8`,mode:$n(e)?ar(e).mode&511:384}),ir(n,e)}function Bo(e){let t=`${e}.bak-${Ro()}`,n=t,r=2;for(;$n(n);)n=`${t}-${r}`,r+=1;Zn(e,n,Xn.COPYFILE_EXCL)}function Vo(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ho(e){if(!$n(e.path))return{source:`{}
|
|
140
|
+
`,config:{}};let t=tr(e.path,`utf8`),n=[],r=Wr(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Gr(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!Vo(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!Vo(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Uo(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Wo(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!Vo(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Go(e){if(!Vo(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Uo(t)||Wo(e,t)}function Ko(e){let t=Hr(e.source,Ur(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:Lo}));$n(e.target.path)&&Bo(e.target.path),zo(e.target.path,t)}function qo(e,t){let n=Er(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Jo(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Yo(e){if(!e||!$n(e))return;let t=rr(e);return Jo(t)?void 0:t}function Xo(e={}){let t=Yo(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??ee}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function Zo(e=!1,t=Xo()){let n=Ft(),r={command:t.command,args:t.args,env:{SEO_CONFIG_DIR:n.configDir,SEO_MCP_CONFIGURED_BY:`seo`}};return e&&(r.type=`stdio`),r}function Qo(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function $o(e,t,n){let r=Po(e.executable),i=n.runCommand(r,[`mcp`,`get`,`seo`]);if(i.error?.code===`ENOENT`)throw Error("Cannot install SEO MCP for Codex: the `codex` command is not available on PATH. Install Codex CLI or choose another MCP client.");let a=i.status===0;if(a&&!Qo(i.stdout))throw Error(`Cannot ${t} SEO MCP: ${e.path} already has an unmanaged mcp_servers.seo entry.`);if(t===`install`&&a&&!n.reinstall||t===`uninstall`&&!a)return{client:e.client,path:e.path,changed:!1};if(t===`install`&&a){let e=n.runCommand(r,[`mcp`,`remove`,`seo`]);if(e.error||e.status!==0){let t=e.error?.message??e.stderr.trim()??`unknown error`;throw Error(`Cannot reinstall SEO MCP for Codex: ${t}`)}}let o=Ft(),s=t===`install`?[`mcp`,`add`,`seo`,`--env`,`SEO_CONFIG_DIR=${o.configDir}`,`--env`,`SEO_MCP_CONFIGURED_BY=seo`,`--`,n.serverCommand.command,...n.serverCommand.args]:[`mcp`,`remove`,`seo`],c=n.runCommand(r,s);if(c.error||c.status!==0){let e=c.error?.message??c.stderr.trim()??`unknown error`;throw Error(`Cannot ${t} SEO MCP for Codex: ${e}`)}return{client:e.client,path:e.path,changed:!0}}function es(e,t={}){let n={runCommand:t.runCommand??qo,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Xo()};if(e.kind===`native`)return $o(e,`install`,n);let{source:r,config:i}=Ho(e),a=i.mcpServers?.seo,o=Zo(e.includeType,n.serverCommand);if(Cr(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Go(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Ko({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function ts(e,t={}){if(e.kind===`native`)return $o(e,`uninstall`,{runCommand:t.runCommand??qo,reinstall:!1,serverCommand:Xo()});if(!$n(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Ho(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Go(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Ko({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function ns(e={}){let t=Er(`npx`,[`-y`,`skills`,`add`,`iannuttall/seo`],{stdio:e.quiet?`ignore`:`inherit`});return t.error?{status:`failed`,error:t.error.message}:t.status===0?{status:`installed`}:{status:`failed`,error:`npx skills add exited with status ${t.status??`unknown`}.`}}function rs(e,t=[]){let n=[{value:`google`,label:`Google Analytics`,hint:`Use a property available to your Google login`},...t.filter(e=>e.capabilities.some(e=>e.id===`landing-page-visits`)&&e.id!==`google`).map(e=>({value:`extension:${e.id}`,label:e.displayName,hint:`Installed provider from ${e.package}`}))];return e?[{value:`keep`,label:`Keep ${d(e)}`,hint:`Leave this project connection unchanged`},...n,{value:`remove`,label:`Remove traffic analytics`}]:[...n,{value:`skip`,label:`Skip traffic analytics`}]}async function is(e,t={},n=!0){if(!e.capabilities.some(e=>e.id===`landing-page-visits`))throw new E(`INVALID_INPUT`,`Provider ${e.id} does not supply landing-page visits.`);let r={...t},i={};for(let t of e.connection.fields.filter(e=>e.kind===`account`)){if(r[t.id]?.trim())continue;if(!n){if(t.required!==!1)throw new E(`INVALID_INPUT`,`${t.label} is required.`);continue}let e=U(await Rr({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();e&&(r[t.id]=e)}let a=e.connection.normalizeAccount?e.connection.normalizeAccount(r):r;try{Object.assign(i,await _({providerId:e.id,account:a,fields:e.connection.fields}))}catch{}if(e.id===`clicky`&&!i.sitekey){let e=await Ht(a.siteId??``);e&&(i.sitekey=e.siteKey)}for(let t of e.connection.fields.filter(e=>e.kind===`secret`)){let r=t.envVar?process.env[t.envVar]?.trim():void 0;if(r&&(i[t.id]=r),i[t.id])continue;if(!n)throw new E(`AUTH_REQUIRED`,t.envVar?`Set ${t.envVar} to connect ${e.displayName}.`:`${t.label} is required to connect ${e.displayName}.`);let a=U(await Ir({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();a&&(i[t.id]=a)}return e.connection.verificationNotice&&O(e.connection.verificationNotice,`Connection check`),await zn({providerId:e.id,account:a,credentials:i}),Object.keys(i).length>0&&await m({providerId:e.id,account:a,credentials:i}),{provider:`extension`,extension:{providerId:e.id,account:a}}}function as(e){let t=e.canSkip?[{value:`skip`,label:`Skip for now`}]:[];return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},...t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},...t]}async function os(e,t){let n=[],r=!0,i=0;async function a(){for(;i<e.length;){let a=e[i];if(i+=1,a)try{let e=await Ye(a.property,t);n.push(...e.filter(e=>e.webStreamData).map(e=>({account:a.account,property:a.property,propertyName:a.label,stream:e})))}catch{r=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>a())),{candidates:n,complete:r}}async function ss(e){let t=await Tn();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};let n=typeof e[`search-console-account`]==`string`?e[`search-console-account`]:void 0,r=xn();if(n){let e=r.find(e=>e.accountEmail.toLowerCase()===n.toLowerCase());if(!e)throw new E(`AUTH_REQUIRED`,`Google account ${n} is not connected. Run \`seo auth login\` first.`);return{status:`already-connected`,accountEmail:e.accountEmail}}if(e[`skip-auth`])return{status:`skipped`};if(!z({json:e.json===!0})){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new E(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.")}if(r.length>0){let n=U(await Lr({message:`Google account for Search Console`,options:[...r.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`},...typeof e.site==`string`?[{value:{type:`skip`},label:`Skip for now`}]:[]]}));if(n.type===`account`)return kn(n.accountEmail),{status:`already-connected`,accountEmail:n.accountEmail};if(n.type===`skip`)return{status:`skipped`};if(t.sharedConfigured||t.byoConfigured){let e=await fo();return O(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}}let i=U(await Lr({message:`Connect Google now?`,options:as({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));if(i===`skip`)return{status:`skipped`};i===`setup`&&fn({clientId:U(await Rr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:U(await Ir({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))});let a=await fo();return O(`Connected as ${a.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:a.account_email}}async function cs(e){if(e.property)return{propertyId:e.property,accountEmail:e.accountEmail,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??z()))return;let t=(await Qe(e.accountEmail).catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:hn(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await os(t,e.accountEmail),i=r?li(e.site,n):[],a=new Map;for(let e of i)a.set(e.property,e);let o=[...a.values()];if(o.length===1){let t=o[0];return t?{propertyId:t.property,accountEmail:e.accountEmail,selection:`matched`,reason:ui(t,e.site)}:void 0}let s=o.length?o.map(e=>({property:e.property,label:e.propertyName,account:e.account,match:e})):t,c=U(await Lr({message:o.length?`Several Google Analytics properties match this site. Which property should seo use?`:r?`No Google Analytics web stream clearly matches this site. Attach a property?`:`Some Google Analytics web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip Google Analytics for now`},...s.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?ui(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,accountEmail:e.accountEmail,selection:`manual`,reason:c.match?ui(c.match,e.site):r?`Selected ${c.label} during setup. Its web stream did not clearly match ${e.site}.`:`Selected ${c.label} during setup. seo could not read every Google Analytics web stream, so it did not guess a match.`}}async function ls(e){if(e.googleProperty&&e.clickySiteId)throw new E(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);let t=e.interactive??z();if(e.clickySiteId){let n=(await Ne()).registry.get(`clicky`);if(!n)throw new E(`PROVIDER_UNAVAILABLE`,"The Clicky provider package is not installed. Run `seo providers install @seoskill/clicky-provider`.");return is(n,{siteId:e.clickySiteId},t)}if(e.googleProperty){let n=await us({requested:e.googleAccount,interactive:t}),r=await cs({property:e.googleProperty,accountEmail:n,site:e.site,interactive:t});return r?{provider:`google`,google:r}:void 0}if(!t)return;let n=await Ne(),r=U(await Lr({message:`Traffic analytics`,options:rs(e.current,n.registry.list())}));if(r===`keep`)return;if(r===`remove`)return{provider:`none`};if(r===`skip`)return;if(r.startsWith(`extension:`)){let e=r.slice(10),t=n.registry.get(e);if(!t)throw new E(`INVALID_INPUT`,`Provider ${e} is installed but could not be loaded. Run \`seo providers doctor\`.`);return is(t)}let i=await cs({accountEmail:await us({interactive:t}),site:e.site,interactive:t});return i||O(`No Google Analytics property was available for this Google login. You can connect one later.`,`Traffic analytics skipped`),i?{provider:`google`,google:i}:void 0}async function us(e){let t=await Tn();if(t.activeMode===`service-account`)return t.identity;let n=xn();if(e.requested){let t=n.find(t=>t.accountEmail.toLowerCase()===e.requested?.toLowerCase());if(!t)throw new E(`AUTH_REQUIRED`,`Google account ${e.requested} is not connected. Run \`seo auth login\` first.`);return t.accountEmail}if(!e.interactive)return n.find(e=>e.active)?.accountEmail;if(n.length===0)return(await fo()).account_email;let r=U(await Lr({message:`Google account for Google Analytics`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(r.type===`account`)return r.accountEmail;let i=await fo();return O(`Connected as ${i.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),i.account_email}async function ds(e){return e[`skip-skill`]||!z({json:e.json===!0})?{status:`skipped`}:U(await Ar({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?ns():{status:`declined`}}async function fs(e){if(e[`skip-mcp`]||!z({json:e.json===!0}))return[];let t=Io();if(t.length===0||!U(await Ar({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=U(await Pr({message:`Which MCP clients?`,options:t.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:t.map(e=>e.client)}));return t.filter(e=>n.includes(e.client)).map(e=>{try{return es(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function ps(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function ms(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function hs(){process.stdout.write(`
|
|
141
|
+
`),ka({title:`Help other people find SEO Skill`,body:`If SEO Skill saved you time, a GitHub star helps other people find it.`,command:`https://github.com/iannuttall/seo`})}function gs(e){let t=e.filter(e=>e.error).length,n=e.length-t;return e.length===0?`not installed`:t===0?`${n} installed`:n===0?`${t} skipped`:`${n} installed, ${t} skipped`}function _s(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
|
|
142
|
+
`)}async function vs(e){let t=L(e);if(t||jr(process.argv[2]===`start`?`seo start`:`seo setup`),e[`dry-run`]){let e=[`seo auth login`,`seo start`,`seo report --project acme`,`seo refresh-priorities --project acme --verify-content`,`seo technical-watch --project acme`];t?B({dryRun:!0,next:e}):(O(e.join(`
|
|
143
|
+
`),`This setup will guide you through`),Fr(`Dry run complete.`));return}$t();let n=z({json:t});n&&O(`A project profile remembers one site and its report defaults so future commands stay short.`,`Project profiles`);let r=Mt(),i=await jo({clients:r,interactive:n,requestedId:A(e.id),skipProfile:M(e[`skip-profile`])}),a=i.mode===`update`?i.client:void 0,o=A(e.site)??a?.siteUrl,c={site:o,options:{allowDefault:i.mode===`update`,json:t,refresh:M(e.refresh)}},l=n?void 0:await Ha(c),u=await ss(o?{...e,site:o}:e),f=u.status,p=l??await Ha({...c,options:{...c.options,account:u.accountEmail}}),m=Co(p);if(i.mode===`skip`){let n=await fs(e),r=await ds(e),i=`--site ${ms(p)}`,a=[`seo report ${i}`,`seo refresh-priorities ${i} --verify-content`,`seo technical-watch ${i}`],o={site:p,auth:f,mcp:n,skill:r,next:a};if(t){B(o);return}V([[`Project profile`,`not saved`],[`GSC property`,p],[`Auth`,f],[`MCP`,gs(n)],[`SEO skill`,ps(r)]]);let s=_s(n);s&&O(s,`MCP setup skipped`),r.error&&O(r.error,`SEO skill install failed`),O(a.join(`
|
|
144
|
+
`),`Try next`),Fr(`Setup complete.`),hs();return}let h=A(e.name)??(n?U(await Rr({message:`Project name`,placeholder:a?.name??m,defaultValue:a?.name??m})):a?.name??m),g=a?.id??(i.mode===`create`&&i.requestedId?wo(i.requestedId):Ao(h,r)),_=a?.startUrl??So(p)??``,v=A(e.url)??(n?U(await Rr({message:`Website URL to crawl`,placeholder:_||`https://example.com`,defaultValue:_})):_||void 0),y=e.urls===void 0?a?.watchUrls??[]:I(e.urls),b=await ls({googleProperty:A(e[`google-analytics-property`]),googleAccount:A(e[`google-analytics-account`]),clickySiteId:A(e[`clicky-site-id`]),current:kt(a),site:p,interactive:n}),x=b?.provider===`google`?b.google:void 0,S=b?.provider===`extension`?b.extension:void 0,ee=a?.brandTerms??s({id:g,name:h,siteUrl:p}),te=e.brand===void 0?ee:I(e.brand),ne=N(e.weekday)??a?.technicalWeekday??1,C=M(e.default)??a?.isDefault??!0,w={name:h,siteUrl:p,startUrl:v,watchUrls:y,brandTerms:te,analytics:b===void 0?void 0:b.provider===`google`?{...a?.analytics,selected:`google`,google:{propertyId:b.google.propertyId}}:b.provider===`extension`?{...a?.analytics,selected:`extension:${b.extension.providerId}`,extensions:{...a?.analytics.extensions,[b.extension.providerId]:{account:b.extension.account}}}:{},googleAccounts:{...a?.googleAccounts,...u.accountEmail?{searchConsole:u.accountEmail}:{},...x?.accountEmail?{googleAnalytics:x.accountEmail}:{}},technicalWeekday:ne,isDefault:C},T=a?Vn(a.id,w):Pn({id:g,...w}),re=await fs(e),ie=await ds(e),ae=[`seo report --project ${T.id}`,`seo refresh-priorities --project ${T.id} --verify-content`,`seo technical-watch --project ${T.id}`],oe={client:T,site:p,auth:f,googleAnalytics:x,extension:S,mcp:re,skill:ie,next:ae};if(t){B(oe);return}let se=kt(T);V([[`Project profile`,`${T.name} (${T.id})`],[`GSC property`,T.siteUrl],[`Search Console account`,T.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,T.startUrl??`not set`],[`Watch URLs`,String(T.watchUrls.length)],[`Brand terms`,T.brandTerms.join(`, `)||`not set`],[`Traffic analytics`,se?d(se):`not connected (optional)`],...x?[[`Google Analytics selection`,x.reason]]:[],...T.googleAccounts?.googleAnalytics?[[`Google Analytics account`,T.googleAccounts.googleAnalytics]]:[],[`Auth`,f],[`MCP`,gs(re)],[`SEO skill`,ps(ie)]]);let ce=_s(re);ce&&O(ce,`MCP setup skipped`),ie.error&&O(ie.error,`SEO skill install failed`),O(ae.join(`
|
|
145
|
+
`),`Try next`),Fr(`Setup complete.`),hs()}const ys=D({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional Clicky site ID. Set SEO_CLICKY_SITEKEY outside a terminal.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>vs(e)}),bs=D({...ys,meta:{name:`start`,description:`Start here: connect first-party data, save a profile, and get the first report commands`}}),xs=D({...ys,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),Ss=D({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:D({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=L(e),n=en(j(e)),r=await Fe({site:await Ha({site:A(e.site)??n?.siteUrl,options:{json:t,refresh:M(e.refresh)}}),id:n?.id,name:n?.name,days:N(e.days),limit:N(e.limit),minImpressions:N(e[`min-impressions`]),refresh:M(e.refresh)}),i=M(e.save)&&n?Pn({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(M(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){B({...r,saved:i});return}V([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),H([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function Cs(e){Pa(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function ws(e){V([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Search Console account`,e.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Google Analytics account`,e.googleAccounts?.googleAnalytics??`default Google account`],[`Clicky site`,e.analytics.clicky?.siteId??`not set`],[`Selected analytics`,e.analytics.selected??`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const Ts=D({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional legacy Clicky site ID. New connections use seo start.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=A(e.id),r=n?en(n)??en(wo(n)):void 0,i=A(e[`google-analytics-property`]),a=A(e[`google-analytics-account`]),o=A(e[`search-console-account`]),s=A(e[`clicky-site-id`]);if(i&&s)throw new E(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(a&&!i&&!r?.analytics.google?.propertyId)throw new E(`INVALID_INPUT`,`--google-analytics-account needs a Google Analytics property.`);let c=await Ha({site:A(e.site)??r?.siteUrl,options:{json:t,account:o}}),l={name:A(e.name),siteUrl:c,startUrl:A(e.url),watchUrls:e.urls===void 0?void 0:I(e.urls),brandTerms:e.brand===void 0?void 0:I(e.brand),analytics:i||a?{...r?.analytics,selected:`google`,google:{propertyId:i??r?.analytics.google?.propertyId??``}}:s?{...r?.analytics,selected:`clicky`,clicky:{siteId:s}}:void 0,googleAccounts:o||a?{...r?.googleAccounts,...o?{searchConsole:o}:{},...a?{googleAnalytics:a}:{}}:void 0,technicalWeekday:N(e.weekday),isDefault:M(e.default)},u=r?Vn(r.id,l):Pn({id:n,...l});if(t){B(u);return}ws(u)}}),Es=D({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=mt(A(e.id)??``);if(L(e)){B(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),Ds=D({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``,n=pn(t);if(L(e)){B({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),Os=D({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:xs,brand:Ss,list:D({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Mt();if(L(e)){B({clients:t});return}Cs(t)}}),add:Ts,show:D({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=en(A(e.id));if(!t)throw Error(`Project not found.`);if(L(e)){B(t);return}ws(t)}}),default:Es,delete:Ds}}),ks=D({...Os,meta:{name:`projects`,description:`Manage saved project profiles`}}),As=D({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:D({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":zi(`Fetch the page for content checks. Defaults to true.`,`Skip fetching the page for content checks.`),js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for page extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC and HTTP cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=await Ae({site:n.site,url:A(e.url)??``,days:N(e.days),limit:N(e.limit),minImpressions:N(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),verifyContent:M(e[`verify-content`])!==!1,js:M(e.js)?!0:`auto`,refresh:M(e.refresh)});if(t){B(r);return}q({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:G(r.summary.queries)},{label:`Opportunities`,value:G(r.summary.opportunities)},{label:`Estimated lift`,value:G(r.summary.estimatedClickLift)}]}),Y(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
|
|
146
146
|
Suggested sections
|
|
147
147
|
`),J([`Heading`,`Queries`],r.brief.sections.map(e=>[K(e.heading,48),K(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
|
|
148
148
|
Intent mix
|
|
149
|
-
`),J([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,
|
|
149
|
+
`),J([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,G(e.queries),G(e.impressions),G(e.clicks)]))),X(`Caveats`,r.caveats)}})}}),js={site:`--site`,url:`--url`,section:`--section`,date:`--date`,pages:`--pages`,dimensions:`--dimensions`,detail:`--detail`,offset:`--offset`},Ms=e=>/^[\w:/.,<>@#?=&%+-]+$/.test(String(e))?String(e):`"${e}"`,Ns=(e,t)=>[`seo ${e}`,...Object.entries(t??{}).filter(([,e])=>e!==void 0).map(([e,t])=>`${js[e]??`--${e}`} ${Ms(t??``)}`)].join(` `),Ps={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},account:{type:`string`,description:`Saved Google account email.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local cache and fetch fresh data.`}},Fs={"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`}},Is={detail:{type:`string`,description:`summary (default), rows for the next page, or full.`},offset:{type:`string`,description:`rows only: the first row.`}};function Ls(e){let t=A(e.detail)??`summary`;if(![`summary`,`rows`,`full`].includes(t))throw new E(`INVALID_INPUT`,`--detail is summary, rows or full.`);return t}async function Rs(e,t){let n={json:L(e),refresh:M(e.refresh),account:A(e.account)};if(!t||j(e))return xo({client:j(e)},n);let r=vt((await C(n.refresh,n.account)).map(e=>e.siteUrl),t);if(!r)throw new E(`PROPERTY_NOT_FOUND`,`This Google login has no Search Console property for ${t}. Run \`seo sites\` to list the properties it can read.`);return r}async function zs(e,t){try{let n=await t(T({call:Ns,account:A(e.account),refresh:M(e.refresh)}));L(e)?B(n.data):process.stdout.write(`${n.text}\n`)}catch(e){throw Ce(e)}}const Bs=D({meta:{name:`report`,description:`The main Search Console report: overview and every section, then one section on request`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},section:{type:`string`,description:`Optional. One of ${yn.join(`, `)}.`},...Fs,...Is,...Ps},run:async({args:e})=>{let t=A(e.section);if(t&&!yn.includes(t))throw new E(`INVALID_INPUT`,`--section is one of ${yn.join(`, `)}.`);let n=await Rs(e,A(e.site));await zs(e,r=>Bt({site:n,section:t,startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),detail:Ls(e),offset:N(e.offset)},r))}}),Vs=D({meta:{name:`page`,description:`One URL: live page facts, Google index status and its queries`},args:{url:{type:`string`,description:`The page URL.`,required:!0},...Fs,...Is,...Ps},run:async({args:t})=>{let n=A(t.url)??``;if(!/^https?:\/\/\S+$/.test(n))throw new E(`INVALID_INPUT`,`--url must be a full http(s) URL.`);let r=await Rs(t,n);await zs(t,i=>e({site:r,url:n,startDate:A(t[`start-date`]),endDate:A(t[`end-date`]),detail:Ls(t),offset:N(t.offset)},i))}}),Hs=D({meta:{name:`compare`,description:`Equal periods before and after a date, with the pages and queries that moved`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},date:{type:`string`,description:`YYYY-MM-DD: the first day after the change.`,required:!0},pages:{type:`string`,description:`Optional URL text, such as /blog/.`},...Ps},run:async({args:e})=>{let t=await Rs(e,A(e.site));await zs(e,n=>et({site:t,date:A(e.date)??``,pages:A(e.pages)},n))}}),Us=D({meta:{name:`index`,description:`Pages that lost most of their clicks, checked live and with URL Inspection`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},...Fs,...Ps},run:async({args:e})=>{let t=await Rs(e,A(e.site));await zs(e,n=>Ee({site:t,startDate:A(e[`start-date`]),endDate:A(e[`end-date`])},n))}}),Ws=D({meta:{name:`data`,description:`Raw Search Console or Bing rows for any dimensions, beside the previous period`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},source:{type:`string`,description:`search_console (default) or bing. Bing takes the first dimension, query or page.`},dimensions:{type:`string`,description:`Comma-separated: ${ln.join(`, `)}. Default query.`},page:{type:`string`,description:`Optional: only rows for pages whose URL contains this.`},query:{type:`string`,description:`Optional: only rows for queries that contain this.`},...Fs,...Is,...Ps},run:async({args:e})=>{let t=F(e.dimensions)??[`query`],n=t.filter(e=>!ln.includes(e));if(n.length)throw new E(`INVALID_INPUT`,`Unknown dimensions: ${n.join(`, `)}. Use ${ln.join(`, `)}.`);let r=A(e.source)??`search_console`;if(![`search_console`,`bing`].includes(r))throw new E(`INVALID_INPUT`,`--source is search_console or bing.`);let i=await Rs(e,A(e.site));if(r===`bing`){await zs(e,n=>ut({site:i,dimension:t[0]===`page`?`page`:`query`,startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),detail:Ls(e),offset:N(e.offset)},n));return}let a=A(e.page),o=A(e.query);await zs(e,n=>Sn({site:i,dimensions:t,filters:[...a?[{dimension:`page`,operator:`contains`,expression:a}]:[],...o?[{dimension:`query`,operator:`contains`,expression:o}]:[]],startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),detail:Ls(e),offset:N(e.offset)},n))}}),Gs=D({meta:{name:`research`,description:`Keyword, SERP, competitor and backlink data from your DataForSEO or Ahrefs key`},args:{action:{type:`positional`,required:!0,description:Tt.join(`, `)},provider:{type:`string`,description:`${En.join(`, `)}. Default: the first one with a saved key.`},keywords:{type:`string`,description:`Comma-separated keywords.`},target:{type:`string`,description:`Domain or URL.`},country:{type:`string`,description:`Two-letter country code. Default us.`},...Is,json:Ps.json},run:async({args:e})=>{let t=A(e.action);if(!Tt.includes(t))throw new E(`INVALID_INPUT`,`The action is one of ${Tt.join(`, `)}.`);let n=await y(),r={dataforseo:`x-dataforseo-login`,ahrefs:`x-ahrefs-key`},i=A(e.provider)??En.find(e=>n.has(r[e]))??`dataforseo`;if(!En.includes(i))throw new E(`INVALID_INPUT`,`--provider is one of ${En.join(`, `)}.`);if(!n.has(r[i]))throw new E(`AUTH_CONFIG_REQUIRED`,`No ${i} key saved. Run \`seo providers ${i} connect\` first.`);let a=F(e.keywords)??[],o=A(e.target),s=A(e.country);await zs(e,()=>an({provider:i,action:t,keywords:a,target:o,country:s,detail:Ls(e),offset:N(e.offset)},n,{call:e=>Ns(`research`,{action:t,provider:i,keywords:a.join(`,`)||void 0,target:o,country:s,detail:`rows`,offset:e}).replace(`--action `,``)}))}});var Ks=class{stream;buffer=``;constructor(e){this.stream=e}async write(e){this.buffer+=e,this.buffer.length>=65536&&await this.flush()}async flush(){if(!this.buffer)return;let e=this.buffer;this.buffer=``,this.stream.write(e)||await new Promise((e,t)=>{let n=()=>{i(),e()},r=e=>{i(),t(e)},i=()=>{this.stream.off(`drain`,n),this.stream.off(`error`,r)};this.stream.once(`drain`,n),this.stream.once(`error`,r)})}};function qs(e){return` `.repeat(e)}function Js(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function Ys(e,t,n,r,i=!1){let a=Js(t);if(a===null)return await e.write(`null`),!0;if(typeof a==`string`||typeof a==`boolean`)return await e.write(JSON.stringify(a)),!0;if(typeof a==`number`)return await e.write(Number.isFinite(a)?String(a):`null`),!0;if(typeof a==`bigint`&&JSON.stringify(a),a===void 0||typeof a==`function`||typeof a==`symbol`)return i&&await e.write(`null`),i;if(!a||typeof a!=`object`)return!1;if(r.has(a))throw TypeError(`Converting circular structure to JSON`);r.add(a);try{if(Array.isArray(a)){if(a.length===0)return await e.write(`[]`),!0;await e.write(`[
|
|
150
150
|
`);for(let t=0;t<a.length;t+=1)t>0&&await e.write(`,
|
|
151
|
-
`),await e.write(
|
|
151
|
+
`),await e.write(qs(n+1)),await Ys(e,a[t],n+1,r,!0);return await e.write(`\n${qs(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=Js(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
|
|
152
152
|
`);for(let i=0;i<t.length;i+=1){let[a,o]=t[i];i>0&&await e.write(`,
|
|
153
|
-
`),await e.write(`${
|
|
154
|
-
`),await n.flush()}async function
|
|
153
|
+
`),await e.write(`${qs(n+1)}${JSON.stringify(a)}: `),await Ys(e,o,n+1,r)}return await e.write(`\n${qs(n)}}`),!0}finally{r.delete(a)}}async function Xs(e,t){let n=new Ks(e);await Ys(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
|
|
154
|
+
`),await n.flush()}async function Zs(e,t){if(!e){await Xs(process.stdout,t);return}await vr(dr(e),{recursive:!0});let n=Qn(e);try{await Xs(n,t),n.end(),await Kr(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function Qs(e){return e===`high`?`fail`:`warning`}function $s(e,t=Pt(e)){let n=Qt(e),r=e.status===`failed`||e.summary.highIssues>0?`fail`:e.status===`partial`?`unknown`:e.summary.mediumIssues>0||e.summary.lowIssues>0?`warning`:`pass`,i=e.access.samples.slice(0,5).map(e=>({status:`fail`,title:e.evidence.guidance.summary,explanation:`${e.evidence.provider} returned ${e.evidence.kind} evidence for the crawler request.`,evidence:[e.url],fix:e.evidence.guidance.recommendedAction})),a=t.slice(0,10).map(e=>({status:Qs(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:Bn(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix})),o=n.slice(0,10).map(e=>({status:`warning`,title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:Bn(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));Ma({title:e.config.strategy===`health`?`Sitemap health pass`:`Site crawl`,target:e.config.url,status:r,summary:e.status===`partial`?`Partial evidence from ${e.summary.totalPages} retained pages. Review the caveats before treating missing findings as an all-clear.`:`${e.summary.totalPages} pages retained with ${e.issues.length} findings.`,metrics:[{label:`Pages`,value:e.summary.totalPages},{label:`High`,value:e.summary.highIssues,status:`fail`},{label:`Medium`,value:e.summary.mediumIssues,status:`warning`},{label:`Low`,value:e.summary.lowIssues,status:`info`},{label:`Failed fetches`,value:e.summary.failedUrls}],sections:[{title:`Access diagnostics`,diagnostics:i},{title:`Prioritised fixes`,diagnostics:a},{title:`Review before changing anything`,diagnostics:o},...!i.length&&!a.length&&!o.length?[{title:`Checks`,diagnostics:[{status:`pass`,title:`No prioritised findings were observed`,explanation:`The evaluated crawl evidence did not produce a prioritised fix or review item.`}]}]:[]],notes:[`Strategy: ${e.config.strategy}. Data: ${e.status}. Report: ${e.id}.`,...e.warnings,...e.caveats]})}const ec={"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},"clicky-site-id":{type:`string`,description:`Clicky site ID for landing-page visits. Defaults from --project when saved.`}};function tc(e,t){let n=A(e[`google-analytics-property`]),r=A(e[`clicky-site-id`]);if(n&&r)throw new E(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);return n?{provider:`google`,propertyId:n}:r?{provider:`clicky`,siteId:i(r)}:t}const nc={high:3,medium:2,low:1},rc=D({meta:{name:`crawl`,description:`Run a fast sitemap health pass or a full technical crawl`},args:{crawlUrl:{type:`positional`,required:!1,description:`Site start URL. Optional with --sitemap-url.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...ec,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Start with sitemap --health for large sites.`},health:{type:`boolean`,default:!1,description:`Run the uncached sitemap status and redirect pass before a full audit. No page-body analysis.`},"sitemap-url":{type:`string`,description:`Explicit sitemap URL. Selects sitemap mode when --mode is omitted.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl, up to 10,000. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth, up to 64. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches, up to 16. Defaults to 8, or progressively up to 4 with --health.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links. Reserved for full link checks.`},js:{type:`boolean`,default:!1,description:`Legacy alias for --rendering on.`},rendering:{type:`string`,description:`JavaScript rendering mode: auto, on, or off. Defaults to auto.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Alias for --format json.`},format:{type:`string`,description:`Output format: pretty, json, csv, html, markdown, or junit. JUnit requires --health.`},output:{type:`string`,description:`Write output to this path instead of stdout.`},csv:{type:`string`,description:`CSV table to render: issues or pages. Defaults to issues.`},save:{type:`boolean`,default:!1,description:`Save the crawl report locally.`},severity:{type:`string`,description:`Only show top fixes at this severity.`},"fail-on":{type:`string`,description:`Exit non-zero when issues exist at this severity or above.`}},run:async({args:e})=>{let t=L(e),n=sc(e.format,t),r=A(e.output),i=cc(e.csv),a=ac(e.severity),o=ac(e[`fail-on`]),s=j(e),c=await lc(e),l=oc(e),u=A(e[`sitemap-url`]),d=M(e.health),f=ic(e.mode);if(n===`junit`&&!d)throw Error(`--format junit requires --health.`);if((d||u)&&f&&f!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(d&&Vi(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((d||u)&&c.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let p=A(e.site)||s||!l&&!c.length&&!u?await W({client:s,site:A(e.site),options:{json:t}}):void 0,m=l??p?.client?.startUrl??(p?.site?So(p.site):void 0)??(u?new URL(`/`,u).toString():void 0)??c[0];if(!m)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let h=await ft({url:m,projectId:p?.client?.id,site:p?.site,mode:f??(d||u?`sitemap`:c.length?`list`:void 0),strategy:d?`health`:`full`,sitemapUrl:u,urls:c,maxPages:N(e[`max-pages`]),maxDepth:N(e[`max-depth`]),concurrency:N(e.concurrency),refresh:M(e.refresh),fetchRate:Wi({...e,"fetch-concurrency":e.concurrency}),include:F(e.include),exclude:F(e.exclude),googleAnalyticsPropertyId:A(e[`google-analytics-property`]),analyticsConnection:tc(e,kt(p?.client)),useSitemap:!Vi(e,`sitemap`),respectRobots:!Vi(e,`robots`),checkExternal:!Vi(e,`external`),js:Bi(e)}),g=M(e.save)?fe(h):void 0,_=Pt(h,{severity:a}),v=o?h.issues.some(e=>nc[e.severity]>=nc[o]):!1,y=h.status===`failed`,b={...h,topFixes:_,reviewObservations:Qt(h,{severity:a}),...o?{failOn:o,failedThreshold:v}:{},...g?{saved:g}:{}};if(n===`json`){await Zs(r,b),(y||v)&&(process.exitCode=1);return}if(n===`csv`){await dc(r,i===`pages`?Dt(h):gt(h)),(y||v)&&(process.exitCode=1);return}if(n===`html`){await dc(r,Jt(h,_)),(y||v)&&(process.exitCode=1);return}if(n===`junit`){await dc(r,On(h)),(y||v)&&(process.exitCode=1);return}if(n===`markdown`){await dc(r,bt(h,_)),(y||v)&&(process.exitCode=1);return}if(r){await dc(r,dn(h,_)),(y||v)&&(process.exitCode=1);return}$s(h,_),(g||o)&&(process.stdout.write(`
|
|
155
155
|
Run metadata
|
|
156
|
-
`),
|
|
156
|
+
`),V([[`Saved report`,g?.id??`no`],[`Fail threshold`,o??`off`]])),(y||v)&&(process.exitCode=1)}});function ic(e){let t=A(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function ac(e){let t=A(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function oc(e){let t=A(e.crawlUrl),n=A(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function sc(e,t){let n=A(e);if(t&&n&&n!==`json`)throw Error(`Use either --json or --format, not both.`);if(!n)return t?`json`:`pretty`;if([`pretty`,`json`,`csv`,`html`,`markdown`,`junit`].includes(n))return n;throw Error(`Format must be one of: pretty, json, csv, html, markdown, junit.`)}function cc(e){let t=A(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function lc(e){let t=F(e.urls)??[],n=A(e[`urls-file`]);return n&&t.push(...uc(await yr(n,`utf8`))),[...new Set(t)]}function uc(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function dc(e,t){if(!e){process.stdout.write(t);return}await vr(dr(e),{recursive:!0}),await Sr(e,t),process.stdout.write(`Wrote ${e}\n`)}async function fc(e,t){let n=j(e),r=A(e.site);if(!(!n&&!r))return(await W({client:n,site:r,options:{json:t}})).site}function pc(e){Pa(e.map(e=>({title:e.id,description:e.site??e.url,meta:[e.status,`${e.totalPages} page${e.totalPages===1?``:`s`}`,`${e.issueCount} issue${e.issueCount===1?``:`s`}`,e.createdAt]})),{empty:`No saved crawl reports.`})}function mc(e){$s(e)}function hc(e){q({title:`Crawl report comparison`,target:e.after.url,status:e.comparability.status!==`comparable`||e.completeness.status!==`complete`?`unknown`:e.summary.newStatusErrors>0||e.summary.issueGroupsWorse>0?`warning`:`pass`,summary:e.headline,metrics:[{label:`Before`,value:`${e.before.id} (${e.before.generatedAt})`},{label:`After`,value:`${e.after.id} (${e.after.generatedAt})`},{label:`Comparability`,value:e.comparability.status},{label:`Completeness`,value:e.completeness.status},{label:`Pages`,value:`${e.after.summary.totalPages} (${e.summary.pageDelta>=0?`+`:``}${e.summary.pageDelta})`},{label:`Issues`,value:`${e.after.summary.highIssues+e.after.summary.mediumIssues+e.after.summary.lowIssues} (${e.summary.issueDelta>=0?`+`:``}${e.summary.issueDelta})`},{label:`New status errors`,value:e.summary.newStatusErrors},{label:`Fixed status errors`,value:e.summary.fixedStatusErrors},{label:`Issue groups worse`,value:e.summary.issueGroupsWorse},{label:`Issue groups better`,value:e.summary.issueGroupsBetter},{label:`Changed pages`,value:e.summary.changedPages},{label:`Added pages`,value:e.summary.addedPages},{label:`Removed pages`,value:e.summary.removedPages}]}),Y(`Top actions`,e.topActions.map(e=>({label:e.title,context:e.plainEnglish,action:e.action})),e.topActions.length),e.pageChanges.length&&(process.stdout.write(`
|
|
157
157
|
Changed pages
|
|
158
|
-
`),
|
|
158
|
+
`),H([`Kind`,`Changes`,`URL`],e.pageChanges.slice(0,15).map(e=>[e.kind,e.changes.join(`, `),K(e.url,80)])),e.pageChanges.length>15&&process.stdout.write(`Showing 15 of ${e.pageChanges.length}. Use --json for all changes.\n`)),e.issueChanges.length&&(process.stdout.write(`
|
|
159
159
|
Issue movement
|
|
160
|
-
`),V([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),X(`Caveats`,e.caveats)}function lc(e,t){if(t.value===`latest`)return e.find(e=>e.id!==t.skipId);if(t.skipId){let n=e.findIndex(e=>e.id===t.skipId);return(n>=0?e.slice(n+1):e).find(e=>e.id!==t.skipId)}return e[1]}function uc(e){if(e.value!==`latest`&&e.value!==`previous`)return Ut(e.value);let t=lc(xe({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?Ut(t.id):void 0}const dc=E({meta:{name:`crawl-reports`,description:`List, show, or delete saved crawl reports`},args:{site:{type:`string`,description:`GSC property URL for filtering reports.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},id:{type:`string`,description:`Saved crawl report id to show.`},latest:{type:`boolean`,default:!1,description:`Show the latest saved crawl report.`},delete:{type:`string`,description:`Delete a saved crawl report by id.`},rerun:{type:`string`,description:`Rerun a saved crawl report by id, or pass latest.`},compare:{type:`string`,description:`Compare a saved report id, latest, or previous.`},against:{type:`string`,description:`Baseline report id. Defaults to previous.`},limit:{type:`string`,description:`Maximum reports to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await ac(e,t),r=k(e.delete),a=k(e.rerun),o=k(e.compare),s=k(e.id);if(r){let e=g(r);if(t){z({id:r,deleted:e});return}process.stdout.write(e?`Deleted crawl report ${r}.\n`:`No saved crawl report found for ${r}.\n`);return}if(a){let e=a===`latest`?p(n):Ut(a);if(!e)throw Error(a===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${a}.`);let r=await cn({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),o=i(r);if(t){z({...r,rerunOf:e.id,saved:o,topFixes:pt(r),reviewObservations:_n(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),sc(r);return}if(o){let r=uc({value:o,site:n});if(!r)throw Error(`No saved crawl report found for ${o}.`);let i=k(e.against)??`previous`,a=uc({value:i,site:r.site??n,skipId:r.id});if(!a)throw Error(`No baseline crawl report found for ${i}. Save at least two reports or pass --against <id>.`);let s=bt({before:a,after:r});if(t){z(s);return}cc(s);return}if(s||j(e.latest)){let e=s?Ut(s):p(n);if(!e)throw Error(s?`No saved crawl report found for ${s}.`:`No saved crawl reports found.`);if(t){z(e);return}sc(e);return}let c=xe({site:n,limit:M(e.limit)});if(t){z({reports:c});return}oc(c)}}),fc=E({meta:{name:`gsc-query`,description:`Run a raw Search Console Search Analytics query`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await Bi(e.body,e[`body-file`])??{startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),dimensions:P(e.dimensions)??[`query`,`page`],type:k(e.type)??`web`,rowLimit:M(e.limit),dataState:`final`},n=I(e),r=await mo({client:A(e),site:k(e.site)??k(t.siteUrl)},{json:n,refresh:j(e.refresh),account:k(e.account)});delete t.siteUrl;let i=await re(r,t,{refresh:j(e.refresh),accountEmail:k(e.account)}),a=M(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){z({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}q({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),V([`Keys`,`Clicks`,`Impr`,`CTR`,`Pos`],o.slice(0,25).map(e=>[e.keys.join(` | `),Math.round(e.clicks),Math.round(e.impressions),e.ctr.toFixed(3),e.position.toFixed(1)]))}}),pc=E({meta:{name:`url-inspect`,description:`Inspect a URL with the Search Console URL Inspection API`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await mo({client:A(e),site:k(e.site)},{json:t,account:k(e.account)}),r=k(e.url);if(!r)throw Error(`Pass --url.`);let i=await fe({siteUrl:n,inspectionUrl:r,accountEmail:k(e.account),languageCode:k(e.language)});if(t){z(i);return}let a=i.inspectionResult?.indexStatusResult;q({title:`URL Inspection`,target:r,status:a===void 0?`unknown`:a.verdict===`PASS`?`pass`:`warning`,summary:a===void 0?`Google did not return index status evidence for this URL.`:`${a.verdict??`Unknown`}: ${a.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:n},{label:`Robots`,value:a?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:a?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:a?.googleCanonical??`Unknown`}]})}}),mc={days:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},recentDays:{cli:`recent`,arg:{type:`string`,description:`Recent anomaly window in days. Defaults to 14.`}},range:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},limit:{cli:`limit`,arg:{type:`string`,description:`Maximum rows to return.`}},checkLimit:{cli:`check-limit`,arg:{type:`string`,description:`Maximum candidate pages to fetch and check.`}},minImpressions:{cli:`min-impressions`,arg:{type:`string`,description:`Minimum query impressions.`}},includeBrand:{cli:`include-brand`,arg:{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`}},verifyContent:{cli:`verify-content`,arg:{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`}},verifyLimit:{cli:`verify-limit`,arg:{type:`string`,description:`Maximum opportunity URLs to verify.`}},js:{cli:`js`,arg:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`}},fetchConcurrency:{cli:`fetch-concurrency`,arg:{type:`string`,description:`Maximum concurrent page fetches per host, up to 16. Defaults to 4.`}},fetchIntervalCap:{cli:`fetch-interval-cap`,arg:{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`}},fetchIntervalMs:{cli:`fetch-interval-ms`,arg:{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`}},refresh:{cli:`refresh`,arg:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}}};function Z(e,t={}){return Object.fromEntries(e.map(e=>{let n=mc[e];return[n.cli,{...n.arg,...t[e]}]}))}function hc(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function gc(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const _c=E({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await je({site:await mo({client:A(e),site:k(e.site)},{json:t,refresh:j(e.refresh)}),days:M(e.days),recentDays:M(e.recent),refresh:j(e.refresh)});if(t){z(n);return}q({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,hc(e.baselineMean),hc(e.comparisonMean),gc(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&X(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),vc=E({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),"padding-days":{type:`string`,description:`Days around update windows to count as overlap.`},"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=await mo({client:n,site:k(e.site)},{json:t,refresh:j(e.refresh)}),i=await Hn({site:r,days:M(e.days),recentDays:M(e.recent),paddingDays:M(e[`padding-days`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)});if(t){z(i);return}q({title:`Google update correlation`,target:i.site,status:`unknown`,summary:i.summary,metrics:[{label:`Classification`,value:i.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:i.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:i.confidence},{label:`Updates matched`,value:i.overlappingUpdates.length},{label:`Known confounders`,value:i.confounders.length},{label:`Source`,value:i.source.name}]}),X(`Evidence`,i.evidence),X(`Report caveats`,i.caveats),X(`Recommended next checks`,i.actions),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,hc(e.baselineMean),hc(e.comparisonMean),gc(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
|
|
160
|
+
`),H([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),X(`Caveats`,e.caveats)}function gc(e,t){if(t.value===`latest`)return e.find(e=>e.id!==t.skipId);if(t.skipId){let n=e.findIndex(e=>e.id===t.skipId);return(n>=0?e.slice(n+1):e).find(e=>e.id!==t.skipId)}return e[1]}function _c(e){if(e.value!==`latest`&&e.value!==`previous`)return re(e.value);let t=gc(b({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?re(t.id):void 0}const vc=D({meta:{name:`crawl-reports`,description:`List, show, or delete saved crawl reports`},args:{site:{type:`string`,description:`GSC property URL for filtering reports.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},id:{type:`string`,description:`Saved crawl report id to show.`},latest:{type:`boolean`,default:!1,description:`Show the latest saved crawl report.`},delete:{type:`string`,description:`Delete a saved crawl report by id.`},rerun:{type:`string`,description:`Rerun a saved crawl report by id, or pass latest.`},compare:{type:`string`,description:`Compare a saved report id, latest, or previous.`},against:{type:`string`,description:`Baseline report id. Defaults to previous.`},limit:{type:`string`,description:`Maximum reports to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await fc(e,t),r=A(e.delete),i=A(e.rerun),a=A(e.compare),o=A(e.id);if(r){let e=ve(r);if(t){B({id:r,deleted:e});return}process.stdout.write(e?`Deleted crawl report ${r}.\n`:`No saved crawl report found for ${r}.\n`);return}if(i){let e=i===`latest`?we(n):re(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await ft({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),a=fe(r);if(t){B({...r,rerunOf:e.id,saved:a,topFixes:Pt(r),reviewObservations:Qt(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),mc(r);return}if(a){let r=_c({value:a,site:n});if(!r)throw Error(`No saved crawl report found for ${a}.`);let i=A(e.against)??`previous`,o=_c({value:i,site:r.site??n,skipId:r.id});if(!o)throw Error(`No baseline crawl report found for ${i}. Save at least two reports or pass --against <id>.`);let s=bn({before:o,after:r});if(t){B(s);return}hc(s);return}if(o||M(e.latest)){let e=o?re(o):we(n);if(!e)throw Error(o?`No saved crawl report found for ${o}.`:`No saved crawl reports found.`);if(t){B(e);return}mc(e);return}let s=b({site:n,limit:N(e.limit)});if(t){B({reports:s});return}pc(s)}}),yc=D({meta:{name:`gsc-query`,description:`Run a raw Search Console Search Analytics query`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await Ki(e.body,e[`body-file`])??{startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),dimensions:F(e.dimensions)??[`query`,`page`],type:A(e.type)??`web`,rowLimit:N(e.limit),dataState:`final`},n=L(e),r=await xo({client:j(e),site:A(e.site)??A(t.siteUrl)},{json:n,refresh:M(e.refresh),account:A(e.account)});delete t.siteUrl;let i=await Be(r,t,{refresh:M(e.refresh),accountEmail:A(e.account)}),a=N(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){B({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}q({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),H([`Keys`,`Clicks`,`Impr`,`CTR`,`Pos`],o.slice(0,25).map(e=>[e.keys.join(` | `),Math.round(e.clicks),Math.round(e.impressions),e.ctr.toFixed(3),e.position.toFixed(1)]))}}),bc=D({meta:{name:`url-inspect`,description:`Inspect a URL with the Search Console URL Inspection API`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await xo({client:j(e),site:A(e.site)},{json:t,account:A(e.account)}),r=A(e.url);if(!r)throw Error(`Pass --url.`);let i=await Ke({siteUrl:n,inspectionUrl:r,accountEmail:A(e.account),languageCode:A(e.language)});if(t){B(i);return}let a=i.inspectionResult?.indexStatusResult;q({title:`URL Inspection`,target:r,status:a===void 0?`unknown`:a.verdict===`PASS`?`pass`:`warning`,summary:a===void 0?`Google did not return index status evidence for this URL.`:`${a.verdict??`Unknown`}: ${a.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:n},{label:`Robots`,value:a?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:a?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:a?.googleCanonical??`Unknown`}]})}}),xc={days:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},recentDays:{cli:`recent`,arg:{type:`string`,description:`Recent anomaly window in days. Defaults to 14.`}},range:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},limit:{cli:`limit`,arg:{type:`string`,description:`Maximum rows to return.`}},checkLimit:{cli:`check-limit`,arg:{type:`string`,description:`Maximum candidate pages to fetch and check.`}},minImpressions:{cli:`min-impressions`,arg:{type:`string`,description:`Minimum query impressions.`}},includeBrand:{cli:`include-brand`,arg:{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`}},verifyContent:{cli:`verify-content`,arg:{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`}},verifyLimit:{cli:`verify-limit`,arg:{type:`string`,description:`Maximum opportunity URLs to verify.`}},js:{cli:`js`,arg:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`}},fetchConcurrency:{cli:`fetch-concurrency`,arg:{type:`string`,description:`Maximum concurrent page fetches per host, up to 16. Defaults to 4.`}},fetchIntervalCap:{cli:`fetch-interval-cap`,arg:{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`}},fetchIntervalMs:{cli:`fetch-interval-ms`,arg:{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`}},refresh:{cli:`refresh`,arg:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}}};function Z(e,t={}){return Object.fromEntries(e.map(e=>{let n=xc[e];return[n.cli,{...n.arg,...t[e]}]}))}function Sc(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Cc(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const wc=D({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await te({site:await xo({client:j(e),site:A(e.site)},{json:t,refresh:M(e.refresh)}),days:N(e.days),recentDays:N(e.recent),refresh:M(e.refresh)});if(t){B(n);return}q({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Sc(e.baselineMean),Sc(e.comparisonMean),Cc(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&X(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),Tc=D({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),"padding-days":{type:`string`,description:`Days around update windows to count as overlap.`},"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=j(e),r=await xo({client:n,site:A(e.site)},{json:t,refresh:M(e.refresh)}),i=await ue({site:r,days:N(e.days),recentDays:N(e.recent),paddingDays:N(e[`padding-days`]),knownConfounders:A(e[`known-change`])?[A(e[`known-change`])??``]:void 0,includeChangeLog:!M(e[`ignore-change-log`]),refresh:M(e.refresh)});if(t){B(i);return}q({title:`Google update correlation`,target:i.site,status:`unknown`,summary:i.summary,metrics:[{label:`Classification`,value:i.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:i.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:i.confidence},{label:`Updates matched`,value:i.overlappingUpdates.length},{label:`Known confounders`,value:i.confounders.length},{label:`Source`,value:i.source.name}]}),X(`Evidence`,i.evidence),X(`Report caveats`,i.caveats),X(`Recommended next checks`,i.actions),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,Sc(e.baselineMean),Sc(e.comparisonMean),Cc(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
|
|
161
161
|
Updates
|
|
162
|
-
`),
|
|
162
|
+
`),H([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),so(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function Ec(){return new Date().toISOString().slice(0,10)}function Dc(e){let t=A(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}const Oc=D({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:D({meta:{name:`list`,description:`List recorded SEO changes`},args:{site:{type:`string`},limit:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=se({site:A(e.site),limit:N(e.limit)});if(L(e)){B({changes:t});return}H([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:D({meta:{name:`add`,description:`Record an SEO change`},args:{site:{type:`string`},scope:{type:`string`,default:`page`},target:{type:`string`,required:!0},title:{type:`string`,required:!0},description:{type:`string`},date:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=$e({site:await Ha({site:A(e.site),options:{json:t}}),scope:Dc(e.scope),target:A(e.target)??``,title:A(e.title)??``,description:A(e.description),changedAt:A(e.date)??Ec()});if(t){B(n);return}V([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:D({meta:{name:`delete`,description:`Delete a recorded SEO change`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=A(e.id)??``,n=gn(t);if(L(e)){B({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:D({meta:{name:`measure`,description:`Measure a change with equal finalized before/after windows`},args:{id:{type:`string`},site:{type:`string`},scope:{type:`string`},target:{type:`string`},title:{type:`string`},date:{type:`string`},before:{type:`string`},after:{type:`string`},json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=A(e.id);if(!n&&(!A(e.site)||!A(e.scope)||!A(e.target)||!A(e.date)))throw Error(`Pass --id, or pass --site, --scope, --target, and --date for an ad hoc measurement.`);let r=await c({id:n,site:n?void 0:await Ha({site:A(e.site),options:{json:t}}),scope:n?void 0:Dc(e.scope),target:n?void 0:A(e.target),title:A(e.title),changedAt:n?void 0:A(e.date),beforeDays:P(e.before,`--before`),afterDays:P(e.after,`--after`),refresh:M(e.refresh)});if(t){B(r);return}q({title:`Change measurement`,target:r.change.title,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.note,metrics:[{label:`Verdict`,value:r.verdict},{label:`Confidence`,value:r.confidence},{label:`Before`,value:`${r.before.startDate} to ${r.before.endDate}`},{label:`After`,value:`${r.after.startDate} to ${r.after.endDate}`},{label:`Window`,value:`${r.window.effectiveDays}/${r.window.requestedDays} finalized days`}]}),H([`Metric`,`Before`,`After`,`Delta`],[[`Clicks`,r.before.metrics?.clicks??`unavailable`,r.after.metrics?.clicks??`unavailable`,r.delta.clicks===null?`unavailable`:r.delta.clickPct===null?`${r.delta.clicks}`:`${r.delta.clicks} (${r.delta.clickPct}%)`],[`Impressions`,r.before.metrics?.impressions??`unavailable`,r.after.metrics?.impressions??`unavailable`,r.delta.impressions===null?`unavailable`:r.delta.impressionPct===null?`${r.delta.impressions}`:`${r.delta.impressions} (${r.delta.impressionPct}%)`],[`CTR`,r.before.metrics?.ctr??`unavailable`,r.after.metrics?.ctr??`unavailable`,r.delta.ctr??`unavailable`],[`Position`,r.before.metrics?.position??`unavailable`,r.after.metrics?.position??`unavailable`,r.delta.position??`unavailable`]]),X(`Warnings`,r.warnings),X(`Caveats`,r.caveats)}})}});function kc(e){let t=A(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function Ac(e){let t=A(e)??`contains`;if(t!==`equals`&&t!==`contains`&&t!==`regex`)throw Error(`Invalid --match. Use equals, contains, or regex.`);return t}const jc=D({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:D({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=Ve(A(e.site));if(L(e)){B({groups:t});return}H([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:D({meta:{name:`add`,description:`Create a reusable page or query group`},args:{site:{type:`string`},name:{type:`string`,required:!0},dimension:{type:`string`,default:`page`},match:{type:`string`,default:`contains`},pattern:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=We({site:await Ha({site:A(e.site),options:{json:t}}),name:A(e.name)??``,dimension:kc(e.dimension),matchType:Ac(e.match),pattern:A(e.pattern)??``});if(t){B(n);return}V([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:D({meta:{name:`delete`,description:`Delete a content group`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=A(e.id)??``,n=w(t);if(L(e)){B({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function Mc(){return new Date().toISOString().slice(0,10)}function Nc(e){let t=A(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}function Pc(e){return e?Nc(e):void 0}function Fc(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function Ic(e){return e==null?`unavailable`:G(e)}function Lc(e,t){return e===null?`unavailable`:`${G(e)} (${Fc(t)})`}function Rc(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function zc(e){q({title:`SEO test report`,target:e.change.title,status:e.dataStatus===`complete`?`info`:`unknown`,summary:e.note,metrics:[{label:`Verdict`,value:e.verdict},{label:`Confidence`,value:e.confidence},{label:`Before`,value:`${e.before.startDate} to ${e.before.endDate}`},{label:`After`,value:`${e.after.startDate} to ${e.after.endDate}`},{label:`Window`,value:`${e.window.effectiveDays}/${e.window.requestedDays} finalized days`}]}),H([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,Ic(e.before.metrics?.clicks),Ic(e.after.metrics?.clicks),Lc(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,Ic(e.before.metrics?.impressions),Ic(e.after.metrics?.impressions),Lc(e.delta.impressions,e.delta.impressionPct)],[`GSC CTR`,e.before.metrics?`${(e.before.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.after.metrics?`${(e.after.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.delta.ctr===null?`unavailable`:`${(e.delta.ctr*100).toFixed(1)} pts`],[`GSC position`,Rc(e.before.metrics?.position),Rc(e.after.metrics?.position),Rc(e.delta.position)]]),e.analytics&&(process.stdout.write(`
|
|
163
163
|
Google Analytics impact
|
|
164
|
-
`),
|
|
164
|
+
`),H([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,G(e.analytics.before.metrics.sessions),G(e.analytics.after.metrics.sessions),`${G(e.analytics.delta.sessions)} (${Fc(e.analytics.delta.sessionPct)})`],[`Conversions`,G(e.analytics.before.metrics.conversions),G(e.analytics.after.metrics.conversions),`${G(e.analytics.delta.conversions)} (${Fc(e.analytics.delta.conversionPct)})`],[`Revenue`,e.analytics.before.metrics.totalRevenue.toFixed(2),e.analytics.after.metrics.totalRevenue.toFixed(2),`${e.analytics.delta.totalRevenue.toFixed(2)} (${Fc(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
|
|
165
165
|
Control comparison
|
|
166
|
-
`),B([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:W(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),X(`Warnings`,e.warnings),X(`Caveats`,e.caveats)}const Nc=E({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:E({meta:{name:`list`,description:`List saved SEO tests`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},limit:{type:`string`,description:`Maximum tests to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=Ue({site:(n||k(e.site)?await U({client:n,site:k(e.site),options:{json:t}}):void 0)?.site,limit:M(e.limit)});if(t){z({tests:r});return}V([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:E({meta:{name:`create`,description:`Create a local SEO test`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,default:`page`,description:`Change scope: site, page, query, or group.`},target:{type:`string`,required:!0,description:`Changed URL, query, group pattern, or site marker.`},title:{type:`string`,required:!0,description:`Plain-English name for the test.`},description:{type:`string`,description:`Optional note about what changed.`},date:{type:`string`,description:`Change date in YYYY-MM-DD. Defaults to today.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t}}),r=C({site:n.site,scope:Ec(e.scope),target:k(e.target)??``,title:k(e.title)??``,description:k(e.description),changedAt:k(e.date)??Tc()});if(t){z(r);return}B([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:E({meta:{name:`report`,description:`Report an SEO test with equal finalized before/after windows`},args:{id:{type:`string`,description:`Saved SEO test id.`},site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,description:`Ad-hoc change scope: site, page, query, or group.`},target:{type:`string`,description:`Ad-hoc changed URL, query, group pattern, or site marker.`},title:{type:`string`,description:`Ad-hoc test title.`},date:{type:`string`,description:`Ad-hoc change date in YYYY-MM-DD.`},property:{type:`string`,description:`Google Analytics property ID.`},"control-scope":{type:`string`,description:`Control scope: site, page, query, or group.`},"control-target":{type:`string`,description:`Control URL, query, group pattern, or site marker.`},"control-title":{type:`string`,description:`Plain-English name for the control group.`},before:{type:`string`,description:`Days before the change date. Defaults to 28.`},after:{type:`string`,description:`Days after the change date. Defaults to 28.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console and Google Analytics cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=k(e.id),r=!n||A(e)||k(e.site)?await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}):void 0,i=await ye({id:n,site:n?void 0:r?.site,scope:n?void 0:Dc(e.scope),target:n?void 0:k(e.target),title:k(e.title),changedAt:n?void 0:k(e.date),googleAnalyticsPropertyId:k(e.property)??r?.client?.analytics.google?.propertyId,controlScope:Dc(e[`control-scope`]),controlTarget:k(e[`control-target`]),controlTitle:k(e[`control-title`]),beforeDays:N(e.before,`--before`),afterDays:N(e.after,`--after`),refresh:j(e.refresh)});if(t){z(i);return}Mc(i)}})}});function Pc(e){if(!e)return;let t=Date.now();return e=>{let n=((Date.now()-t)/1e3).toFixed(1);process.stderr.write(`[seo ${n}s] ${e}\n`)}}const Fc={client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to the configured default.`},site:{type:`string`,description:`GSC property URL/id when not using a saved project.`}};function Ic(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function Lc(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function Rc(e){let t=new Date().toISOString().slice(0,10),n=Lc(e.clientId??e.site);return lr(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function zc(e,t){await fr(e,{recursive:!0});let n=[];for(let r of t){let t=lr(e,r.filename);await gr(t,_t(r.rows,r.headers),`utf8`),n.push(t)}return n}function Bc(e,t){process.stdout.write(`Wrote ${Oe(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Vc=E({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...Fc,out:Ic(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site)}),n=await pe({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),js:j(e.js)?!0:`auto`,rate:Ri(e),refresh:j(e.refresh),progress:Pc(!0)}),r=k(e.out)??Rc({report:`diagnose`,clientId:t.client?.id,site:t.site});Bc(r,await zc(r,Kt(n)))}}),Hc=E({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Fc,out:Ic(`refresh-priorities`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to export. Defaults to 25.`}}),...qs,"verify-content":Ni(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await An({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),googleAnalyticsPropertyId:k(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,analyticsConnection:Js(e,ct(t.client)),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),refresh:j(e.refresh)}),r=k(e.out)??Rc({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});Bc(r,await zc(r,Tn(n)))}}),Uc=E({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Vc,"update-postmortem":E({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Fc,out:Ic(`update-postmortem`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await At({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)}),r=k(e.out)??Rc({report:`update-postmortem`,clientId:t.client?.id,site:t.site});Bc(r,await zc(r,$r(n)))}}),"refresh-priorities":Hc}}),Wc=E({...ps,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Gc=E({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:E({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=me();if(I(e)){z(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ma(t.sizeBytes)} retained.\n`)}})}}),Kc=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function qc(e,t){return j(e.all)?t:Kc.filter(({flag:t})=>j(e[t])).map(({client:e})=>e)}const Jc=E({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:E({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-D41t9hLG.js`);await t({test:j(e.test)})}}),install:E({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(j(e.uninstall)&&j(e.reinstall))throw new T(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Eo(),n=ko({targets:t}),r=I(e),i=qc(e,n.map(e=>e.client));if(i.length===0){if(!R({json:r}))throw new T(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=H(await Or({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new T(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>j(e.uninstall)?Jo(t):qo(t,{reinstall:j(e.reinstall)}));if(r){z({operation:j(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Yc=E({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await Vt({startUrl:k(e.url)??``,site:k(e.site),limit:M(e.limit),refresh:j(e.refresh),js:j(e.js)?!0:`auto`});if(I(e)){z(t);return}if(q({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
|
|
166
|
+
`),V([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:G(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),X(`Warnings`,e.warnings),X(`Caveats`,e.caveats)}const Bc=D({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:D({meta:{name:`list`,description:`List saved SEO tests`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},limit:{type:`string`,description:`Maximum tests to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=j(e),r=se({site:(n||A(e.site)?await W({client:n,site:A(e.site),options:{json:t}}):void 0)?.site,limit:N(e.limit)});if(t){B({tests:r});return}H([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:D({meta:{name:`create`,description:`Create a local SEO test`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,default:`page`,description:`Change scope: site, page, query, or group.`},target:{type:`string`,required:!0,description:`Changed URL, query, group pattern, or site marker.`},title:{type:`string`,required:!0,description:`Plain-English name for the test.`},description:{type:`string`,description:`Optional note about what changed.`},date:{type:`string`,description:`Change date in YYYY-MM-DD. Defaults to today.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t}}),r=$e({site:n.site,scope:Nc(e.scope),target:A(e.target)??``,title:A(e.title)??``,description:A(e.description),changedAt:A(e.date)??Mc()});if(t){B(r);return}V([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:D({meta:{name:`report`,description:`Report an SEO test with equal finalized before/after windows`},args:{id:{type:`string`,description:`Saved SEO test id.`},site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,description:`Ad-hoc change scope: site, page, query, or group.`},target:{type:`string`,description:`Ad-hoc changed URL, query, group pattern, or site marker.`},title:{type:`string`,description:`Ad-hoc test title.`},date:{type:`string`,description:`Ad-hoc change date in YYYY-MM-DD.`},property:{type:`string`,description:`Google Analytics property ID.`},"control-scope":{type:`string`,description:`Control scope: site, page, query, or group.`},"control-target":{type:`string`,description:`Control URL, query, group pattern, or site marker.`},"control-title":{type:`string`,description:`Plain-English name for the control group.`},before:{type:`string`,description:`Days before the change date. Defaults to 28.`},after:{type:`string`,description:`Days after the change date. Defaults to 28.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console and Google Analytics cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=A(e.id),r=!n||j(e)||A(e.site)?await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}):void 0,i=await c({id:n,site:n?void 0:r?.site,scope:n?void 0:Pc(e.scope),target:n?void 0:A(e.target),title:A(e.title),changedAt:n?void 0:A(e.date),googleAnalyticsPropertyId:A(e.property)??r?.client?.analytics.google?.propertyId,controlScope:Pc(e[`control-scope`]),controlTarget:A(e[`control-target`]),controlTitle:A(e[`control-title`]),beforeDays:P(e.before,`--before`),afterDays:P(e.after,`--after`),refresh:M(e.refresh)});if(t){B(i);return}zc(i)}})}});function Vc(e){if(!e)return;let t=Date.now();return e=>{let n=((Date.now()-t)/1e3).toFixed(1);process.stderr.write(`[seo ${n}s] ${e}\n`)}}const Hc={client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to the configured default.`},site:{type:`string`,description:`GSC property URL/id when not using a saved project.`}};function Uc(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function Wc(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function Gc(e){let t=new Date().toISOString().slice(0,10),n=Wc(e.clientId??e.site);return hr(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function Kc(e,t){await vr(e,{recursive:!0});let n=[];for(let r of t){let t=hr(e,r.filename);await Sr(t,Mn(r.rows,r.headers),`utf8`),n.push(t)}return n}function qc(e,t){process.stdout.write(`Wrote ${ct(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Jc=D({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...Hc,out:Uc(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await W({client:j(e),site:A(e.site)}),n=await qe({site:t.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:t.client?.brandTerms,includeBrand:M(e[`include-brand`]),verifyContent:M(e[`verify-content`]),verifyLimit:N(e[`verify-limit`]),js:M(e.js)?!0:`auto`,rate:Wi(e),refresh:M(e.refresh),progress:Vc(!0)}),r=A(e.out)??Gc({report:`diagnose`,clientId:t.client?.id,site:t.site});qc(r,await Kc(r,jt(n)))}}),Yc=D({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Hc,out:Uc(`refresh-priorities`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to export. Defaults to 25.`}}),...ec,"verify-content":zi(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await W({client:j(e),site:A(e.site),options:{refresh:M(e.refresh)}}),n=await Re({site:t.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:t.client?.brandTerms,includeBrand:M(e[`include-brand`]),googleAnalyticsPropertyId:A(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,analyticsConnection:tc(e,kt(t.client)),verifyContent:M(e[`verify-content`]),verifyLimit:N(e[`verify-limit`]),refresh:M(e.refresh)}),r=A(e.out)??Gc({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});qc(r,await Kc(r,it(n)))}}),Xc=D({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Jc,"update-postmortem":D({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Hc,out:Uc(`update-postmortem`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await W({client:j(e),site:A(e.site),options:{refresh:M(e.refresh)}}),n=await f({site:t.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:t.client?.brandTerms,includeBrand:M(e[`include-brand`]),knownConfounders:A(e[`known-change`])?[A(e[`known-change`])??``]:void 0,includeChangeLog:!M(e[`ignore-change-log`]),refresh:M(e.refresh)}),r=A(e.out)??Gc({report:`update-postmortem`,clientId:t.client?.id,site:t.site});qc(r,await Kc(r,ai(n)))}}),"refresh-priorities":Yc}}),Zc=D({...bs,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Qc=D({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:D({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=me();if(L(e)){B(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ra(t.sizeBytes)} retained.\n`)}})}}),$c=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function el(e,t){return M(e.all)?t:$c.filter(({flag:t})=>M(e[t])).map(({client:e})=>e)}const tl=D({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:D({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-C9P8dY80.js`);await t({test:M(e.test)})}}),install:D({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(M(e.uninstall)&&M(e.reinstall))throw new E(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=No(),n=Io({targets:t}),r=L(e),i=el(e,n.map(e=>e.client));if(i.length===0){if(!z({json:r}))throw new E(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=U(await Pr({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new E(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>M(e.uninstall)?ts(t):es(t,{reinstall:M(e.reinstall)}));if(r){B({operation:M(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),nl=D({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await je({startUrl:A(e.url)??``,site:A(e.site),limit:N(e.limit),refresh:M(e.refresh),js:M(e.js)?!0:`auto`});if(L(e)){B(t);return}if(q({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
|
|
167
167
|
Recommended actions
|
|
168
|
-
`),
|
|
168
|
+
`),H([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,K(e.url,56),K(e.action,72)])),Y(`Top crawl actions`,t.recommendations.map(e=>({label:e.category,context:`${e.severity}, ${K(e.url,96)}`,action:e.action})))),t.items.length&&(process.stdout.write(`
|
|
169
169
|
Changed URLs
|
|
170
|
-
`),
|
|
170
|
+
`),H([`Kind`,`URL`,`Changes`,`Recommendation`],t.items.slice(0,50).map(e=>[e.kind,K(e.url,56),e.changes.join(`, `),e.recommendation?.title??`-`]))),t.warnings.length){process.stdout.write(`
|
|
171
171
|
Warnings
|
|
172
|
-
`);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function
|
|
172
|
+
`);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function rl(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the project or GSC property.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":zi(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function il(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"stale-days":{type:`string`,description:`Mark checks stale after this many days. Defaults to 8.`},refresh:{type:`boolean`,default:!1,description:`Refresh property selection data when prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function al(e){let t=e.toLowerCase();return[`pass`,`passed`,`complete`,`completed`,`joined`].includes(t)?`pass`:[`warn`,`warning`,`partial`,`review`].includes(t)?`warning`:[`fail`,`failed`,`error`].includes(t)?`fail`:[`unknown`,`unavailable`].includes(t)?`unknown`:`info`}function ol(e){let t=e.steps.map(e=>al(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;Ma({title:e.workflow,target:e.site,status:n,summary:e.summary,metrics:[{label:`Completed`,value:t.filter(e=>e===`pass`).length,status:`pass`},{label:`Review`,value:t.filter(e=>e===`warning`).length,status:`warning`},{label:`Unknown`,value:t.filter(e=>e===`unknown`).length,status:`unknown`}],sections:[{title:`Evidence collection`,diagnostics:e.steps.map(e=>({status:al(e.status),title:e.tool,explanation:e.summary,evidence:[`Status: ${e.status}`]}))},{title:`Priority actions`,diagnostics:e.actions.map(e=>({status:`warning`,title:e.title,explanation:`Confidence: ${e.confidence}.`,fix:e.action}))}]})}function sl(e){if(ol(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
|
|
173
173
|
Crawl actions
|
|
174
|
-
`),
|
|
174
|
+
`),H([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,K(e.url,56),K(e.action,72)])),Y(`Top crawl actions`,e.output.crawl.recommendations.map(e=>({label:e.severity,context:K(e.url,96),action:e.action})))),e.output.index?.items.length){let t=e.output.index.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,25);t.length&&(process.stdout.write(`
|
|
175
175
|
Index reviews, changes, and failed checks
|
|
176
|
-
`),
|
|
176
|
+
`),H([`Check`,`Change`,`Index state`,`Evidence`,`URL`],t.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,K((e.errorCode??e.issueCodes.join(`, `))||`none`,40),K(e.url,64)])))}e.output.recovery?.items.length&&(process.stdout.write(`
|
|
177
177
|
Recoverable URLs
|
|
178
|
-
`),
|
|
178
|
+
`),H([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,G(e.clicks),G(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),Y(`Top recovery actions`,e.output.recovery.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${G(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function cl(e){q({title:`Technical monitoring status`,target:e.site,status:e.health===`clear`?`pass`:e.health===`attention`?`warning`:`unknown`,summary:e.summary.attention>0?`${e.summary.attention} checks need attention.`:e.summary.stale>0||e.summary.notRun>0?`${e.summary.stale} checks are stale and ${e.summary.notRun} have not run.`:`No monitoring checks currently need attention.`,metrics:[{label:`Health`,value:e.health},{label:`Attention`,value:e.summary.attention},{label:`Stale`,value:e.summary.stale},{label:`Not run`,value:e.summary.notRun}]}),H([`Check`,`Status`,`Last run`,`Summary`],e.checks.map(e=>[e.name,e.status,e.lastRunAt??`-`,e.summary]));let t=e.checks.filter(e=>e.action);t.length&&(process.stdout.write(`
|
|
179
179
|
Next actions
|
|
180
|
-
`),
|
|
180
|
+
`),H([`Check`,`Action`],t.map(e=>[e.name,K(e.action??``,96)])),Y(`Next action details`,t.map(e=>({label:e.name,action:e.action??``}))))}function ll(e,t){let n=A(t);return n?`--${e} ${JSON.stringify(n)}`:void 0}function ul(e,t){return M(t)?`--${e}`:void 0}function dl(e){return M(e)===!1?`--recover-links=false`:void 0}const fl=D({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:D({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:rl(),run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=I(e.urls),i=I(e.sitemaps),a=A(e.url)??n.client?.startUrl??So(n.site),o=await Ln({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:I(e.properties).length?I(e.properties):void 0,limit:N(e.limit),languageCode:A(e.language),dailyLimit:N(e[`daily-limit`]),inspectLimit:N(e[`inspect-limit`]),maxUrls:N(e[`max-urls`]),refresh:M(e.refresh),js:M(e.js)?!0:`auto`,recoverLinks:M(e[`recover-links`]),recoverDays:N(e[`recover-days`]),recoverLimit:N(e[`recover-limit`]),recoverMinClicks:N(e[`recover-min-clicks`]),recoverMinImpressions:N(e[`recover-min-impressions`])});if(t){B(o);return}sl(o)}}),status:D({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:il(),run:async({args:e})=>{let t=L(e),n=Ut({site:(await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}})).site,staleAfterDays:N(e[`stale-days`])});if(t){B(n);return}cl(n)}}),cron:D({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...rl(),hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly monitoring day. Defaults to client setting, then Monday.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=N(e.hour)??9,i=N(e.minute)??0,a=N(e.weekday)??n.client?.technicalWeekday??1,o=[`seo monitoring run`,n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`,ll(`url`,e.url),ll(`urls`,e.urls),ll(`sitemaps`,e.sitemaps),ll(`properties`,e.properties),ll(`limit`,e.limit),ll(`daily-limit`,e[`daily-limit`]),ll(`inspect-limit`,e[`inspect-limit`]),ll(`max-urls`,e[`max-urls`]),ll(`language`,e.language),ll(`recover-days`,e[`recover-days`]),ll(`recover-limit`,e[`recover-limit`]),ll(`recover-min-clicks`,e[`recover-min-clicks`]),ll(`recover-min-impressions`,e[`recover-min-impressions`]),dl(e[`recover-links`]),ul(`refresh`,e.refresh),ul(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){B({site:n.site,line:s});return}V([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),pl=D({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"crawl-report-id":{type:`string`,description:`Saved crawl report id. Defaults to the latest for the site.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs. Defaults to declarations captured by the crawl.`},days:{type:`string`,description:`Finalized Search Console date range. Defaults to 90 days.`},"row-limit":{type:`string`,description:`Maximum Search Console page rows. Defaults to 100000.`},"max-sitemap-urls":{type:`string`,description:`Maximum sitemap inventory URLs. Defaults to 100000.`},limit:{type:`string`,description:`Maximum URLs returned in each evidence group. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=I(e.sitemaps),i=await pe({site:n.site,crawlReportId:A(e[`crawl-report-id`]),sitemaps:r.length?r:void 0,days:N(e.days),rowLimit:N(e[`row-limit`]),maxSitemapUrls:N(e[`max-sitemap-urls`]),itemsPerSection:N(e.limit),refresh:M(e.refresh)});if(t){B(i);return}q({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
|
|
181
181
|
Representative URL Inspection candidates
|
|
182
|
-
`),
|
|
182
|
+
`),H([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[K(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),X(`How to read this report`,i.caveats)}}),ml=D({meta:{name:`index-watch`,description:`Inspect URLs with GSC and alert on index status changes`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},urls:{type:`string`,description:`Comma-separated URLs to inspect.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs to plan quota-aware monitoring.`},plan:{type:`boolean`,default:!1,description:`Plan sitemap URL allocation across GSC properties without inspecting URLs.`},properties:{type:`string`,description:`Comma-separated GSC properties to use for planning. Defaults to account properties.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"target-days":{type:`string`,description:`Target number of days to inspect all monitored URLs. Defaults to 1.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for planning. Defaults to 50000.`},"inspect-limit":{type:`string`,description:`Maximum URLs to inspect in this run from sitemap mode. Defaults to 100.`},language:{type:`string`,description:`Optional URL Inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=I(e.urls),r=await Ha({site:A(e.site),options:{json:t}}),i=I(e.sitemaps);if(M(e.plan)){if(!i.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let n=await ie({site:r,sitemaps:i,properties:I(e.properties).length?I(e.properties):void 0,dailyLimit:N(e[`daily-limit`]),targetCycleDays:N(e[`target-days`]),maxUrls:N(e[`max-urls`])});if(t){B(n);return}q({title:`Index monitoring plan`,target:n.site,status:n.warnings.length>0?`warning`:`info`,summary:`${n.summary.urlCount} sitemap URLs can be checked in an estimated ${ct(n.summary.estimatedCycleDays,`day`)}.`,metrics:[{label:`Sitemap URLs`,value:n.summary.urlCount},{label:`Properties`,value:n.summary.properties},{label:`Daily capacity`,value:n.summary.dailyCapacity},{label:`Target cycle`,value:ct(n.summary.targetCycleDays,`day`)},{label:`Suggested properties`,value:n.summary.suggestedProperties}]}),n.properties.length&&H([`Property`,`URLs`,`Cycle`,`Sample URL`],n.properties.map(e=>[e.property,e.urlCount,ct(e.cycleDays,`day`),e.sampleUrls[0]??`-`])),n.suggestions.length&&(process.stdout.write(`
|
|
183
183
|
Suggested URL-prefix properties
|
|
184
|
-
`),
|
|
184
|
+
`),H([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],n.suggestions.map(e=>[e.property,e.urlCount,e.currentProperty,ct(e.estimatedCycleDays,`day`),e.reason]))),X(`Warnings`,n.warnings.slice(0,10));return}if(i.length){let n=await Te({site:r,sitemaps:i,properties:I(e.properties).length?I(e.properties):void 0,dailyLimit:N(e[`daily-limit`]),inspectLimit:N(e[`inspect-limit`]),maxUrls:N(e[`max-urls`]),languageCode:A(e.language)});if(t){B(n);return}q({title:`Index monitoring run`,target:n.site,status:n.summary.failed>0||n.summary.quotaBlocked>0?`unknown`:n.summary.alerts>0||n.summary.regressions>0?`warning`:n.summary.attempted>0?`pass`:`info`,summary:`${n.summary.inspected} URLs inspected, with ${n.summary.alerts} new alerts and ${n.summary.regressions} regressions.`,metrics:[{label:`Inventory URLs`,value:n.summary.inventoryUrls},{label:`Selected`,value:n.summary.selected},{label:`Inspected`,value:n.summary.inspected},{label:`Failed`,value:n.summary.failed},{label:`Quota blocked`,value:n.summary.quotaBlocked},{label:`Current reviews`,value:n.summary.currentIssues},{label:`Regressions`,value:n.summary.regressions},{label:`Recoveries`,value:n.summary.recoveries},{label:`New alerts`,value:n.summary.alerts}]}),n.properties.length&&H([`Property`,`Inventory`,`Selected`,`Inspected`,`Failed`,`Current`,`Alerts`],n.properties.map(e=>[e.property,e.inventoryUrls,e.selectedUrls,e.inspected,e.failed+e.quotaBlocked,e.currentIssues,e.alerts]));let a=n.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,50);a.length&&(process.stdout.write(`
|
|
185
185
|
Index reviews, changes, and failed checks
|
|
186
|
-
`),
|
|
186
|
+
`),H([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),X(`Warnings`,n.warnings.slice(0,10));return}if(!n.length)throw Error(`Pass at least one URL with --urls.`);let a=await x({site:r,urls:n,languageCode:A(e.language),dailyLimit:N(e[`daily-limit`])});if(t){B(a);return}q({title:`Index watch`,target:a.site,status:a.summary.failed>0||a.summary.quotaBlocked>0?`unknown`:a.summary.alerts>0||a.summary.regressions>0?`warning`:a.summary.attempted>0?`pass`:`info`,summary:`${a.summary.inspected} URLs inspected, with ${a.summary.alerts} new alerts.`,metrics:[{label:`Property`,value:a.source.property},{label:`Attempted`,value:a.summary.attempted},{label:`Inspected`,value:a.summary.inspected},{label:`Failed`,value:a.summary.failed},{label:`Quota blocked`,value:a.summary.quotaBlocked},{label:`Current reviews`,value:a.summary.currentIssues},{label:`Regressions`,value:a.summary.regressions},{label:`Recoveries`,value:a.summary.recoveries},{label:`New alerts`,value:a.summary.alerts}]}),H([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),hl=D({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`minImpressions`,`refresh`,`js`],{days:{description:`GSC lookback window. Defaults to 90.`},limit:{description:`Maximum GSC pages to check. Defaults to 50.`},minImpressions:{description:`Minimum impressions to check a page. Defaults to 100.`},refresh:{description:`Bypass local GSC and HTTP caches.`},js:{description:`Force JavaScript rendering for final page extraction.`}}),"min-clicks":{type:`string`,description:`Minimum clicks to check a page. Defaults to 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await ge({site:await xo({client:j(e),site:A(e.site)},{json:t}),days:N(e.days),limit:N(e.limit),minClicks:N(e[`min-clicks`]),minImpressions:N(e[`min-impressions`]),refresh:M(e.refresh),js:M(e.js)?!0:`auto`});if(t){B(n);return}q({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${G(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:G(n.summary.checked)},{label:`Recoverable`,value:G(n.summary.recoverable)},{label:`High severity`,value:G(n.summary.high)},{label:`Clicks at risk`,value:G(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:G(n.summary.impressionsAtRisk)}]}),n.items.length&&(J([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,G(e.clicks),G(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),Y(`Top recovery actions`,n.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${G(e.clicks)} clicks at risk`,action:e.recommendation.action})))),X(`Warnings`,n.warnings.slice(0,10))}}),gl=D({meta:{name:`redirect-trace`,description:`Trace redirects and report final indexability/canonical issues`},args:{url:{type:`string`,required:!0,description:`URL to trace.`},"max-hops":{type:`string`,description:`Maximum redirect hops to follow. Defaults to 10.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache for final page extraction.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for final page extraction.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ye({url:A(e.url)??``,maxHops:N(e[`max-hops`]),refresh:M(e.refresh),js:M(e.js)?!0:`auto`});if(L(e)){B(t);return}q({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),H([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,K(e.url,72),K(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
|
|
187
187
|
Final page
|
|
188
|
-
`),
|
|
188
|
+
`),V([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),X(`Warnings`,t.warnings.slice(0,10))}}),_l=D({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in Google Analytics`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum Google Analytics rows per query. Defaults to 100000.`},"result-limit":{type:`string`,description:`Maximum ranked landing pages returned. Defaults to 25.`},limit:{type:`string`,description:`Legacy alias for --max-rows.`},refresh:{type:`boolean`,default:!1,description:`Bypass cached Google Analytics responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await Ua({client:j(e),options:{json:t}}),r=await le({property:await Wa({property:A(e.property)??n?.analytics.google?.propertyId,options:{json:t}}),startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),maxRows:P(e[`max-rows`],`--max-rows`),resultLimit:P(e[`result-limit`],`--result-limit`),limit:P(e.limit,`--limit`),refresh:e.refresh===!0});if(t){B(r);return}if(q({title:`AI referral report`,target:r.property,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus===`complete`?`Complete`:`Partial`,status:r.dataStatus===`complete`?`pass`:`unknown`},{label:`AI sessions`,value:G(r.summary.sessions)},{label:`AI users`,value:r.summary.totalUsers===null?`Unavailable`:G(r.summary.totalUsers)},{label:`Sources`,value:G(r.summary.sources)},{label:`Landing pages`,value:G(r.summary.landingPages)},{label:`Landing-page output`,value:`${G(r.selection.landingPages.returnedRows)} returned / ${G(r.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:G(r.dataSource.sourceDiscovery.returnedRows)}]}),r.sources.length&&(process.stdout.write(`
|
|
189
189
|
AI sources
|
|
190
|
-
`),J([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,
|
|
190
|
+
`),J([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,G(e.sessions),G(e.eventCount),ro(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
|
|
191
191
|
Landing pages
|
|
192
|
-
`),J([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[K(e.landingPage,64),
|
|
192
|
+
`),J([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[K(e.landingPage,64),G(e.sessions),G(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
|
|
193
193
|
Evidence warnings
|
|
194
|
-
`);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),dl=E({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`minImpressions`,`refresh`],{minImpressions:{description:`Minimum query impressions. Defaults to 50.`}}),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),r=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),i=await pn({site:r.site,days:N(e.days,`--days`),limit:N(e.limit,`--limit`),minImpressions:N(e[`min-impressions`],`--min-impressions`),brandTerms:[...r.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),refresh:n});if(t){z(i);return}q({title:`Cannibalisation report`,target:i.site,status:i.dataStatus===`complete`?`info`:`unknown`,summary:i.summary.verdict,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`Range`,value:`${i.range.startDate} to ${i.range.endDate}`},{label:`Eligible`,value:W(i.summary.eligibleClusters)},{label:`Returned`,value:W(i.summary.returnedClusters)},{label:`Suppressed brand queries`,value:W(i.summary.suppressedQueries)},{label:`GSC completeness`,value:i.source.completeness},{label:`Brand queries`,value:j(e[`include-brand`])?`included`:`excluded`}]}),J([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],i.items.map(e=>[K(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:W(e.propertyImpressions),W(e.pageExposureImpressions),e.hhi.toFixed(2),K(e.suggestedOwnerUrl,56)])),Y(`Top cannibalisation actions`,i.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${K(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),X(`Report caveats`,i.caveats),X(`Provider usage`,[i.ledgerSummary])}}),fl=E({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 1 per 5,000 retained impressions, from 30 to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),r=await U({client:A(e),site:k(e.site),options:{json:t}}),i=await n({site:r.site,minImpressions:M(e[`min-impressions`]),brandTerms:r.client?.brandTerms,includeBrand:j(e[`include-brand`])});if(t){z(i);return}q({title:`CTR underperformers`,target:i.site,status:i.dataStatus===`partial`?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Underperformers`,value:W(i.summary.underperformers)},{label:`Estimated click gap`,value:W(i.summary.estimatedClickShortfall)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Evidence`,value:i.dataStatus},{label:`GSC completeness`,value:i.source.completeness}]}),X(`Why this matters`,[`These queries already have visibility; the fastest win is usually better SERP framing, not new content.`,`Start with high-impression rows where actual CTR is far below the expected CTR for that ranking position.`]),X(`Recommended actions`,i.recommendations),X(`Report caveats`,i.caveats),J([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],i.items.map(e=>[K(e.query,36),K(e.url,48),Xa(e.position),W(e.impressions),G(e.actualCtr),G(e.expectedCtr),W(e.clickShortfall),K(e.recommendation.action,72)])),Y(`Top CTR actions`,i.items.map(e=>({label:e.query,context:`${W(e.clickShortfall)} click gap, ${G(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function pl(e){return e>0?Xa(e):`n/a`}const ml=E({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`refresh`]),comparison:{type:`string`,description:`Comparison: previous-period or year-over-year.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},"min-drop-pct":{type:`string`,description:`Minimum click drop percentage. Defaults to 20.`},"min-previous-clicks":{type:`string`,description:`Minimum previous-window clicks for a query/page row. Defaults to 2.`},"min-click-loss":{type:`string`,description:`Minimum absolute click loss. Defaults to 1.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),r=k(e.comparison),i=N(e.days,`--days`),a=N(e.limit,`--limit`),o=N(e[`min-drop-pct`],`--min-drop-pct`),s=N(e[`min-previous-clicks`],`--min-previous-clicks`),c=N(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new T(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),u=await ue({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){z(u);return}q({title:`Decaying pages`,target:u.site,status:u.dataStatus===`complete`?u.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:u.summary.verdict,metrics:[{label:`Evidence`,value:u.dataStatus},{label:`Comparison`,value:u.comparison},{label:`Observed declines`,value:W(u.summary.returnedRows)},{label:`Eligible declines`,value:W(u.summary.eligibleRows)},{label:`Decay clusters`,value:W(u.summary.groups)},{label:`Observed click decline`,value:W(u.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:W(u.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:u.summary.brandFiltering},{label:`Minimum drop`,value:`${u.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:W(u.filters.minPreviousClicks)},{label:`GSC completeness`,value:u.source.completeness}]}),X(`Why this matters`,[`This report compares retained query/page rows and only counts declines observed in both windows.`,`Position, CTR, and impression movements are signals for investigation, not proof of cause.`]),X(`Recommended actions`,u.recommendations),X(`Report caveats`,u.caveats),X(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(J([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[K(e.label,42),W(e.count),W(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,K(e.sampleQueries[0]??`-`,40),K(e.recommendation,72)])),Y(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${W(e.count)} retained rows, ${W(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),J([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],u.items.map(e=>[K(e.query,32),K(e.template.label,26),K(e.url,44),e.signals.join(`, `),W(e.clickLoss),`${W(e.previous.clicks)} -> ${W(e.current.clicks)}`,`${W(e.previous.impressions)} -> ${W(e.current.impressions)}`,`${G(e.previous.ctr)} -> ${G(e.current.ctr)}`,`${pl(e.previous.position)} -> ${pl(e.current.position)}`,K(e.recommendation.action,64)])),Y(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${W(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),hl=E({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0,description:`Target page that should receive relevant internal links.`},...Z([`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),r=N(e.days,`--days`),i=N(e.limit,`--limit`),a=N(e[`check-limit`],`--check-limit`),o=N(e[`min-impressions`],`--min-impressions`),s=zi(e),c=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),l=await ge({site:c.site,targetUrl:k(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),js:j(e.js)?!0:void 0,rate:s,refresh:n});if(t){z(l);return}q({title:`Internal link opportunities`,target:l.targetUrl,status:l.summary.failedChecks>0||l.summary.uncheckedCandidates>0?`unknown`:`info`,summary:l.summary.verdict,metrics:[{label:`Target state`,value:l.target.verification},{label:`Preferred target`,value:l.target.preferredUrl},{label:`Target queries`,value:W(l.summary.targetQueries)},{label:`Matched sources`,value:W(l.summary.candidateSources)},{label:`Checked sources`,value:W(l.summary.checkedSources)},{label:`Attempted sources`,value:W(l.summary.attemptedSources)},{label:`Review candidates`,value:W(l.summary.returnedSources)},{label:`Technical exclusions`,value:W(l.summary.technicalExclusions)},{label:`Existing links`,value:W(l.summary.existingLinksObserved)},{label:`Failed checks`,value:W(l.summary.failedChecks)},{label:`Unchecked sources`,value:W(l.summary.uncheckedCandidates)},{label:`GSC completeness`,value:l.source.completeness},{label:`Brand queries`,value:l.summary.brandFiltering}]}),X(`Why this matters`,[`Exact-query overlap is useful affinity evidence; precision lexical matches are review evidence only.`,`Source and target pages are checked for redirects, indexability, canonicals, and existing link placement before an action is returned.`]),X(`Recommended actions`,l.recommendations),X(`Report caveats`,l.caveats),l.items.length&&(J([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[K(e.sourceUrl,52),W(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),Y(`Top internal-link reviews`,l.items.map(e=>({label:K(e.sourceUrl,96),context:`${W(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),X(`Provider usage`,[l.ledgerSummary]))}}),gl=E({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0},...Z([`days`,`limit`,`minImpressions`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum page queries to inspect. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 10.`},includeBrand:{description:`Include branded queries in page opportunity reports.`},js:{description:`Force JavaScript rendering for page extraction.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),"verify-content":Ni(`Fetch the page for title/H1/body checks. Defaults to true.`,`Skip fetching the page for title/H1/body checks.`),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await he({site:n.site,url:k(e.url)??``,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`])!==!1,js:j(e.js)?!0:`auto`,refresh:j(e.refresh)});if(t){z(r);return}if(q({title:`Page opportunities`,target:r.url,status:r.verification.status===`failed`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Title`,value:r.page?.title??`Not fetched`},{label:`Fetch`,value:vo(r.page?.fetchDiagnostics)},{label:`Verification`,value:r.verification.status},{label:`Rows`,value:`${W(r.selection.returnedRows)} returned / ${W(r.selection.eligibleRows)} eligible / ${W(r.selection.sourceRows)} source`},{label:`Queries`,value:W(r.summary.queries)},{label:`Impressions`,value:W(r.summary.impressions)},{label:`Opportunities`,value:W(r.summary.opportunities)},{label:`CTR shortfall`,value:W(r.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:r.summary.focus},{label:`Evidence`,value:r.dataStatus}]}),X(`Why this matters`,[`This report starts from queries where this exact URL already has impressions, so recommendations stay tied to first-party demand.`,`Content verification separates body gaps from title/meta framing issues, which helps avoid adding copy when the page already covers the query.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
|
|
194
|
+
`);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),vl=D({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`minImpressions`,`refresh`],{minImpressions:{description:`Minimum query impressions. Defaults to 50.`}}),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=M(e.refresh),r=await W({client:j(e),site:A(e.site),options:{json:t,refresh:n}}),i=await Ct({site:r.site,days:P(e.days,`--days`),limit:P(e.limit,`--limit`),minImpressions:P(e[`min-impressions`],`--min-impressions`),brandTerms:[...r.client?.brandTerms??[],...F(e[`brand-terms`])??[]],includeBrand:M(e[`include-brand`]),refresh:n});if(t){B(i);return}q({title:`Cannibalisation report`,target:i.site,status:i.dataStatus===`complete`?`info`:`unknown`,summary:i.summary.verdict,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`Range`,value:`${i.range.startDate} to ${i.range.endDate}`},{label:`Eligible`,value:G(i.summary.eligibleClusters)},{label:`Returned`,value:G(i.summary.returnedClusters)},{label:`Suppressed brand queries`,value:G(i.summary.suppressedQueries)},{label:`GSC completeness`,value:i.source.completeness},{label:`Brand queries`,value:M(e[`include-brand`])?`included`:`excluded`}]}),J([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],i.items.map(e=>[K(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:G(e.propertyImpressions),G(e.pageExposureImpressions),e.hhi.toFixed(2),K(e.suggestedOwnerUrl,56)])),Y(`Top cannibalisation actions`,i.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${K(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),X(`Report caveats`,i.caveats),X(`Provider usage`,[i.ledgerSummary])}}),yl=D({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 1 per 5,000 retained impressions, from 30 to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t}}),r=await sn({site:n.site,minImpressions:N(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`])});if(t){B(r);return}q({title:`CTR underperformers`,target:r.site,status:r.dataStatus===`partial`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Underperformers`,value:G(r.summary.underperformers)},{label:`Estimated click gap`,value:G(r.summary.estimatedClickShortfall)},{label:`Brand queries`,value:r.summary.brandFiltering},{label:`Evidence`,value:r.dataStatus},{label:`GSC completeness`,value:r.source.completeness}]}),X(`Why this matters`,[`These queries already have visibility; the fastest win is usually better SERP framing, not new content.`,`Start with high-impression rows where actual CTR is far below the expected CTR for that ranking position.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),J([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[K(e.query,36),K(e.url,48),no(e.position),G(e.impressions),ro(e.actualCtr),ro(e.expectedCtr),G(e.clickShortfall),K(e.recommendation.action,72)])),Y(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${G(e.clickShortfall)} click gap, ${ro(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function bl(e){return e>0?no(e):`n/a`}const xl=D({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`refresh`]),comparison:{type:`string`,description:`Comparison: previous-period or year-over-year.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},"min-drop-pct":{type:`string`,description:`Minimum click drop percentage. Defaults to 20.`},"min-previous-clicks":{type:`string`,description:`Minimum previous-window clicks for a query/page row. Defaults to 2.`},"min-click-loss":{type:`string`,description:`Minimum absolute click loss. Defaults to 1.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),r=M(e.refresh),i=A(e.comparison),a=P(e.days,`--days`),o=P(e.limit,`--limit`),s=P(e[`min-drop-pct`],`--min-drop-pct`),c=P(e[`min-previous-clicks`],`--min-previous-clicks`),l=P(e[`min-click-loss`],`--min-click-loss`);if(i!==void 0&&i!==`previous-period`&&i!==`year-over-year`)throw new E(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let u=await W({client:j(e),site:A(e.site),options:{json:t,refresh:r}}),d=await n({site:u.site,days:a,limit:o,comparison:i,brandTerms:[...u.client?.brandTerms??[],...F(e[`brand-terms`])??[]],includeBrand:M(e[`include-brand`]),minDropPct:s,minPreviousClicks:c,minClickLoss:l,refresh:r});if(t){B(d);return}q({title:`Decaying pages`,target:d.site,status:d.dataStatus===`complete`?d.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:d.summary.verdict,metrics:[{label:`Evidence`,value:d.dataStatus},{label:`Comparison`,value:d.comparison},{label:`Observed declines`,value:G(d.summary.returnedRows)},{label:`Eligible declines`,value:G(d.summary.eligibleRows)},{label:`Decay clusters`,value:G(d.summary.groups)},{label:`Observed click decline`,value:G(d.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:G(d.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:d.summary.brandFiltering},{label:`Minimum drop`,value:`${d.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:G(d.filters.minPreviousClicks)},{label:`GSC completeness`,value:d.source.completeness}]}),X(`Why this matters`,[`This report compares retained query/page rows and only counts declines observed in both windows.`,`Position, CTR, and impression movements are signals for investigation, not proof of cause.`]),X(`Recommended actions`,d.recommendations),X(`Report caveats`,d.caveats),X(`Warnings`,d.warnings),d.items.length&&(d.groups.length&&(J([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],d.groups.map(e=>[K(e.label,42),G(e.count),G(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,K(e.sampleQueries[0]??`-`,40),K(e.recommendation,72)])),Y(`Top decay cluster actions`,d.groups.map(e=>({label:e.label,context:`${G(e.count)} retained rows, ${G(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),J([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],d.items.map(e=>[K(e.query,32),K(e.template.label,26),K(e.url,44),e.signals.join(`, `),G(e.clickLoss),`${G(e.previous.clicks)} -> ${G(e.current.clicks)}`,`${G(e.previous.impressions)} -> ${G(e.current.impressions)}`,`${ro(e.previous.ctr)} -> ${ro(e.current.ctr)}`,`${bl(e.previous.position)} -> ${bl(e.current.position)}`,K(e.recommendation.action,64)])),Y(`Top decay actions`,d.items.map(e=>({label:e.query,context:`${e.template.label}, ${G(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),Sl=D({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0,description:`Target page that should receive relevant internal links.`},...Z([`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=M(e.refresh),r=P(e.days,`--days`),i=P(e.limit,`--limit`),a=P(e[`check-limit`],`--check-limit`),o=P(e[`min-impressions`],`--min-impressions`),s=Gi(e),c=await W({client:j(e),site:A(e.site),options:{json:t,refresh:n}}),l=await Jn({site:c.site,targetUrl:A(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...F(e[`brand-terms`])??[]],includeBrand:M(e[`include-brand`]),js:M(e.js)?!0:void 0,rate:s,refresh:n});if(t){B(l);return}q({title:`Internal link opportunities`,target:l.targetUrl,status:l.summary.failedChecks>0||l.summary.uncheckedCandidates>0?`unknown`:`info`,summary:l.summary.verdict,metrics:[{label:`Target state`,value:l.target.verification},{label:`Preferred target`,value:l.target.preferredUrl},{label:`Target queries`,value:G(l.summary.targetQueries)},{label:`Matched sources`,value:G(l.summary.candidateSources)},{label:`Checked sources`,value:G(l.summary.checkedSources)},{label:`Attempted sources`,value:G(l.summary.attemptedSources)},{label:`Review candidates`,value:G(l.summary.returnedSources)},{label:`Technical exclusions`,value:G(l.summary.technicalExclusions)},{label:`Existing links`,value:G(l.summary.existingLinksObserved)},{label:`Failed checks`,value:G(l.summary.failedChecks)},{label:`Unchecked sources`,value:G(l.summary.uncheckedCandidates)},{label:`GSC completeness`,value:l.source.completeness},{label:`Brand queries`,value:l.summary.brandFiltering}]}),X(`Why this matters`,[`Exact-query overlap is useful affinity evidence; precision lexical matches are review evidence only.`,`Source and target pages are checked for redirects, indexability, canonicals, and existing link placement before an action is returned.`]),X(`Recommended actions`,l.recommendations),X(`Report caveats`,l.caveats),l.items.length&&(J([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[K(e.sourceUrl,52),G(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),Y(`Top internal-link reviews`,l.items.map(e=>({label:K(e.sourceUrl,96),context:`${G(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),X(`Provider usage`,[l.ledgerSummary]))}}),Cl=D({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0},...Z([`days`,`limit`,`minImpressions`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum page queries to inspect. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 10.`},includeBrand:{description:`Include branded queries in page opportunity reports.`},js:{description:`Force JavaScript rendering for page extraction.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),"verify-content":zi(`Fetch the page for title/H1/body checks. Defaults to true.`,`Skip fetching the page for title/H1/body checks.`),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=await qn({site:n.site,url:A(e.url)??``,days:N(e.days),limit:N(e.limit),minImpressions:N(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),verifyContent:M(e[`verify-content`])!==!1,js:M(e.js)?!0:`auto`,refresh:M(e.refresh)});if(t){B(r);return}if(q({title:`Page opportunities`,target:r.url,status:r.verification.status===`failed`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Title`,value:r.page?.title??`Not fetched`},{label:`Fetch`,value:To(r.page?.fetchDiagnostics)},{label:`Verification`,value:r.verification.status},{label:`Rows`,value:`${G(r.selection.returnedRows)} returned / ${G(r.selection.eligibleRows)} eligible / ${G(r.selection.sourceRows)} source`},{label:`Queries`,value:G(r.summary.queries)},{label:`Impressions`,value:G(r.summary.impressions)},{label:`Opportunities`,value:G(r.summary.opportunities)},{label:`CTR shortfall`,value:G(r.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:r.summary.focus},{label:`Evidence`,value:r.dataStatus}]}),X(`Why this matters`,[`This report starts from queries where this exact URL already has impressions, so recommendations stay tied to first-party demand.`,`Content verification separates body gaps from title/meta framing issues, which helps avoid adding copy when the page already covers the query.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
|
|
195
195
|
`:`GSC rows were found, but none met the report criteria.
|
|
196
|
-
`);return}J([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],r.items.map(e=>[e.opportunityType,K(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),
|
|
197
|
-
`),process.stdout.write(`${a.ledgerSummary}\n`);return}J([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],a.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),
|
|
198
|
-
`),
|
|
196
|
+
`);return}J([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],r.items.map(e=>[e.opportunityType,K(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),ro(e.ctr),e.expectedCtr===void 0?`n/a`:ro(e.expectedCtr),G(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:G(e.estimatedCtrClickShortfall),K(e.recommendation,72)])),Y(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${G(e.impressions)} impressions`,action:e.recommendation})))}}),wl=D({meta:{name:`query-cluster`,description:`Cluster GSC queries into repeated demand themes`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},scope:{type:`string`},...Z([`includeBrand`,`minImpressions`,`limit`],{includeBrand:{description:`Include branded queries in query clustering.`},minImpressions:{description:`Minimum impressions per query. Defaults to 25.`},limit:{description:`Maximum clusters to return. Defaults to 25; max 100.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t}}),r=await Kt({site:n.site,scope:A(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),minImpressions:N(e[`min-impressions`]),limit:N(e.limit)});if(t){B(r);return}q({title:`Query clusters`,target:r.site,status:`info`,summary:r.summary.verdict,metrics:[{label:`Scope`,value:r.scope??`All pages`},{label:`Clusters`,value:G(r.summary.clusters)},{label:`Queries`,value:G(r.summary.queries)},{label:`Impressions`,value:G(r.summary.impressions)},{label:`Clicks`,value:G(r.summary.clicks)},{label:`Minimum impressions`,value:G(r.summary.minImpressions)},{label:`Result limit`,value:G(r.summary.limit)},{label:`High opportunity`,value:G(r.summary.highOpportunityClusters)},{label:`Brand queries`,value:r.summary.brandFiltering}]}),X(`Why this matters`,[`Clusters show repeated demand themes, so they are better inputs for page sections, hubs, and templates than one-off query exports.`,`Prioritise clusters with high impressions and low clicks; they usually reveal unclear intent coverage or weak SERP framing.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),J([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[K(e.label,32),e.intent,e.queries.length,G(e.totals?.impressions??0),ro(e.totals?.ctr??0),ro(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:G(e.estimatedClickLift),K(e.recommendation??``,72)])),Y(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${G(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${G(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),Tl=D({meta:{name:`quick-wins`,description:`Find high-ranking low-CTR query/page opportunities`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`minImpressions`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=N(e[`verify-limit`]),r=M(e[`verify-content`])===!0||n!==void 0,i=await W({client:j(e),site:A(e.site),options:{json:t}}),a=await Oe({site:i.site,days:N(e.days),limit:N(e.limit),minImpressions:N(e[`min-impressions`]),brandTerms:[...i.client?.brandTerms??[],...F(e[`brand-terms`])??[]],includeBrand:M(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:M(e.js)?!0:void 0,rate:Wi(e),refresh:M(e.refresh)});if(t){B(a);return}q({title:`Quick wins`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:G(a.summary.eligibleRows)},{label:`Returned rows`,value:G(a.summary.returnedRows)},{label:`Query clusters`,value:G(a.summary.repeatedQueryGroups)},{label:`Template patterns`,value:G(a.summary.templatePatterns)},{label:`CTR click shortfall`,value:G(a.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:io(a)}]}),X(`Why this matters`,[`These rows have GSC average positions from 4 to 10 and observed CTR below a site-peer or versioned fallback target.`,`Page evidence is needed before treating a CTR shortfall as a title, content, or technical finding.`]),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(J([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[K(e.templateLabel,34),G(e.urlCount),G(e.rowCount),G(e.totalEstimatedCtrClickShortfall),G(e.totalImpressions),K(e.action,72)])),Y(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${G(e.urlCount)} URLs, ${G(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(J([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[K(e.label,44),G(e.urlCount),G(e.rowCount),G(e.totalEstimatedCtrClickShortfall),G(e.totalImpressions),K(e.sampleUrls[0]??`-`,46),K(e.recommendation,72)])),Y(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${G(e.urlCount)} URLs, ${G(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),J([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],a.items.map(e=>[K(e.query,36),K(e.template.label,24),K(e.url,48),no(e.position),G(e.impressions),`${ro(e.ctr)} / ${ro(e.targetCtr)}`,G(e.estimatedCtrClickShortfall),To(e.contentVerification?.fetchDiagnostics),Eo(e.contentVerification?.classification),K(e.recommendation.action,64)])),Y(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${no(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action}))))}}),El=D({meta:{name:`second-page`,description:`Find URLs averaging positions above 10 through 20 in GSC`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`range`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=N(e[`verify-limit`]),r=M(e[`verify-content`])===!0||n!==void 0,i=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),a=await Se({site:i.site,range:N(e.days),minImpressions:N(e[`min-impressions`]),limit:A(e.limit)?Number(A(e.limit)):10,brandTerms:i.client?.brandTerms,includeBrand:M(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:M(e.js)?!0:void 0,rate:Wi(e),refresh:M(e.refresh)});if(t){B(a);return}if(q({title:`Second-page opportunities`,target:a.site,status:a.source.possiblyTruncated||a.summary.fetchFailures>0?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible pages`,value:G(a.summary.eligiblePages)},{label:`Returned pages`,value:G(a.summary.returnedPages)},{label:`Eligible queries`,value:G(a.summary.eligibleQueries)},{label:`Impressions`,value:G(a.summary.eligibleImpressions)},{label:`Content issues`,value:G(a.summary.contentIssues)},{label:`Technical issues`,value:G(a.summary.technicalIssues)},{label:`Fetch failures`,value:G(a.summary.fetchFailures)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:io(a)}]}),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
|
|
197
|
+
`),process.stdout.write(`${a.ledgerSummary}\n`);return}J([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],a.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),G(e.queryCount),To(e.fetchDiagnostics),Eo(e.contentVerification?.classification),e.recommendation.action])),Y(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${no(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),Dl=D({meta:{name:`audit-page`,description:`Fetch one page and audit title, metadata, content, links, and schema`},args:{url:{type:`string`,required:!0,description:`URL to audit.`},site:{type:`string`,description:`Optional GSC property for page search metrics, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`}},run:async({args:e})=>{let t=j(e),n=A(e.site),r=t?await Ua({project:t,options:{json:L(e)}}):void 0,i=t||n?void 0:await Ua({options:{json:L(e)}}),a=await Ue({url:A(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:M(e.js)?!0:`auto`,refresh:M(e.refresh)});if(L(e)){B(a);return}q({title:`Page audit`,target:a.url,status:a.issues.some(e=>e.severity===`high`)?`fail`:a.issues.length>0?`warning`:`pass`,summary:a.issues.length>0?`${a.issues.length} ${a.issues.length===1?`issue needs`:`issues need`} review.`:`No page issues were found by the available checks.`,metrics:[{label:`Final URL`,value:a.page.finalUrl},{label:`Title`,value:a.page.title??`Missing`},{label:`Meta description`,value:a.page.metaDescription??`Missing`},{label:`Words`,value:a.page.wordCount},{label:`Fetch`,value:To(a.fetchDiagnostics)}],diagnostics:[{title:`Issues`,items:a.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:`${e.code}: ${e.principle}`,explanation:e.detail}))}]})}});function Ol(e){let t=A(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new E(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function kl(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function Al(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const jl=D({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:D({meta:{name:`audit`,description:`Run a Lighthouse/Core Web Vitals performance audit`},args:{url:{type:`string`,description:`URL to audit. Defaults to the selected project crawl URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},strategy:{type:`string`,default:`mobile`,description:`Audit strategy: mobile or desktop.`},"lighthouse-bin":{type:`string`,description:`Custom Lighthouse binary path. Defaults to the bundled version.`},"crux-key":{type:`string`,description:`CrUX API key. Prefer SEO_CRUX_API_KEY to avoid shell history.`},raw:{type:`boolean`,default:!1,description:`Include the full Lighthouse JSON in JSON output.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local performance report cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=L(e),n=await Ua({client:j(e),options:{json:t}}),r=A(e.url)??n?.startUrl;if(!r)throw Error(`No URL selected. Pass --url or use --project with a crawl URL.`);let i=await a({url:r,strategy:Ol(e.strategy),lighthouseBin:A(e[`lighthouse-bin`]),cruxApiKey:A(e[`crux-key`]),refresh:M(e.refresh),includeRaw:t&&M(e.raw)});if(t){B(i);return}q({title:`Performance report`,target:i.url,status:i.dataStatus===`partial`?`unknown`:i.grade===`good`?`pass`:`warning`,summary:i.headline,metrics:[{label:`Source`,value:i.source},{label:`Strategy`,value:i.strategy},{label:`Lab score`,value:i.score===void 0?`Unknown`:`${i.score}/100`},{label:`Lab grade`,value:i.grade},{label:`Evidence`,value:i.dataStatus},{label:`FCP`,value:kl(i.metrics.firstContentfulPaint)},{label:`LCP`,value:kl(i.metrics.largestContentfulPaint)},{label:`TBT`,value:kl(i.metrics.totalBlockingTime)},{label:`CLS`,value:kl(i.metrics.cumulativeLayoutShift)},{label:`INP`,value:kl(i.metrics.interactionToNextPaint)},{label:`Server response`,value:kl(i.metrics.serverResponseTime)},{label:`Fallback fetch`,value:kl(i.metrics.fallbackFetchDuration)},{label:`Field data`,value:i.fieldData?`${i.fieldData.scope} ${i.fieldData.formFactor.toLowerCase()} (${i.fieldData.assessment.status})`:i.fieldDataStatus.status},{label:`Field LCP p75`,value:Al(i.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:Al(i.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:Al(i.fieldData?.metrics.cumulativeLayoutShift)},{label:`Field period`,value:i.fieldData?.collectionPeriod?.firstDate&&i.fieldData.collectionPeriod.lastDate?`${i.fieldData.collectionPeriod.firstDate} to ${i.fieldData.collectionPeriod.lastDate}`:`Unavailable`}]}),Y(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),X(`Caveats`,i.caveats)}})}}),Ml=D({meta:{name:`diagnose`,description:`Find where Google Search performance changed`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=await qe({site:n.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),refresh:M(e.refresh),progress:Vc(!t)});if(t){B(r);return}q({title:`SEO diagnosis`,target:r.site,status:r.dataStatus===`complete`?r.priorities.length>0?`warning`:`pass`:`unknown`,summary:r.priorities.length>0?`${r.priorities.length} priorities need review.`:`No priorities were returned by the available evidence.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Update overlap`,value:r.summary.updateAttribution.replaceAll(`-`,` `)},{label:`Significant anomalies`,value:r.summary.significantAnomalies},{label:`Update matches`,value:r.summary.updateMatches},{label:`Decay items`,value:r.summary.decayItems},{label:`Cannibal items`,value:r.summary.cannibalItems},{label:`Striking distance`,value:r.summary.strikingDistanceItems},{label:`Quick wins`,value:r.summary.quickWinItems}]}),H([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&H([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),Y(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),Nl=D({meta:{name:`doctor`,description:`Check local auth, scopes, config, and defaults`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await _e();if(L(e)){B(t),t.ok||(process.exitCode=1);return}Aa(`SEO doctor`,xa(t.checks)),process.stdout.write(`
|
|
198
|
+
`),ja(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),Pl=e=>{let t=A(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},Fl=D({meta:{name:`segment-impact`,description:`Compare GSC movement by page, query, device, or country`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},dimension:{type:`string`,default:`page`,description:`Segment by page, query, country, or device.`},...Z([`days`,`limit`,`refresh`],{days:{description:`Recent window length from 1 to 240 days. Defaults to 28.`},limit:{description:`Maximum segment rows to print. Defaults to 25.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),compare:{type:`string`,description:`Legacy 1-240 day comparison length. Must equal the current window.`},"start-date":{type:`string`,description:`Exact current-window start date in YYYY-MM-DD format.`},"end-date":{type:`string`,description:`Exact current-window end date in YYYY-MM-DD format.`},"max-rows":{type:`string`,description:`Maximum retained GSC rows per window. Defaults to 100000.`},"unmatched-limit":{type:`string`,description:`Maximum one-window evidence rows to return. Defaults to 25.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=Pl(e.dimension),r=P(e.days,`--days`),i=P(e.compare,`--compare`),a=P(e.limit,`--limit`),o=P(e[`unmatched-limit`],`--unmatched-limit`),s=P(e[`max-rows`],`--max-rows`),c=await Rt({site:(await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}})).site,dimension:n,days:r,compareDays:i,startDate:A(e[`start-date`]),endDate:A(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:M(e.refresh)});if(t){B(c);return}q({title:`Segment impact`,target:c.site,status:c.dataStatus===`complete`?`info`:`unknown`,summary:c.summary.verdict,metrics:[{label:`Dimension`,value:c.dimension},{label:`Evidence`,value:c.dataStatus},{label:`Before`,value:`${c.before.startDate} to ${c.before.endDate}`},{label:`After`,value:`${c.after.startDate} to ${c.after.endDate}`}]}),H([`Segment`,`Clicks before`,`Clicks after`,`Delta`,`Pos delta`],c.items.map(e=>[e.key,e.beforeClicks,e.afterClicks,e.clickDelta,e.positionDelta??`-`])),c.unmatchedSegments.length&&(process.stdout.write(`
|
|
199
199
|
One-window evidence (not treated as zero)
|
|
200
|
-
`),
|
|
201
|
-
`);return}J([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],a.items.map(e=>[K(e.query,36),K(e.template.label,24),K(e.url,48),W(e.impressions),G(e.ctr),Xa(e.position),e.priority.score,vo(e.contentVerification?.fetchDiagnostics),yo(e.contentVerification?.classification),K(e.recommendation.action,72)])),Y(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${Xa(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action})))}});function jl(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function Ml(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function Nl(e){return e.limit===null?e.used===null?`not reported`:`${e.used.toLocaleString(`en-US`)} used; no key limit`:`${(e.remaining??0).toLocaleString(`en-US`)} of ${e.limit.toLocaleString(`en-US`)} remaining`}const Pl=E({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:E({meta:{name:`connect`,description:`Validate and save an Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!R({json:I(e)}))throw new T(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${S}.`);Tr(`Connect Ahrefs`),D(`Paste an API v3 key from Ahrefs Account settings. The validation check and Domain Rating are free; other research can consume API units.`,`API key`);let t=H(await Ar({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new Me({apiKey:t}).limitsAndUsage(),r=await ee(t);D(`${n.subscription} subscription. API key units: ${Nl(n.apiKeyUnits)}.`,`Connection verified`),kr(`Saved in the ${jl(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),status:E({meta:{name:`status`,description:`Show the local Ahrefs connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the API key with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await We(),n=!!e.check,r=n&&t?await new Me().limitsAndUsage():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,liveCheck:r?{status:`passed`,subscription:r.subscription,apiKeyExpiresAt:r.apiKeyExpiresAt,usageResetsAt:r.usageResetsAt,apiKeyUnits:r.apiKeyUnits,workspaceUnits:r.workspaceUnits,observedAt:r.observedAt,requestCostUnits:r.requestCostUnits}:{status:n?`unavailable`:`not-requested`}};if(I(e)){z(i);return}B([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,jl(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Subscription`,r.subscription],[`API key units`,Nl(r.apiKeyUnits)],[`Workspace units`,Nl(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),disconnect:E({meta:{name:`disconnect`,description:`Remove the saved Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Re();let t=!!process.env[S]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${S} to fully disconnect.`:`Ahrefs is disconnected.`};I(e)?z(n):process.stdout.write(`${n.note}\n`)}})}}),Fl=E({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:E({meta:{name:`connect`,description:`Validate and save DataForSEO API credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!R({json:I(e)}))throw new T(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${de} and ${ke}.`);Tr(`Connect DataForSEO`),D(`Use the API login and API password from DataForSEO API Access. The validation call is free; later research reports can make charged requests.`,`API credentials`);let t=H(await Mr({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=H(await Ar({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new Pn({credentials:()=>r}).userData(),a=await Ve(r);D(`${i.login} is connected with an account balance of ${Ml(i.balanceMicros)}.`,`Connection verified`),kr(`Saved in the ${jl(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),status:E({meta:{name:`status`,description:`Show the local DataForSEO connection`},args:{check:{type:`boolean`,default:!1,description:`Verify credentials with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await o(),n=!!e.check,r=n&&t?await new Pn().userData():void 0,i={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:r?{status:`passed`,account:r.login,timezone:r.timezone,balanceMicros:r.balanceMicros,observedAt:r.observedAt,requestCostMicros:r.requestCostMicros}:{status:n?`unavailable`:`not-requested`}};if(I(e)){z(i);return}B([[`Connected`,i.connected?`yes`:`no`],[`Credential`,jl(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Account`,r.login],[`Balance`,Ml(r.balanceMicros)]]:[]])}}),disconnect:E({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Bn();let t=!!process.env[de]?.trim(),n=!!process.env[ke],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${de} and ${ke} to fully disconnect.`:`DataForSEO is disconnected.`};I(e)?z(r):process.stdout.write(`${r.note}\n`)}})}});function Il(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new T(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new T(`INVALID_INPUT`,`--account must be a JSON object.`);let n=Object.entries(t);if(n.length>20||n.some(([e,t])=>!e||e.length>64||typeof t!=`string`||t.length>4096))throw new T(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function Ll(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new T(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new T(`INVALID_INPUT`,`--params must be a JSON object.`);let n=Object.entries(t);if(n.length>100||n.some(([e])=>!/^[A-Za-z][A-Za-z0-9-]{0,63}$/u.test(e))||Buffer.byteLength(e)>64*1024)throw new T(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function Rl(e){let t=await rt(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new T(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new T(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function zl(e,t){let n=new Set(e.connection.fields.filter(e=>e.kind===`account`).map(e=>e.id)),r=Object.keys(t).find(e=>!n.has(e));if(r)throw new T(`INVALID_INPUT`,`${r} is not an account field for ${e.displayName}.`);let i=e.connection.normalizeAccount?e.connection.normalizeAccount(t):{...t};if(Object.keys(i).find(e=>!n.has(e)))throw new T(`PROVIDER_UNAVAILABLE`,`${e.displayName} returned an unknown account field.`);for(let t of e.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1))if(!i[t.id]?.trim())throw new T(`INVALID_INPUT`,`${t.label} is required.`);return i}async function Bl(){return f()}const Vl=E({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{list:E({meta:{name:`list`,description:`List installed provider packages`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Bl();if(I(e)){z(t);return}t.installed.length>0?V([`Installed provider`,`Package`,`Version`,`Status`],t.installed.map(e=>[e.id,e.package,e.version,e.loadStatus])):process.stdout.write(`No provider extensions are installed.
|
|
202
|
-
`)}}),describe:
|
|
203
|
-
`),`Package`),
|
|
204
|
-
`);return}
|
|
205
|
-
`),
|
|
206
|
-
`),
|
|
207
|
-
`),
|
|
208
|
-
`),
|
|
209
|
-
`),
|
|
210
|
-
`),
|
|
211
|
-
`),
|
|
212
|
-
`),
|
|
213
|
-
`),
|
|
200
|
+
`),H([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),X(`Warnings`,c.warnings)}});function Il(e){return`${G(e)} ${e===1?`row`:`rows`}`}const Ll=D({meta:{name:`striking-distance`,description:`Find query/page rows averaging positions above 10 through 20`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=N(e[`verify-limit`]),r=M(e[`verify-content`])===!0||n!==void 0,i=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),a=await nt({site:i.site,days:N(e.days),minImpressions:N(e[`min-impressions`]),limit:N(e.limit),brandTerms:i.client?.brandTerms,includeBrand:M(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:M(e.js)?!0:void 0,rate:Wi(e),refresh:M(e.refresh)});if(t){B(a);return}if(q({title:`Striking-distance opportunities`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:G(a.summary.eligibleRows)},{label:`Returned rows`,value:G(a.summary.returnedRows)},{label:`Template groups`,value:G(a.summary.groups)},{label:`Impressions`,value:G(a.summary.eligibleImpressions)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Verification`,value:a.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:a.dataStatus}]}),X(`Why this matters`,[`These rows have a GSC average position above 10 and at most 20. Treat them as candidates for investigation, not guaranteed page-two rankings.`,`Groups use all eligible rows. A shared-template label is only a candidate until recurring evidence is verified across distinct URLs.`]),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),a.groups.length&&(J([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[K(e.label,36),G(e.rowCount),G(e.uniqueUrls),G(e.totalImpressions),no(e.impressionWeightedPosition),e.actionScope,K(e.recommendation.action,72)])),Y(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${Il(e.rowCount)}, ${G(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
|
|
201
|
+
`);return}J([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],a.items.map(e=>[K(e.query,36),K(e.template.label,24),K(e.url,48),G(e.impressions),ro(e.ctr),no(e.position),e.priority.score,To(e.contentVerification?.fetchDiagnostics),Eo(e.contentVerification?.classification),K(e.recommendation.action,72)])),Y(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${no(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action})))}});function Rl(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function zl(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function Bl(e){return e.limit===null?e.used===null?`not reported`:`${e.used.toLocaleString(`en-US`)} used; no key limit`:`${(e.remaining??0).toLocaleString(`en-US`)} of ${e.limit.toLocaleString(`en-US`)} remaining`}const Vl=D({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:D({meta:{name:`connect`,description:`Validate and save an Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!z({json:L(e)}))throw new E(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${ce}.`);jr(`Connect Ahrefs`),O(`Paste an API v3 key from Ahrefs Account settings. The validation check and Domain Rating are free; other research can consume API units.`,`API key`);let t=U(await Ir({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new Me({apiKey:t}).limitsAndUsage(),r=await Ze(t);O(`${n.subscription} subscription. API key units: ${Bl(n.apiKeyUnits)}.`,`Connection verified`),Fr(`Saved in the ${Rl(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),status:D({meta:{name:`status`,description:`Show the local Ahrefs connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the API key with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ae(),n=!!e.check,r=n&&t?await new Me().limitsAndUsage():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,liveCheck:r?{status:`passed`,subscription:r.subscription,apiKeyExpiresAt:r.apiKeyExpiresAt,usageResetsAt:r.usageResetsAt,apiKeyUnits:r.apiKeyUnits,workspaceUnits:r.workspaceUnits,observedAt:r.observedAt,requestCostUnits:r.requestCostUnits}:{status:n?`unavailable`:`not-requested`}};if(L(e)){B(i);return}V([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Rl(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Subscription`,r.subscription],[`API key units`,Bl(r.apiKeyUnits)],[`Workspace units`,Bl(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),disconnect:D({meta:{name:`disconnect`,description:`Remove the saved Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await mn();let t=!!process.env[ce]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${ce} to fully disconnect.`:`Ahrefs is disconnected.`};L(e)?B(n):process.stdout.write(`${n.note}\n`)}})}}),Hl="Run `seo providers bing connect` with a new API key.",Ul=D({meta:{name:`bing`,description:`Connect Bing Webmaster Tools for Bing search data`},subCommands:{connect:D({meta:{name:`connect`,description:`Check and save a Bing Webmaster Tools API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!z({json:L(e)}))throw new E(`AUTH_REQUIRED`,`Run \`seo providers bing connect\` in a terminal. Agents and CI can set ${ne}.`);jr(`Connect Bing Webmaster Tools`),O(`In Bing Webmaster Tools, open Settings, then API access, and copy the API key. Bing data is free.`,`API key`);let t=U(await Ir({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await St(An({apiKey:t},{signIn:Hl})),r=await S(t);O(`${n.filter(e=>e.verified).length} verified sites.`,`Connection verified`),Fr(`Saved in the ${Rl(r)}.`)}}),status:D({meta:{name:`status`,description:`Show the Bing Webmaster connection`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ge(),n=t?await St(An({apiKey:t.apiKey},{signIn:Hl})):null,r={connected:!!t,credentialSource:t?.source??null,sites:n?.filter(e=>e.verified).map(e=>e.url)??[]};if(L(e)){B(r);return}process.stdout.write(t?`Connected (${Rl(t.source)}). Verified sites: ${r.sites.join(`, `)||`none`}.\n`:"Not connected. Run `seo providers bing connect`.\n")}}),disconnect:D({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await ze();let t=!!process.env[ne]?.trim(),n=t?`The saved key is removed. Clear ${ne} to fully disconnect.`:`Bing is disconnected.`;L(e)?B({removed:!0,environment:t}):process.stdout.write(`${n}\n`)}})}}),Wl=D({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:D({meta:{name:`connect`,description:`Validate and save DataForSEO API credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!z({json:L(e)}))throw new E(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${de} and ${ke}.`);jr(`Connect DataForSEO`),O(`Use the API login and API password from DataForSEO API Access. The validation call is free; later research reports can make charged requests.`,`API credentials`);let t=U(await Rr({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=U(await Ir({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new Rn({credentials:()=>r}).userData(),a=await He(r);O(`${i.login} is connected with an account balance of ${zl(i.balanceMicros)}.`,`Connection verified`),Fr(`Saved in the ${Rl(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),status:D({meta:{name:`status`,description:`Show the local DataForSEO connection`},args:{check:{type:`boolean`,default:!1,description:`Verify credentials with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await o(),n=!!e.check,r=n&&t?await new Rn().userData():void 0,i={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:r?{status:`passed`,account:r.login,timezone:r.timezone,balanceMicros:r.balanceMicros,observedAt:r.observedAt,requestCostMicros:r.requestCostMicros}:{status:n?`unavailable`:`not-requested`}};if(L(e)){B(i);return}V([[`Connected`,i.connected?`yes`:`no`],[`Credential`,Rl(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Account`,r.login],[`Balance`,zl(r.balanceMicros)]]:[]])}}),disconnect:D({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Kn();let t=!!process.env[de]?.trim(),n=!!process.env[ke],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${de} and ${ke} to fully disconnect.`:`DataForSEO is disconnected.`};L(e)?B(r):process.stdout.write(`${r.note}\n`)}})}});function Gl(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new E(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new E(`INVALID_INPUT`,`--account must be a JSON object.`);let n=Object.entries(t);if(n.length>20||n.some(([e,t])=>!e||e.length>64||typeof t!=`string`||t.length>4096))throw new E(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function Kl(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new E(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new E(`INVALID_INPUT`,`--params must be a JSON object.`);let n=Object.entries(t);if(n.length>100||n.some(([e])=>!/^[A-Za-z][A-Za-z0-9-]{0,63}$/u.test(e))||Buffer.byteLength(e)>64*1024)throw new E(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function ql(e){let t=await In(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new E(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new E(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function Jl(e,t){let n=new Set(e.connection.fields.filter(e=>e.kind===`account`).map(e=>e.id)),r=Object.keys(t).find(e=>!n.has(e));if(r)throw new E(`INVALID_INPUT`,`${r} is not an account field for ${e.displayName}.`);let i=e.connection.normalizeAccount?e.connection.normalizeAccount(t):{...t};if(Object.keys(i).find(e=>!n.has(e)))throw new E(`PROVIDER_UNAVAILABLE`,`${e.displayName} returned an unknown account field.`);for(let t of e.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1))if(!i[t.id]?.trim())throw new E(`INVALID_INPUT`,`${t.label} is required.`);return i}async function Yl(){return p()}const Xl=D({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{list:D({meta:{name:`list`,description:`List installed provider packages`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Yl();if(L(e)){B(t);return}t.installed.length>0?H([`Installed provider`,`Package`,`Version`,`Status`],t.installed.map(e=>[e.id,e.package,e.version,e.loadStatus])):process.stdout.write(`No provider extensions are installed.
|
|
202
|
+
`)}}),describe:D({meta:{name:`describe`,description:`Describe one installed provider for an agent`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await g(A(e.id)??``);if(L(e)){B(t);return}V([[`Provider`,t.displayName],[`Package`,`${t.package}@${t.version}`],[`Description`,t.description],[`Capabilities`,t.capabilities.map(e=>e.id).join(`, `)||`none`],[`Actions`,t.actions.map(e=>e.id).join(`, `)||`none`]])}}),install:D({meta:{name:`install`,description:`Install one provider package from npm`},args:{package:{type:`positional`,required:!0,description:`npm package name with an optional exact version.`},yes:{type:`boolean`,default:!1,description:`Approve third-party code installation without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.package)??``,n=L(e),r=M(e.yes),i=z({json:n});if(!r&&!i)throw new E(`INVALID_INPUT`,`Provider installation runs third-party code. Review the package, then pass --yes to approve it in JSON or CI mode.`);let a;try{a=await l(t)}catch(e){throw new E(`INVALID_INPUT`,e instanceof Error?e.message:String(e))}if(!r&&(jr(`Install provider package`),O([`${a.package}@${a.version}`,`Publisher: ${a.publisher??`not supplied by npm`}`,`Repository: ${a.repository??`not supplied by npm`}`,`Integrity: ${a.integrity}`].join(`
|
|
203
|
+
`),`Package`),O(`This package can read files, use the network, and run with the same local permissions as seo. Installing it is not a security review or endorsement.`,`Third-party code`),!U(await Ar({message:`Install ${a.package}@${a.version}?`,initialValue:!1})))){Fr(`Provider was not installed.`);return}let o;try{o=await Fn(a)}catch(e){throw new E(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}let s={installed:o,package:a};if(n){B(s);return}Fr(`Installed provider ${o.id} from ${o.package}@${o.version}.`)}}),remove:D({meta:{name:`remove`,description:`Remove one installed provider extension`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},yes:{type:`boolean`,default:!1,description:`Remove the package without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``,n=L(e),r=M(e.yes),i=z({json:n});if(!r&&!i)throw new E(`INVALID_INPUT`,`Pass --yes to remove a provider extension in JSON or CI mode.`);if(!r&&!U(await Ar({message:`Remove provider ${t}?`,initialValue:!1})))return;let a=await Ie(t);if(!a)throw new E(`INVALID_INPUT`,`Provider ${t} is not installed.`);if(n){B({removed:a});return}process.stdout.write(`Removed ${t}.\n`)}}),connect:D({meta:{name:`connect`,description:`Validate and save one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},account:{type:`string`,description:`Account fields as JSON. Secret fields must use their environment variables.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``,n=L(e),i=z({json:n}),a=await ql(t),o={...Wt(t),...Gl(A(e.account))},s={};if(i){jr(`Connect ${a.displayName}`),a.connection.verificationNotice&&O(a.connection.verificationNotice,`Connection check`);for(let e of a.connection.fields){if(e.kind===`account`){let t=U(await Rr({message:e.label,placeholder:e.description,initialValue:o[e.id],validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(o[e.id]=t.trim());continue}if(e.envVar&&process.env[e.envVar]?.trim())continue;let t=U(await Ir({message:e.label,validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(s[e.id]=t.trim())}}o=Jl(a,o);let c={...s};Object.keys(c).length===0&&(c=await _({providerId:t,account:o,fields:a.connection.fields})),await u({providerId:t,account:o,credentials:c}),Object.keys(s).length>0&&await m({providerId:t,account:o,credentials:s}),r(t,o);let l={provider:t,connected:!0,account:o,capabilities:(a.capabilities??[]).map(e=>e.id),actions:(a.actions??[]).map(e=>e.id)};n?B(l):Fr(`Connected ${a.displayName}. Run seo providers describe ${t} to see what it can do.`)}}),status:D({meta:{name:`status`,description:`Show one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},check:{type:`boolean`,default:!1,description:`Run the provider connection check.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``,n=await ql(t),r=Wt(t),i;try{i=await _({providerId:t,account:r,fields:n.connection.fields})}catch{i=void 0}let a=n.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1).every(e=>!!r[e.id]?.trim())&&i!==void 0,o=M(e.check);if(o&&!a)throw new E(`AUTH_REQUIRED`,`Provider ${t} is not connected. Run \`seo providers connect ${t}\`.`);o&&i&&await u({providerId:t,account:r,credentials:i});let s={provider:t,installed:!0,connected:a,account:r,capabilities:(n.capabilities??[]).map(e=>e.id),actions:(n.actions??[]).map(e=>e.id),check:o?`passed`:`not-requested`};if(L(e)){B(s);return}H([`Provider`,`Connected`,`Capabilities`,`Check`],[[n.displayName,a?`yes`:`no`,s.capabilities.join(`, `),s.check]])}}),disconnect:D({meta:{name:`disconnect`,description:`Remove one saved provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``;await ql(t),await Le({providerId:t,account:Wt(t)}),be(t);let n={provider:t,disconnected:!0,note:`Environment variables were not changed.`};L(e)?B(n):process.stdout.write(`Disconnected ${t}. ${n.note}\n`)}}),run:D({meta:{name:`run`,description:`Run one action supplied by an installed provider`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},action:{type:`positional`,required:!0,description:`Provider action id.`},account:{type:`string`,description:`Account fields as JSON. Defaults to the saved connection.`},params:{type:`string`,description:`Action parameters as a JSON object.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local provider cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.id)??``,n=A(e.action)??``;await ql(t),B(await Vt({providerId:t,actionId:n,account:{...Wt(t),...Gl(A(e.account))},params:Kl(A(e.params)),refresh:M(e.refresh)}))}}),doctor:D({meta:{name:`doctor`,description:`Validate installed provider packages and entry points`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Yl(),n={ok:t.installed.every(e=>e.loadStatus===`ready`),installed:t.installed};if(L(e)){B(n);return}if(n.installed.length===0){process.stdout.write(`No provider extensions are installed.
|
|
204
|
+
`);return}H([`Provider`,`Package`,`Version`,`Status`],n.installed.map(e=>[e.id,e.package,e.version,e.error??e.loadStatus]))}}),ahrefs:Vl,bing:Ul,dataforseo:Wl}}),Zl={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,experiments:`Experiments`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function Ql(e){let t=A(e);if(t){if(!Cn.includes(t))throw new E(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${Cn.join(`, `)}.`);return t}}async function $l(e,t){let n=A(e),r=A(t);if(n&&r)throw new E(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await yr(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new E(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new E(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function eu(e){if(!e.isError)return;let t=e.structuredContent;throw new E(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function tu(e){if(!e||typeof e!=`object`)return[];let t=e,n=new Set(t.required??[]);return Object.entries(t.properties??{}).map(([e,t])=>{let r=Array.isArray(t.type)?t.type.join(` | `):t.type??`any`;return{name:e,type:r===`array`&&t.items?.type?`${t.items.type}[]`:t.format?`${r} (${t.format})`:r,required:n.has(e),description:t.description}})}const nu=D({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:D({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${Cn.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=on(Ql(e.category));if(L(e)){B({reports:t,categories:Cn});return}Na(t,{noun:`report`,categoryLabels:Zl}),process.stdout.write(`
|
|
205
|
+
`),ka({title:`Need the detail?`,body:`Describe one report to see when to use it, what it needs, and what the result can prove.`,command:`seo reports describe <id>`})}}),describe:D({meta:{name:`describe`,description:`Show the purpose and parameter schema for one report`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Lt(A(e.id)??``);if(L(e)){B({report:t});return}Aa(t.name,t.description),process.stdout.write(`
|
|
206
|
+
`),V([[`ID`,t.id],[`Category`,t.category],[`Outcome`,t.outcome]]),process.stdout.write(`
|
|
207
|
+
`),Ia(`Use when`,t.useWhen),process.stdout.write(`
|
|
208
|
+
`),Ia(`Avoid when`,t.avoidWhen),process.stdout.write(`
|
|
209
|
+
`),Ia(`Read in order`,t.readOrder),process.stdout.write(`
|
|
210
|
+
`),Ia(`Do not claim`,t.doNotClaim),process.stdout.write(`
|
|
211
|
+
`),Fa(`Verify`,t.verify),process.stdout.write(`
|
|
212
|
+
`),ka({title:`Implement findings`,body:t.agentWorkflow.completion,command:t.agentWorkflow.actionView.cli}),t.related.length>0&&(process.stdout.write(`
|
|
213
|
+
`),Ia(`Related reports`,t.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
|
|
214
214
|
Parameters
|
|
215
|
-
`),
|
|
216
|
-
`),
|
|
217
|
-
`),
|
|
218
|
-
`),
|
|
219
|
-
`),
|
|
220
|
-
`),
|
|
221
|
-
`||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new
|
|
215
|
+
`),La(tu(t.inputSchema)),process.stdout.write(`
|
|
216
|
+
`),ka({title:`Need the exact schema?`,command:`seo reports describe ${t.id} --json`})}}),run:D({meta:{name:`run`,description:`Run one report with JSON parameters`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},params:{type:`string`,description:`Report parameters as a JSON object.`},"params-file":{type:`string`,description:`Read report parameters from a JSON file.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and retained inventories without the full report body.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await st(A(e.id)??``,await $l(e.params,e[`params-file`]),{view:M(e[`actions-only`])?`actions`:`full`});if(eu(t),L(e)){B(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),ru=D({meta:{name:`rules`,description:`List crawler rule ids and default guidance`},args:{category:{type:`string`,description:`Filter rules by category.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.category),n=Pe().filter(e=>!t||e.category===t);if(L(e)){B({rules:n});return}Na(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),iu=D({meta:{name:`explain`,description:`Explain one crawler rule in plain English`},args:{rule:{type:`string`,required:!0,description:`Rule id to explain, for example missing_title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e.rule)??``,n=Bn(t);if(!n)throw Error(`Unknown rule: ${t}`);if(L(e)){B(n);return}V([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
|
|
217
|
+
`),Fa(`Why it matters`,n.whyItMatters),process.stdout.write(`
|
|
218
|
+
`),Fa(`How to fix`,n.howToFix),process.stdout.write(`
|
|
219
|
+
`),Fa(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
|
|
220
|
+
`),Fa(`How to verify`,n.howToVerify)}});function au(e){return JSON.stringify(e)}const ou=D({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:D({meta:{name:`cron`,description:`Print crontab lines for local recurring SEO workflows`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL for technical-watch. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs for index-watch.`},hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly technical-watch day. Defaults to client setting, then Monday.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t}}),r=n.site,i=A(e.url)??n.client?.startUrl??So(n.site),a=N(e.hour)??9,o=N(e.minute)??0,s=N(e.weekday)??n.client?.technicalWeekday??1,c=n.client?`--project ${au(n.client.id)}`:`--site ${au(r)}`,l=A(e.urls)??n.client?.watchUrls.join(`,`),u=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,c,i?`--url ${au(i)}`:void 0,l?`--urls ${au(l)}`:void 0,`--json`].filter(Boolean).join(` `)}];if(t){B({site:r,lines:u});return}let d=$t();V([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of u)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${au(`${d.logDir}/${e.name}.log`)} 2>&1\n`)}})}});function su(){return lu(`skills`,process.env.SEO_SKILLS_DIR)}function cu(){return lu(`evals`,process.env.SEO_EVALS_DIR)}function lu(e,t){if(t)return hr(t);let n=dr(_r(import.meta.url));for(let t=0;t<6;t+=1){let t=pr(n,e);if($n(pr(t,`README.md`)))return t;n=dr(n)}throw new E(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function uu(e){return tr(pr(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const du=`claude -p`;function fu(){let e=cu();return nr(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:ur(t.name,`.json`),path:pr(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function pu(e,t){let n;try{n=tr(t,`utf8`)}catch{throw new E(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new E(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new E(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new E(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new E(`INVALID_INPUT`,`Eval file ${e}.json must have a non-empty evals array.`);let a=new Set;return{subject:e,evals:i.evals.map((t,n)=>{let r=t,i=`${e}.json eval ${n+1}`;if(!Number.isInteger(r?.id))throw new E(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new E(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new E(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new E(`INVALID_INPUT`,`${i}: expected_output must be a non-empty string.`);if(!Array.isArray(r.assertions)||r.assertions.length===0||r.assertions.some(e=>typeof e!=`string`||e.trim().length===0))throw new E(`INVALID_INPUT`,`${i}: assertions must be a non-empty array of non-empty strings.`);if(r.files!==void 0&&!Array.isArray(r.files))throw new E(`INVALID_INPUT`,`${i}: files must be an array.`);return{id:o,prompt:r.prompt,expected_output:r.expected_output,assertions:r.assertions,files:r.files??[]}})}}function mu(){let e=pr(su(),`seo`,`SKILL.md`);if(!$n(e))throw new E(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return tr(e,`utf8`)}function hu(e){let t=[],n=``,r=!1,i=!1,a=!1;for(let o=0;o<e.length;o+=1){let s=e[o];if(i){s===`'`?i=!1:n+=s,r=!0;continue}if(a){s===`"`?a=!1:s===`\\`&&(e[o+1]===`"`||e[o+1]===`\\`)?(n+=e[o+1],o+=1):n+=s,r=!0;continue}if(s===`'`){i=!0,r=!0;continue}if(s===`"`){a=!0,r=!0;continue}if(s===`\\`){o+1<e.length&&(n+=e[o+1],o+=1,r=!0);continue}if(s===` `||s===` `||s===`
|
|
221
|
+
`||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new E(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function gu(e,t){let n=hu(e);if(n.length===0)throw new E(`INVALID_INPUT`,`Agent command must not be empty.`);let r=n.some(e=>e.includes(`{prompt}`));return{argv:r?n.map(e=>e.split(`{prompt}`).join(t)):n,usesPlaceholder:r}}function _u(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function vu(e,t,n,r=!1){let i=[`You are judging whether an AI agent response satisfies a list of assertions.`,`Judge only against the assertions. Be strict but fair.`,``,`Original request:`,e,``,`Agent response:`,t,``,`Assertions to evaluate, in this exact order:`,`<assertions>`,n.map((e,t)=>`${t+1}. ${e}`).join(`
|
|
222
222
|
`),`</assertions>`,``,`Judge outcomes, not narration. An assertion about reading, checking, or`,`following fields or steps passes when the reply demonstrably used that`,`information or produced the result it protects, even if the reply never`,`names the field or step. Fail it only when the reply contradicts it or`,`shows the work was not done.`,``,`Reply with STRICT JSON only. No prose, no markdown, no code fence:`,`{"results":[{"assertion":"<assertion text>","pass":true,"reason":"<short reason>"}]}`,`Return exactly ${n.length} result${n.length===1?``:`s`}, one per assertion, in the same order.`].join(`
|
|
223
|
-
`);return r?`${i}\n\nYour previous reply could not be parsed as the required JSON with one result per assertion. Reply again with STRICT JSON only, in the exact shape shown, and nothing else.`:i}function
|
|
223
|
+
`);return r?`${i}\n\nYour previous reply could not be parsed as the required JSON with one result per assertion. Reply again with STRICT JSON only, in the exact shape shown, and nothing else.`:i}function yu(e){let t=e.indexOf(`{`);if(t===-1)return;let n=0,r=!1,i=!1;for(let a=t;a<e.length;a+=1){let o=e[a];if(r){i?i=!1:o===`\\`?i=!0:o===`"`&&(r=!1);continue}if(o===`"`)r=!0;else if(o===`{`)n+=1;else if(o===`}`&&(--n,n===0))try{return JSON.parse(e.slice(t,a+1))}catch{return}}}function bu(e,t){let n=yu(e);if(!n||typeof n!=`object`)return{error:`Judge reply did not contain a JSON object.`};let r=n.results;return Array.isArray(r)?r.length===t.length?{verdicts:t.map((e,t)=>{let n=r[t];return{assertion:e,verdict:n?.pass===!0?`pass`:`fail`,reason:typeof n?.reason==`string`&&n.reason.trim().length>0?n.reason.trim():`No reason given.`,source:`judge verdict`}})}:{error:`Judge returned ${r.length} results for ${t.length} assertions.`}:{error:`Judge reply had no results array.`}}function xu(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=Tr(i,a,{stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){n({stdout:``,stderr:``,code:null,timedOut:!1,spawnError:e instanceof Error?e.message:String(e),durationMs:Date.now()-r});return}let s=``,c=``,l=!1,u=!1,d,f=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),p=e=>{l||(l=!0,clearTimeout(f),d&&clearTimeout(d),n({...e,durationMs:Date.now()-r}))};o.stdout?.on(`data`,e=>{s+=e}),o.stderr?.on(`data`,e=>{c+=e}),o.on(`error`,e=>{p({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{p({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function Su(e,t,n){let{argv:r,usesPlaceholder:i}=gu(e,t);return xu(r,{input:i?void 0:t,timeoutMs:n})}async function Cu(e,t){let n=Date.now(),r=_u(t.skillBody,e.prompt),i=await Su(t.agent,r,t.timeoutMs),a=i.stdout.trim(),o={id:e.id,prompt:e.prompt,reply:a,agentDurationMs:i.durationMs};if(i.spawnError)return{...o,status:`errored`,error:`Agent command failed to start: ${i.spawnError}`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(i.timedOut)return{...o,status:`errored`,error:`Agent timed out after ${Math.round(t.timeoutMs/1e3)}s.`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:wu(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=vu(e.prompt,a,e.assertions,r>0),l=await Su(t.judge,i,t.timeoutMs);if(s+=l.durationMs,l.spawnError)return{...o,status:`errored`,error:`Judge command failed to start: ${l.spawnError}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(l.timedOut)return{...o,status:`errored`,error:`Judge timed out after ${Math.round(t.timeoutMs/1e3)}s.`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)};let u=bu(l.stdout,e.assertions);if(`verdicts`in u){let e=u.verdicts.some(e=>e.verdict===`fail`);return{...o,status:e?`failed`:`passed`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:u.verdicts}}c=u.error}return{...o,status:`errored`,error:`Judge output could not be parsed after a retry: ${c}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)}}function wu(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Tu(e,t,n){let r=Array(e.length),i=0,a=async()=>{for(;;){let t=i;if(i+=1,t>=e.length)return;r[t]=await n(e[t],t)}},o=Math.max(1,Math.min(t,e.length));return await Promise.all(Array.from({length:o},()=>a())),r}function Eu(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Du(e){let t=[],n=A(e.subject);n&&t.push(n);let r=e._;if(Array.isArray(r))for(let e of r)typeof e==`string`&&e.length>0&&t.push(e);return[...new Set(t)]}function Ou(e){let t=fu();if(e.length===0)return t;let n=new Map(t.map(e=>[e.subject,e]));return e.map(e=>{let r=n.get(e);if(!r){let n=t.map(e=>e.subject).join(`, `);throw new E(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function ku(e){let t=fu().map(e=>{let t=pu(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){B({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
|
|
224
224
|
`);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
|
|
225
|
-
`);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function
|
|
225
|
+
`);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Au(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function ju(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
|
|
226
226
|
|
|
227
227
|
`):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
|
|
228
228
|
|
|
229
|
-
`);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${
|
|
230
|
-
`);for(let t of e.assertions)process.stdout.write(` - [ ] ${t.assertion}\n`);continue}for(let t of e.assertions){let e=t.verdict===`pass`?`pass`:t.verdict===`fail`?`fail`:`unjudged`;process.stdout.write(` - ${e}: ${t.assertion}\n`),t.reason&&process.stdout.write(` judge verdict: ${t.reason}\n`)}}let e=
|
|
231
|
-
`);for(let e of t)
|
|
232
|
-
`),
|
|
233
|
-
`)}}),eval:
|
|
234
|
-
`),
|
|
235
|
-
`)}}),
|
|
229
|
+
`);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Au(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Nu(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Nu(e.reply,200)}\n`),process.stdout.write(` assertions to review:
|
|
230
|
+
`);for(let t of e.assertions)process.stdout.write(` - [ ] ${t.assertion}\n`);continue}for(let t of e.assertions){let e=t.verdict===`pass`?`pass`:t.verdict===`fail`?`fail`:`unjudged`;process.stdout.write(` - ${e}: ${t.assertion}\n`),t.reason&&process.stdout.write(` judge verdict: ${t.reason}\n`)}}let e=Eu(n.evals);process.stdout.write(`\nSubject ${n.subject}: ${e.passed} passed, ${e.failed} failed, ${e.errored} errored${e.unjudged?`, ${e.unjudged} unjudged`:``}.\n\n`)}let n=Mu(e);process.stdout.write(`Totals: ${n.passed} passed, ${n.failed} failed, ${n.errored} errored${n.unjudged?`, ${n.unjudged} unjudged`:``} across ${n.evals} evals.\n`)}function Mu(e){let t={passed:0,failed:0,errored:0,unjudged:0,evals:0};for(let n of e)t.passed+=n.passed,t.failed+=n.failed,t.errored+=n.errored,t.unjudged+=n.unjudged,t.evals+=n.evals.length;return t}function Nu(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const Pu=D({meta:{name:`eval`,description:`Run behaviour evals for the seo skill against a local agent and judge the results`},args:{subject:{type:`positional`,required:!1,description:`Eval subject (report id or job). Omit to run every subject.`},list:{type:`boolean`,default:!1,description:`List eval subjects with their eval counts and exit.`},id:{type:`string`,description:`Run one eval id within a single subject.`},agent:{type:`string`,default:du,description:`Agent command. Use {prompt} to place the prompt in argv, otherwise it is sent on stdin.`},"judge-agent":{type:`string`,description:`Judge command. Defaults to the same command as --agent.`},judge:zi(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:zi(`Send the seo router skill as context before the prompt.`,`Omit the seo router skill context.`),timeout:{type:`string`,default:`300`,description:`Timeout in seconds per agent or judge call.`},concurrency:{type:`string`,default:`1`,description:`Evals to run in parallel (max 4).`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e);if(e.list===!0){ku(t);return}let n=Ou(Du(e)),r=N(e.id);if(r!==void 0&&n.length!==1)throw new E(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=N(e.timeout)??300;if(!(i>0))throw new E(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,N(e.concurrency)??1)),o=A(e.agent)??du,s=e.judge===!1?null:A(e[`judge-agent`])??o,c=e.skill===!1?null:mu(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=pu(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new E(`INVALID_INPUT`,`No eval with id ${r} in subject ${e.subject}.`);t||process.stderr.write(`Running ${n.length} eval${n.length===1?``:`s`} for ${e.subject}...\n`);let i=await Tu(n,a,e=>Cu(e,l)),o=Eu(i);u.push({subject:e.subject,...o,evals:i})}let d=Mu(u),f=d.failed>0||d.errored>0;t?B({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):ju(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Fu(){let e=su();return nr(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&$n(pr(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:uu(pr(e,t.name)),path:pr(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function Iu(e){let t=Fu();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new E(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const Lu=D({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:D({meta:{name:`list`,description:`Show the packaged SEO skill`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=Fu();if(L(e)){B({skills:t});return}Aa(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
|
|
231
|
+
`);for(let e of t)V([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
|
|
232
|
+
`),ka({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:D({meta:{name:`path`,description:`Print a packaged agent skill path`},args:{name:{type:`positional`,description:`Skill name. Omit it for the skill directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=A(e.name),n=t?Iu(t)[0]?.path:su();L(e)?B({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:D({meta:{name:`install`,description:`Install the SEO skill for coding agents`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=L(e),n=ns({quiet:t});if(t){B(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new E(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
|
|
233
|
+
`)}}),eval:Pu}}),Ru=D({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=lt(),n=t();V(e.map(e=>[e.label,`${e.path} · ${Ra(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
|
|
234
|
+
`),V(Object.entries(n.counts).map(([e,t])=>[e,String(t)]))}}),zu=D({meta:{name:`reset`,description:`Delete saved credentials and all local data`},args:{yes:{type:`boolean`,default:!1,description:`Confirm deletion without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e);if(!e.yes&&!z({json:t}))throw new E(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||U(await Ar({message:`Delete saved credentials and all local data?`,initialValue:!1})))){kr(`Reset aborted.`);return}await dt(),t?B({reset:!0}):process.stdout.write(`Reset complete.
|
|
235
|
+
`)}}),Bu=D({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await C(M(e.refresh),A(e.account));if(L(e)){B({sites:t});return}H([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Vu=D({meta:{name:`updates`,description:`List official Google Search Status ranking updates`},args:{product:{type:`string`,default:`Ranking`},limit:{type:`string`,default:`10`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await Xe({product:A(e.product),limit:N(e.limit)});if(L(e)){B({updates:t});return}Pa(t.map(e=>({title:e.name,meta:[e.type,e.status,`${e.start.slice(0,10)} to ${e.end?.slice(0,10)??`present`}`]})),{empty:`No matching Google Search updates.`})}}),Hu={ci:`disabled automatically in CI or tests`,do_not_track:`disabled by DO_NOT_TRACK`,environment:`disabled by a telemetry environment variable`,local_setting:`disabled in local settings`,invalid_state:`disabled because the local state file is invalid`};function Uu(e){return{enabled:e.enabled,...e.reason?{reason:e.reason}:{},stateFile:e.stateFile,...e.state?{firstRunAt:e.state.firstRunAt,cohort:e.state.cohort,sentMilestones:e.state.sentMilestones}:{}}}function Wu(e,t){let n=Uu(e);if(t){B(n);return}V([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?Hu[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
|
|
236
236
|
Only tool usage is sent. URLs, report data, Google data, and identifiers are never sent.
|
|
237
237
|
`),process.stdout.write(`Details: https://seoskill.dev/telemetry
|
|
238
|
-
`)}const Iu={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Lu=E({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:E({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Iu},run:async({args:e})=>Fu(Gt(),I(e))}),enable:E({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Iu},run:async({args:e})=>{Fu(Ct(!0),I(e))}}),disable:E({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Iu},run:async({args:e})=>{Fu(Ct(!1),I(e))}})}}),Ru=E({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...qs,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Defaults to site.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches. Defaults to 8.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},limit:{type:`string`,description:`Maximum queue items to print. Defaults to 25.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=await Vu(e),i=Bu(e),a=k(e.site)||n||!i&&!r.length?await U({client:n,site:k(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?ho(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await dt({url:o,projectId:a?.client?.id,site:a?.site,mode:zu(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:M(e[`max-pages`]),maxDepth:M(e[`max-depth`]),concurrency:M(e.concurrency),refresh:j(e.refresh),fetchRate:Ri({...e,"fetch-concurrency":e.concurrency}),include:P(e.include),exclude:P(e.exclude),googleAnalyticsPropertyId:k(e[`google-analytics-property`]),analyticsConnection:Js(e,ct(a?.client)),useSitemap:!Fi(e,`sitemap`),respectRobots:!Fi(e,`robots`),checkExternal:!Fi(e,`external`),js:!!j(e.js),limit:M(e.limit)});if(t){z(s);return}$c(s),V([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),Y(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function zu(e){let t=k(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Bu(e){let t=k(e.crawlUrl),n=k(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Vu(e){let t=P(e.urls)??[],n=k(e[`urls-file`]);return n&&t.push(...Hu(await pr(n,`utf8`))),[...new Set(t)]}function Hu(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}const Uu={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function Wu(e){let t=k(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new T(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(k(e.output)||k(e.view))throw new T(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(j(e.json))throw new T(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=k(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new T(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:k(e.output),view:n}}function Gu(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function Ku(e){let t=(e.date??new Date).toISOString().slice(0,10),n=Gu(e.projectId??e.site)||`site`;return lr(process.cwd(),`${n}-${Gu(e.reportName)}-${t}.html`)}async function qu(e){let t=lr(e.options.output??Ku({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await fr(ar(t),{recursive:!0}),await gr(t,Yr({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function Ju(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}function Yu(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function Xu(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function Zu(e){try{return new URL(e).origin}catch{throw new T(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function Qu(e){let t=e.topRows??20,{evidence:n}=e;return{source:n.source,exportedAt:n.exportedAt,importedAt:n.importedAt,files:n.files,queries:{totalRows:n.queries.totalRows,capped:n.queries.capped,topRowsShown:Math.min(t,n.queries.rows.length),rows:n.queries.rows.slice(0,t)},pages:{totalRows:n.pages.totalRows,capped:n.pages.capped,topRowsShown:Math.min(t,n.pages.rows.length),rows:n.pages.rows.slice(0,t)},...e.reconciliation?{reconciliation:e.reconciliation}:{},...e.pageInventory?{pageInventory:e.pageInventory}:{},warnings:n.warnings,caveats:n.caveats}}function $u(e){if(!e.rows.length)return;let t=e.rows.slice(0,15);process.stdout.write(`\nContent inventory${e.pageCount>1?` page ${e.page} of ${e.pageCount}`:``} (${e.rows.length} exported page${e.rows.length===1?``:`s`} with suggested dispositions)\n`),V([`Path`,`Clicks`,`Impressions`,`Position`,`Suggested`],t.map(e=>[K(e.path,48),e.clicks,e.impressions,e.position??`-`,e.suggestedDisposition])),e.rows.length>t.length&&process.stdout.write(`Showing ${t.length} of ${e.rows.length} tiered rows.\n`),e.pageCount>1&&process.stdout.write(`Showing inventory page ${e.page} of ${e.pageCount}; ${e.totalPages} retained rows are available.\n`),process.stdout.write(`${e.note}\n`)}function ed(e){let t=e.evidence.queries.totalRows,n=e.evidence.pages.totalRows,r=`Loaded a local Search Console export (${t} query row${t===1?``:`s`}, ${n} page row${n===1?``:`s`}).`;if(!e.reconciliation)return r;let i=e.reconciliation.unreachedCount;return i===0?`${r} Every exported page URL was reached by the crawl.`:`${r} ${i} exported page URL${i===1?``:`s`} with impressions were not reached by this crawl.`}function td(e){let t=e.crawledUrls;return`Found ${e.highIssues} high, ${e.mediumIssues} medium, and ${e.lowIssues} low technical issues across ${t} crawled ${t===1?`page`:`pages`}.`}function nd(e){return pt(e,{limit:e.issues.length})}function rd(e){return _n(e,{limit:e.issues.length})}function id(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function ad(e,t){if(e===t)return 0;let n=[...e],r=[...t],i=Math.min(n.length,r.length);for(let e=0;e<i;e+=1){let t=(n[e]?.codePointAt(0)??0)-(r[e]?.codePointAt(0)??0);if(t!==0)return t}return n.length-r.length}function od(e){return e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,` `).trim()}function sd(e){try{let t=new URL(e),n=t.pathname.length>1&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname;return`${t.origin}${n}${t.search}`}catch{return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}}function cd(e,t=[]){return[...e.map(e=>({url:e.finalUrl||e.url,title:e.title})),...t]}function ld(e){try{let t=new URL(e);return`${t.pathname}${t.search}`||`/`}catch{return e}}function ud(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}const dd=[`review: shares a search phrase with another page title; confirm query-to-page evidence and intent before consolidating`,`keep: has clicks in the export window`,`review: has impressions but no crawl path reached it`,`update: has impressions with a weak average position (average position above 20 in the export)`,`review: low observed demand in the export window`];function fd(e){let t=e.maxRows??50,n=e.page??1;if(!Number.isSafeInteger(t)||t<1)throw new T(`INVALID_INPUT`,`Inventory page size must be positive.`);if(!Number.isSafeInteger(n)||n<1)throw new T(`INVALID_INPUT`,`Inventory page must be positive.`);let r=new Set;for(let t of e.reconciliation?.unreachedPages??[]){let e=ud(t.url);e!==null&&r.add(e)}let i=new Map,a=[...e.crawledPages].sort((e,t)=>ad(e.url,t.url));for(let e of a){if(typeof e.title!=`string`)continue;let t=ud(e.url);t!==null&&(i.has(t)||i.set(t,e.title))}let o=new Map;for(let t of e.overlap?.candidates??[])for(let e of t.pages){let n=ud(e.url);n!==null&&(o.has(n)||o.set(n,t.query))}let s=[...e.pages].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||ad(e.url,t.url)),c=Math.ceil(s.length/t);if(c>0&&n>c)throw new T(`INVALID_INPUT`,`Inventory page ${n} does not exist; choose a page from 1 to ${c}.`);let l=(n-1)*t,u=s.slice(l,l+t).map(t=>{let n=ud(t.url),a=n===null||!e.reconciliation?null:r.has(n)?!1:!e.reconciliation.capped||null,s=n===null?null:i.get(n)??null,c=n===null?null:o.get(n)??null,l,u;return c===null?t.clicks>0?(l=`keep`,u=`has clicks in the export window`):t.impressions>0&&a===!1?(l=`review`,u=`has impressions but no crawl path reached it`):t.impressions>0&&t.position!==null&&t.position>20?(l=`update`,u=`has impressions with a weak average position`):(l=`review`,u=`low observed demand in the export window`):(l=`review`,u=`shares a search phrase with another page title and needs query-to-page and intent verification`),{url:t.url,path:ld(t.url),clicks:t.clicks,impressions:t.impressions,position:t.position,reachedByCrawl:a,title:s,overlapQuery:c,suggestedDisposition:l,tier:u}}),d=c>1,f=[`Suggested dispositions are heuristic tiers from the export numbers and crawl reach. Decide each page with the owner.`];return d&&f.push(`This is inventory page ${n} of ${c}. Fetch every page before treating the returned inventory as complete.`),e.sourceCapped===!0&&f.push(`The source import was capped, so rows missing from this inventory are unknown.`),{rows:u,totalPages:e.pages.length,capped:e.sourceCapped===!0||d,sourceCapped:e.sourceCapped===!0,page:n,pageSize:t,pageCount:c,nextPage:n<c?n+1:null,criteria:[...dd],note:f.join(` `)}}function pd(e){let t=e.maxQueries??50,n=e.maxCandidates??10,r=new Map;for(let t of e.queries){let e=od(t.query);if(e.split(` `).length<2)continue;let n=r.get(e);n?(n.impressions+=t.impressions,n.clicks+=t.clicks):r.set(e,{impressions:t.impressions,clicks:t.clicks})}let i=r.size,a=[...r.entries()].map(([e,t])=>({query:e,...t})).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||ad(e.query,t.query)).slice(0,t),o=new Map,s=[...e.pages].sort((e,t)=>ad(e.url,t.url));for(let e of s){if(typeof e.title!=`string`)continue;let t=od(e.title);if(!t)continue;let n=sd(e.url);o.has(n)||o.set(n,{url:e.url,title:e.title,normalizedTitle:t})}let c=[...o.values()],l=[];for(let e of a){let t=` ${e.query} `,n=c.filter(e=>` ${e.normalizedTitle} `.includes(t));n.length<2||l.push({query:e.query,impressions:e.impressions,clicks:e.clicks,pages:n.map(e=>({url:e.url,title:e.title})).sort((e,t)=>ad(e.url,t.url))})}l.sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||ad(e.query,t.query));let u=i>a.length,d=l.length>n;return{candidates:l.slice(0,n),eligibleQueries:i,consideredQueries:a.length,queryLimitReached:u,candidateLimitReached:d,capped:u||d}}const md={high:0,medium:1,low:2};function hd(e,t){let n=md[e.fix.severity]-md[t.fix.severity];return n===0?t.fix.count===e.fix.count?t.fix.score===e.fix.score?e.fix.ruleId<t.fix.ruleId?-1:+(e.fix.ruleId>t.fix.ruleId):t.fix.score-e.fix.score:t.fix.count-e.fix.count:n}function gd(e){let t=e.unreachedAudit,n=[...e.topFixes.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.topFixes??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(hd),r=[...e.reviewObservations.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.reviewObservations??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(hd),i=[];for(let{fix:t,prefix:r,source:a}of n){let n=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`fix`,title:`${r}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:t.howToFix,confidence:`high`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...n?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:n,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}let a=e.reconciliation;if(a&&a.unreachedCount>0){let e=a.unreachedPages.reduce((e,t)=>e+t.impressions,0);i.push({id:`export:unreached-pages`,kind:`review`,title:`${a.unreachedCount} exported page URL${a.unreachedCount===1?``:`s`} with search impressions were not reached by this crawl`,action:`These pages carry ${e} combined impressions across the ${a.unreachedPages.length} retained export rows, but no crawl path reached them. Add internal links or sitemap entries when they should be reachable. The join used URL paths from a local export; treat it as a discovery lead within this crawl bound, not proof of orphan status.`,confidence:`medium`,affectedCount:a.unreachedCount,evidence:{retainedRows:a.unreachedPages.length,impressions:e},review:{question:`Should each retained exported page be reachable from the current site?`,changeOnlyIf:`Add a crawl path only for a page that should remain part of the current site and has no intended exclusion.`,ifNotNeeded:`Record no change for a page that is intentionally retired, excluded, or awaiting an owner decision.`,doNot:[`Do not call a page orphaned from this path-only join.`,`Do not redirect unrelated pages to one generic destination.`]},verification:{expected:`Every retained exported page is reachable in the rerun or explicitly accounted for as deferred or no-change with owner intent.`}})}let o=e.topicOverlap,s=o?.candidates[0];if(o&&s){let e=o.candidates.length,t=s.pages.map(e=>ld(e.url));i.push({id:`export:topic-overlap`,kind:`review`,title:`topic overlap candidates: ${e} exported ${e===1?`query appears`:`queries appear`} in 2+ page titles`,action:`Top candidate: "${s.query}" (${s.impressions} impressions, ${s.clicks} clicks) appears in the titles of ${t.join(` and `)}. This is a title-based heuristic built from the export's separate query and page tables. The export contains no query-to-page mapping, so it cannot show which pages Google served for the query. Verify with the cannibalisation report (seo cannibal) after connecting a Search Console property.`,confidence:`medium`,affectedCount:e,evidence:{topQuery:s.query,topPages:s.pages,impressions:s.impressions,clicks:s.clicks},review:{question:`Does query-to-page evidence confirm that these pages compete for the same search intent?`,changeOnlyIf:`Consolidate or redirect only after query-to-page evidence and page intent confirm real overlap.`,ifNotNeeded:`Keep the pages separate and record why their intents differ.`,doNot:[`Do not merge or redirect pages from a title-match heuristic alone.`]},verification:{expected:`Confirm intent with query-to-page evidence before consolidating or redirecting either page.`}})}let c=e.pageInventory,l=c?.rows[0];if(c&&l){let e={keep:0,update:0,review:0};for(let t of c.rows)e[t.suggestedDisposition]+=1;let t=[...c.pageCount>1?[`page ${c.page} of ${c.pageCount}; ${c.rows.length} of ${c.totalPages} retained rows`]:[],...c.sourceCapped?[`source import capped`]:[]],n=t.length?` (${t.join(`; `)})`:``;i.push({id:`export:content-inventory`,kind:`review`,title:`content inventory: ${c.rows.length} exported page${c.rows.length===1?``:`s`} tiered with suggested dispositions`,action:`Suggested tiers: keep ${e.keep}, update ${e.update}, review ${e.review}${n}. Example: ${l.path} (${l.clicks} clicks, ${l.impressions} impressions, position ${l.position??`not in the export`}) is tiered ${l.suggestedDisposition} because it ${l.tier}. ${c.note} Work the inventory page by page instead of applying one blanket policy.`,confidence:`medium`,affectedCount:c.rows.length,evidence:{dispositions:e,capped:c.capped},review:{question:`Has every returned inventory row received an evidence-backed disposition?`,changeOnlyIf:`Apply a content change, removal, or redirect only after deciding that row from its own evidence and product relevance.`,ifConfirmed:`Record one disposition and reason for every returned inventory row.`,ifNotNeeded:`Keep a page unchanged when its evidence and current product role support keeping it.`,doNot:[`Do not use one blanket disposition for the inventory.`,`Do not redirect unrelated URLs to a generic hub.`]},verification:{expected:`Every returned inventory row has an owner-confirmed keep, update, or review decision; every inventory page has been fetched; redirected rows resolve to the intended destination.`}})}for(let{fix:t,prefix:n,source:a}of r){let r=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`review`,title:`${n}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:`Observed state that needs intent confirmation before treating it as a defect. ${t.howToFix}`,confidence:`medium`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...r?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:r,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}return i}function _d(e,t){let n=[];for(let r of e.unreachedPages.slice(0,t))try{let t=new URL(r.url);n.push(`${e.crawlOrigin}${t.pathname}${t.search}`)}catch{}return n}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function vd(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:nd(t),reviewObservations:rd(t),issueGroupsComplete:!0}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function yd(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function bd(e){return`topFixes`in e?{...e,topFixes:e.topFixes.map(yd),reviewObservations:e.reviewObservations.map(yd)}:e}function xd(e,t){return t?[...e,...t]:[...e]}function Sd(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of id(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
|
|
238
|
+
`)}const Gu={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Ku=D({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:D({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Gu},run:async({args:e})=>Wu(qt(),L(e))}),enable:D({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Gu},run:async({args:e})=>{Wu(Et(!0),L(e))}}),disable:D({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Gu},run:async({args:e})=>{Wu(Et(!1),L(e))}})}}),qu=D({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...ec,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Defaults to site.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches. Defaults to 8.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},limit:{type:`string`,description:`Maximum queue items to print. Defaults to 25.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=j(e),r=await Xu(e),i=Yu(e),a=A(e.site)||n||!i&&!r.length?await W({client:n,site:A(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?So(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await h({url:o,projectId:a?.client?.id,site:a?.site,mode:Ju(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:N(e[`max-pages`]),maxDepth:N(e[`max-depth`]),concurrency:N(e.concurrency),refresh:M(e.refresh),fetchRate:Wi({...e,"fetch-concurrency":e.concurrency}),include:F(e.include),exclude:F(e.exclude),googleAnalyticsPropertyId:A(e[`google-analytics-property`]),analyticsConnection:tc(e,kt(a?.client)),useSitemap:!Vi(e,`sitemap`),respectRobots:!Vi(e,`robots`),checkExternal:!Vi(e,`external`),js:!!M(e.js),limit:N(e.limit)});if(t){B(s);return}ol(s),H([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),Y(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function Ju(e){let t=A(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Yu(e){let t=A(e.crawlUrl),n=A(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Xu(e){let t=F(e.urls)??[],n=A(e[`urls-file`]);return n&&t.push(...Zu(await yr(n,`utf8`))),[...new Set(t)]}function Zu(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}const Qu={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function $u(e){let t=A(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new E(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(A(e.output)||A(e.view))throw new E(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(M(e.json))throw new E(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=A(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new E(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:A(e.output),view:n}}function ed(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function td(e){let t=(e.date??new Date).toISOString().slice(0,10),n=ed(e.projectId??e.site)||`site`;return hr(process.cwd(),`${n}-${ed(e.reportName)}-${t}.html`)}async function nd(e){let t=hr(e.options.output??td({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await vr(dr(t),{recursive:!0}),await Sr(t,ti({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function rd(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}function id(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function ad(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function od(e){try{return new URL(e).origin}catch{throw new E(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function sd(e){let t=e.topRows??20,{evidence:n}=e;return{source:n.source,exportedAt:n.exportedAt,importedAt:n.importedAt,files:n.files,queries:{totalRows:n.queries.totalRows,capped:n.queries.capped,topRowsShown:Math.min(t,n.queries.rows.length),rows:n.queries.rows.slice(0,t)},pages:{totalRows:n.pages.totalRows,capped:n.pages.capped,topRowsShown:Math.min(t,n.pages.rows.length),rows:n.pages.rows.slice(0,t)},...e.reconciliation?{reconciliation:e.reconciliation}:{},...e.pageInventory?{pageInventory:e.pageInventory}:{},warnings:n.warnings,caveats:n.caveats}}function cd(e){if(!e.rows.length)return;let t=e.rows.slice(0,15);process.stdout.write(`\nContent inventory${e.pageCount>1?` page ${e.page} of ${e.pageCount}`:``} (${e.rows.length} exported page${e.rows.length===1?``:`s`} with suggested dispositions)\n`),H([`Path`,`Clicks`,`Impressions`,`Position`,`Suggested`],t.map(e=>[K(e.path,48),e.clicks,e.impressions,e.position??`-`,e.suggestedDisposition])),e.rows.length>t.length&&process.stdout.write(`Showing ${t.length} of ${e.rows.length} tiered rows.\n`),e.pageCount>1&&process.stdout.write(`Showing inventory page ${e.page} of ${e.pageCount}; ${e.totalPages} retained rows are available.\n`),process.stdout.write(`${e.note}\n`)}function ld(e){let t=e.evidence.queries.totalRows,n=e.evidence.pages.totalRows,r=`Loaded a local Search Console export (${t} query row${t===1?``:`s`}, ${n} page row${n===1?``:`s`}).`;if(!e.reconciliation)return r;let i=e.reconciliation.unreachedCount;return i===0?`${r} Every exported page URL was reached by the crawl.`:`${r} ${i} exported page URL${i===1?``:`s`} with impressions were not reached by this crawl.`}function ud(e){let t=e.crawledUrls;return`Found ${e.highIssues} high, ${e.mediumIssues} medium, and ${e.lowIssues} low technical issues across ${t} crawled ${t===1?`page`:`pages`}.`}function dd(e){return Pt(e,{limit:e.issues.length})}function fd(e){return Qt(e,{limit:e.issues.length})}function pd(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function md(e,t){if(e===t)return 0;let n=[...e],r=[...t],i=Math.min(n.length,r.length);for(let e=0;e<i;e+=1){let t=(n[e]?.codePointAt(0)??0)-(r[e]?.codePointAt(0)??0);if(t!==0)return t}return n.length-r.length}function hd(e){return e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,` `).trim()}function gd(e){try{let t=new URL(e),n=t.pathname.length>1&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname;return`${t.origin}${n}${t.search}`}catch{return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}}function _d(e,t=[]){return[...e.map(e=>({url:e.finalUrl||e.url,title:e.title})),...t]}function vd(e){try{let t=new URL(e);return`${t.pathname}${t.search}`||`/`}catch{return e}}function yd(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}const bd=[`review: shares a search phrase with another page title; confirm query-to-page evidence and intent before consolidating`,`keep: has clicks in the export window`,`review: has impressions but no crawl path reached it`,`update: has impressions with a weak average position (average position above 20 in the export)`,`review: low observed demand in the export window`];function xd(e){let t=e.maxRows??50,n=e.page??1;if(!Number.isSafeInteger(t)||t<1)throw new E(`INVALID_INPUT`,`Inventory page size must be positive.`);if(!Number.isSafeInteger(n)||n<1)throw new E(`INVALID_INPUT`,`Inventory page must be positive.`);let r=new Set;for(let t of e.reconciliation?.unreachedPages??[]){let e=yd(t.url);e!==null&&r.add(e)}let i=new Map,a=[...e.crawledPages].sort((e,t)=>md(e.url,t.url));for(let e of a){if(typeof e.title!=`string`)continue;let t=yd(e.url);t!==null&&(i.has(t)||i.set(t,e.title))}let o=new Map;for(let t of e.overlap?.candidates??[])for(let e of t.pages){let n=yd(e.url);n!==null&&(o.has(n)||o.set(n,t.query))}let s=[...e.pages].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||md(e.url,t.url)),c=Math.ceil(s.length/t);if(c>0&&n>c)throw new E(`INVALID_INPUT`,`Inventory page ${n} does not exist; choose a page from 1 to ${c}.`);let l=(n-1)*t,u=s.slice(l,l+t).map(t=>{let n=yd(t.url),a=n===null||!e.reconciliation?null:r.has(n)?!1:!e.reconciliation.capped||null,s=n===null?null:i.get(n)??null,c=n===null?null:o.get(n)??null,l,u;return c===null?t.clicks>0?(l=`keep`,u=`has clicks in the export window`):t.impressions>0&&a===!1?(l=`review`,u=`has impressions but no crawl path reached it`):t.impressions>0&&t.position!==null&&t.position>20?(l=`update`,u=`has impressions with a weak average position`):(l=`review`,u=`low observed demand in the export window`):(l=`review`,u=`shares a search phrase with another page title and needs query-to-page and intent verification`),{url:t.url,path:vd(t.url),clicks:t.clicks,impressions:t.impressions,position:t.position,reachedByCrawl:a,title:s,overlapQuery:c,suggestedDisposition:l,tier:u}}),d=c>1,f=[`Suggested dispositions are heuristic tiers from the export numbers and crawl reach. Decide each page with the owner.`];return d&&f.push(`This is inventory page ${n} of ${c}. Fetch every page before treating the returned inventory as complete.`),e.sourceCapped===!0&&f.push(`The source import was capped, so rows missing from this inventory are unknown.`),{rows:u,totalPages:e.pages.length,capped:e.sourceCapped===!0||d,sourceCapped:e.sourceCapped===!0,page:n,pageSize:t,pageCount:c,nextPage:n<c?n+1:null,criteria:[...bd],note:f.join(` `)}}function Sd(e){let t=e.maxQueries??50,n=e.maxCandidates??10,r=new Map;for(let t of e.queries){let e=hd(t.query);if(e.split(` `).length<2)continue;let n=r.get(e);n?(n.impressions+=t.impressions,n.clicks+=t.clicks):r.set(e,{impressions:t.impressions,clicks:t.clicks})}let i=r.size,a=[...r.entries()].map(([e,t])=>({query:e,...t})).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||md(e.query,t.query)).slice(0,t),o=new Map,s=[...e.pages].sort((e,t)=>md(e.url,t.url));for(let e of s){if(typeof e.title!=`string`)continue;let t=hd(e.title);if(!t)continue;let n=gd(e.url);o.has(n)||o.set(n,{url:e.url,title:e.title,normalizedTitle:t})}let c=[...o.values()],l=[];for(let e of a){let t=` ${e.query} `,n=c.filter(e=>` ${e.normalizedTitle} `.includes(t));n.length<2||l.push({query:e.query,impressions:e.impressions,clicks:e.clicks,pages:n.map(e=>({url:e.url,title:e.title})).sort((e,t)=>md(e.url,t.url))})}l.sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||md(e.query,t.query));let u=i>a.length,d=l.length>n;return{candidates:l.slice(0,n),eligibleQueries:i,consideredQueries:a.length,queryLimitReached:u,candidateLimitReached:d,capped:u||d}}const Cd={high:0,medium:1,low:2};function wd(e,t){let n=Cd[e.fix.severity]-Cd[t.fix.severity];return n===0?t.fix.count===e.fix.count?t.fix.score===e.fix.score?e.fix.ruleId<t.fix.ruleId?-1:+(e.fix.ruleId>t.fix.ruleId):t.fix.score-e.fix.score:t.fix.count-e.fix.count:n}function Td(e){let t=e.unreachedAudit,n=[...e.topFixes.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.topFixes??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(wd),r=[...e.reviewObservations.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.reviewObservations??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(wd),i=[];for(let{fix:t,prefix:r,source:a}of n){let n=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`fix`,title:`${r}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:t.howToFix,confidence:`high`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...n?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:n,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}let a=e.reconciliation;if(a&&a.unreachedCount>0){let e=a.unreachedPages.reduce((e,t)=>e+t.impressions,0);i.push({id:`export:unreached-pages`,kind:`review`,title:`${a.unreachedCount} exported page URL${a.unreachedCount===1?``:`s`} with search impressions were not reached by this crawl`,action:`These pages carry ${e} combined impressions across the ${a.unreachedPages.length} retained export rows, but no crawl path reached them. Add internal links or sitemap entries when they should be reachable. The join used URL paths from a local export; treat it as a discovery lead within this crawl bound, not proof of orphan status.`,confidence:`medium`,affectedCount:a.unreachedCount,evidence:{retainedRows:a.unreachedPages.length,impressions:e},review:{question:`Should each retained exported page be reachable from the current site?`,changeOnlyIf:`Add a crawl path only for a page that should remain part of the current site and has no intended exclusion.`,ifNotNeeded:`Record no change for a page that is intentionally retired, excluded, or awaiting an owner decision.`,doNot:[`Do not call a page orphaned from this path-only join.`,`Do not redirect unrelated pages to one generic destination.`]},verification:{expected:`Every retained exported page is reachable in the rerun or explicitly accounted for as deferred or no-change with owner intent.`}})}let o=e.topicOverlap,s=o?.candidates[0];if(o&&s){let e=o.candidates.length,t=s.pages.map(e=>vd(e.url));i.push({id:`export:topic-overlap`,kind:`review`,title:`topic overlap candidates: ${e} exported ${e===1?`query appears`:`queries appear`} in 2+ page titles`,action:`Top candidate: "${s.query}" (${s.impressions} impressions, ${s.clicks} clicks) appears in the titles of ${t.join(` and `)}. This is a title-based heuristic built from the export's separate query and page tables. The export contains no query-to-page mapping, so it cannot show which pages Google served for the query. Verify with the cannibalisation report (seo cannibal) after connecting a Search Console property.`,confidence:`medium`,affectedCount:e,evidence:{topQuery:s.query,topPages:s.pages,impressions:s.impressions,clicks:s.clicks},review:{question:`Does query-to-page evidence confirm that these pages compete for the same search intent?`,changeOnlyIf:`Consolidate or redirect only after query-to-page evidence and page intent confirm real overlap.`,ifNotNeeded:`Keep the pages separate and record why their intents differ.`,doNot:[`Do not merge or redirect pages from a title-match heuristic alone.`]},verification:{expected:`Confirm intent with query-to-page evidence before consolidating or redirecting either page.`}})}let c=e.pageInventory,l=c?.rows[0];if(c&&l){let e={keep:0,update:0,review:0};for(let t of c.rows)e[t.suggestedDisposition]+=1;let t=[...c.pageCount>1?[`page ${c.page} of ${c.pageCount}; ${c.rows.length} of ${c.totalPages} retained rows`]:[],...c.sourceCapped?[`source import capped`]:[]],n=t.length?` (${t.join(`; `)})`:``;i.push({id:`export:content-inventory`,kind:`review`,title:`content inventory: ${c.rows.length} exported page${c.rows.length===1?``:`s`} tiered with suggested dispositions`,action:`Suggested tiers: keep ${e.keep}, update ${e.update}, review ${e.review}${n}. Example: ${l.path} (${l.clicks} clicks, ${l.impressions} impressions, position ${l.position??`not in the export`}) is tiered ${l.suggestedDisposition} because it ${l.tier}. ${c.note} Work the inventory page by page instead of applying one blanket policy.`,confidence:`medium`,affectedCount:c.rows.length,evidence:{dispositions:e,capped:c.capped},review:{question:`Has every returned inventory row received an evidence-backed disposition?`,changeOnlyIf:`Apply a content change, removal, or redirect only after deciding that row from its own evidence and product relevance.`,ifConfirmed:`Record one disposition and reason for every returned inventory row.`,ifNotNeeded:`Keep a page unchanged when its evidence and current product role support keeping it.`,doNot:[`Do not use one blanket disposition for the inventory.`,`Do not redirect unrelated URLs to a generic hub.`]},verification:{expected:`Every returned inventory row has an owner-confirmed keep, update, or review decision; every inventory page has been fetched; redirected rows resolve to the intended destination.`}})}for(let{fix:t,prefix:n,source:a}of r){let r=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`review`,title:`${n}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:`Observed state that needs intent confirmation before treating it as a defect. ${t.howToFix}`,confidence:`medium`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...r?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:r,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}return i}function Ed(e,t){let n=[];for(let r of e.unreachedPages.slice(0,t))try{let t=new URL(r.url);n.push(`${e.crawlOrigin}${t.pathname}${t.search}`)}catch{}return n}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function Dd(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:dd(t),reviewObservations:fd(t),issueGroupsComplete:!0}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function Od(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function kd(e){return`topFixes`in e?{...e,topFixes:e.topFixes.map(Od),reviewObservations:e.reviewObservations.map(Od)}:e}function Ad(e,t){return t?[...e,...t]:[...e]}function jd(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of pd(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
|
|
239
239
|
`),e.reviewObservations.length&&process.stdout.write(`${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} need confirmation before they become implementation work.\n`);return}process.stdout.write(t.providerFree?`
|
|
240
240
|
Prioritised technical fixes
|
|
241
241
|
`:e.searchDataJoined?`
|
|
242
242
|
Technical fixes with search value
|
|
243
243
|
`:`
|
|
244
244
|
Technical fixes (no Search Console data joined)
|
|
245
|
-
`),e.searchDataJoined?
|
|
245
|
+
`),e.searchDataJoined?H([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} visits${e.scoreFactors.conversions===void 0?``:` / ${e.scoreFactors.conversions} conv.`}`,K(e.howToVerify,72)])):H([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,K(e.howToVerify,72)])),e.reviewObservations.length&&(t.providerFree?(process.stdout.write(`
|
|
246
246
|
Review observations (confirm intent before treating as defects)
|
|
247
|
-
`),
|
|
247
|
+
`),H([`Rule`,`Severity`,`Affected URLs`,`Sample`],e.reviewObservations.map(e=>[e.ruleId,e.severity,e.count,K(e.sampleUrls[0]??``,60)]))):process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`))}function Md(e){process.stdout.write(`
|
|
248
248
|
Search Console export (local file evidence)
|
|
249
|
-
`),process.stdout.write(`${
|
|
249
|
+
`),process.stdout.write(`${ld(e)}\n`);for(let t of e.evidence.warnings)process.stdout.write(`Warning: ${t}\n`);let t=e.reconciliation?.unreachedPages??[];if(t.length&&(process.stdout.write(`
|
|
250
250
|
Exported page URLs not reached by this crawl (by impressions)
|
|
251
|
-
`),
|
|
251
|
+
`),H([`URL`,`Clicks`,`Impressions`,`Position`],t.slice(0,10).map(e=>[K(e.url,64),e.clicks,e.impressions,e.position??`-`]))),e.unreachedAudit){let t=e.unreachedAudit;process.stdout.write(`\nAudited ${t.auditedUrls} of the ${t.totalUnreached} unreached URLs in this run\n`);let n=[...t.topFixes,...t.reviewObservations];n.length&&H([`Rule`,`Severity`,`Affected URLs`,`Sample`],n.map(e=>[e.ruleId,e.severity,e.count,K(e.sampleUrls[0]??``,60)]))}e.topicOverlap?.candidates.length&&(process.stdout.write(`
|
|
252
252
|
Topic overlap candidates (title-based heuristic; the export has no query-to-page mapping)
|
|
253
|
-
`),
|
|
253
|
+
`),H([`Query`,`Impressions`,`Pages`],e.topicOverlap.candidates.map(e=>[K(e.query,40),e.impressions,K(e.pages.map(e=>vd(e.url)).join(`, `),64)]))),e.pageInventory&&cd(e.pageInventory);for(let t of e.evidence.caveats)process.stdout.write(`Note: ${t}\n`)}function Nd(e){process.stdout.write(`
|
|
254
254
|
Provider-backed sections skipped
|
|
255
|
-
`);let t=`dataSources`in e?
|
|
255
|
+
`);let t=`dataSources`in e?pd(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`Google Analytics: not connected.`];for(let e of n)process.stdout.write(`${e}\n`);process.stdout.write("Run `seo start` when you want traffic, query, ranking, and analytics evidence added to the report.\n")}function Pd(e){let t=[],n=e.technicalCrawl;return n&&(`topFixes`in n?t.push({title:`Technical crawl evidence`,summary:`${n.summary.crawledUrls} pages were included in the ${n.status===`reused`?`saved`:`new`} crawl evidence.`,items:[...n.topFixes.map(e=>`${e.ruleId}: ${e.title}. ${e.count} affected URLs. ${e.recommendation}`),...pd(n.dataSources)]}):t.push({title:`Technical crawl evidence`,summary:n.reason??`Technical evidence was ${n.status}.`,items:[]})),e.followups?.length&&t.push({title:`Recommended next commands`,items:e.followups.map(e=>`${e.command}: ${e.why}`)}),t}function Fd(e,t,n){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and Google Analytics sections were skipped because they are not connected.`;let r=[ud(e.summary)];return t&&r.push(ld(t)),n&&r.push(`Audited ${n.auditedUrls} of the ${n.totalUnreached} unreached URLs in this run; their findings are in the action list.`),r.push(`Search Console and Google Analytics are not connected; provider-backed sections were skipped.`),r.join(` `)}function Id(e){if(!(`topFixes`in e))return{tool:`seo_crawl`,status:`skipped`,summary:e.reason??`Technical crawl evidence was ${e.status}.`};let t=e.summary.crawledUrls;return{tool:`seo_crawl`,status:`completed`,summary:`${e.status===`reused`?`Loaded`:`Completed`} technical evidence for ${t} crawled ${t===1?`page`:`pages`}.`}}function Ld(e){return e?[{tool:`seo_search_console_export`,status:`completed`,summary:ld(e)}]:[]}function Rd(e){if(e.unreachedCount===0)return;let t=[];for(let n of e.unreachedPages.slice(0,10))try{let r=new URL(n.url);t.push(`${e.crawlOrigin}${r.pathname}${r.search}`)}catch{}if(!t.length)return;let n=e.unreachedCount-t.length;return{command:`seo reports run audit-urls --params '${JSON.stringify({urls:t})}' --json`,why:`Fetch page-level evidence for exported URLs this crawl never reached${n>0?` (first ${t.length} of ${e.unreachedCount})`:``}.${e.originMismatch?` URLs were mapped onto the --url origin by path because the export origin differs.`:``}`}}function zd(e,t={}){if(t.searchDataAvailable===!1){let e=[];return t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`?(Q(e,`seo crawl --url ${ad(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${ad(t.crawlStartUrl)} --save`,`Create the technical crawl that this report needs before opening a page-level follow-up.`)):t.crawlStartUrl&&Q(e,`seo audit-page --url ${ad(t.crawlStartUrl)}`,`Inspect one important page in detail after the site-level crawl.`),Q(e,`seo start`,`Connect Search Console when you want traffic, query, and ranking evidence alongside the crawl.`),e}let n=t.projectId?`--project ${ad(t.projectId)}`:`--site ${ad(e.site)}`,r=e.output.narrative.diagnosis,i=r.decay.rangeDays,a=[],o=new Set((r.skippedSections??[]).map(e=>e.section));Q(a,`seo refresh-priorities ${n} --days ${i} --verify-content --limit 25`,`Turn the report into a ranked action queue with content checks.`),r.decay.summary.eligibleRows>0&&Q(a,`seo decaying ${n} --days ${i} --limit 25`,`Inspect query/page declines observed in both retained-row windows.`),r.cannibalization.items.length>0&&Q(a,`seo cannibal ${n} --days ${i} --limit 25`,`Find queries split across competing URLs.`),r.quickWins.summary.eligibleRows>0&&Q(a,`seo quick-wins ${n} --days ${i} --verify-content --verify-limit 5`,`Review average-position rows below their heuristic CTR target with optional page evidence.`),r.strikingDistance.summary.eligibleRows>0&&Q(a,`seo second-page ${n} --days ${i} --verify-content --verify-limit 5`,`Work pages sitting just outside page-one rankings.`),(o.has(`traffic anomaly`)||o.has(`update correlation`))&&(Q(a,`seo quick-wins ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: lower thresholds and look for early content wins.`),Q(a,`seo second-page ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: inspect early second-page opportunities.`));let s=r.segments.page.items.find(e=>e.key.startsWith(`http`));return s&&Q(a,`seo audit-page --url ${ad(s.key)}`,`Audit the biggest moved URL for title, metadata, links, and schema.`),t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`&&(Q(a,`seo crawl --url ${ad(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${ad(t.crawlStartUrl)} ${n} --save`,`Create the first technical crawler baseline with plain-English fixes and JSON-ready issue data.`)),Q(a,`seo technical-watch ${n} --limit 50`,`Save a crawl/index baseline so future reports can flag technical drift.`),a.slice(0,6)}function Bd(e){e.length&&(process.stdout.write(`
|
|
256
256
|
Recommended next commands
|
|
257
|
-
`),V([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function Md(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function Nd(e){return E({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:Ni(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and the content inventory without the full report body. JSON only.`},"search-console-export":{type:`string`,description:`Path to a local Search Console export (a directory or CSV with the standard query and page tables). URL mode only; query and page tables stay separate.`},"inventory-page":{type:`string`,default:`1`,description:`Page of local Search Console export inventory rows to return. URL mode only.`},...Uu}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=I(t),r=e.printFollowups?Wu(t):void 0,a=e.printFollowups&&j(t.full),o=e.printFollowups&&j(t[`actions-only`]);if(o&&!n)throw new T(`INVALID_INPUT`,`Use --actions-only with --json so findings and completion outcomes stay machine-readable.`);if(o&&a)throw new T(`INVALID_INPUT`,`Choose either --actions-only or --full, not both.`);let s=A(t),c=k(t.site),l=e.printFollowups?k(t.url):void 0,u=!!(s||c||!l),d=e.printFollowups?k(t[`search-console-export`]):void 0,f=e.printFollowups?M(t[`inventory-page`])??1:1;if(!Number.isSafeInteger(f)||f<1)throw new T(`INVALID_INPUT`,`--inventory-page must be a positive integer.`);if(f!==1&&!d)throw new T(`INVALID_INPUT`,`Use --inventory-page only with --search-console-export.`);if(d&&u)throw new T(`INVALID_INPUT`,`Use --search-console-export only with --url. A connected property already provides query and page evidence, and imported tables must not be blended with it.`);let p=u?await U({client:s,site:c,options:{json:n,refresh:j(t.refresh)}}):void 0,m=p?.site??Zu(l??``),h=await zn({site:m,days:M(t.days),recentDays:M(t.recent),limit:M(t.limit),brandTerms:p?.client?.brandTerms,includeBrand:j(t[`include-brand`]),refresh:j(t.refresh),skipSearchData:!u}),g=e.workflowName?{...h,workflow:e.workflowName}:h,_=e.printFollowups?await wt({site:m,searchSite:p?.site,url:l??p?.client?.startUrl??ho(m),projectId:p?.client?.id,googleAnalyticsPropertyId:p?.client?.analytics.google?.propertyId,analyticsConnection:ct(p?.client),crawl:!Fi(t,`crawl`),refresh:j(t.refresh),maxPages:M(t[`crawl-max-pages`]),maxDepth:M(t[`crawl-max-depth`])}):void 0,v=_?vd(_):void 0,y=d?await Ei({path:d}):void 0,b=y&&_?.report&&l?ji({pages:y.pages.rows,crawledUrls:_.report.pages.flatMap(e=>[e.url,e.finalUrl]),crawlOrigin:Zu(l)}):void 0,ee=y?{evidence:y,reconciliation:b}:void 0,x=await(async()=>{if(!b||b.unreachedCount===0)return;let e=_d(b,20);if(!e.length)return;let n=await cn({url:e[0]??``,urls:e,mode:`list`,maxPages:e.length,useSitemap:!1,refresh:j(t.refresh)});return i(n,void 0,{retention:`baseline`}),{reportId:n.id,auditedUrls:n.summary.totalPages,totalUnreached:b.unreachedCount,capped:b.unreachedCount>e.length,issueGroupsComplete:!0,topFixes:nd(n),reviewObservations:rd(n),pages:n.pages.map(e=>({url:e.finalUrl||e.url,title:e.title??null}))}})(),te=_?.report?cd(_.report.pages,x?.pages):void 0,S=y&&te?pd({queries:y.queries.rows,pages:te}):void 0,ne=y&&te?fd({pages:y.pages.rows,sourceCapped:y.pages.capped,page:f,reconciliation:b,crawledPages:te,overlap:S}):void 0,C=_?.report?gd({crawlReportId:_.report.id,topFixes:nd(_.report),reviewObservations:rd(_.report),reconciliation:b,...x?{unreachedAudit:{reportId:x.reportId,topFixes:x.topFixes,reviewObservations:x.reviewObservations,titlePrefix:`unreached pages:`}}:{},...S?{topicOverlap:S}:{},...ne?{pageInventory:ne}:{}}):void 0,w=e.printFollowups?Ad(g,{crawlStartUrl:l??p?.client?.startUrl??ho(m),projectId:p?.client?.id,technicalBaselineStatus:_?.status,searchDataAvailable:u}):void 0;if(w&&b&&(!x||x.capped)){let e=kd(b);e&&w.unshift(e)}if(n){let t=e.printFollowups&&!a?Yu(h,e.workflowName):g,n=xd(t.actions,C),r=!u&&v?{...t,summary:Ed(v,ee,x),steps:[Dd(v),...Od(ee),...t.steps],actions:n,...a?{}:{output:{searchSections:{status:`skipped`,reason:`No Search Console property was selected. Pass --site or --project, or run seo start to connect one.`}}}}:{...t,actions:n},i=v||w?{...r,...e.printFollowups?{detail:a?`full`:`summary`}:{},...v?{technicalCrawl:e.printFollowups&&!a?bd(v):v}:{},...y?{searchConsoleExport:{...Qu({evidence:y,reconciliation:b,pageInventory:ne}),...x?{unreachedPagesAudit:x}:{},...S?{topicOverlapCandidates:S}:{}}}:{},...w?{nextCommands:w}:{}}:t,s={reportId:`report`,preferRootActions:!0,verify:`Rerun seo report with the same inputs and confirm the finding is absent or its evidence now matches the intended state.`,...!u&&v&&`issueGroupsComplete`in v&&v.issueGroupsComplete?{coverage:`complete`}:{}};z(Qt(o?Tt(i,s):ln(i,s)));return}if(r){Ju(await qu({report:g.output.narrative,reportName:`report`,title:u?`SEO report`:`Technical SEO report`,options:r,projectId:p?.client?.id,additionalSections:Td({technicalCrawl:v,followups:w})}));return}u?(process.stdout.write(`${g.output.narrative.markdown}\n\n`),$c(g)):Md(m),v&&(Sd(v,{providerFree:!u}),y&&Cd({evidence:y,reconciliation:b,unreachedAudit:x,topicOverlap:S,pageInventory:ne}),u||wd(v)),w&&jd(w)}})}Nd({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`});const Pd=Nd({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),Fd=E({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),...qs,"verify-content":Ni(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await An({site:n.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),googleAnalyticsPropertyId:k(e[`google-analytics-property`]),analyticsConnection:Js(e,ct(n.client)),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),refresh:j(e.refresh)});if(t){z(r);return}$c(r),V([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),Y(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),Id=E({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":Ni(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=F(e.urls),i=F(e.sitemaps),a=k(e.url)??n.client?.startUrl??ho(n.site),o=await it({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:F(e.properties).length?F(e.properties):void 0,limit:M(e.limit),languageCode:k(e.language),dailyLimit:M(e[`daily-limit`]),inspectLimit:M(e[`inspect-limit`]),maxUrls:M(e[`max-urls`]),refresh:j(e.refresh),js:j(e.js)?!0:`auto`,recoverLinks:j(e[`recover-links`]),recoverDays:M(e[`recover-days`]),recoverLimit:M(e[`recover-limit`]),recoverMinClicks:M(e[`recover-min-clicks`]),recoverMinImpressions:M(e[`recover-min-impressions`])});if(t){z(o);return}el(o)}});function Ld(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Rd(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function zd(e,t){let n=Rd(t);n.length&&(process.stdout.write(`\n${e}\n`),V([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,Ld(t.clickDelta),Ld(t.impressionDelta),Ld(t.positionDelta)])))}function Bd(e){process.stdout.write(`
|
|
257
|
+
`),H([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function Vd(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function Hd(e){return D({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:zi(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and the content inventory without the full report body. JSON only.`},"search-console-export":{type:`string`,description:`Path to a local Search Console export (a directory or CSV with the standard query and page tables). URL mode only; query and page tables stay separate.`},"inventory-page":{type:`string`,default:`1`,description:`Page of local Search Console export inventory rows to return. URL mode only.`},...Qu}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=L(t),r=e.printFollowups?$u(t):void 0,i=e.printFollowups&&M(t.full),a=e.printFollowups&&M(t[`actions-only`]);if(a&&!n)throw new E(`INVALID_INPUT`,`Use --actions-only with --json so findings and completion outcomes stay machine-readable.`);if(a&&i)throw new E(`INVALID_INPUT`,`Choose either --actions-only or --full, not both.`);let o=j(t),s=A(t.site),c=e.printFollowups?A(t.url):void 0,l=!!(o||s||!c),u=e.printFollowups?A(t[`search-console-export`]):void 0,d=e.printFollowups?N(t[`inventory-page`])??1:1;if(!Number.isSafeInteger(d)||d<1)throw new E(`INVALID_INPUT`,`--inventory-page must be a positive integer.`);if(d!==1&&!u)throw new E(`INVALID_INPUT`,`Use --inventory-page only with --search-console-export.`);if(u&&l)throw new E(`INVALID_INPUT`,`Use --search-console-export only with --url. A connected property already provides query and page evidence, and imported tables must not be blended with it.`);let f=l?await W({client:o,site:s,options:{json:n,refresh:M(t.refresh)}}):void 0,p=f?.site??od(c??``),m=await v({site:p,days:N(t.days),recentDays:N(t.recent),limit:N(t.limit),brandTerms:f?.client?.brandTerms,includeBrand:M(t[`include-brand`]),refresh:M(t.refresh),skipSearchData:!l}),h=e.workflowName?{...m,workflow:e.workflowName}:m,g=e.printFollowups?await Un({site:p,searchSite:f?.site,url:c??f?.client?.startUrl??So(p),projectId:f?.client?.id,googleAnalyticsPropertyId:f?.client?.analytics.google?.propertyId,analyticsConnection:kt(f?.client),crawl:!Vi(t,`crawl`),refresh:M(t.refresh),maxPages:N(t[`crawl-max-pages`]),maxDepth:N(t[`crawl-max-depth`])}):void 0,_=g?Dd(g):void 0,y=u?await Mi({path:u}):void 0,b=y&&g?.report&&c?Li({pages:y.pages.rows,crawledUrls:g.report.pages.flatMap(e=>[e.url,e.finalUrl]),crawlOrigin:od(c)}):void 0,x=y?{evidence:y,reconciliation:b}:void 0,S=await(async()=>{if(!b||b.unreachedCount===0)return;let e=Ed(b,20);if(!e.length)return;let n=await ft({url:e[0]??``,urls:e,mode:`list`,maxPages:e.length,useSitemap:!1,refresh:M(t.refresh)});return fe(n,void 0,{retention:`baseline`}),{reportId:n.id,auditedUrls:n.summary.totalPages,totalUnreached:b.unreachedCount,capped:b.unreachedCount>e.length,issueGroupsComplete:!0,topFixes:dd(n),reviewObservations:fd(n),pages:n.pages.map(e=>({url:e.finalUrl||e.url,title:e.title??null}))}})(),ee=g?.report?_d(g.report.pages,S?.pages):void 0,te=y&&ee?Sd({queries:y.queries.rows,pages:ee}):void 0,ne=y&&ee?xd({pages:y.pages.rows,sourceCapped:y.pages.capped,page:d,reconciliation:b,crawledPages:ee,overlap:te}):void 0,C=g?.report?Td({crawlReportId:g.report.id,topFixes:dd(g.report),reviewObservations:fd(g.report),reconciliation:b,...S?{unreachedAudit:{reportId:S.reportId,topFixes:S.topFixes,reviewObservations:S.reviewObservations,titlePrefix:`unreached pages:`}}:{},...te?{topicOverlap:te}:{},...ne?{pageInventory:ne}:{}}):void 0,w=e.printFollowups?zd(h,{crawlStartUrl:c??f?.client?.startUrl??So(p),projectId:f?.client?.id,technicalBaselineStatus:g?.status,searchDataAvailable:l}):void 0;if(w&&b&&(!S||S.capped)){let e=Rd(b);e&&w.unshift(e)}if(n){let t=e.printFollowups&&!i?id(m,e.workflowName):h,n=Ad(t.actions,C),r=!l&&_?{...t,summary:Fd(_,x,S),steps:[Id(_),...Ld(x),...t.steps],actions:n,...i?{}:{output:{searchSections:{status:`skipped`,reason:`No Search Console property was selected. Pass --site or --project, or run seo start to connect one.`}}}}:{...t,actions:n},o=_||w?{...r,...e.printFollowups?{detail:i?`full`:`summary`}:{},..._?{technicalCrawl:e.printFollowups&&!i?kd(_):_}:{},...y?{searchConsoleExport:{...sd({evidence:y,reconciliation:b,pageInventory:ne}),...S?{unreachedPagesAudit:S}:{},...te?{topicOverlapCandidates:te}:{}}}:{},...w?{nextCommands:w}:{}}:t,s={reportId:`report`,preferRootActions:!0,verify:`Rerun seo report with the same inputs and confirm the finding is absent or its evidence now matches the intended state.`,...!l&&_&&`issueGroupsComplete`in _&&_.issueGroupsComplete?{coverage:`complete`}:{}};B(tn(a?Wn(o,s):pt(o,s)));return}if(r){rd(await nd({report:h.output.narrative,reportName:`report`,title:l?`SEO report`:`Technical SEO report`,options:r,projectId:f?.client?.id,additionalSections:Pd({technicalCrawl:_,followups:w})}));return}l?(process.stdout.write(`${h.output.narrative.markdown}\n\n`),ol(h)):Vd(p),_&&(jd(_,{providerFree:!l}),y&&Md({evidence:y,reconciliation:b,unreachedAudit:S,topicOverlap:te,pageInventory:ne}),l||Nd(_)),w&&Bd(w)}})}Hd({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`});const Ud=Hd({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),Wd=D({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),...ec,"verify-content":zi(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=await Re({site:n.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),googleAnalyticsPropertyId:A(e[`google-analytics-property`]),analyticsConnection:tc(e,kt(n.client)),verifyContent:M(e[`verify-content`]),verifyLimit:N(e[`verify-limit`]),refresh:M(e.refresh)});if(t){B(r);return}ol(r),H([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),Y(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),Gd=D({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":zi(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=I(e.urls),i=I(e.sitemaps),a=A(e.url)??n.client?.startUrl??So(n.site),o=await Ln({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:I(e.properties).length?I(e.properties):void 0,limit:N(e.limit),languageCode:A(e.language),dailyLimit:N(e[`daily-limit`]),inspectLimit:N(e[`inspect-limit`]),maxUrls:N(e[`max-urls`]),refresh:M(e.refresh),js:M(e.js)?!0:`auto`,recoverLinks:M(e[`recover-links`]),recoverDays:N(e[`recover-days`]),recoverLimit:N(e[`recover-limit`]),recoverMinClicks:N(e[`recover-min-clicks`]),recoverMinImpressions:N(e[`recover-min-impressions`])});if(t){B(o);return}sl(o)}});function Kd(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function qd(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function Jd(e,t){let n=qd(t);n.length&&(process.stdout.write(`\n${e}\n`),H([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,Kd(t.clickDelta),Kd(t.impressionDelta),Kd(t.positionDelta)])))}function Yd(e){process.stdout.write(`
|
|
258
258
|
Segment coverage
|
|
259
|
-
`),
|
|
259
|
+
`),H([`Dimension`,`Eligible`,`Returned`,`Limited`,`Result cap`,`Source cap`,`Source capped`],[`page`,`query`,`device`,`country`].map(t=>{let n=e[t].coverage;return[t,n.eligibleRows,n.returnedRows,n.limitedRows,n.resultLimit,n.sourceRowLimit,n.sourcePossiblyTruncated?`yes`:`no`]}))}function Xd(e){e.length&&(process.stdout.write(`
|
|
260
260
|
Template movement
|
|
261
|
-
`),V([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,Ld(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),X(`Template actions`,e.map(e=>e.summary)),X(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Hd=E({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await At({site:n.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)});if(t){z(r);return}q({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),X(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),X(`Postmortem findings`,r.output.insights.map(e=>e.summary)),X(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),Vd(r.output.templateMovement),X(`Update evidence`,r.output.update.evidence),Bd(r.output.segments),zd(`Page winners and losers`,r.output.segments.page),zd(`Query winners and losers`,r.output.segments.query),zd(`Device winners and losers`,r.output.segments.device),zd(`Country winners and losers`,r.output.segments.country),eo(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),Ud=new Set([`database`,`filesystem`,`internal`,`unknown`]);function Wd(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function Gd(e){return Ln(e[0])?e[0]:void 0}function Kd(e,t){return gt(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function qd(e){let t=On(e.error);if(!Ud.has(t.errorCategory))return;let n=Gd(e.args),r=Kd(e.report,n),i=[vt(e.error).code,t.errorCategory,t.failureReason,t.failureContext].filter(Boolean).join(` | `),a=new URL(`https://github.com/iannuttall/seo/issues/new`);return a.searchParams.set(`template`,`bug.yml`),a.searchParams.set(`title`,`bug: ${r} failed`),a.searchParams.set(`version`,e.version??Ae),a.searchParams.set(`runtime`,`Node ${Wd(e.nodeVersion??process.version)} on ${e.processPlatform??rr()} (${e.processArch??tr()})`),a.searchParams.set(`diagnostics`,i),a.toString()}const Jd={"diagnose-property":`seo report`,"refresh-priorities":`seo report`,"query-cluster":`seo report`,"quick-wins":`seo report --section quick-wins`,"ctr-underperformers":`seo report --section low-ctr`,"striking-distance":`seo report --section striking-distance`,"second-page":`seo report --section second-page`,decaying:`seo report --section content-decay`,cannibal:`seo report --section cannibalization`,"traffic-anomaly":`seo report --section traffic-drop`,"page-opportunities":`seo page --url <url>`,"audit-page":`seo page --url <url>`,"url-inspect":`seo page --url <url>`,"segment-impact":`seo compare --date <date>`,"update-correlate":`seo compare --date <date>`,"update-postmortem":`seo compare --date <date>`,"link-recover":`seo index`,"gsc-query":`seo data`},Yd={"agent-readiness":null,"ai-readiness":null,"community-intent":null,"entity-readiness":null,llms:null,"monthly-report":`seo report`,okf:null,pseo:null,"report-narrative":`seo report`,"seo-to-ai-query":null,"server-logs":null,"client keyword-sets":null,links:`seo research backlinks --target <domain>`,"export knowledge":null,"export monthly":`seo report --json`,"export narrative":`seo report --json`,"export pseo":null,indexnow:null,"project keyword-sets":null,"projects keyword-sets":null,"providers bing":null,"providers semrush":`seo providers`,"providers limits":null,"providers spend":null,"providers dataforseo limits":null,"providers dataforseo spend":null,"providers ahrefs limits":null},Xd=`Spend is controlled in your provider account. Set limits in DataForSEO, Ahrefs or the other provider.`,Zd={"providers limits":Xd,"providers spend":Xd,"providers dataforseo limits":Xd,"providers dataforseo spend":Xd,"providers ahrefs limits":Xd},Qd=new Set([`client`,`project`,`projects`]);function $d(e){let t=e.findIndex(e=>e.startsWith(`-`)),n=t===-1?e:e.slice(0,t),r=+(n[0]===`help`);for(let e=3;e>=1;--e){let t=n.slice(r,r+e).join(` `);if(n.length-r>=e&&Object.hasOwn(Yd,t))return t}return null}function ef(e){let t=Object.hasOwn(Jd,e)?Jd[e]:void 0;return t?`\`seo ${e}\` is now \`${t}\`. The old name stops working in 0.4.0.`:null}function tf(e){let t=$d(e);if(!t)return null;let n=Yd[t],r=t.split(` `)[0]??``,i=n?`Use \`${n}\` instead.`:Qd.has(r)?"Run `seo projects --help` for the project commands.":r===`providers`?"Run `seo providers --help` for the provider commands.":"Run `seo help` for the commands.";return`\`seo ${t}\` was removed in 0.3.0. ${Object.hasOwn(Zd,t)?Zd[t]:`SEO Skill now focuses on Search Console, analytics and keyword data.`} ${i}`}function nf(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function rf(e,t){return t===`win32`?`${e}.cmd`:e}function af(e,t=process.platform){let n=nf(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:rf(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:rf(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:rf(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:rf(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function of(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function sf(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function cf(e){if(e)try{return Zn(e)}catch{return e}}async function lf(e){let t=await jr({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return Er(t)?`later`:t}function uf(e){return br(e.command,e.args,{stdio:`inherit`}).status}async function df(e,t={}){if(!sf({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=cf(t.cliPath??process.argv[1]),r=n?af(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Vr({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!of(i.current,i.latest))&&await(t.prompt??lf)({command:r,update:i})!==`later`)return(t.run??uf)(r)===0?(Dr.success(`Updated seo to ${i.latest}. Run your command again.`),0):(Dr.error(`The update failed. Your current version is unchanged.`),1)}const ff={name:`seo`,version:Ae},pf=[{title:`Start here`,commands:[[`seo start`,`Connect Search Console and save a project profile`],[`seo report`,`The main report: overview and every section`],[`seo report --section quick-wins`,`One section of the report, with live page checks`]]},{title:`Commands`,commands:[[`seo page --url <url>`,`One page: live facts, index status, queries`],[`seo compare --date <date>`,`Before and after a date`],[`seo index`,`Pages that lost most of their clicks`],[`seo research <action>`,`Keyword, SERP, competitor and backlink data from your key`],[`seo data`,`Raw Search Console rows`],[`seo crawl`,`Crawl a site and list technical issues`],[`seo internal-links`,`Pages that could link to a target page`]]},{title:`Setup`,commands:[[`seo projects list`,`List saved project profiles`],[`seo sites`,`List Search Console properties`],[`seo mcp install`,`Add the local MCP server to your agents`],[`seo providers`,`Connect DataForSEO or Ahrefs`],[`seo doctor`,`Check local auth and config`]]}],mf=[...pf,{title:`More commands`,commands:[[`seo crawl-reports`,`List saved crawl reports`],[`seo crawl-diff`,`Compare crawl changes`],[`seo crawl-queue`,`Run a crawl and rank the fix queue`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo redirect-trace`,`Trace redirects`],[`seo perf audit`,`Run a page performance audit`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check saved URL Inspection snapshots`],[`seo ai-referrals`,`AI referral traffic in Google Analytics`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page and query groups`],[`seo monitoring`,`Run and review monitoring`],[`seo schedule cron`,`Print cron entries`],[`seo export`,`Export report data to CSV`],[`seo reports list`,`List every structured report`],[`seo skill list`,`Show the packaged SEO skill`],[`seo auth`,`Manage Google auth`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete saved credentials and all local data`]]}];function hf(e){wa(`seo v${ff.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
`),H([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,Kd(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),X(`Template actions`,e.map(e=>e.summary)),X(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Zd=D({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=L(e),n=await W({client:j(e),site:A(e.site),options:{json:t,refresh:M(e.refresh)}}),r=await f({site:n.site,days:N(e.days),recentDays:N(e.recent),limit:N(e.limit),brandTerms:n.client?.brandTerms,includeBrand:M(e[`include-brand`]),knownConfounders:A(e[`known-change`])?[A(e[`known-change`])??``]:void 0,includeChangeLog:!M(e[`ignore-change-log`]),refresh:M(e.refresh)});if(t){B(r);return}q({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),X(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),X(`Postmortem findings`,r.output.insights.map(e=>e.summary)),X(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),Xd(r.output.templateMovement),X(`Update evidence`,r.output.update.evidence),Yd(r.output.segments),Jd(`Page winners and losers`,r.output.segments.page),Jd(`Query winners and losers`,r.output.segments.query),Jd(`Device winners and losers`,r.output.segments.device),Jd(`Country winners and losers`,r.output.segments.country),so(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),Qd=new Set([`database`,`filesystem`,`internal`,`unknown`]);function $d(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function ef(e){return Hn(e[0])?e[0]:void 0}function tf(e,t){return yt(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function nf(e){let t=jn(e.error);if(!Qd.has(t.errorCategory))return;let n=ef(e.args),r=tf(e.report,n),i=[Nn(e.error).code,t.errorCategory,t.failureReason,t.failureContext].filter(Boolean).join(` | `),a=new URL(`https://github.com/iannuttall/seo/issues/new`);return a.searchParams.set(`template`,`bug.yml`),a.searchParams.set(`title`,`bug: ${r} failed`),a.searchParams.set(`version`,e.version??ee),a.searchParams.set(`runtime`,`Node ${$d(e.nodeVersion??process.version)} on ${e.processPlatform??lr()} (${e.processArch??sr()})`),a.searchParams.set(`diagnostics`,i),a.toString()}const rf={"diagnose-property":`seo report`,"refresh-priorities":`seo report`,"query-cluster":`seo report`,"quick-wins":`seo report --section quick-wins`,"ctr-underperformers":`seo report --section low-ctr`,"striking-distance":`seo report --section striking-distance`,"second-page":`seo report --section second-page`,decaying:`seo report --section content-decay`,cannibal:`seo report --section cannibalization`,"traffic-anomaly":`seo report --section traffic-drop`,"page-opportunities":`seo page --url <url>`,"audit-page":`seo page --url <url>`,"url-inspect":`seo page --url <url>`,"segment-impact":`seo compare --date <date>`,"update-correlate":`seo compare --date <date>`,"update-postmortem":`seo compare --date <date>`,"link-recover":`seo index`,"gsc-query":`seo data`},af={"agent-readiness":null,"ai-readiness":null,"community-intent":null,"entity-readiness":null,llms:null,"monthly-report":`seo report`,okf:null,pseo:null,"report-narrative":`seo report`,"seo-to-ai-query":null,"server-logs":null,"client keyword-sets":null,links:`seo research backlinks --target <domain>`,"export knowledge":null,"export monthly":`seo report --json`,"export narrative":`seo report --json`,"export pseo":null,indexnow:null,"project keyword-sets":null,"projects keyword-sets":null,"providers semrush":`seo providers`,"providers limits":null,"providers spend":null,"providers dataforseo limits":null,"providers dataforseo spend":null,"providers ahrefs limits":null},of=`Spend is controlled in your provider account. Set limits in DataForSEO, Ahrefs or the other provider.`,sf={"providers limits":of,"providers spend":of,"providers dataforseo limits":of,"providers dataforseo spend":of,"providers ahrefs limits":of},cf=new Set([`client`,`project`,`projects`]);function lf(e){let t=e.findIndex(e=>e.startsWith(`-`)),n=t===-1?e:e.slice(0,t),r=+(n[0]===`help`);for(let e=3;e>=1;--e){let t=n.slice(r,r+e).join(` `);if(n.length-r>=e&&Object.hasOwn(af,t))return t}return null}function uf(e){let t=Object.hasOwn(rf,e)?rf[e]:void 0;return t?`\`seo ${e}\` is now \`${t}\`. The old name stops working in 0.4.0.`:null}function df(e){let t=lf(e);if(!t)return null;let n=af[t],r=t.split(` `)[0]??``,i=n?`Use \`${n}\` instead.`:cf.has(r)?"Run `seo projects --help` for the project commands.":r===`providers`?"Run `seo providers --help` for the provider commands.":"Run `seo help` for the commands.";return`\`seo ${t}\` was removed in 0.3.0. ${Object.hasOwn(sf,t)?sf[t]:`SEO Skill now focuses on Search Console, analytics and keyword data.`} ${i}`}function ff(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function pf(e,t){return t===`win32`?`${e}.cmd`:e}function mf(e,t=process.platform){let n=ff(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:pf(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:pf(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:pf(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:pf(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function hf(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function gf(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function _f(e){if(e)try{return rr(e)}catch{return e}}async function vf(e){let t=await Lr({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return Mr(t)?`later`:t}function yf(e){return Er(e.command,e.args,{stdio:`inherit`}).status}async function bf(e,t={}){if(!gf({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=_f(t.cliPath??process.argv[1]),r=n?mf(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>qr({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!hf(i.current,i.latest))&&await(t.prompt??vf)({command:r,update:i})!==`later`)return(t.run??yf)(r)===0?(Nr.success(`Updated seo to ${i.latest}. Run your command again.`),0):(Nr.error(`The update failed. Your current version is unchanged.`),1)}function xf(e){if(!(!process.stderr.isTTY||process.env.CI)&&!(e.includes(`--json`)||e[0]===`mcp`))try{let{configDir:e,configFile:t}=Ft();if(!$n(t))return;let n=pr(e,`notices.json`),r=$n(n)?JSON.parse(tr(n,`utf8`)):[];if(r.includes(`0.3`))return;process.stderr.write(`
|
|
262
|
+
SEO Skill 0.3: eight commands replace the 70+ old reports, and old report names print the command to use instead.
|
|
263
|
+
Free hosted MCP server, nothing to install: https://seoskill.dev/docs/mcp
|
|
264
|
+
To stay on the old version: npm i -g seo@0.2.41
|
|
265
|
+
What changed: https://github.com/iannuttall/seo/blob/main/CHANGELOG.md
|
|
266
|
+
|
|
267
|
+
`),or(n,JSON.stringify([...r,`0.3`]),{mode:384})}catch{}}const Sf={name:`seo`,version:ee},Cf=[{title:`Start here`,commands:[[`seo start`,`Connect Search Console and save a project profile`],[`seo report`,`The main report: overview and every section`],[`seo report --section quick-wins`,`One section of the report, with live page checks`]]},{title:`Commands`,commands:[[`seo page --url <url>`,`One page: live facts, index status, queries`],[`seo compare --date <date>`,`Before and after a date`],[`seo index`,`Pages that lost most of their clicks`],[`seo research <action>`,`Keyword, SERP, competitor and backlink data from your key`],[`seo data`,`Raw Search Console rows`],[`seo crawl`,`Crawl a site and list technical issues`],[`seo internal-links`,`Pages that could link to a target page`]]},{title:`Setup`,commands:[[`seo projects list`,`List saved project profiles`],[`seo sites`,`List Search Console properties`],[`seo mcp install`,`Add the local MCP server to your agents`],[`seo providers`,`Connect DataForSEO or Ahrefs`],[`seo doctor`,`Check local auth and config`]]}],wf=[...Cf,{title:`More commands`,commands:[[`seo crawl-reports`,`List saved crawl reports`],[`seo crawl-diff`,`Compare crawl changes`],[`seo crawl-queue`,`Run a crawl and rank the fix queue`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo redirect-trace`,`Trace redirects`],[`seo perf audit`,`Run a page performance audit`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check saved URL Inspection snapshots`],[`seo ai-referrals`,`AI referral traffic in Google Analytics`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page and query groups`],[`seo monitoring`,`Run and review monitoring`],[`seo schedule cron`,`Print cron entries`],[`seo export`,`Export report data to CSV`],[`seo reports list`,`List every structured report`],[`seo skill list`,`Show the packaged SEO skill`],[`seo auth`,`Manage Google auth`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete saved credentials and all local data`]]}];function Tf(e){Aa(`seo v${Sf.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
|
|
268
|
+
`),Na(e.flatMap(e=>e.commands.map(([t,n])=>({category:e.title,id:t,name:n}))),{noun:`command`,categoryLabels:Object.fromEntries(e.map(e=>[e.title,e.title]))}),process.stdout.write(`
|
|
269
|
+
`),Fa(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Ef=D({meta:{name:`seo`,version:Sf.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Zc,analytics:lo,auth:_o,mcp:tl,doctor:Nl,privacy:Ru,telemetry:Ku,reset:zu,cache:bo,logs:Qc,crawl:rc,"crawl-queue":qu,"crawl-reports":vc,client:Os,content:As,project:ks,projects:ks,providers:Xl,"change-log":Oc,tests:Bc,"content-groups":jc,"crawl-diff":nl,diagnose:Ml,"diagnose-property":Ud,export:Xc,"index-coverage":pl,"index-watch":ml,"link-recover":hl,monitoring:fl,rules:ru,explain:iu,"refresh-priorities":Wd,"redirect-trace":gl,schedule:ou,skill:Lu,start:bs,setup:ys,report:Bs,page:Vs,compare:Hs,index:Us,data:Ws,research:Gs,reports:nu,"segment-impact":Fl,"striking-distance":Ll,"technical-watch":Gd,"update-postmortem":Zd,sites:Bu,"gsc-query":yc,"url-inspect":bc,updates:Vu,"traffic-anomaly":wc,"update-correlate":Tc,"ai-referrals":_l,"audit-page":Dl,"second-page":El,cannibal:vl,decaying:xl,"quick-wins":Tl,"page-opportunities":Cl,perf:jl,"internal-links":Sl,"ctr-underperformers":yl,"query-cluster":wl},run:async()=>{process.argv.slice(2).length===0&&process.stdout.write("Use `seo start` to set up, then `seo report` for the main report.\n")}}),Df={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,"diagnose-property":`search-performance-overview`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"query-cluster":`query-clusters`,report:`search-performance-overview`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Of=new Set([`ai-referrals`,`audit-page`,`crawl-diff`,`ctr-underperformers`,`index-coverage`,`index-watch`,`internal-links`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),kf=new Set(on().map(e=>e.id));function Af(e,t){let n=e.indexOf(t);return n>=0?e[n+1]:e.find(e=>e.startsWith(`${t}=`))?.slice(t.length+1)}function jf(e){let[t,n,r]=e;if(t)return t in Xt?Je(t,{section:Af(e,`--section`),action:n})??void 0:t===`reports`&&n===`run`?r&&kf.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`tests`&&n===`report`?`measure-change`:Df[t]??(Of.has(t)?t:void 0)}const $=process.argv.slice(2);xf($);const Mf=df($);Mf&&(process.stderr.write(`${Mf}\n`),process.exit(2));const Nf=uf($[0]??``);Nf&&!$.some(e=>[`--help`,`-h`].includes(e))&&process.stderr.write(`${Nf}\n`);const Pf={agent:ht()},Ff=$[0]===`telemetry`,If=$[0]===`mcp`&&$[1]===`serve`;!Ff&&!If&&Dn(Pf);const Lf=await bf(Sf,{argv:$});Lf!==void 0&&process.exit(Lf),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(Tf(Cf),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(Tf(wf),process.exit(0));const Rf=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;Rf!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Rf]);const zf=Rf.some(e=>[`--help`,`-h`].includes(e)),Bf=$.length===1&&[`--version`,`-v`].includes($[0]??``),Vf=zf||Bf?void 0:jf(Rf);if(zf||Bf)await Or(Ef);else{Vf&&At(Vf,Pf);try{await Dr(Ef,{rawArgs:Rf}),Vf&&Zt(Vf,Pf),[`init`,`setup`,`start`].includes(Rf[0]??``)&&!Rf.includes(`--dry-run`)&&rt(Pf)}catch(e){if(Vf)Nt(Vf,e,Pf);else if(!Ff){let t=ef(Rf);t&&un(t,e,Pf)}let t=Nn(e);if($.includes(`--json`))process.stdout.write(`${JSON.stringify(at(t),null,2)}\n`);else{process.stderr.write(`Error: ${t.message}\n`);let n=nf({args:Rf,error:e,report:Vf});n&&process.stderr.write(`Report this bug: ${n}\n`)}process.exitCode=t.exitCode}}export{};
|
|
264
270
|
//# sourceMappingURL=cli.js.map
|